]> git.phdru.name Git - xsetbg.git/blob - xsetbg.py
Removed svn:keywords.
[xsetbg.git] / xsetbg.py
1 #! /usr/bin/env python
2 """Set a random background image (XWin)
3
4 Select a random image from a (list of) directory(s)
5 and set it as the desktop wallpaper (display it in the root window)
6 using xli program.
7
8 """
9
10 __author__ = "Oleg Broytman <phd@phdru.name>"
11 __copyright__ = "Copyright (C) 2000-2012 PhiloSoft Design"
12 __license__ = "GNU GPL"
13
14 __all__ = ['host', 'port', 'change']
15
16
17 import sys, os
18 from ConfigParser import SafeConfigParser
19
20 import anydbm
21 import random
22 import shelve
23 import subprocess
24 from time import time
25 from fcntl import flock, LOCK_EX, LOCK_UN, LOCK_NB
26
27
28 def error(error_str, error_code=1):
29    sys.stderr.write("%s: Error: %s\n" % (sys.argv[0], error_str))
30    sys.exit(error_code)
31
32
33 xsetbg_dir = os.path.dirname(os.path.abspath(__file__))
34 os.chdir(xsetbg_dir)
35
36 config = SafeConfigParser()
37 config.read("xsetbg.conf")
38
39 if config.has_option("images", "directory") or \
40    config.has_option("images", "directory0") or \
41    config.has_option("images", "directory1"):
42    image_dirs = []
43    if config.has_option("images", "directory"):
44       image_dirs.append(config.get("images", "directory"))
45    if config.has_option("images", "directory0"):
46       image_dirs.append(config.get("images", "directory0"))
47    if config.has_option("images", "directory1"):
48       image_dirs.append(config.get("images", "directory1"))
49    i = 2
50    while True:
51       option = "directory%d" % i
52       if config.has_option("images", option):
53          image_dirs.append(config.get("images", option))
54          i += 1
55       else:
56          break
57 else:
58    image_dirs = ["images"]
59
60 image_dirs = [os.path.join(xsetbg_dir,
61    os.path.expandvars(os.path.expanduser(dirname)))
62       for dirname in image_dirs
63 ]
64
65 # minimum time in seconds between background image changes
66 if config.has_option("xsetbg", "min_pause"):
67    min_pause = config.getint("xsetbg", "min_pause")
68 else:
69    min_pause = 60
70
71 borders = config.get("xsetbg", "borders").split(',')
72 if config.has_option("xsetbg", "borders"):
73    borders = [border.strip() for border in config.get("xsetbg", "borders").split(',')]
74 else:
75    borders = ["darkcyan", "steelblue", "midnightblue"]
76
77 # minimum time in seconds between occurences of the same image
78 if config.has_option("xsetbg", "min_delay"):
79    min_delay = config.getint("xsetbg", "min_delay")
80 else:
81    min_delay = 3600*24 # 24 hours
82
83 # httpd settings
84 if config.has_option("httpd", "host"):
85    host = config.get("httpd", "host")
86 else:
87    host = 'localhost'
88
89 if config.has_option("httpd", "port"):
90    port = config.getint("httpd", "port")
91 else:
92    error("Config must specify a port to listen. Abort.")
93
94 del config
95
96
97 os.umask(0066) # octal; -rw-------; make the global persistent dictionary
98                # readable only by the user
99 global_db_name = "xsetbg.db"
100
101
102 # DB keys
103 timestamp_key = "timestamp"
104 filename_key = "filename"
105 old_filename_key = "old_filename"
106
107
108 # Create the database if it is not exists yet.
109
110 try:
111    global_db = shelve.open(global_db_name, flag='c')
112 except anydbm.error, msg:
113    if str(msg) == "db type could not be determined":
114       os.remove(global_db_name)
115       global_db = shelve.open(global_db_name, flag='c')
116
117 # Remove old filenames
118 old_time = time() - min_delay
119
120 to_delete = [timestamp_key]
121 for key in global_db.keys():
122    if key.startswith('/') and global_db[key] < old_time:
123       to_delete.append(key)
124
125 for key in to_delete:
126    try:
127       del global_db[key]
128    except KeyError:
129       pass
130
131 global_db.close() # Close DB in the parent process
132
133
134 images = []
135
136 for image_dir in image_dirs:
137    # List images in all subdirectories
138    for dirpath, dirs, files in os.walk(image_dir):
139       images.extend([os.path.join(dirpath, file) for file in files])
140
141 if not images:
142    error("No images found. Abort.")
143
144
145 def change(force=False):
146    # Use the program's file as the lock file:
147    # lock it to prevent two processes run in parallel.
148    lock_file = open("xsetbg.py", 'r')
149    try:
150       flock(lock_file, LOCK_EX|LOCK_NB)
151    except IOError: # already locked
152       lock_file.close()
153       return
154
155
156    global_db = None
157    try:
158       # Reopen the global persistent dictionary
159       global_db = shelve.open(global_db_name, 'w')
160
161       timestamp = global_db.get(timestamp_key)
162       current_time = time()
163
164       if not force and timestamp is not None and \
165          current_time - timestamp < min_pause: # Too early to change background
166             return
167
168       # Save current time
169       global_db[timestamp_key] = current_time
170
171       # Select a random image and check if we've seen it recently;
172       # loop until we can find a new image (never seen before) or old enough.
173       for i in xrange(len(images)): # ensure the loop is not infinite
174          image_name = random.choice(images)
175          if global_db.has_key(image_name):
176             image_time = global_db[image_name]
177             if current_time - image_time > min_delay:
178                break
179          else:
180             break
181       global_db[image_name] = current_time
182
183       # Save filename
184       if global_db.has_key(filename_key):
185          global_db[old_filename_key] = global_db[filename_key]
186       global_db[filename_key] = image_name
187
188       program_options = ["xli", "-onroot", "-quiet"] + \
189          ["-center", "-border", random.choice(borders), "-zoom", "auto",
190             image_name]
191
192       rc = subprocess.call(program_options)
193       if rc:
194          error("cannot execute xli!")
195
196    finally:
197       # Unlock and close the lock file
198       flock(lock_file, LOCK_UN)
199       lock_file.close()
200       # Flush and close the global persistent dictionary
201       if global_db: global_db.close()