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 -- displays the menu given an table of entires, returns selected text
159 local infile
= os
.tmpname()
160 local fh
= io
.open (infile
, "w+")
162 for n
in pairs(tbl
) do
168 local outfile
= os
.tmpname()
170 os
.execute ("dmenu < " .. infile
.. " > " .. outfile
)
172 fh
= io
.open (outfile
, "r")
175 local sel
= fh
:read("*l")
181 -- ------------------------------------------------------------------------
182 -- displays the a tag selection menu, returns selected tag
184 local tmpfile
= os
.tmpname()
186 os
.execute ("wmiir ls /tag | sed 's|/||; /^sel$/d' | dmenu > " .. tmpfile
)
188 local fh
= io
.open (tmpfile
, "rb")
191 local tag = fh
:read("*l")
197 -- ------------------------------------------------------------------------
198 -- displays the a program menu, returns selected program
200 local tmpfile
= os
.tmpname()
202 os
.execute ("dmenu_path | dmenu > " .. tmpfile
)
204 local fh
= io
.open (tmpfile
, "rb")
207 local prog
= fh
:read("*l")
213 -- ------------------------------------------------------------------------
214 -- displays the a program menu, returns selected program
218 for s
in wmixp
:idir ("/tag") do
219 if s
.name
and not (s
.name
== "sel") then
227 -- ------------------------------------------------------------------------
228 -- displays the a program menu, returns selected program
230 local v
= wmixp
:read("/ctl") or ""
231 return v
:match("view%s+(%S+)")
234 -- ------------------------------------------------------------------------
235 -- changes the current view
236 -- if the argument is a number it shifts the view left or right by that count
237 -- if the argument is a string it moves to that view name
238 function setview(sel
)
239 local cur
= getview()
240 local all
= gettags()
243 -- nothing to do if we have less then 2 tags
246 elseif type(sel
) == "number" then
248 if (sel
< - #all
) or (sel
> #all
) then
249 error ("view selector is out of range")
252 -- find the one that's selected index
255 for i
,v
in pairs (all
) do
256 if v
== cur
then curi
= i
end
260 local newi
= math
.fmod(#all
+ curi
+ sel
- 1, #all
) + 1
261 if (newi
< - #all
) or (newi
> #all
) then
262 error ("error computng new view")
267 elseif not (type(sel
) == "string") then
268 error ("number or string argument expected")
272 write ("/ctl", "view " .. sel
)
275 function toggleview()
276 local last
= view_hist
[#view_hist
]
282 -- ========================================================================
284 -- ========================================================================
286 local action_handlers
= {
288 write ("/ctl", "quit")
291 exec
= function (act
, args
)
292 local what
= args
or wmiirc
293 write ("/ctl", "exec " .. what
)
297 posix
.exec ("lua", wmiirc
)
301 -- TODO: consider storing list of executables around, and
302 -- this will then reinitialize that list
303 log (" TODO: rehash")
307 -- TODO: this should eventually update something on the /rbar
308 log (" TODO: status")
312 -- ========================================================================
314 -- ========================================================================
316 local key_handlers
= {
317 ["*"] = function (key
)
321 -- execution and actions
322 ["Mod1-Return"] = function (key
)
323 local xterm
= getconf("xterm")
324 log (" executing: " .. xterm
)
325 os
.execute (xterm
.. " &")
327 ["Mod1-a"] = function (key
)
328 local text
= menu (action_handlers
)
332 local si
= text
:find("%s")
334 act
,args
= string.match(text
.. " ", "(%w+)%s(.+)")
337 local fn
= action_handlers
[act
]
344 ["Mod1-p"] = function (key
)
345 local prog
= progmenu()
347 log (" executing: " .. prog
)
348 os
.execute (prog
.. " &")
351 ["Mod1-Shift-c"] = function (key
)
352 write ("/client/sel/ctl", "kill")
355 -- HJKL active selection
356 ["Mod1-h"] = function (key
)
357 write ("/tag/sel/ctl", "select left")
359 ["Mod1-l"] = function (key
)
360 write ("/tag/sel/ctl", "select right")
362 ["Mod1-j"] = function (key
)
363 write ("/tag/sel/ctl", "select down")
365 ["Mod1-k"] = function (key
)
366 write ("/tag/sel/ctl", "select up")
370 ["Mod1-Shift-h"] = function (key
)
371 write ("/tag/sel/ctl", "send sel left")
373 ["Mod1-Shift-l"] = function (key
)
374 write ("/tag/sel/ctl", "send sel right")
376 ["Mod1-Shift-j"] = function (key
)
377 write ("/tag/sel/ctl", "send sel down")
379 ["Mod1-Shift-k"] = function (key
)
380 write ("/tag/sel/ctl", "send sel up")
384 ["Mod1-space"] = function (key
)
385 write ("/tag/sel/ctl", "select toggle")
387 ["Mod1-Shift-space"] = function (key
)
388 write ("/tag/sel/ctl", "send sel toggle")
392 ["Mod4-#"] = function (key
, num
)
393 setview (tostring(num
))
395 ["Mod4-Shift-#"] = function (key
, num
)
396 write ("/client/sel/tags", tostring(num
))
398 ["Mod1-comma"] = function (key
)
401 ["Mod1-period"] = function (key
)
404 ["Mod1-r"] = function (key
)
408 -- switching views and retagging
409 ["Mod1-t"] = function (key
)
410 local tag = tagmenu()
416 ["Mod1-Shift-t"] = function (key
)
417 local tag = tagmenu()
419 local cli
= read ("/client/sel/ctl")
420 write ("/client/" .. cli
.. "/tags", tag)
423 ["Mod1-Control-t"] = function (key
)
424 log (" TODO: Mod1-Control-t: " .. key
)
428 ["Mod1-d"] = function (key
)
429 write("/tag/sel/ctl", "colmode sel default")
431 ["Mod1-s"] = function (key
)
432 write("/tag/sel/ctl", "colmode sel stack")
434 ["Mod1-m"] = function (key
)
435 write("/tag/sel/ctl", "colmode sel max")
439 -- ------------------------------------------------------------------------
440 -- update the /keys wmii file with the list of all handlers
442 function update_active_keys ()
445 for x
,y
in pairs(key_handlers
) do
447 local i
= x
:find("#")
460 local all_keys
= table.concat(t
, "\n")
461 log ("setting /keys to...\n" .. all_keys
.. "\n");
462 write ("/keys", all_keys
)
466 -- ========================================================================
468 -- ========================================================================
470 local ev_handlers
= {
471 ["*"] = function (ev
, arg
)
472 log ("ev: " .. ev
.. " - " .. arg
)
475 -- exit if another wmiirc started up
476 Start
= function (ev
, arg
)
477 if arg
== "wmiirc" then
483 CreateTag
= function (ev
, arg
)
484 local fc
= getctl("focuscolors") or ""
485 create ("/lbar/" .. arg
, fc
.. " " .. arg
)
487 DestroyTag
= function (ev
, arg
)
488 remove ("/lbar/" .. arg
)
491 FocusTag
= function (ev
, arg
)
492 local fc
= getctl("focuscolors") or ""
493 log ("FocusTag: " .. arg
:gsub("%W",".") .. '--')
494 create ("/lbar/" .. arg
, fc
.. " " .. arg
)
495 write ("/lbar/" .. arg
, fc
.. " " .. arg
)
497 UnfocusTag
= function (ev
, arg
)
498 local nc
= getctl("normcolors") or ""
499 log ("UnfocusTag: " .. arg
:gsub("%W",".") .. '--')
500 create ("/lbar/" .. arg
, nc
.. " " .. arg
)
501 write ("/lbar/" .. arg
, nc
.. " " .. arg
)
503 -- don't duplicate the last entry
504 if not (arg
== view_hist
[#view_hist
]) then
505 view_hist
[#view_hist
+1] = arg
507 -- limit to view_hist_max
508 if #view_hist
> view_hist_max
then
509 table.remove(view_hist
, 1)
514 -- key event handling
515 Key
= function (ev
, arg
)
518 -- can we find an exact match?
519 local fn
= key_handlers
[arg
]
521 local key
= arg
:gsub("-%d+", "-#")
522 -- can we find a match with a # wild card for the number
523 fn
= key_handlers
[key
]
525 -- convert the trailing number to a number
526 num
= tonumber(arg
:match("-(%d+)"))
528 -- everything else failed, try default match
529 fn
= key_handlers
["*"]
537 -- mouse handling on the lbar
538 LeftBarClick
= function (ev
, arg
)
539 local button
,tag = string.match(arg
, "(%w+)%s+(%w+)")
544 ClientFocus
= function (ev
, arg
)
545 log ("ClientFocus: " .. arg
)
547 ColumnFocus
= function (ev
, arg
)
548 log ("ColumnFocus: " .. arg
)
552 UrgentTag
= function (ev
, arg
)
553 log ("UrgentTag: " .. arg
)
554 -- wmiir xwrite "/lbar/$@" "*$@"
556 NotUrgentTag
= function (ev
, arg
)
557 log ("NotUrgentTag: " .. arg
)
558 -- wmiir xwrite "/lbar/$@" "$@"
563 -- ========================================================================
564 -- MAIN INTERFACE FUNCTIONS
565 -- ========================================================================
568 xterm
= 'x-terminal-emulator'
571 -- ------------------------------------------------------------------------
572 -- write configuration to /ctl wmii file
573 -- setctl({ "var" = "val", ...})
574 -- setctl("var, "val")
575 function setctl (first
,second
)
576 if type(first
) == "table" and second
== nil then
578 for x
, y
in pairs(first
) do
579 write ("/ctl", x
.. " " .. y
)
582 elseif type(first
) == "string" and type(second
) == "string" then
583 write ("/ctl", first
.. " " .. second
)
586 error ("expecting a table or two string arguments")
590 -- ------------------------------------------------------------------------
591 -- read a value from /ctl wmii file
592 function getctl (name
)
594 for s
in iread("/ctl") do
595 local var
,val
= s
:match("(%w+)%s+(.+)")
603 -- ------------------------------------------------------------------------
604 -- set an internal wmiirc.lua variable
605 -- setconf({ "var" = "val", ...})
606 -- setconf("var, "val")
607 function setconf (first
,second
)
608 if type(first
) == "table" and second
== nil then
610 for x
, y
in pairs(first
) do
614 elseif type(first
) == "string" and type(second
) == "string" then
615 config
[first
] = second
618 error ("expecting a table or two string arguments")
622 -- ------------------------------------------------------------------------
623 -- read an internal wmiirc.lua variable
624 function getconf (name
)
628 -- ------------------------------------------------------------------------
629 -- run the event loop and process events, this function does not exit
630 function run_event_loop ()
632 log("wmii: updating active keys")
634 update_active_keys ()
636 log("wmii: starting event loop")
638 for ev
, arg
in ievents() do
640 local fn
= ev_handlers
[ev
] or ev_handlers
["*"]
645 log("wmii: event loop exited")