2 -- Copyrigh (c) 2007, Bart Trojanowski <bart@jukie.net>
4 -- Simple wmiir like interface.
6 -- The current intent is to wrap around the wmiir executable.
7 -- This is just a proof of concept, and eventually this will
8 -- be rewritten in C to use libixp.
10 -- git://www.jukie.net/wmiirc-lua.git/
12 package
.cpath
= package
.cpath
.. ";" .. os
.getenv("HOME") .. "/.wmii-3.5/ixp/?.so"
17 local io
= require("io")
18 local os
= require("os")
19 local posix
= require("posix")
20 local string = require("string")
21 local table = require("table")
22 local math
= require("math")
27 local tostring = tostring
28 local tonumber = tonumber
32 -- ========================================================================
34 -- ========================================================================
36 -- wmiir points to the wmiir executable
39 -- wmii_adr is the address we use when connecting using ixp
40 local wmii_adr
= os
.getenv("WMII_ADDRESS")
41 or ("unix!/tmp/ns." .. os
.getenv("USER") .. "."
42 .. os
.getenv("DISPLAY"):match("(:%d+)") .. "/wmii")
44 -- wmixp is the ixp context we use to talk to wmii
45 local wmixp
= ixp
.new(wmii_adr
)
47 -- history of previous views, view_hist[#view_hist] is the last one
48 local view_hist
= {} -- sorted with 1 being the oldest
49 local view_hist_max
= 10 -- max number to keep track of
51 -- ========================================================================
53 -- ========================================================================
55 -- ------------------------------------------------------------------------
56 -- log, right now write to stderr
58 io
.stderr
:write (str
.. "\n")
61 -- ========================================================================
62 -- MAIN ACCESS FUNCTIONS
63 -- ========================================================================
65 -- ------------------------------------------------------------------------
66 -- returns an iterator
67 function ls (dir
, fmt
)
68 local verbose
= fmt
and fmt
:match("l")
70 local s
= wmixp
:stat(dir
)
72 return function () return nil end
74 if s
.modestr
:match("^[^d]") then
76 return stat2str(verbose
, s
)
80 local itr
= wmixp
:idir (dir
)
91 return stat2str(verbose
, s
)
97 function stat2str(verbose
, stat
)
99 return string.format("%s %s %s %5d %s %s", stat
.modestr
, stat
.uid
, stat
.gid
, stat
.length
, stat
.timestr
, stat
.name
)
101 if stat
.modestr
:match("^d") then
102 return stat
.name
.. "/"
109 -- ------------------------------------------------------------------------
110 -- read all contents of a wmii virtual file
112 return wmixp
:read (file
)
115 -- ------------------------------------------------------------------------
116 -- return an iterator which walks all the lines in the file
119 -- for event in wmii.iread("/event")
122 function iread (file
)
123 return wmixp
:iread(file
)
126 -- ------------------------------------------------------------------------
127 -- returns an events iterator
129 local it
= iread("/event")
133 return string.match(line
, "(%S+)%s(.+)")
137 -- ------------------------------------------------------------------------
138 -- create a wmii file, optionally write data to it
139 function create (file
, data
)
140 wmixp
:create(file
, data
)
143 -- ------------------------------------------------------------------------
144 -- remove a wmii file
145 function remove (file
)
149 -- ------------------------------------------------------------------------
150 -- write a value to a wmii virtual file system
151 function write (file
, value
)
152 wmixp
:write (file
, value
)
155 -- ------------------------------------------------------------------------
156 -- setup a table describing dmenu command
157 local function dmenu_cmd (prompt
)
158 local cmdt
= { "dmenu", "-b" }
159 local fn
= getctl("font")
161 cmdt
[#cmdt
+1] = "-fn"
164 local normcolors
= getctl("normcolors")
166 local nf
, nb
= normcolors
:match("(#%x+)%s+(#%x+)%s#%x+")
168 cmdt
[#cmdt
+1] = "-nf"
169 cmdt
[#cmdt
+1] = "'" .. nf
.. "'"
172 cmdt
[#cmdt
+1] = "-nb"
173 cmdt
[#cmdt
+1] = "'" .. nb
.. "'"
176 local focuscolors
= getctl("focuscolors")
178 local sf
, sb
= focuscolors
:match("(#%x+)%s+(#%x+)%s#%x+")
180 cmdt
[#cmdt
+1] = "-sf"
181 cmdt
[#cmdt
+1] = "'" .. sf
.. "'"
184 cmdt
[#cmdt
+1] = "-sb"
185 cmdt
[#cmdt
+1] = "'" .. sb
.. "'"
190 cmdt
[#cmdt
+1] = "'" .. prompt
.. "'"
196 -- ------------------------------------------------------------------------
197 -- displays the menu given an table of entires, returns selected text
198 function menu (tbl
, prompt
)
199 local dmenu
= dmenu_cmd(prompt
)
201 local infile
= os
.tmpname()
202 local fh
= io
.open (infile
, "w+")
205 for i
,v
in pairs(tbl
) do
206 if type(i
) == 'number' and type(v
) == 'string' then
215 local outfile
= os
.tmpname()
217 dmenu
[#dmenu
+1] = "<"
218 dmenu
[#dmenu
+1] = infile
219 dmenu
[#dmenu
+1] = ">"
220 dmenu
[#dmenu
+1] = outfile
222 local cmd
= table.concat(dmenu
," ")
225 fh
= io
.open (outfile
, "r")
228 local sel
= fh
:read("*l")
234 -- ------------------------------------------------------------------------
235 -- displays the a tag selection menu, returns selected tag
237 local tags
= gettags()
239 return menu(tags
, "tag:")
242 -- ------------------------------------------------------------------------
243 -- displays the a program menu, returns selected program
245 local dmenu
= dmenu_cmd("cmd:")
247 local outfile
= os
.tmpname()
249 dmenu
[#dmenu
+1] = ">"
250 dmenu
[#dmenu
+1] = outfile
252 local cmd
= "dmenu_path |" .. table.concat(dmenu
," ")
255 local fh
= io
.open (outfile
, "rb")
258 local prog
= fh
:read("*l")
264 -- ------------------------------------------------------------------------
265 -- displays the a program menu, returns selected program
269 for s
in wmixp
:idir ("/tag") do
270 if s
.name
and not (s
.name
== "sel") then
278 -- ------------------------------------------------------------------------
279 -- displays the a program menu, returns selected program
281 local v
= wmixp
:read("/ctl") or ""
282 return v
:match("view%s+(%S+)")
285 -- ------------------------------------------------------------------------
286 -- changes the current view
287 -- if the argument is a number it moves to that view at that index
288 -- if the argument is a string it moves to that view name
289 function setview(sel
)
290 local cur
= getview()
291 local all
= gettags()
296 -- nothing to do if we have less then 2 tags
299 elseif type(sel
) == "number" then
302 elseif not (type(sel
) == "string") then
303 error ("number or string argument expected")
307 write ("/ctl", "view " .. sel
)
310 -- ------------------------------------------------------------------------
311 -- chnages to current view by offset given
312 function setviewofs(jump
)
313 local cur
= getview()
314 local all
= gettags()
317 -- nothing to do if we have less then 2 tags
322 if (jump
< - #all
) or (jump
> #all
) then
323 error ("view selector is out of range")
326 -- find the one that's selected index
329 for i
,v
in pairs (all
) do
330 if v
== cur
then curi
= i
end
334 local newi
= math
.fmod(#all
+ curi
+ jump
- 1, #all
) + 1
335 if (newi
< - #all
) or (newi
> #all
) then
336 error ("error computng new view")
339 write ("/ctl", "view " .. all
[newi
])
342 -- ------------------------------------------------------------------------
343 -- toggle between last view and current view
344 function toggleview()
345 local last
= view_hist
[#view_hist
]
351 -- ========================================================================
353 -- ========================================================================
355 local action_handlers
= {
357 write ("/ctl", "quit")
360 exec
= function (act
, args
)
361 local what
= args
or wmiirc
362 write ("/ctl", "exec " .. what
)
366 posix
.exec ("lua", wmiirc
)
370 -- TODO: consider storing list of executables around, and
371 -- this will then reinitialize that list
372 log (" TODO: rehash")
376 -- TODO: this should eventually update something on the /rbar
377 log (" TODO: status")
381 -- ========================================================================
383 -- ========================================================================
385 local key_handlers
= {
386 ["*"] = function (key
)
390 -- execution and actions
391 ["Mod1-Return"] = function (key
)
392 local xterm
= getconf("xterm")
393 log (" executing: " .. xterm
)
394 os
.execute (xterm
.. " &")
396 ["Mod1-a"] = function (key
)
397 local text
= menu(action_handlers
, "action:")
401 local si
= text
:find("%s")
403 act
,args
= string.match(text
.. " ", "(%w+)%s(.+)")
406 local fn
= action_handlers
[act
]
413 ["Mod1-p"] = function (key
)
414 local prog
= progmenu()
416 log (" executing: " .. prog
)
417 os
.execute (prog
.. " &")
420 ["Mod1-Shift-c"] = function (key
)
421 write ("/client/sel/ctl", "kill")
424 -- HJKL active selection
425 ["Mod1-h"] = function (key
)
426 write ("/tag/sel/ctl", "select left")
428 ["Mod1-l"] = function (key
)
429 write ("/tag/sel/ctl", "select right")
431 ["Mod1-j"] = function (key
)
432 write ("/tag/sel/ctl", "select down")
434 ["Mod1-k"] = function (key
)
435 write ("/tag/sel/ctl", "select up")
439 ["Mod1-Shift-h"] = function (key
)
440 write ("/tag/sel/ctl", "send sel left")
442 ["Mod1-Shift-l"] = function (key
)
443 write ("/tag/sel/ctl", "send sel right")
445 ["Mod1-Shift-j"] = function (key
)
446 write ("/tag/sel/ctl", "send sel down")
448 ["Mod1-Shift-k"] = function (key
)
449 write ("/tag/sel/ctl", "send sel up")
453 ["Mod1-space"] = function (key
)
454 write ("/tag/sel/ctl", "select toggle")
456 ["Mod1-Shift-space"] = function (key
)
457 write ("/tag/sel/ctl", "send sel toggle")
461 ["Mod4-#"] = function (key
, num
)
464 ["Mod4-Shift-#"] = function (key
, num
)
465 write ("/client/sel/tags", tostring(num
))
467 ["Mod1-comma"] = function (key
)
470 ["Mod1-period"] = function (key
)
473 ["Mod1-r"] = function (key
)
477 -- switching views and retagging
478 ["Mod1-t"] = function (key
)
479 local tag = tagmenu()
485 ["Mod1-Shift-t"] = function (key
)
486 local tag = tagmenu()
488 local cli
= read ("/client/sel/ctl")
489 write ("/client/" .. cli
.. "/tags", tag)
492 ["Mod1-Control-t"] = function (key
)
493 log (" TODO: Mod1-Control-t: " .. key
)
497 ["Mod1-d"] = function (key
)
498 write("/tag/sel/ctl", "colmode sel default")
500 ["Mod1-s"] = function (key
)
501 write("/tag/sel/ctl", "colmode sel stack")
503 ["Mod1-m"] = function (key
)
504 write("/tag/sel/ctl", "colmode sel max")
508 -- ------------------------------------------------------------------------
509 -- update the /keys wmii file with the list of all handlers
510 function update_active_keys ()
513 for x
,y
in pairs(key_handlers
) do
515 local i
= x
:find("#")
528 local all_keys
= table.concat(t
, "\n")
529 --log ("setting /keys to...\n" .. all_keys .. "\n");
530 write ("/keys", all_keys
)
533 -- ------------------------------------------------------------------------
534 -- update the /lbar wmii file with the current tags
535 function update_displayed_tags ()
537 local fc
= getctl("focuscolors") or ""
538 local nc
= getctl("normcolors") or ""
540 -- build up a table of existing tags in the /lbar
543 for s
in wmixp
:idir ("/lbar") do
547 -- for all actual tags in use create any entries in /lbar we don't have
548 -- clear the old table entries if we have them
549 local cur
= getview()
550 local all
= gettags()
552 for i
,v
in pairs(all
) do
558 create ("/lbar/" .. v
, color
.. " " .. v
)
560 write ("/lbar/" .. v
, color
.. " " .. v
)
564 -- anything left in the old table should be removed now
565 for i
,v
in pairs(old
) do
572 -- ========================================================================
574 -- ========================================================================
576 local ev_handlers
= {
577 ["*"] = function (ev
, arg
)
578 log ("ev: " .. ev
.. " - " .. arg
)
581 -- exit if another wmiirc started up
582 Start
= function (ev
, arg
)
583 if arg
== "wmiirc" then
589 CreateTag
= function (ev
, arg
)
590 local nc
= getctl("normcolors") or ""
591 create ("/lbar/" .. arg
, nc
.. " " .. arg
)
593 DestroyTag
= function (ev
, arg
)
594 remove ("/lbar/" .. arg
)
597 FocusTag
= function (ev
, arg
)
598 local fc
= getctl("focuscolors") or ""
599 create ("/lbar/" .. arg
, fc
.. " " .. arg
)
600 write ("/lbar/" .. arg
, fc
.. " " .. arg
)
602 UnfocusTag
= function (ev
, arg
)
603 local nc
= getctl("normcolors") or ""
604 create ("/lbar/" .. arg
, nc
.. " " .. arg
)
605 write ("/lbar/" .. arg
, nc
.. " " .. arg
)
607 -- don't duplicate the last entry
608 if not (arg
== view_hist
[#view_hist
]) then
609 view_hist
[#view_hist
+1] = arg
611 -- limit to view_hist_max
612 if #view_hist
> view_hist_max
then
613 table.remove(view_hist
, 1)
618 -- key event handling
619 Key
= function (ev
, arg
)
622 -- can we find an exact match?
623 local fn
= key_handlers
[arg
]
625 local key
= arg
:gsub("-%d+", "-#")
626 -- can we find a match with a # wild card for the number
627 fn
= key_handlers
[key
]
629 -- convert the trailing number to a number
630 num
= tonumber(arg
:match("-(%d+)"))
632 -- everything else failed, try default match
633 fn
= key_handlers
["*"]
641 -- mouse handling on the lbar
642 LeftBarClick
= function (ev
, arg
)
643 local button
,tag = string.match(arg
, "(%w+)%s+(%w+)")
648 ClientFocus
= function (ev
, arg
)
649 log ("ClientFocus: " .. arg
)
651 ColumnFocus
= function (ev
, arg
)
652 log ("ColumnFocus: " .. arg
)
656 UrgentTag
= function (ev
, arg
)
657 log ("UrgentTag: " .. arg
)
658 -- wmiir xwrite "/lbar/$@" "*$@"
660 NotUrgentTag
= function (ev
, arg
)
661 log ("NotUrgentTag: " .. arg
)
662 -- wmiir xwrite "/lbar/$@" "$@"
667 -- ========================================================================
668 -- MAIN INTERFACE FUNCTIONS
669 -- ========================================================================
672 xterm
= 'x-terminal-emulator'
675 -- ------------------------------------------------------------------------
676 -- write configuration to /ctl wmii file
677 -- setctl({ "var" = "val", ...})
678 -- setctl("var, "val")
679 function setctl (first
,second
)
680 if type(first
) == "table" and second
== nil then
682 for x
, y
in pairs(first
) do
683 write ("/ctl", x
.. " " .. y
)
686 elseif type(first
) == "string" and type(second
) == "string" then
687 write ("/ctl", first
.. " " .. second
)
690 error ("expecting a table or two string arguments")
694 -- ------------------------------------------------------------------------
695 -- read a value from /ctl wmii file
696 function getctl (name
)
698 for s
in iread("/ctl") do
699 local var
,val
= s
:match("(%w+)%s+(.+)")
707 -- ------------------------------------------------------------------------
708 -- set an internal wmiirc.lua variable
709 -- setconf({ "var" = "val", ...})
710 -- setconf("var, "val")
711 function setconf (first
,second
)
712 if type(first
) == "table" and second
== nil then
714 for x
, y
in pairs(first
) do
718 elseif type(first
) == "string" and type(second
) == "string" then
719 config
[first
] = second
722 error ("expecting a table or two string arguments")
726 -- ------------------------------------------------------------------------
727 -- read an internal wmiirc.lua variable
728 function getconf (name
)
732 -- ------------------------------------------------------------------------
733 -- run the event loop and process events, this function does not exit
734 function run_event_loop ()
735 log("wmii: updating lbar")
737 update_displayed_tags ()
739 log("wmii: updating active keys")
741 update_active_keys ()
743 log("wmii: starting event loop")
745 for ev
, arg
in ievents() do
747 local fn
= ev_handlers
[ev
] or ev_handlers
["*"]
752 log("wmii: event loop exited")