#!/usr/bin/env python2 # # TODO: # - handle "-iconic" # - on startup make sure the first pic that gets displayed is actually # selected # - handle multiple playlists in the config file # - "pause" should be in the "shuttle" controls # - check if file exists before trying to display it # - allow clicking on the countdown timer to pause/unpause # - unpaused timer: setting bg should use "default" bg color # - the overall window should not resize, even if stuff doesn't fit # - have various options to select tiling, etc. from time import sleep import thread import sys import glob import os from os.path import * import string from Tkinter import * from Dialog import * # constants def_rc_file = expanduser('~/.tkwallpaper.py.rc') playlist_prefix = 'pl_' class Timer: def __init__(self, duration, alarm): self.duration = duration self.time_left = 0 self.alarm = alarm self.pause = 0 self.mutex = thread.allocate() self.reset() def reset(self): self.time_left = self.duration def set_duration(self, duration): self.duration = duration self.reset() def tick(self): if self.time_left > 0: self.time_left -= 1 if self.time_left == 0: self.alarm() def run(self): thread.start_new(self._clock_thread, ()) def toggle_pause(self): self.pause = not self.pause def is_paused(self): return self.pause def _clock_thread(self): while 1: sleep(1) if not self.pause: self.tick() class PicChangeTimer(Timer): def __init__(self, tcl_dur, tcl_left, alarm): self.tcl_dur = tcl_dur self.tcl_left = tcl_left Timer.__init__(self, tcl_dur.get()*60, alarm) self.upd_tcl_left() tcl_dur.trace_variable("w", self.upd_dur) def upd_dur(self, not_implemented_yet=1234, bar=1234, baz=1234): try: self.duration = self.tcl_dur.get()*60 except ValueError: pass else: self.reset() def upd_tcl_left(self): self.tcl_left.set(time_format(self.time_left)) def reset(self): Timer.reset(self) self.upd_tcl_left() def tick(self): Timer.tick(self) self.upd_tcl_left() def time_format(totsecs): hrs = totsecs/3600 mins = (totsecs % 3600)/60 secs = totsecs % 60 if hrs: return "%02d:%02d:%02d" % (hrs, mins, secs) else: return "%02d:%02d" % (mins, secs) class StatusBar(Frame): def __init__(self, master): Frame.__init__(self, master) self.label = Label(self, borderwidth=1, relief=SUNKEN, anchor=W) self.label.pack(fill=X, pady=3, padx=3) self.timer = Timer(5, self.clear) self.timer.run() def set(self, format, *args): self.label.config(text=format % args) self.label.update() self.timer.reset() def clear(self): self.label.config(text="") class ImageList(Listbox): def __init__(self, master): Listbox.__init__(self, master, selectmode=EXTENDED) self.pics = [] self.curpic = 0 self.bind('', self._dbl_clk) self.callbacks = () def add_sel_cb(self, cb): if cb not in self.callbacks: self.callbacks += (cb,) return 1 else: return 0 def add_pics(self, pics): self.pics += pics pics = map(basename, pics) apply(self.insert, (END,)+tuple(pics)) def rm_pic(self, pic): self.pics.remove(pic) print "Still don't know how to remove from MultiListbox" def clear(self): self.delete(0,END) self.curpic = 0 self.pics = [] def select_next(self): self.curpic += 1 if self.curpic >= len(self.pics): self.curpic = 0 self._sel_updated() def select_prev(self): self.curpic -= 1 if self.curpic < 0: self.curpic = len(self.pics)-1 self._sel_updated() def _dbl_clk(self, event): self.curpic = self.nearest(event.y) self._sel_updated() def _sel_updated(self): self.select_clear(0, END) self.select_set(self.curpic) self.see(self.curpic) for cb in self.callbacks: cb(self.pics[self.curpic]) class App(Tk): def __init__(self): Tk.__init__(self, className='tkwallpaper') self.option_add('*font','-*-helvetica-medium-r-*-*-12-*-*-*-*-*-*-*') self.option_add('*padY','0') #self.option_add('*borderWidth','1') self.wm_iconname('tkwallpaper.py') #self.wm_command('Tkwallpaper') #self.wm_group('.') self.playlists = () self._init_vars() self._init_geom() self._init_menu() # bind some important events self.imglist.add_sel_cb(self._show_pic) # create and start the image timer self.timer = PicChangeTimer(self.ipic_delay, self.time_left, self._cb_timer_alarm) self.timer.run() # read in the configuration; if missing, start a new one self.read_config(def_rc_file) # final touch self.status.set("Welcome to "+basename(sys.argv[0])+"!") def read_config(self, rc_file): # TODO: this is only the beginnings of a configuration file scheme try: fl = open(rc_file) exec fl in globals() fl.close() self.playlist_menu.delete(0,END) for key in globals().keys(): if key[:len(playlist_prefix)] == playlist_prefix: self.playlists += (key,) self.playlist_menu.add_command(label=key, command=self.playlist_change) # select the first playlist and cause it to load self.playlist_menu.activate(0) self.playlist_menu.invoke(0) except IOError: print "Unable to open %s; using defaults." self.imglist.add_pics(glob.glob('/home/mac/pics/*.jpg')) self.imglist.add_pics(glob.glob('/home/mac/pics/*.gif')) self.imglist.add_pics(glob.glob('/home/mac/pics/*.png')) def playlist_change(self): # figure out the chosen playlist pl = self.playlist_menu.entrycget('active', 'label') # because we most likely added new menu options we should tell the # OptionMenu that the choice has been updated # TODO: is there a better way? perhaps use _setit for callback? self.playlist.set(pl) # empty out the ImageList self.imglist.clear() # fill the listbox with new files self.imglist.add_pics(globals()[pl]) # possibly randomize the listbox entries # select first pic, restart timer self.imglist._sel_updated() def _init_vars(self): # create the Tcl variables we're going to use self.playlist = StringVar() self.ipic_delay = IntVar() self.time_left = StringVar() self.opt_shuffle = BooleanVar() self.opt_pause = BooleanVar() # initialize them self.playlist.set('default') self.ipic_delay.set(30) self.time_left.set(30) self.opt_shuffle.set('false') self.opt_pause.set('false') def _init_geom(self): # create geometry and pack it self.F_pl_ctrl = Frame(self) self.F_pl_ctrl.pack(side=TOP, fill=X) self.imglist = ImageList(self) self.imglist.pack(side=TOP, expand=1, fill=BOTH) self.F_time_ctrl = Frame(self) self.F_time_ctrl.pack(side=TOP, fill=X) self.status = StatusBar(self) self.status.pack(side=TOP, fill=X) Label(self.F_pl_ctrl, text="Playlist:")\ .pack(side=LEFT) foo = OptionMenu(self.F_pl_ctrl, self.playlist, "default") self.playlist_menu = foo['menu'] foo.pack(side=LEFT) Button(self.F_pl_ctrl, text="Next", command=self._cb_next_pic)\ .pack(side=RIGHT) Button(self.F_pl_ctrl, text="Prev", command=self._cb_prev_pic)\ .pack(side=RIGHT) Label(self.F_time_ctrl, text="Delay (min):")\ .pack(side=LEFT) Entry(self.F_time_ctrl, textvariable=self.ipic_delay, width=4,\ relief=SUNKEN).pack(side=LEFT) self.time_left_box = Entry(self.F_time_ctrl, textvariable=self.time_left,\ width=4, relief=FLAT, state=DISABLED) self.time_left_box.pack(side=RIGHT) Label(self.F_time_ctrl, text="Time left:")\ .pack(side=RIGHT) def _init_menu(self): self.menu = Menu(self, borderwidth=1, activeborderwidth=0) self.config(menu=self.menu) self.plist_menu = Menu(self.menu, tearoff=0, activeborderwidth=0, borderwidth=1) self.menu.add_cascade(label="Playlist", menu=self.plist_menu) self.plist_menu.add_command(label="New", command=not_implemented_yet) self.plist_menu.add_command(label="Save", command=not_implemented_yet) self.plist_menu.add_command(label="Delete", command=not_implemented_yet) self.plist_menu.add_separator() self.plist_menu.add_command(label="Exit", command=sys.exit) self.pics_menu = Menu(self.menu, tearoff=0, activeborderwidth=0, borderwidth=1) self.menu.add_cascade(label="Pics", menu=self.pics_menu) self.pics_menu.add_command(label="Add", command=not_implemented_yet) self.pics_menu.add_command(label="Remove", command=not_implemented_yet) self.opts_menu = Menu(self.menu, tearoff=0, activeborderwidth=0, borderwidth=1) self.menu.add_cascade(label="Options", menu=self.opts_menu) self.opts_menu.add_checkbutton(label="Random", command=not_implemented_yet) self.opts_menu.add_checkbutton(label="Pause", variable=self.opt_pause, command=self._cb_pause_touched) self.opts_menu.add_separator() self.opts_menu.add_command(label="Save config", command=not_implemented_yet) def _cb_timer_alarm(self): self.imglist.select_next() def _show_pic(self, pic): if not exists(pic): self.status.set('File '+pic+' does not exist.') sleep(2) return self.status.set('Loading file: '+basename(pic)) os.system('nice -19 xv -root -quit -max -fixed -rmode 5 +noresetroot "'+pic+'"') self.wm_title(basename(pic)) self.timer.reset() def _cb_next_pic(self): self.imglist.select_next() def _cb_prev_pic(self): self.imglist.select_prev() def _cb_pause_touched(self): if self.timer.is_paused() != self.opt_pause.get(): self.timer.toggle_pause() if self.timer.is_paused(): self.time_left_box.config(bg='gray70') else: self.time_left_box.config(bg='gray85') # TODO: this should be a GUI popup; it's easy not to notice the tty output... def not_implemented_yet(): Dialog(None,{ 'title':'Warning', 'text':'This feature has not yet been implemented.', 'bitmap':'warning', 'default':0, 'strings':('OK',), }) # create the App app = App() # start the Tk event loop mainloop()