remove debug printf's from ixp lib
[wmiirc-lua.git] / wmii.lua
blob0e5f9f403d621e42fecf170791bfe590cdeb5def
1 --
2 -- Copyrigh (c) 2007, Bart Trojanowski <bart@jukie.net>
3 --
4 -- Simple wmiir like interface.
5 --
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.
9 --
10 -- git://www.jukie.net/wmiirc-lua.git/
12 package.cpath = package.cpath .. ";" .. os.getenv("HOME") .. "/.wmii-3.5/ixp/?.so"
13 require "ixp"
14 local ixp = ixp
16 local base = _G
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")
23 local type = type
24 local error = error
25 local print = print
26 local pairs = pairs
27 local tostring = tostring
28 local tonumber = tonumber
30 module("wmii")
32 -- ========================================================================
33 -- MODULE VARIABLES
34 -- ========================================================================
36 -- wmiir points to the wmiir executable
37 local wmiir = "wmiir"
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 -- ========================================================================
52 -- LOCAL HELPERS
53 -- ========================================================================
55 -- ------------------------------------------------------------------------
56 -- log, right now write to stderr
57 function log (str)
58 io.stderr:write (str .. "\n")
59 end
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)
71 if not s then
72 return function () return nil end
73 end
74 if s.modestr:match("^[^d]") then
75 return function ()
76 return stat2str(verbose, s)
77 end
78 end
80 local itr = wmixp:idir (dir)
81 if not itr then
82 --return function ()
83 return nil
84 --end
85 end
88 return function ()
89 local s = itr()
90 if s then
91 return stat2str(verbose, s)
92 end
93 return nil
94 end
95 end
97 function stat2str(verbose, stat)
98 if verbose then
99 return string.format("%s %s %s %5d %s %s", stat.modestr, stat.uid, stat.gid, stat.length, stat.timestr, stat.name)
100 else
101 if stat.modestr:match("^d") then
102 return stat.name .. "/"
103 else
104 return stat.name
109 -- ------------------------------------------------------------------------
110 -- read all contents of a wmii virtual file
111 function read (file)
112 return wmixp:read (file)
115 -- ------------------------------------------------------------------------
116 -- return an iterator which walks all the lines in the file
118 -- example:
119 -- for event in wmii.iread("/event")
120 -- ...
121 -- end
122 function iread (file)
123 return wmixp:iread(file)
126 -- ------------------------------------------------------------------------
127 -- returns an events iterator
128 function ievents ()
129 local it = iread("/event")
131 return function ()
132 local line = it()
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)
146 wmixp: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
157 function menu (tbl)
159 local infile = os.tmpname()
160 local fh = io.open (infile, "w+")
162 for n in pairs(tbl) do
163 fh:write (n)
164 fh:write ("\n")
166 fh:close()
168 local outfile = os.tmpname()
170 os.execute ("dmenu < " .. infile .. " > " .. outfile)
172 fh = io.open (outfile, "r")
173 os.remove (outfile)
175 local sel = fh:read("*l")
176 fh:close()
178 return sel
181 -- ------------------------------------------------------------------------
182 -- displays the a tag selection menu, returns selected tag
183 function tagmenu ()
184 local tmpfile = os.tmpname()
186 os.execute ("wmiir ls /tag | sed 's|/||; /^sel$/d' | dmenu > " .. tmpfile)
188 local fh = io.open (tmpfile, "rb")
189 os.remove (tmpfile)
191 local tag = fh:read("*l")
192 io.close (fh)
194 return tag
197 -- ------------------------------------------------------------------------
198 -- displays the a program menu, returns selected program
199 function progmenu ()
200 local tmpfile = os.tmpname()
202 os.execute ("dmenu_path | dmenu > " .. tmpfile)
204 local fh = io.open (tmpfile, "rb")
205 os.remove (tmpfile)
207 local prog = fh:read("*l")
208 io.close (fh)
210 return prog
213 -- ------------------------------------------------------------------------
214 -- displays the a program menu, returns selected program
215 function gettags()
216 local t = {}
217 local s
218 for s in wmixp:idir ("/tag") do
219 if s.name and not (s.name == "sel") then
220 t[#t + 1] = s.name
223 table.sort(t)
224 return t
227 -- ------------------------------------------------------------------------
228 -- displays the a program menu, returns selected program
229 function getview()
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()
242 if #all < 2 then
243 -- nothing to do if we have less then 2 tags
244 return
246 elseif type(sel) == "number" then
247 -- range check
248 if (sel < - #all) or (sel > #all) then
249 error ("view selector is out of range")
252 -- find the one that's selected index
253 local curi = nil
254 local i,v
255 for i,v in pairs (all) do
256 if v == cur then curi = i end
259 -- adjust by index
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")
265 sel = all[newi]
267 elseif not (type(sel) == "string") then
268 error ("number or string argument expected")
271 -- set new view
272 write ("/ctl", "view " .. sel)
275 function toggleview()
276 local last = view_hist[#view_hist]
277 if last then
278 setview(last)
282 -- ========================================================================
283 -- ACTION HANDLERS
284 -- ========================================================================
286 local action_handlers = {
287 quit = function ()
288 write ("/ctl", "quit")
289 end,
291 exec = function (act, args)
292 local what = args or wmiirc
293 write ("/ctl", "exec " .. what)
294 end,
296 wmiirc = function ()
297 posix.exec ("lua", wmiirc)
298 end,
300 rehash = function ()
301 -- TODO: consider storing list of executables around, and
302 -- this will then reinitialize that list
303 log (" TODO: rehash")
304 end,
306 status = function ()
307 -- TODO: this should eventually update something on the /rbar
308 log (" TODO: status")
312 -- ========================================================================
313 -- KEY HANDLERS
314 -- ========================================================================
316 local key_handlers = {
317 ["*"] = function (key)
318 log ("*: " .. key)
319 end,
321 -- execution and actions
322 ["Mod1-Return"] = function (key)
323 local xterm = getconf("xterm")
324 log (" executing: " .. xterm)
325 os.execute (xterm .. " &")
326 end,
327 ["Mod1-a"] = function (key)
328 local text = menu (action_handlers)
329 if text then
330 local act = text
331 local args = nil
332 local si = text:find("%s")
333 if si then
334 act,args = string.match(text .. " ", "(%w+)%s(.+)")
336 if act then
337 local fn = action_handlers[act]
338 if fn then
339 fn (act,args)
343 end,
344 ["Mod1-p"] = function (key)
345 local prog = progmenu()
346 if prog then
347 log (" executing: " .. prog)
348 os.execute (prog .. " &")
350 end,
351 ["Mod1-Shift-c"] = function (key)
352 write ("/client/sel/ctl", "kill")
353 end,
355 -- HJKL active selection
356 ["Mod1-h"] = function (key)
357 write ("/tag/sel/ctl", "select left")
358 end,
359 ["Mod1-l"] = function (key)
360 write ("/tag/sel/ctl", "select right")
361 end,
362 ["Mod1-j"] = function (key)
363 write ("/tag/sel/ctl", "select down")
364 end,
365 ["Mod1-k"] = function (key)
366 write ("/tag/sel/ctl", "select up")
367 end,
369 -- HJKL movement
370 ["Mod1-Shift-h"] = function (key)
371 write ("/tag/sel/ctl", "send sel left")
372 end,
373 ["Mod1-Shift-l"] = function (key)
374 write ("/tag/sel/ctl", "send sel right")
375 end,
376 ["Mod1-Shift-j"] = function (key)
377 write ("/tag/sel/ctl", "send sel down")
378 end,
379 ["Mod1-Shift-k"] = function (key)
380 write ("/tag/sel/ctl", "send sel up")
381 end,
383 -- floating vs tiled
384 ["Mod1-space"] = function (key)
385 write ("/tag/sel/ctl", "select toggle")
386 end,
387 ["Mod1-Shift-space"] = function (key)
388 write ("/tag/sel/ctl", "send sel toggle")
389 end,
391 -- work spaces
392 ["Mod4-#"] = function (key, num)
393 setview (tostring(num))
394 end,
395 ["Mod4-Shift-#"] = function (key, num)
396 write ("/client/sel/tags", tostring(num))
397 end,
398 ["Mod1-comma"] = function (key)
399 setview (-1)
400 end,
401 ["Mod1-period"] = function (key)
402 setview (1)
403 end,
404 ["Mod1-r"] = function (key)
405 toggleview()
406 end,
408 -- switching views and retagging
409 ["Mod1-t"] = function (key)
410 local tag = tagmenu()
411 if tag then
412 setview (tag)
415 end,
416 ["Mod1-Shift-t"] = function (key)
417 local tag = tagmenu()
418 if tag then
419 local cli = read ("/client/sel/ctl")
420 write ("/client/" .. cli .. "/tags", tag)
422 end,
423 ["Mod1-Control-t"] = function (key)
424 log (" TODO: Mod1-Control-t: " .. key)
425 end,
427 -- column modes
428 ["Mod1-d"] = function (key)
429 write("/tag/sel/ctl", "colmode sel default")
430 end,
431 ["Mod1-s"] = function (key)
432 write("/tag/sel/ctl", "colmode sel stack")
433 end,
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 ()
443 local t = {}
444 local x, y
445 for x,y in pairs(key_handlers) do
446 if x:find("%w") then
447 local i = x:find("#")
448 if i then
449 local j
450 for j=0,9 do
451 t[#t + 1]
452 = x:sub(1,i-1) .. j
454 else
455 t[#t + 1]
456 = tostring(x)
460 local all_keys = table.concat(t, "\n")
461 log ("setting /keys to...\n" .. all_keys .. "\n");
462 write ("/keys", all_keys)
466 -- ========================================================================
467 -- EVENT HANDLERS
468 -- ========================================================================
470 local ev_handlers = {
471 ["*"] = function (ev, arg)
472 log ("ev: " .. ev .. " - " .. arg)
473 end,
475 -- exit if another wmiirc started up
476 Start = function (ev, arg)
477 if arg == "wmiirc" then
478 posix.exit (0)
480 end,
482 -- tag management
483 CreateTag = function (ev, arg)
484 local fc = getctl("focuscolors") or ""
485 create ("/lbar/" .. arg, fc .. " " .. arg)
486 end,
487 DestroyTag = function (ev, arg)
488 remove ("/lbar/" .. arg)
489 end,
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)
496 end,
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)
512 end,
514 -- key event handling
515 Key = function (ev, arg)
516 log ("Key: " .. arg)
517 local num = nil
518 -- can we find an exact match?
519 local fn = key_handlers[arg]
520 if not fn then
521 local key = arg:gsub("-%d+", "-#")
522 -- can we find a match with a # wild card for the number
523 fn = key_handlers[key]
524 if fn then
525 -- convert the trailing number to a number
526 num = tonumber(arg:match("-(%d+)"))
527 else
528 -- everything else failed, try default match
529 fn = key_handlers["*"]
532 if fn then
533 fn (arg, num)
535 end,
537 -- mouse handling on the lbar
538 LeftBarClick = function (ev, arg)
539 local button,tag = string.match(arg, "(%w+)%s+(%w+)")
540 setview (tag)
541 end,
543 -- focus updates
544 ClientFocus = function (ev, arg)
545 log ("ClientFocus: " .. arg)
546 end,
547 ColumnFocus = function (ev, arg)
548 log ("ColumnFocus: " .. arg)
549 end,
551 -- urgent tag?
552 UrgentTag = function (ev, arg)
553 log ("UrgentTag: " .. arg)
554 -- wmiir xwrite "/lbar/$@" "*$@"
555 end,
556 NotUrgentTag = function (ev, arg)
557 log ("NotUrgentTag: " .. arg)
558 -- wmiir xwrite "/lbar/$@" "$@"
563 -- ========================================================================
564 -- MAIN INTERFACE FUNCTIONS
565 -- ========================================================================
567 local config = {
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
577 local x, y
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)
585 else
586 error ("expecting a table or two string arguments")
590 -- ------------------------------------------------------------------------
591 -- read a value from /ctl wmii file
592 function getctl (name)
593 local s
594 for s in iread("/ctl") do
595 local var,val = s:match("(%w+)%s+(.+)")
596 if var == name then
597 return val
600 return nil
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
609 local x, y
610 for x, y in pairs(first) do
611 config[x] = y
614 elseif type(first) == "string" and type(second) == "string" then
615 config[first] = second
617 else
618 error ("expecting a table or two string arguments")
622 -- ------------------------------------------------------------------------
623 -- read an internal wmiirc.lua variable
624 function getconf (name)
625 return config[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")
637 local ev, arg
638 for ev, arg in ievents() do
640 local fn = ev_handlers[ev] or ev_handlers["*"]
641 if fn then
642 fn (ev, arg)
645 log("wmii: event loop exited")