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