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