#!/usr/bin/python """Use xss_base to make a simple clone of chbg Doesn't bother with the fades, just selects the images""" from __future__ import division import xss_base import os import Image import sys import random import optparse import subprocess import time noisy = False def fit_rect((in_w,in_h), (target_w, target_h)): """Fit input rectangle maximally within target rectangle""" in_ratio = in_w/in_h if target_h * in_ratio > target_w: return (target_w, target_w / in_ratio) else: return (target_h * in_ratio, target_h) def int_rect((f_w,f_h)): """convert rectangle to ints to keep PIL happy""" return (int(f_w), int(f_h)) class chbg(xss_base.xss_base): """pick backgrounds from a directory""" delay = 5 def __init__(self, picture_dir): xss_base.xss_base.__init__(self) self.recent = [] # scan directories up front, but just keep sizes self.picture_dir = picture_dir self.sizes = {} self.age = {} now = time.time() for picfile in os.listdir(self.picture_dir): picpath = os.path.join(self.picture_dir, picfile) if not os.path.isfile(picpath): continue try: tmp_im = Image.open(picpath) except IOError, exc: if not picfile.endswith(".whence"): print "Image.open failed on", picfile, "with:", exc continue if tmp_im.mode != "RGB": # TODO: put_pil_image supports "1" but I don't know why # TODO: support "P" for gif # TODO: support "RGBA" for png # TODO: support "L" for some jpgs if noisy: print "Wrong PIL mode", tmp_im.mode, "for", picfile, "- only RGB/ZPixmap supported" continue # w, h = tmp_im.size # if w <= self.rootscreen.width_in_pixels or h <= self.rootscreen.height_in_pixels: # print "pic normally small thus boring, skipping" # continue self.sizes[picpath] = tmp_im.size self.age[picpath] = now - os.path.getmtime(picpath) def render(self): imgpaths = self.sizes.keys() # bias towards newest ones; make 10 of them as likely as the rest? if len(imgpaths) > 10: emphasis = int(len(imgpaths)/9) young_line = sorted(self.age.values())[10] for picpath, age in self.age.items(): if age < young_line: imgpaths.extend([picpath] * emphasis) # add some bias against repetition - try again if we repeat one of the last N while True: picpath = random.choice(imgpaths) if picpath not in self.recent: print "chose", os.path.basename(picpath) break print "suppressed", os.path.basename(picpath) # 350 paths -> 35-deep suppression; 1 path -> none self.recent.append(picpath) if len(self.recent) > len(imgpaths)/10: self.recent.pop(0) # picpath = sorted(self.sizes.keys())[0] tmp_im = Image.open(picpath) if noisy: print "Trying:", os.path.basename(picpath) subprocess.call(["identify", picpath]) geom = self.window.get_geometry() w, h = fit_rect(tmp_im.size, (geom.width, geom.height)) if (w, h) != tmp_im.size: if noisy: print "Resizing", tmp_im.size, "to", (w, h) tmp_im = tmp_im.resize(int_rect((w, h)), Image.ANTIALIAS) # center (modern python lacks a rectangle module...) x_off = geom.width/2 - w/2 y_off = geom.height/2 - h/2 self.window.clear_area(0, 0, 0, 0) # clear_window self.window.put_pil_image(self.gc, x_off, y_off, tmp_im) if __name__ == "__main__": # TODO: add arg-stripping to xss_base parser = optparse.OptionParser(usage=__doc__) # parser.add_option("--window-id") parser.add_option("--noisy", action="store_true") # options, args = parser.parse_args(args=map(lambda x: # x.replace("-w", "--w"), # sys.argv[1:])) options, args = xss_base.parse_args(parser) assert os.getenv("XSCREENSAVER_WINDOW"), os.environ if options.window_id: assert os.getenv("XSCREENSAVER_WINDOW") == options.window_id, options.window_id noisy = options.noisy picdir, = args saver = chbg(picdir) saver.run()