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