#!/usr/bin/env python # vi: syntax=python import run_local from light9.Submaster import Submasters, combine_subdict from light9.subclient import SubClient import Tix as Tk # TODO: move to Utility? class circcycle: """Like itertools.cycle, but with a prev() method too. You lose all iterator benefits by using this, since it will store the whole sequence/iterator in memory. Also, do not run this on an infinite iterator, as it tuple'ifies the input.""" def __init__(self, sequence): self.sequence = tuple(sequence) self.index = None def __iter__(self): return self def change_index(self, delta): if self.index is None: if delta > 0: self.index = (-1 + delta) % len(self.sequence) elif delta < 0: self.index = delta % len(self.sequence) else: self.index = (self.index + delta) % len(self.sequence) def next(self): self.change_index(1) return self.sequence[self.index] def prev(self): self.change_index(-1) return self.sequence[self.index] class AbstractSimpleController(SubClient): """Simple controller with minimal input and output: Input is 4 directions and 3 buttons. Output is an integer and a color and maybe more. Left + B1/right + B1: prev/next sub Y-axis + B2: set current level B3: toggle keep/solo mode Double-B3: clear kept levels""" def __init__(self, subnames): SubClient.__init__(self) self.subnames = subnames self.refresh() def refresh(self): # reload subs from disk self.submasters = Submasters() self.all_subs = circcycle(self.subnames) self.current_sub = self.submasters.get_sub_by_name(self.all_subs.next()) self.current_level = 1.0 self.kept_levels = {} # subname : level [0..1] self.keep_solo_mode = 'keep' # either 'keep' or 'solo' def clear_kept_levels(self): self.kept_levels.clear() def next(self): if self.keep_solo_mode == 'keep': self.kept_levels[self.current_sub] = self.current_level self.current_sub = self.submasters.get_sub_by_name(self.all_subs.next()) def prev(self): if self.keep_solo_mode == 'keep': self.kept_levels[self.current_sub] = self.current_level self.current_sub = self.submasters.get_sub_by_name(self.all_subs.prev()) def toggle_keep_mode(self): if self.keep_solo_mode == 'keep': self.keep_solo_mode = 'solo' else: self.keep_solo_mode = 'keep' def get_levels_as_sub(self): if self.keep_solo_mode == 'keep': # send all levels in self.kept_levels levelsub = combine_subdict(self.kept_levels) else: levelsub = self.current_sub * self.current_level return levelsub class TkGyro(Tk.Canvas, AbstractSimpleController): def __init__(self, master, subnames): Tk.Canvas.__init__(self, master, bg='black', bd=0, highlightthickness=0, confine=None) AbstractSimpleController.__init__(self, subnames) height = int(self.winfo_screenheight()) width = int(self.winfo_screenwidth()) self.left = self.create_rectangle((0, 0, width / 2, height), tags='left', fill='black') self.right = self.create_rectangle((width / 2, 0, width, height), tags='right', fill='black') # the text is disabled so that it doesn't receive events self.modetext = self.create_text((width / 2, height / 2), font='Courier 200', fill='white', text=self.keep_solo_mode, state='disabled') self.flashtextafter = '' # current after timer for displaying text def setfill(item, color): self.itemconfig(item, fill=color) def setlevel(evt): if evt.state & 512: y = (height - evt.y) / float(height) self.flash_text('<%d>' % (y * 100)) self.current_level = y self.send_levels() data = ((self.left, 'left', '#000099', self.prev), (self.right, 'right', '#990000', self.next)) for item, tag, color, method in data: self.tag_bind(tag, '', lambda evt, item=item, color=color: setfill(item, color)) self.tag_bind(tag, '', lambda evt, item=item, color=color: setfill(item, 'black')) self.tag_bind(tag, '', lambda evt, item=item, color=color: setfill(item, 'green'), '+') self.tag_bind(tag, '', lambda evt, item=item, color=color: setfill(item, color), '+') self.tag_bind(tag, '', lambda evt, method=method: method(), '+') # B2+drag sets current level, double-B2 resets kept levels self.tag_bind('all', '', setlevel, '+') self.tag_bind('all', '', lambda evt: self.clear_kept_levels()) # B3 toggles between keep and solo mode self.tag_bind('all', '', lambda evt: self.toggle_keep_mode()) self.send_levels_loop() def toggle_keep_mode(self): AbstractSimpleController.toggle_keep_mode(self) self.show_current_mode() self.send_levels() def show_current_mode(self): if self.keep_solo_mode == 'keep': self.keep_solo_mode = 'keep' else: self.keep_solo_mode = '' self.itemconfig(self.modetext, text=self.keep_solo_mode) def clear_kept_levels(self): AbstractSimpleController.clear_kept_levels(self) self.flash_text('cleared') self.send_levels() def flash_text(self, text): self.itemconfig(self.modetext, text=text) self.after_cancel(self.flashtextafter) self.flashtextafter = self.after(2000, self.show_current_mode) def next(self): AbstractSimpleController.next(self) self.flash_text(self.current_sub.name) self.send_levels() def prev(self): AbstractSimpleController.prev(self) self.flash_text(self.current_sub.name) self.send_levels() if __name__ == "__main__": import sys, fileinput subnames = sys.argv[1:] if not subnames: subnames = [line.strip() for line in fileinput.input()] root = Tk.Tk() # these are hints to Fvwm2 if you add this to your .fvwm2rc: # Style "*NOTITLE*" NoTitle # Style "*NOBORDER*" BorderWidth 0, NoHandles # Style "*ONTOP*" StaysOnTop Sticky # hopefully, there's a better way to do this within Tk root.title("NOTITLE NOBORDER ONTOP") root.wm_geometry('%sx%s' % (root.winfo_screenwidth(), root.winfo_screenheight())) gyro = TkGyro(root, subnames) gyro.pack(fill='both', expand=1) root.bind('', lambda evt: root.destroy()) root.maxsize(root.winfo_screenwidth(), root.winfo_screenheight()) Tk.mainloop()