#!/usr/bin/env python # leash.py - a simple curses-based ui for boodler # For this script to be useful, you must be running Boodler with # the listen.Leash module: # python boodler.py --listen listen.Leash # By Paul Mazaitis: comments to comment@goob.com # Version 1.0 - 02.10.2002 # Version 0.9 - 02.03.2002 # Licensed under the GPL (see http://www.gnu.org/licenses/gpl.html) # Note: this is not happy code. But it works, more or less. import curses, traceback import socket, getopt import os, re, string, sys from boodle import listen from boodle.agent import * # constants sendboodler = 1 keepgoing = 1 # Command line options (opts,args) = getopt.getopt(sys.argv[1:], 'qh', ['quiet', 'help']) if (len(opts) > 0 ) : if (opts[0][0] == '-h') or (opts[0][0] == '--help') : print 'usage:', sys.argv[0], '[--quiet] [--help]' print ' --quiet: do not send events to boodler process' print ' --help: print this message' print '' sys.exit() if (sys.version[0] == '1' or sys.version[0:3] == '2.0'): print 'Sorry; leash needs python of at least version 2.1 to run...' print '' sys.exit() # Functions def main(stdscr): global sendboodler global opts if (len(opts) > 0) : if (opts[0][0] == '-q') or (opts[0][0] == '--quiet') : sendboodler = 0 global statuswidth global statusheight global statuswidth_t global groupheight_t global groupwidth_t global effectheight_t global effectwidth_t # Screen Constants geo = stdscr.getmaxyx() if (geo[1] < 40) or (geo[0] < 10) : curses.echo() curses.nocbreak() curses.curs_set(1) curses.endwin() print 'Leash needs at least a 40x10 screen!' print '' sys.exit() screenwidth = geo[1] - 1 screenheight = geo[0] - 1 statuswidth = screenwidth statusheight = 4 statuswidth_t = statuswidth - 2 statusheight_t = statusheight - 2 groupwidth = 20 groupheight = screenheight - statusheight groupwidth_t = groupwidth - 2 groupheight_t = groupheight - 2 effectwidth = screenwidth - groupwidth - 1 effectheight = screenheight - statusheight effectwidth_t = effectwidth - 2 effectheight_t = effectheight - 2 # Set up the socket connections and such thing = listen.Listener(handleRCE,31864) global sock try: if (sendboodler): sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.connect(('localhost', 31863)) except: curses.echo() curses.nocbreak() curses.curs_set(1) curses.endwin() print 'Cannot connect to boodler! (run boodler, or use --quiet)' sys.exit() # Set up windows global status_s status_s = stdscr.subwin(statusheight, statuswidth, 0, 0) status_s.box() status_s.addstr(1,1,padstring("Welcome to boodler! (h or 'menu' for help)",statuswidth_t)) status_s.refresh() global group_s group_s = stdscr.subwin(groupheight, groupwidth, statusheight + 1, 0) group_s.box() group_s.refresh() global effect_s effect_s = stdscr.subwin(effectheight, effectwidth, statusheight+1, groupwidth+1) effect_s.box() effect_s.refresh() # Get the big DS of modules global biglist biglist = getdata() # Grumpy defaults # state = hold the current program state in a tuple # Current group, current effect, mode (0 = group, 1 = effect) state = [0,0,0] # display displaygroups(state) displayhelp() # loop for event global keepgoing while (keepgoing == 1) : # while (1) : y = thing.poll() key = stdscr.getch() # Handle key input here if key == curses.KEY_UP : if state[2] == 0 : group_up(state) else: effect_up(state) elif key == curses.KEY_DOWN : if state[2] == 0 : group_down(state) else: effect_down(state) elif key == curses.KEY_RIGHT : if state[2] == 0 : state[2] = 1 displaygroups(state) displayeffects(state) elif key == curses.KEY_LEFT : if state[2] == 1 : state[2] = 0 displaygroups(state) displayeffects(state) elif key == 10 : if state[2] == 0 : state[2] = 1 displaygroups(state) displayeffects(state) else : playsound(state) elif (key == ord('t')) or (key == ord('T')) : playtime() elif (key == ord('s')) or (key == ord('S')) : stopsound() elif (key == ord('h')) or (key == ord('H')) : displayhelp() elif (key == ord('q')) or (key == ord('Q')) : keepgoing = 0 # Here endeth the while def handleRCE(event): global keepgoing if event == 'remote up' : if state[2] == 0 : group_up(state) else: effect_up(state) elif event == 'remote down' : if state[2] == 0 : group_down(state) else: effect_down(state) elif event == 'remote left' : if state[2] == 0 : state[2] = 1 displaygroups(state) displayeffects(state) elif event == 'remote right' : if state[2] == 1 : state[2] = 0 displaygroups(state) displayeffects(state) elif event == 'remote enter' : if state[2] == 0 : state[2] = 1 displaygroups(state) displayeffects(state) else : playsound(state) elif event == 'remote stop' : stopsound() elif event == 'remote menu' : displayhelp() elif event == 'remote play' : playtime() elif event == 'remote exit' : keepgoing = 0 return def padstring(string, margin) : if (len(string) < margin) : padding = ( ' ' * (margin - len(string))) paddedstring = string + padding else : paddedstring = string[0:(margin-4)] + '...' return paddedstring def displayhelp() : global effectheight_t global effectwidth_t helptext = [ "Commands: (keyboard, remote)", "", "Navigaion: arrow keys", "Selection: enter, OK", "Help: h, menu", "Stop: s, stop", "Quit: q, exit", ""] # "Current Time: t, play"] for x in range(0,len(helptext)) : line = padstring(helptext[x], effectwidth_t) effect_s.addstr((x+1),1,line) for y in range(len(helptext), effectheight_t) : effect_s.addstr(y,1,(effectwidth_t * ' ')) effect_s.refresh() return def playtime(): if (sendboodler) : sock.send('time\n') return def stopsound(): global sock global statuswidth_t status_s.addstr(1,1,padstring("Welcome to boodler! (h or 'menu' for help)",statuswidth_t)) status_s.addstr(2,1,(' ' * statuswidth_t)) status_s.refresh() if (sendboodler) : sock.send('agent play.NullAgent \n') return def playsound(state) : global sendboodler global sock global statuswidth_t desc = padstring(biglist[state[0]][1][state[1]]['desc'], statuswidth_t) status_s.addstr(1,1,padstring("You are listening to:",statuswidth_t)) status_s.addstr(2,1, desc) status_s.refresh() if (sendboodler) : sock.send('agent ' + biglist[state[0]][1][state[1]]['module'] + '\n') return def group_up(state): state[1] = 0 if state[0] > 0 : state[0] = state[0] - 1 displaygroups(state) displayeffects(state) return state def group_down(state): state[1] = 0 if (state[0] < (len(biglist) - 1) ) : state[0] = state[0] + 1 displaygroups(state) displayeffects(state) return state def effect_up(state): if state[1] > 0 : state[1] = state[1] - 1 displaygroups(state) displayeffects(state) return state def effect_down(state): if (state[1] < (len(biglist[state[0]][1]) -1)) : state[1] = state[1] + 1 displaygroups(state) displayeffects(state) return state def displaygroups(state): maxoffset = 0 global groupheight_t global groupwidth_t if len(biglist) > groupheight_t : lines = groupheight_t maxoffset = len(biglist) - groupheight_t else: lines = len(biglist) offset = 0 if (state[0] == lines) : offset = 1 if (state[0] > lines) : offset = state[0] - lines + 1 for index in range(offset, (lines+offset)) : name = padstring(biglist[index][0], groupwidth_t) if (index == state[0]) and (state[2] == 0) : group_s.addstr((index+1-offset),1,name, curses.A_REVERSE) elif (index == state[0]) and (state[2] == 1) : group_s.addstr((index+1-offset),1,name, curses.A_BOLD) else: group_s.addstr((index+1-offset),1,name) # Draw the scroll arrows, as necessary group_s.box() if (offset) : # Draw up arrow group_s.addstr(0,2,'(-)') if (maxoffset) and (offset < maxoffset) : # Draw down arrow group_s.addstr((groupheight_t+1),2,'(+)') group_s.refresh() return def displayeffects(state): global effectwidth_t global effectheight_t maxoffset = 0 if len(biglist[state[0]][1]) > effectheight_t : lines = effectheight_t maxoffset = len(biglist[state[0]][1]) - effectheight_t cleanend = 0 else: lines = len(biglist[state[0]][1]) cleanend = 1 offset = 0 if (state[1] == lines) : offset = 1 if (state[1] > lines) : offset = state[1] - lines + 1 for index in range(offset, (lines+offset)) : name = padstring(biglist[state[0]][1][index]['name'], effectwidth_t) if (index == state[1]) and (state[2] == 1) : effect_s.addstr((index+1-offset),1,name, curses.A_REVERSE) else: effect_s.addstr((index+1-offset),1,name) if (cleanend) : for clean in range((lines+1), (effectheight_t + 1)) : effect_s.addstr(clean,1,(effectwidth_t * ' ')) # Draw the scroll arrows, as necessary effect_s.box() if (offset) : # Draw up arrow effect_s.addstr(0,2,'(-)') if (maxoffset) and (offset < maxoffset) : # Draw down arrow effect_s.addstr((effectheight_t+1),2,'(+)') effect_s.refresh() return def getdata(): # Get all of the effects from the boodler effects path envvar, # and update the python path to match effects_dir = os.environ.get('BOODLER_EFFECTS_PATH') if (effects_dir): effects_dir = string.split(effects_dir, ':') for dir in effects_dir: if (len(dir) > 0): sys.path.append(dir) # Get a list of effects, weeding out compiled modules and boodler # system modules # # XXX this only handles the first directory! group_name_list = [] effects_path = os.environ.get('BOODLER_EFFECTS_PATH') if (effects_path) : effects_dirs = string.split(effects_path, ':') for bdir in effects_dirs : if (len(bdir) > 0): for bfile in os.listdir(bdir) : if (re.match(".*\.py$", bfile)) : if not(bfile in ['listen.py', 'manager.py', 'play.py', 'temp.py']) : if not (bfile[0:-3] in group_name_list) : group_name_list.append(bfile[0:-3]) # We want to sort here for display and indexing later group_name_list.sort() biglist = [] for group in group_name_list : things = [] mod = __import__(group) for key in mod.__dict__.keys(): obj = mod.__dict__[key] if (type(obj) == type(Agent) and issubclass(obj, Agent) and (obj.name) and not(obj in [Agent, FadeOutAgent, FadeInOutAgent, StopAgent, NullAgent])): data={ 'module' : group + "." + key, 'name' : key, 'desc' : obj.name } things.append(data) biglist.append((group,things)) return biglist if __name__=='__main__': try: # Initialize curses stdscr=curses.initscr() stdscr.keypad(1) curses.noecho() curses.cbreak() curses.halfdelay(1) curses.curs_set(0) # Enter main loop main(stdscr) # When we come back, set everything to normal curses.echo() curses.nocbreak() curses.curs_set(1) curses.endwin() except: # If we blow it, return the terminal to a useful state curses.echo() curses.nocbreak() curses.curs_set(1) curses.endwin() # traceback.print_exc()