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
= os
.getenv("HOME") .. "/.wmii-3.5"
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 -- ========================================================================
120 -- ========================================================================
122 -- wmiir points to the wmiir executable
123 -- TODO: need to make sure that wmiir is in path, and if not find it
124 local wmiir
= "wmiir"
126 -- wmii_adr is the address we use when connecting using ixp
127 local wmii_adr
= os
.getenv("WMII_ADDRESS")
128 or ("unix!/tmp/ns." .. os
.getenv("USER") .. "."
129 .. os
.getenv("DISPLAY"):match("(:%d+)") .. "/wmii")
131 -- wmixp is the ixp context we use to talk to wmii
132 local wmixp
= ixp
.new(wmii_adr
)
134 -- history of previous views, view_hist[#view_hist] is the last one
135 local view_hist
= {} -- sorted with 1 being the oldest
136 local view_hist_max
= 50 -- max number to keep track of
138 -- allow for a client to be forced to a tag
139 local next_client_goes_to_tag
= nil
141 -- program and action histories
142 local prog_hist
= history
.new (20)
143 local action_hist
= history
.new(10)
145 -- where to find plugins
146 plugin_path
= os
.getenv("HOME") .. "/.wmii-3.5/plugins/?.so;"
147 .. os
.getenv("HOME") .. "/.wmii-3.5/plugins/?.lua;"
148 .. "/usr/local/lib/lua/5.1/wmii/?.so;"
149 .. "/usr/local/share/lua/5.1/wmii/?.lua;"
150 .. "/usr/lib/lua/5.1/wmii/?.so;"
151 .. "/usr/share/lua/5.1/wmii/?.lua"
153 -- where to find wmiirc (see find_wmiirc())
154 wmiirc_path
= os
.getenv("HOME") .. "/.wmii-3.5/wmiirc.lua;"
155 .. os
.getenv("HOME") .. "/.wmii-3.5/wmiirc;"
156 .. "/etc/X11/wmii-3.5/wmiirc.lua;"
157 .. "/etc/X11/wmii-3.5/wmiirc"
159 -- ========================================================================
161 -- ========================================================================
168 Log the message provided in C<str>
170 Currently just writes to io.stderr
175 if get_conf("debug") then
176 io
.stderr
:write (str
.. "\n")
183 =item find_wmiirc ( )
185 Locates the wmiirc script. It looks in ~/.wmii-3.5 and /etc/X11/wmii-3.5
186 for the first lua script bearing the name wmiirc.lua or wmiirc. Returns
191 function find_wmiirc()
193 for fn
in string.gmatch(wmiirc_path
, "[^;]+") do
194 -- try to locate the files locally
195 local file
= io
.open(fn
, "r")
197 local txt
= file
:read("*line")
199 if type(txt
) == 'string' and txt
:match("lua") then
208 -- ========================================================================
209 -- MAIN ACCESS FUNCTIONS
210 -- ========================================================================
215 =item ls ( dir, fmt )
217 List the wmii filesystem directory provided in C<dir>, in the format specified
220 Returns an iterator of TODO
224 function ls (dir
, fmt
)
225 local verbose
= fmt
and fmt
:match("l")
227 local s
= wmixp
:stat(dir
)
229 return function () return nil end
231 if s
.modestr
:match("^[^d]") then
233 return stat2str(verbose
, s
)
237 local itr
= wmixp
:idir (dir
)
248 return stat2str(verbose
, s
)
254 local function stat2str(verbose
, stat
)
256 return string.format("%s %s %s %5d %s %s", stat
.modestr
, stat
.uid
, stat
.gid
, stat
.length
, stat
.timestr
, stat
.name
)
258 if stat
.modestr
:match("^d") then
259 return stat
.name
.. "/"
266 -- ------------------------------------------------------------------------
267 -- read all contents of a wmii virtual file
269 return wmixp
:read (file
)
272 -- ------------------------------------------------------------------------
273 -- return an iterator which walks all the lines in the file
276 -- for event in wmii.iread("/ctl")
280 -- NOTE: don't use iread for files that could block, as this will interfere
281 -- with timer processing and event delivery. Instead fork off a process to
282 -- execute wmiir and read back the responses via callback.
283 function iread (file
)
284 return wmixp
:iread(file
)
287 -- ------------------------------------------------------------------------
288 -- create a wmii file, optionally write data to it
289 function create (file
, data
)
290 wmixp
:create(file
, data
)
293 -- ------------------------------------------------------------------------
294 -- remove a wmii file
295 function remove (file
)
299 -- ------------------------------------------------------------------------
300 -- write a value to a wmii virtual file system
301 function write (file
, value
)
302 wmixp
:write (file
, value
)
305 -- ------------------------------------------------------------------------
306 -- setup a table describing the menu command
307 local function menu_cmd (prompt
)
308 local cmdt
= { wmiir
, "setsid", "dmenu", "-b" }
309 local fn
= get_ctl("font")
311 cmdt
[#cmdt
+1] = "-fn"
314 local normcolors
= get_ctl("normcolors")
316 local nf
, nb
= normcolors
:match("(#%x+)%s+(#%x+)%s#%x+")
318 cmdt
[#cmdt
+1] = "-nf"
319 cmdt
[#cmdt
+1] = "'" .. nf
.. "'"
322 cmdt
[#cmdt
+1] = "-nb"
323 cmdt
[#cmdt
+1] = "'" .. nb
.. "'"
326 local focuscolors
= get_ctl("focuscolors")
328 local sf
, sb
= focuscolors
:match("(#%x+)%s+(#%x+)%s#%x+")
330 cmdt
[#cmdt
+1] = "-sf"
331 cmdt
[#cmdt
+1] = "'" .. sf
.. "'"
334 cmdt
[#cmdt
+1] = "-sb"
335 cmdt
[#cmdt
+1] = "'" .. sb
.. "'"
340 cmdt
[#cmdt
+1] = "'" .. prompt
.. "'"
346 -- ------------------------------------------------------------------------
347 -- displays the menu given an table of entires, returns selected text
348 function menu (tbl
, prompt
)
349 local menu
= menu_cmd(prompt
)
351 local infile
= os
.tmpname()
352 local fh
= io
.open (infile
, "w+")
355 for i
,v
in pairs(tbl
) do
356 if type(i
) == 'number' and type(v
) == 'string' then
365 local outfile
= os
.tmpname()
368 menu
[#menu
+1] = infile
370 menu
[#menu
+1] = outfile
372 local cmd
= table.concat(menu
," ")
375 fh
= io
.open (outfile
, "r")
378 local sel
= fh
:read("*l")
384 -- ------------------------------------------------------------------------
385 -- displays the a tag selection menu, returns selected tag
387 local tags
= get_tags()
389 return menu(tags
, "tag:")
392 -- ------------------------------------------------------------------------
393 -- displays the a program menu, returns selected program
394 function prog_menu ()
395 local menu
= menu_cmd("cmd:")
397 local outfile
= os
.tmpname()
400 menu
[#menu
+1] = outfile
403 for n
in prog_hist
:walk_reverse_unique() do
404 hstt
[#hstt
+1] = "echo '" .. n
.. "' ; "
407 local cmd
= "(" .. table.concat(hstt
)
409 .. table.concat(menu
," ")
412 local fh
= io
.open (outfile
, "rb")
415 local prog
= fh
:read("*l")
421 -- ------------------------------------------------------------------------
422 -- returns a table of sorted tags names
426 for s
in wmixp
:idir ("/tag") do
427 if s
.name
and not (s
.name
== "sel") then
435 -- ------------------------------------------------------------------------
436 -- returns a table of sorted screen names
437 function get_screens()
440 for s
in wmixp
:idir ("/screen") do
441 if s
.name
and not (s
.name
== "sel") then
449 -- ------------------------------------------------------------------------
450 -- returns current view, on current screen or specified screen
451 function get_view(screen
)
452 return get_screen_ctl(screen
, "view") or get_ctl("view")
455 -- ------------------------------------------------------------------------
456 -- changes the current view to the name given
457 function set_view(sel
)
458 local cur
= get_view()
459 local all
= get_tags()
461 if #all
< 2 or sel
== cur
then
462 -- nothing to do if we have less then 2 tags
466 if not (type(sel
) == "string") then
467 error ("string argument expected")
471 write ("/ctl", "view " .. sel
)
474 -- ------------------------------------------------------------------------
475 -- changes the current view to the index given
476 function set_view_index(sel
)
477 local cur
= get_view()
478 local all
= get_tags()
481 -- nothing to do if we have less then 2 tags
485 local num
= tonumber (sel
)
487 error ("number argument expected")
490 local name
= all
[sel
]
491 if not name
or name
== cur
then
496 write ("/ctl", "view " .. name
)
499 -- ------------------------------------------------------------------------
500 -- chnages to current view by offset given
501 function set_view_ofs(jump
)
502 local cur
= get_view()
503 local all
= get_tags()
506 -- nothing to do if we have less then 2 tags
511 if (jump
< - #all
) or (jump
> #all
) then
512 error ("view selector is out of range")
515 -- find the one that's selected index
518 for i
,v
in pairs (all
) do
519 if v
== cur
then curi
= i
end
523 local newi
= math
.fmod(#all
+ curi
+ jump
- 1, #all
) + 1
524 if (newi
< - #all
) or (newi
> #all
) then
525 error ("error computng new view")
528 write ("/ctl", "view " .. all
[newi
])
531 -- ------------------------------------------------------------------------
532 -- toggle between last view and current view
533 function toggle_view()
534 local last
= view_hist
[#view_hist
]
540 -- ========================================================================
542 -- ========================================================================
544 local action_handlers
= {
545 man
= function (act
, args
)
546 local xterm
= get_conf("xterm") or "xterm"
548 if (not page
) or (not page
:match("%S")) then
549 page
= wmiidir
.. "/wmii.3lua"
551 local cmd
= xterm
.. " -e man " .. page
.. " &"
552 log (" executing: " .. cmd
)
553 os
.execute (wmiir
.. " setsid " .. cmd
)
557 write ("/ctl", "quit")
560 exec
= function (act
, args
)
561 local what
= args
or "wmii"
562 log (" asking wmii to exec " .. tostring(what
))
564 write ("/ctl", "exec " .. what
)
567 xlock
= function (act
)
568 local cmd
= get_conf("xlock") or "xscreensaver-command --lock"
569 os
.execute (wmiir
.. " setsid " .. cmd
)
574 local wmiirc
= find_wmiirc()
576 log (" executing: lua " .. wmiirc
)
579 posix
.exec ("/bin/sh", "-c", "exec lua wmiirc")
580 posix
.exec ("/usr/bin/lua", wmiirc
)
583 log("sorry cannot restart; you don't have lua's posix library.")
588 wmixp
:write ("/client/sel/ctl", "Urgent toggle")
593 -- TODO: consider storing list of executables around, and
594 -- this will then reinitialize that list
595 log (" TODO: rehash")
599 -- TODO: this should eventually update something on the /rbar
600 log (" TODO: status")
608 =item add_action_handler (action, fn)
610 Add an Alt-a action handler callback function, I<fn>, for the given action string I<action>.
614 function add_action_handler (action
, fn
)
616 if type(action
) ~= "string" or type(fn
) ~= "function" then
617 error ("expecting a string and a function")
620 if action_handlers
[action
] then
621 error ("action handler already exists for '" .. action
.. "'")
624 action_handlers
[action
] = fn
630 =item remove_action_handler (action)
632 Remove an action handler callback function for the given action string I<action>.
636 function remove_action_handler (action
)
638 action_handlers
[action
] = nil
641 -- ========================================================================
643 -- ========================================================================
645 function ke_fullscreen_toggle()
646 wmixp
:write ("/client/sel/ctl", "Fullscreen toggle")
649 function ke_view_starting_with_letter (letter
)
652 -- find the view name in history in reverse order
653 for i
=#view_hist
,1,-1 do
655 if letter
== v
:sub(1,1) then
661 -- otherwise just pick the first view that matches
662 local all
= get_tags()
663 for i
,v
in pairs(all
) do
664 if letter
== v
:sub(1,1) then
673 function ke_handle_action()
678 for n
in action_hist
:walk_reverse() do
680 actions
[#actions
+1] = n
686 for n
,v
in pairs(action_handlers
) do
688 actions
[#actions
+1] = n
693 local text
= menu(actions
, "action:")
695 log ("Action: " .. text
)
698 local si
= text
:find("%s")
700 act
,args
= string.match(text
.. " ", "(%w+)%s(.+)")
703 local fn
= action_handlers
[act
]
705 action_hist
:add (act
)
706 local r
, err
= pcall (fn
, act
, args
)
708 log ("WARNING: " .. tostring(err
))
716 local key_handlers
= {
717 ["*"] = function (key
)
721 -- execution and actions
722 ["Mod1-Return"] = function (key
)
723 local xterm
= get_conf("xterm") or "xterm"
724 log (" executing: " .. xterm
)
725 os
.execute (wmiir
.. " setsid " .. xterm
.. " &")
727 ["Mod1-Shift-Return"] = function (key
)
728 local tag = tag_menu()
730 local xterm
= get_conf("xterm") or "xterm"
731 log (" executing: " .. xterm
.. " on: " .. tag)
732 next_client_goes_to_tag
= tag
733 os
.execute (wmiir
.. " setsid " .. xterm
.. " &")
736 ["Mod1-a"] = function (key
)
739 ["Mod1-p"] = function (key
)
740 local prog
= prog_menu()
742 prog_hist
:add(prog
:match("(%w+)"))
743 log (" executing: " .. prog
)
744 os
.execute (wmiir
.. " setsid " .. prog
.. " &")
747 ["Mod1-Shift-p"] = function (key
)
748 local tag = tag_menu()
750 local prog
= prog_menu()
752 log (" executing: " .. prog
.. " on: " .. tag)
753 next_client_goes_to_tag
= tag
754 os
.execute (wmiir
.. " setsid " .. prog
.. " &")
758 ["Mod1-Shift-c"] = function (key
)
759 write ("/client/sel/ctl", "kill")
762 -- HJKL active selection
763 ["Mod1-h"] = function (key
)
764 write ("/tag/sel/ctl", "select left")
766 ["Mod1-l"] = function (key
)
767 write ("/tag/sel/ctl", "select right")
769 ["Mod1-j"] = function (key
)
770 write ("/tag/sel/ctl", "select down")
772 ["Mod1-k"] = function (key
)
773 write ("/tag/sel/ctl", "select up")
777 ["Mod1-Shift-h"] = function (key
)
778 write ("/tag/sel/ctl", "send sel left")
780 ["Mod1-Shift-l"] = function (key
)
781 write ("/tag/sel/ctl", "send sel right")
783 ["Mod1-Shift-j"] = function (key
)
784 write ("/tag/sel/ctl", "send sel down")
786 ["Mod1-Shift-k"] = function (key
)
787 write ("/tag/sel/ctl", "send sel up")
791 ["Mod1-space"] = function (key
)
792 write ("/tag/sel/ctl", "select toggle")
794 ["Mod1-Shift-space"] = function (key
)
795 write ("/tag/sel/ctl", "send sel toggle")
798 -- work spaces (# and @ are wildcards for numbers and letters)
799 ["Mod4-#"] = function (key
, num
)
800 -- first attempt to find a view that starts with the number requested
801 local num_str
= tostring(num
)
802 if not ke_view_starting_with_letter (num_str
) then
803 -- if we fail, then set it to the index requested
807 ["Mod4-Shift-#"] = function (key
, num
)
808 write ("/client/sel/tags", tostring(num
))
810 ["Mod4-@"] = function (key
, letter
)
811 ke_view_starting_with_letter (letter
)
813 ["Mod4-Shift-@"] = function (key
, letter
)
814 local all
= get_tags()
816 for i
,v
in pairs(all
) do
817 if letter
== v
:sub(1,1) then
818 write ("/client/sel/tags", v
)
823 ["Mod1-comma"] = function (key
)
826 ["Mod1-period"] = function (key
)
829 ["Mod1-r"] = function (key
)
830 -- got to the last view
834 -- switching views and retagging
835 ["Mod1-t"] = function (key
)
837 local tag = tag_menu()
842 ["Mod1-Shift-t"] = function (key
)
843 -- move selected client to a tag
844 local tag = tag_menu()
846 write ("/client/sel/tags", tag)
849 ["Mod1-Shift-r"] = function (key
)
850 -- move selected client to a tag, and follow
851 local tag = tag_menu()
853 -- get the current window id
854 local xid
= wmixp
:read("/client/sel/ctl") or ""
857 write("/client/sel/tags", tag)
859 -- if the client is still in this tag, then
860 -- it might have been a regexp tag... check
861 local test
= wmixp
:read("/client/sel/ctl")
862 if not test
or test
~= xid
then
863 -- if the window moved, follow it
868 ["Mod1-Control-t"] = function (key
)
869 log (" TODO: Mod1-Control-t: " .. key
)
873 ["Mod1-d"] = function (key
)
874 write("/tag/sel/ctl", "colmode sel default-max")
876 ["Mod1-s"] = function (key
)
877 write("/tag/sel/ctl", "colmode sel stack-max")
879 ["Mod1-m"] = function (key
)
880 write("/tag/sel/ctl", "colmode sel stack+max")
882 ["Mod1-f"] = function (key
)
883 ke_fullscreen_toggle()
886 -- changing client flags
887 ["Shift-Mod1-f"] = function (key
)
888 log ("setting flags")
890 local cli
= get_client ()
892 local flags
= { "suspend", "raw" }
893 local current_flags
= cli
:flags_string()
895 local what
= menu(flags
, "current flags: " .. current_flags
.. " toggle:")
899 ["Mod4-space"] = function (key
)
900 local cli
= get_client ()
908 =item add_key_handler (key, fn)
910 Add a keypress handler callback function, I<fn>, for the given key sequence I<key>.
914 function add_key_handler (key
, fn
)
916 if type(key
) ~= "string" or type(fn
) ~= "function" then
917 error ("expecting a string and a function")
920 if key_handlers
[key
] then
921 -- TODO: we may wish to allow multiple handlers for one keypress
922 error ("key handler already exists for '" .. key
.. "'")
925 key_handlers
[key
] = fn
931 =item remove_key_handler (key)
933 Remove an key handler callback function for the given key I<key>.
935 Returns the handler callback function.
939 function remove_key_handler (key
)
941 local fn
= key_handlers
[key
]
942 key_handlers
[key
] = nil
949 =item remap_key_handler (old_key, new_key)
951 Remove a key handler callback function from the given key I<old_key>,
952 and assign it to a new key I<new_key>.
956 function remap_key_handler (old_key
, new_key
)
958 local fn
= remove_key_handler(old_key
)
960 return add_key_handler (new_key
, fn
)
964 -- ------------------------------------------------------------------------
965 -- update the /keys wmii file with the list of all handlers
966 local alphabet
="abcdefghijklmnopqrstuvwxyz"
967 function update_active_keys ()
970 for x
,y
in pairs(key_handlers
) do
972 local i
= x
:find("#$")
976 t
[#t
+ 1] = x
:sub(1,i
-1) .. j
982 for j
=1,alphabet
:len() do
983 local a
= alphabet
:sub(j
,j
)
984 t
[#t
+ 1] = x
:sub(1,i
-1) .. a
987 t
[#t
+ 1] = tostring(x
)
992 local all_keys
= table.concat(t
, "\n")
993 --log ("setting /keys to...\n" .. all_keys .. "\n");
994 write ("/keys", all_keys
)
997 -- ------------------------------------------------------------------------
998 -- update the /lbar wmii file with the current tags
999 function update_displayed_tags ()
1000 -- list of all screens
1001 local screens
= get_screens()
1003 update_displayed_tags_on_screen()
1008 for i
,s
in pairs(screens
) do
1009 update_displayed_tags_on_screen(s
)
1013 function update_displayed_tags_on_screen(s
)
1014 local lbar
= "/lbar"
1016 lbar
= "/screen/" .. s
.. "/lbar"
1019 -- colours for screen
1020 local fc
= get_screen_ctl(s
, "focuscolors") or get_ctl("focuscolors") or ""
1021 local nc
= get_screen_ctl(s
, "normcolors") or get_ctl("normcolors") or ""
1023 -- build up a table of existing tags in the /lbar
1026 for ent
in wmixp
:idir (lbar
) do
1030 -- for all actual tags in use create any entries in /lbar we don't have
1031 -- clear the old table entries if we have them
1032 local cur
= get_view(s
)
1033 local all
= get_tags()
1035 for i
,v
in pairs(all
) do
1041 create (lbar
.. "/" .. v
, color
.. " " .. v
)
1043 write (lbar
.. "/" .. v
, color
.. " " .. v
)
1047 -- anything left in the old table should be removed now
1048 for i
,v
in pairs(old
) do
1050 remove(lbar
.."/"..i
)
1054 create ("/screen/"..s
.."/lbar/000000000000000000", '-'..s
..'-')
1057 function create_tag_widget(name
)
1058 local nc
= get_ctl("normcolors") or ""
1059 local screens
= get_screens()
1061 create ("/lbar/" .. name
, nc
.. " " .. name
)
1065 for i
,s
in pairs(screens
) do
1066 create ("/screen/"..s
.."/lbar/" .. name
, nc
.. " " .. name
)
1070 function destroy_tag_widget(name
)
1071 local screens
= get_screens()
1073 remove ("/lbar/" .. name
)
1077 for i
,s
in pairs(screens
) do
1078 remove ("/screen/"..s
.."/lbar/" .. name
)
1083 -- ========================================================================
1085 -- ========================================================================
1087 local widget_ev_handlers
= {
1093 =item _handle_widget_event (ev, arg)
1095 Top-level event handler for redispatching events to widgets. This event
1096 handler is added for any widget event that currently has a widget registered
1099 Valid widget events are currently
1101 RightBarMouseDown <buttonnumber> <widgetname>
1102 RightBarClick <buttonnumber> <widgetname>
1104 the "Click" event is sent on mouseup.
1106 The callbacks are given only the button number as their argument, to avoid the
1112 local function _handle_widget_event (ev
, arg
)
1114 log("_handle_widget_event: " .. tostring(ev
) .. " - " .. tostring(arg
))
1116 -- parse arg to strip out our widget name
1117 local number,wname
= string.match(arg
, "(%d+)%s+(.+)")
1119 -- check our dispatch table for that widget
1121 log("Didn't find wname")
1125 local wtable
= widget_ev_handlers
[wname
]
1127 log("No widget cares about" .. wname
)
1131 local fn
= wtable
[ev
] or wtable
["*"]
1133 success
, err
= pcall( fn
, ev
, tonumber(number) )
1135 log("Callback had an error in _handle_widget_event: " .. tostring(err
) )
1139 log("no function found for " .. ev
)
1143 local ev_handlers
= {
1144 ["*"] = function (ev
, arg
)
1145 log ("ev: " .. tostring(ev
) .. " - " .. tostring(arg
))
1148 RightBarClick
= _handle_widget_event
,
1150 -- process timer events
1151 ProcessTimerEvents
= function (ev
, arg
)
1155 -- exit if another wmiirc started up
1156 Start
= function (ev
, arg
)
1158 if arg
== "wmiirc" then
1159 -- backwards compatibility with bash version
1160 log (" exiting; pid=" .. tostring(myid
))
1164 -- ignore if it came from us
1165 local pid
= string.match(arg
, "wmiirc (%d+)")
1167 local pid
= tonumber (pid
)
1168 if not (pid
== myid
) then
1169 log (" exiting; pid=" .. tostring(myid
))
1179 CreateTag
= function (ev
, arg
)
1180 log ("CreateTag: " .. arg
)
1181 create_tag_widget(arg
)
1183 DestroyTag
= function (ev
, arg
)
1184 log ("DestroyTag: " .. arg
)
1185 destroy_tag_widget(arg
)
1187 -- remove the tag from history
1189 for i
=#view_hist
,1,-1 do
1192 table.remove(view_hist
,i
)
1197 FocusTag
= function (ev
, arg
)
1198 log ("FocusTag: " .. arg
)
1200 local tag,scrn
= arg
:match("(%w+)%s*(%w*)")
1205 local file
= "/lbar/" .. tag
1206 if scrn
and scrn
:len() > 0 then
1207 file
= "/screen/" .. scrn
.. file
1210 local fc
= get_screen_ctl(scrn
, "focuscolors") or get_ctl("focuscolors") or ""
1211 log ("# echo " .. fc
.. " " .. tag .. " | wmiir write " .. file
)
1213 create (file
, fc
.. " " .. tag)
1214 write (file
, fc
.. " " .. tag)
1216 UnfocusTag
= function (ev
, arg
)
1217 log ("UnfocusTag: " .. arg
)
1219 local tag,scrn
= arg
:match("(%w+)%s*(%w*)")
1224 local file
= "/lbar/" .. tag
1225 if scrn
and scrn
:len() > 0 then
1226 file
= "/screen/" .. scrn
.. file
1229 local nc
= get_screen_ctl(scrn
, "normcolors") or get_ctl("normcolors") or ""
1230 log ("# echo " .. nc
.. " " .. tag .. " | wmiir write " .. file
)
1232 create (file
, nc
.. " " .. tag)
1233 write (file
, nc
.. " " .. tag)
1235 -- don't duplicate the last entry
1236 if not (tag == view_hist
[#view_hist
]) then
1237 view_hist
[#view_hist
+1] = tag
1239 -- limit to view_hist_max
1240 if #view_hist
> view_hist_max
then
1241 table.remove(view_hist
, 1)
1246 -- key event handling
1247 Key
= function (ev
, arg
)
1248 log ("Key: " .. arg
)
1250 -- can we find an exact match?
1251 local fn
= key_handlers
[arg
]
1253 local key
= arg
:gsub("-%d$", "-#")
1254 -- can we find a match with a # wild card for the number
1255 fn
= key_handlers
[key
]
1257 -- convert the trailing number to a number
1258 magic
= tonumber(arg
:match("-(%d)$"))
1262 local key
= arg
:gsub("-%a$", "-@")
1263 -- can we find a match with a @ wild card for a letter
1264 fn
= key_handlers
[key
]
1266 -- split off the trailing letter
1267 magic
= arg
:match("-(%a)$")
1271 -- everything else failed, try default match
1272 fn
= key_handlers
["*"]
1275 local r
, err
= pcall (fn
, arg
, magic
)
1277 log ("WARNING: " .. tostring(err
))
1282 -- mouse handling on the lbar
1283 LeftBarClick
= function (ev
, arg
)
1284 local button
,tag = string.match(arg
, "(%w+)%s+(%S+)")
1289 ClientFocus
= function (ev
, arg
)
1290 log ("ClientFocus: " .. arg
)
1291 client_focused (arg
)
1293 ColumnFocus
= function (ev
, arg
)
1294 log ("ColumnFocus: " .. arg
)
1298 CreateClient
= function (ev
, arg
)
1299 if next_client_goes_to_tag
then
1300 local tag = next_client_goes_to_tag
1302 next_client_goes_to_tag
= nil
1303 write ("/client/" .. cli
.. "/tags", tag)
1306 client_created (arg
)
1308 DestroyClient
= function (ev
, arg
)
1309 client_destoryed (arg
)
1313 UrgentTag
= function (ev
, arg
)
1314 log ("UrgentTag: " .. arg
)
1315 write ("/lbar/" .. arg
, "*" .. arg
);
1317 NotUrgentTag
= function (ev
, arg
)
1318 log ("NotUrgentTag: " .. arg
)
1319 write ("/lbar/" .. arg
, arg
);
1323 Unresponsive
= function (ev
, arg
)
1324 log ("Unresponsive: " .. arg
)
1325 -- TODO ask the user if it shoudl be killed off
1328 Notice
= function (ev
, arg
)
1329 log ("Notice: " .. arg
)
1330 -- TODO send to the message plugin (or implement there)
1339 =item add_widget_event_handler (wname, ev, fn)
1341 Add an event handler callback for the I<ev> event on the widget named I<wname>
1346 function add_widget_event_handler (wname
, ev
, fn
)
1347 if type(wname
) ~= "string" or type(ev
) ~= "string" or type(fn
) ~= "function" then
1348 error ("expecting string for widget name, string for event name and a function callback")
1351 -- Make sure the widget event handler is present
1352 if not ev_handlers
[ev
] then
1353 ev_handlers
[ev
] = _handle_widget_event
1356 if not widget_ev_handlers
[wname
] then
1357 widget_ev_handlers
[wname
] = { }
1360 if widget_ev_handlers
[wname
][ev
] then
1361 -- TODO: we may wish to allow multiple handlers for one event
1362 error ("event handler already exists on widget '" .. wname
.. "' for '" .. ev
.. "'")
1365 widget_ev_handlers
[wname
][ev
] = fn
1371 =item remove_widget_event_handler (wname, ev)
1373 Remove an event handler callback function for the I<ev> on the widget named I<wname>.
1377 function remove_event_handler (wname
, ev
)
1379 if not widget_ev_handlers
[wname
] then
1383 widget_ev_handlers
[wname
][ev
] = nil
1389 =item add_event_handler (ev, fn)
1391 Add an event handler callback function, I<fn>, for the given event I<ev>.
1395 -- TODO: Need to allow registering widgets for RightBar* events. Should probably be done with its own event table, though
1396 function add_event_handler (ev
, fn
)
1397 if type(ev
) ~= "string" or type(fn
) ~= "function" then
1398 error ("expecting a string and a function")
1401 if ev_handlers
[ev
] then
1402 -- TODO: we may wish to allow multiple handlers for one event
1403 error ("event handler already exists for '" .. ev
.. "'")
1407 ev_handlers
[ev
] = fn
1413 =item remove_event_handler (ev)
1415 Remove an event handler callback function for the given event I<ev>.
1419 function remove_event_handler (ev
)
1421 ev_handlers
[ev
] = nil
1425 -- ========================================================================
1426 -- MAIN INTERFACE FUNCTIONS
1427 -- ========================================================================
1430 xterm
= 'x-terminal-emulator',
1431 xlock
= "xscreensaver-command --lock",
1435 -- ------------------------------------------------------------------------
1436 -- write configuration to /ctl wmii file
1437 -- wmii.set_ctl({ "var" = "val", ...})
1438 -- wmii.set_ctl("var, "val")
1439 function set_ctl (first
,second
)
1440 if type(first
) == "table" and second
== nil then
1442 for x
, y
in pairs(first
) do
1443 write ("/ctl", x
.. " " .. y
)
1446 elseif type(first
) == "string" and type(second
) == "string" then
1447 write ("/ctl", first
.. " " .. second
)
1450 error ("expecting a table or two string arguments")
1454 -- ------------------------------------------------------------------------
1455 -- read a value from /ctl wmii file
1456 -- table = wmii.get_ctl()
1457 -- value = wmii.get_ctl("variable")
1458 function get_ctl (name
)
1461 for s
in iread("/ctl") do
1462 local var
,val
= s
:match("(%w+)%s+(.+)")
1474 -- ------------------------------------------------------------------------
1475 -- write configuration to /screen/*/ctl wmii file
1476 -- wmii.set_screen_ctl("screen", { "var" = "val", ...})
1477 -- wmii.set_screen_ctl("screen", "var, "val")
1478 function set_screen_ctl (screen
, first
, second
)
1479 local ctl
= "/screen/" .. tostring(screen
) .. "/ctl"
1481 error ("screen is not set")
1482 elseif type(first
) == "table" and second
== nil then
1484 for x
, y
in pairs(first
) do
1485 write (ctl
, x
.. " " .. y
)
1488 elseif type(first
) == "string" and type(second
) == "string" then
1489 write (ctl
, first
.. " " .. second
)
1492 error ("expecting a screen name, followed by a table or two string arguments")
1496 -- ------------------------------------------------------------------------
1497 -- read a value from /screen/*/ctl wmii file
1498 -- table = wmii.get_screen_ctl("screen")
1499 -- value = wmii.get_screen_ctl("screen", "variable")
1500 function get_screen_ctl (screen
, name
)
1506 local ctl
= "/screen/" .. tostring(screen
) .. "/ctl"
1507 for s
in iread(ctl
) do
1508 local var
,val
= s
:match("(%w+)%s+(.+)")
1512 -- sometimes first line is the name of the entry
1513 -- in which case there will be no space
1522 -- ------------------------------------------------------------------------
1523 -- set an internal wmiirc.lua variable
1524 -- wmii.set_conf({ "var" = "val", ...})
1525 -- wmii.set_conf("var, "val")
1526 function set_conf (first
,second
)
1527 if type(first
) == "table" and second
== nil then
1529 for x
, y
in pairs(first
) do
1533 elseif type(first
) == "string"
1534 and (type(second
) == "string"
1535 or type(second
) == "number"
1536 or type(second
) == "boolean") then
1537 config
[first
] = second
1540 error ("expecting a table, or string and string/number as arguments")
1544 -- ------------------------------------------------------------------------
1545 -- read an internal wmiirc.lua variable
1546 function get_conf (name
)
1553 -- ========================================================================
1555 -- ========================================================================
1557 -- the event loop instance
1558 local el
= eventloop
.new()
1559 local event_read_fd
= -1
1561 -- ------------------------------------------------------------------------
1562 -- start/restart the core event reading process
1563 local function start_event_reader ()
1564 if event_read_fd
~= -1 then
1565 if el
:check_exec(event_read_fd
) then
1569 log("wmii: starting /event reading process")
1570 event_read_fd
= el
:add_exec (wmiir
.. " read /event",
1572 local line
= line
or "nil"
1574 -- try to split off the argument(s)
1575 local ev
,arg
= string.match(line
, "(%S+)%s+(.+)")
1580 -- now locate the handler function and call it
1581 local fn
= ev_handlers
[ev
] or ev_handlers
["*"]
1583 local r
, err
= pcall (fn
, ev
, arg
)
1585 log ("WARNING: " .. tostring(err
))
1590 log("wmii: ... fd=" .. tostring(event_read_fd
))
1593 -- ------------------------------------------------------------------------
1594 -- run the event loop and process events, this function does not exit
1595 function run_event_loop ()
1596 -- stop any other instance of wmiirc
1597 wmixp
:write ("/event", "Start wmiirc " .. tostring(myid
))
1599 log("wmii: updating lbar")
1601 update_displayed_tags ()
1603 log("wmii: updating rbar")
1605 update_displayed_widgets ()
1607 log("wmii: updating active keys")
1609 update_active_keys ()
1611 log("wmii: starting event loop")
1613 start_event_reader()
1614 local sleep_for
= process_timers()
1615 el
:run_loop(sleep_for
)
1619 -- ========================================================================
1621 -- ========================================================================
1623 api_version
= 0.1 -- the API version we export
1625 plugins
= {} -- all plugins that were loaded
1627 -- ------------------------------------------------------------------------
1628 -- plugin loader which also verifies the version of the api the plugin needs
1630 -- here is what it does
1631 -- - does a manual locate on the file using package.path
1632 -- - reads in the file w/o using the lua interpreter
1633 -- - locates api_version=X.Y string
1634 -- - makes sure that api_version requested can be satisfied
1635 -- - if the plugins is available it will set variables passed in
1636 -- - it then loads the plugin
1638 -- TODO: currently the api_version must be in an X.Y format, but we may want
1639 -- to expend this so plugins can say they want '0.1 | 1.3 | 2.0' etc
1641 function load_plugin(name
, vars
)
1642 local backup_path
= package
.path
or "./?.lua"
1644 log ("loading " .. name
)
1646 -- this is the version we want to find
1647 local api_major
, api_minor
= tostring(api_version
):match("(%d+)%.0*(%d+)")
1648 if (not api_major
) or (not api_minor
) then
1649 log ("WARNING: could not parse api_version in core/wmii.lua")
1653 -- first find the plugin file
1654 local s
, path_match
, full_name
, file
1655 for s
in string.gmatch(plugin_path
, "[^;]+") do
1656 -- try to locate the files locally
1657 local fn
= s
:gsub("%?", name
)
1658 file
= io
.open(fn
, "r")
1669 txt
= file
:read("*all")
1674 log ("WARNING: could not load plugin '" .. name
.. "'")
1678 -- find the api_version line
1679 local line
, plugin_version
1680 for line
in string.gmatch(txt
, "%s*api_version%s*=%s*%d+%.%d+%s*") do
1681 plugin_version
= line
:match("api_version%s*=%s*(%d+%.%d+)%s*")
1682 if plugin_version
then
1687 if not plugin_version
then
1688 log ("WARNING: could not find api_version string in plugin '" .. name
.. "'")
1692 -- decompose the version string
1693 local plugin_major
, plugin_minor
= plugin_version
:match("(%d+)%.0*(%d+)")
1694 if (not plugin_major
) or (not plugin_minor
) then
1695 log ("WARNING: could not parse api_version for '" .. name
.. "' plugin")
1699 -- make a version test
1700 if plugin_major
~= api_major
then
1701 log ("WARNING: " .. name
.. " plugin major version missmatch, is " .. plugin_version
1702 .. " (api " .. tonumber(api_version
) .. ")")
1706 if plugin_minor
> api_minor
then
1707 log ("WARNING: '" .. name
.. "' plugin minor version missmatch, is " .. plugin_version
1708 .. " (api " .. tonumber(api_version
) .. ")")
1712 -- the configuration parameters before loading
1713 if type(vars
) == "table" then
1715 for var
,val
in pairs(vars
) do
1716 local success
= pcall (set_conf
, name
.. "." .. var
, val
)
1718 log ("WARNING: bad variable {" .. tostring(var
) .. ", " .. tostring(val
) .. "} "
1719 .. "given; loading '" .. name
.. "' plugin failed.")
1725 -- actually load the module, but use only the path where we though it should be
1726 package
.path
= path_match
1727 local success
,what
= pcall (require
, name
)
1728 package
.path
= backup_path
1730 log ("WARNING: failed to load '" .. name
.. "' plugin")
1731 log (" - path: " .. tostring(path_match
))
1732 log (" - file: " .. tostring(full_name
))
1733 log (" - plugin's api_version: " .. tostring(plugin_version
))
1734 log (" - reason: " .. tostring(what
))
1739 log ("OK, plugin " .. name
.. " loaded, requested api v" .. plugin_version
)
1740 plugins
[name
] = what
1744 -- ------------------------------------------------------------------------
1749 -- ------------------------------------------------------------------------
1750 -- create a widget object and add it to the wmii /rbar
1753 -- widget = wmii.widget:new ("999_clock")
1754 -- widget = wmii.widget:new ("999_clock", clock_event_handler)
1755 function widget
:new (name
, fn
)
1758 if type(name
) == "string" then
1760 if type(fn
) == "function" then
1764 error ("expected name followed by an optional function as arguments")
1767 setmetatable (o
,self
)
1769 self
.__gc
= function (o
) o
:hide() end
1777 -- ------------------------------------------------------------------------
1778 -- stop and destroy the timer
1779 function widget
:delete ()
1780 widgets
[self
.name
] = nil
1784 -- ------------------------------------------------------------------------
1785 -- displays or updates the widget text
1789 -- w:show("foo", "#888888 #222222 #333333")
1790 -- w:show("foo", cell_fg .. " " .. cell_bg .. " " .. border)
1792 function widget
:show (txt
, colors
)
1793 local colors
= colors
or get_ctl("normcolors") or ""
1794 local txt
= txt
or self
.txt
or ""
1797 towrite
= colors
.. " " .. towrite
1799 if not self
.txt
then
1800 create ("/rbar/" .. self
.name
, towrite
)
1802 write ("/rbar/" .. self
.name
, towrite
)
1807 -- ------------------------------------------------------------------------
1808 -- hides a widget and removes it from the bar
1809 function widget
:hide ()
1811 remove ("/lbar/" .. self
.name
)
1819 =item widget:add_event_handler (ev, fn)
1821 Add an event handler callback for this widget, using I<fn> for event I<ev>
1826 function widget
:add_event_handler (ev
, fn
)
1827 add_widget_event_handler( self
.name
, ev
, fn
)
1831 -- ------------------------------------------------------------------------
1832 -- remove all /rbar entries that we don't have widget objects for
1833 function update_displayed_widgets ()
1834 -- colours for /rbar
1835 local nc
= get_ctl("normcolors") or ""
1837 -- build up a table of existing tags in the /lbar
1840 for s
in wmixp
:idir ("/rbar") do
1844 -- for all actual widgets in use we want to remove them from the old list
1846 for i
,v
in pairs(widgets
) do
1850 -- anything left in the old table should be removed now
1851 for i
,v
in pairs(old
) do
1858 -- ------------------------------------------------------------------------
1859 -- create a new program and for each line it generates call the callback function
1860 -- returns fd which can be passed to kill_exec()
1861 function add_exec (command
, callback
)
1862 return el
:add_exec (command
, callback
)
1865 -- ------------------------------------------------------------------------
1866 -- terminates a program spawned off by add_exec()
1867 function kill_exec (fd
)
1868 return el
:kill_exec (fd
)
1871 -- ------------------------------------------------------------------------
1876 -- ------------------------------------------------------------------------
1877 -- create a timer object and add it to the event loop
1880 -- timer:new (my_timer_fn)
1881 -- timer:new (my_timer_fn, 15)
1882 function timer
:new (fn
, seconds
)
1885 if type(fn
) == "function" then
1888 error ("expected function followed by an optional number as arguments")
1891 setmetatable (o
,self
)
1893 self
.__gc
= function (o
) o
:stop() end
1896 timers
[#timers
+1] = o
1904 -- ------------------------------------------------------------------------
1905 -- stop and destroy the timer
1906 function timer
:delete ()
1909 for i
,t
in pairs(timers
) do
1911 table.remove (timers
,i
)
1917 -- ------------------------------------------------------------------------
1918 -- run the timer given new interval
1919 function timer
:resched (seconds
)
1920 local seconds
= seconds
or self
.interval
1921 if not (type(seconds
) == "number") then
1922 error ("timer:resched expected number as argument")
1925 local now
= tonumber(os
.date("%s"))
1927 self
.interval
= seconds
1928 self
.next_time
= now
+ seconds
1930 -- resort the timer list
1931 table.sort (timers
, timer
.is_less_then
)
1934 -- helper for sorting timers
1935 function timer
:is_less_then(another
)
1936 if not self
.next_time
then
1937 return false -- another is smaller, nil means infinity
1939 elseif not another
.next_time
then
1940 return true -- self is smaller, nil means infinity
1942 elseif self
.next_time
< another
.next_time
then
1943 return true -- self is smaller than another
1946 return false -- another is smaller then self
1949 -- ------------------------------------------------------------------------
1951 function timer
:stop ()
1952 self
.next_time
= nil
1954 -- resort the timer list
1955 table.sort (timers
, timer
.is_less_then
)
1958 -- ------------------------------------------------------------------------
1959 -- figure out how long before the next event
1960 function time_before_next_timer_event()
1961 local tmr
= timers
[1]
1962 if tmr
and tmr
.next_time
then
1963 local now
= tonumber(os
.date("%s"))
1964 local seconds
= tmr
.next_time
- now
1969 return 0 -- sleep for ever
1972 -- ------------------------------------------------------------------------
1973 -- handle outstanding events
1974 function process_timers ()
1975 local now
= tonumber(os
.date("%s"))
1979 for i
,tmr
in pairs (timers
) do
1981 -- prune out removed timers
1982 table.remove(timers
,i
)
1985 elseif not tmr
.next_time
then
1986 -- break out once we find a timer that is stopped
1989 elseif tmr
.next_time
> now
then
1990 -- break out once we get to the future
1994 -- this one is good to go
1995 torun
[#torun
+1] = tmr
1998 for i
,tmr
in pairs (torun
) do
2000 local status
,new_interval
= pcall (tmr
.fn
, tmr
)
2002 new_interval
= new_interval
or self
.interval
2003 if new_interval
and (new_interval
~= -1) then
2004 tmr
:resched(new_interval
)
2007 log ("ERROR: " .. tostring(new_interval
))
2011 local sleep_for
= time_before_next_timer_event()
2015 -- ------------------------------------------------------------------------
2016 -- cleanup everything in preparation for exit() or exec()
2021 log ("wmii: stopping timer events")
2023 for i
,tmr
in pairs (timers
) do
2024 pcall (tmr
.delete
, tmr
)
2028 log ("wmii: terminating eventloop")
2030 pcall(el
.kill_all
,el
)
2032 log ("wmii: disposing of widgets")
2034 -- dispose of all widgets
2035 for i
,v
in pairs(widgets
) do
2040 -- FIXME: it doesn't seem to do what I want
2042 log ("wmii: releasing plugins")
2044 for i,p in pairs(plugins) do
2046 pcall (p.cleanup, p)
2052 log ("wmii: dormant")
2055 -- ========================================================================
2057 -- ========================================================================
2060 -- Notes on client tracking
2062 -- When a client is created wmii sends us a CreateClient message, and
2063 -- we in turn create a 'client' object and store it in the 'clients'
2064 -- table indexed by the client's ID.
2066 -- Each client object stores the following:
2067 -- .xid - the X client ID
2068 -- .pid - the process ID
2069 -- .prog - program object representing the process
2071 -- The client and program objects track the following modes for each program:
2074 -- - for each client window
2075 -- - Mod4-space toggles the state between normal and raw
2076 -- - Mod1-f raw also toggles the state
2077 -- - in raw mode all input goes to the client, except for Mod4-space
2078 -- - a focused client with raw mode enabled is put into raw mode
2081 -- - for each program
2082 -- - Mod1-f suspend toggles the state for current client's program
2083 -- - a focused client, whose program was previous suspended is resumed
2084 -- - an unfocused client, with suspend enabled, will be suspended
2085 -- - suspend/resume is done by sending the STOP/CONT signals to the PID
2088 function xid_to_pid (xid
)
2089 local cmd
= "xprop -id " .. tostring(xid
) .. " _NET_WM_PID"
2090 local file
= io
.popen (cmd
)
2091 local out
= file
:read("*a")
2093 local pid
= out
:match("^_NET_WM_PID.*%s+=%s+(%d+)%s+$")
2094 return tonumber(pid
)
2097 local focused_xid
= nil
2098 local clients
= {} -- table of client objects indexed by xid
2099 local programs
= {} -- table of program objects indexed by pid
2100 local mode_widget
= widget
:new ("999_client_mode")
2102 -- make programs table have weak values
2103 -- programs go away as soon as no clients point to it
2104 local programs_mt
= {}
2105 setmetatable(programs
, programs_mt
)
2106 programs_mt
.__mode
= 'v'
2110 function program
:new (pid
)
2113 setmetatable (o
,self
)
2115 self
.__gc
= function (old
) old
:cont() end
2116 -- initialize the new object
2120 o
.suspend
.toggle
= function (prog
)
2121 prog
.suspend
.enabled
= not prog
.suspend
.enabled
2123 o
.suspend
.enabled
= false -- if true, defocusing suspends (SIGSTOP)
2124 o
.suspend
.active
= true -- if true, focusing resumes (SIGCONT)
2128 function program
:stop ()
2129 if not self
.suspend
.active
then
2130 local cmd
= "kill -STOP " .. tostring(self
.pid
)
2131 log (" executing: " .. cmd
)
2133 self
.suspend
.active
= true
2137 function program
:cont ()
2138 if self
.suspend
.active
then
2139 local cmd
= "kill -CONT " .. tostring(self
.pid
)
2140 log (" executing: " .. cmd
)
2142 self
.suspend
.active
= false
2146 function get_program (pid
)
2147 local prog
= programs
[pid
]
2148 if pid
and not prog
then
2149 prog
= program
:new (pid
)
2150 programs
[pid
] = prog
2157 function client
:new (xid
)
2158 local pid
= xid_to_pid(xid
)
2160 log ("WARNING: failed to convert XID " .. tostring(xid
) .. " to a PID")
2165 setmetatable (o
,self
)
2166 self
.__index
= function (t
,k
)
2167 if k
== 'suspend' then -- suspend mode is tracked per program
2168 return t
.prog
.suspend
2172 self
.__gc
= function (old
) old
.prog
=nil end
2173 -- initialize the new object
2176 o
.prog
= get_program (pid
)
2179 o
.raw
.toggle
= function (cli
)
2180 cli
.raw
.enabled
= not cli
.raw
.enabled
2183 o
.raw
.enabled
= false -- if true, raw mode enabled when client is focused
2187 function client
:stop ()
2188 if self
.suspend
.enabled
then
2193 function client
:cont ()
2197 function client
:set_raw_mode()
2198 if not self
or not self
.raw
.enabled
then -- normal mode
2199 update_active_keys ()
2201 write ("/keys", "Mod4-space")
2205 function client
:toggle(what
)
2206 if what
and self
[what
] then
2207 local ctl
= self
[what
]
2211 log ("xid=" .. tostring (xid
)
2212 .. " pid=" .. tostring (self
.pid
) .. " (" .. tostring (self
.prog
.pid
) .. ")"
2213 .. " what=" .. tostring (what
)
2214 .. " enabled=" .. tostring(ctl
["enabled"]))
2216 mode_widget
:show (self
:flags_string())
2219 function client
:flags_string()
2221 if self
.suspend
.enabled
then ret
= ret
.. "s" else ret
= ret
.. "-" end
2222 if self
.raw
.enabled
then ret
= ret
.. "r" else ret
= ret
.. "-" end
2226 function get_client (xid
)
2227 local xid
= xid
or wmixp
:read("/client/sel/ctl")
2228 local cli
= clients
[xid
]
2230 cli
= client
:new (xid
)
2236 -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
2237 function client_created (xid
)
2238 log ("-client_created " .. tostring(xid
))
2239 return get_client(xid
)
2242 function client_destoryed (xid
)
2243 log ("-client_destoryed " .. tostring(xid
))
2244 if clients
[xid
] then
2245 local cli
= clients
[xid
]
2247 log (" del pid: " .. tostring(cli
.pid
))
2250 if focused_xid
== xid
then
2255 function client_focused (xid
)
2256 log ("-client_focused " .. tostring(xid
))
2257 -- return the current focused xid if nil is passed
2258 if type(xid
) ~= 'string' or not xid
:match("0x[0-9][a-f][A-F]*$") then
2261 -- do nothing if the same xid
2262 if focused_xid
== xid
then
2266 local old
= clients
[focused_xid
]
2267 local new
= get_client(xid
)
2269 -- handle raw mode switch
2270 if not old
or ( old
and new
and old
.raw
.enabled
~= new
.raw
.enabled
) then
2274 -- do nothing if the same pid
2275 if old
and new
and old
.pid
== new
.pid
then
2276 mode_widget
:show (new
:flags_string())
2282 log (" old pid: " .. tostring(old.pid)
2283 .. " xid: " .. tostring(old.xid)
2284 .. " flags: " .. old:flags_string())
2291 log (" new pid: " .. tostring(new.pid)
2292 .. " xid: " .. tostring(new.xid)
2293 .. " flags: " .. new:flags_string())
2298 mode_widget
:show (new
:flags_string())
2304 -- ========================================================================
2306 -- ========================================================================
2319 Used to determine location of wmii's listen socket.
2325 L<wmii(1)>, L<lua(1)>
2329 Bart Trojanowski B<< <bart@jukie.net> >>
2331 =head1 COPYRIGHT AND LICENSE
2333 Copyright (c) 2007, Bart Trojanowski <bart@jukie.net>
2335 This is free software. You may redistribute copies of it under the terms of
2336 the GNU General Public License L<http://www.gnu.org/licenses/gpl.html>. There
2337 is NO WARRANTY, to the extent permitted by law.