]> git.phdru.name Git - xsetbg.git/blob - xsetbg.py
Fix a bug: open xsetbg.py with full 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_path
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(os.path.join(xsetbg_dir, 'xsetbg.py'), 'r')
157
158    try:
159       flock(lock_file, LOCK_EX|LOCK_NB)
160    except IOError: # already locked
161       lock_file.close()
162       return
163
164
165    xsetbg_db = None
166    try:
167       # Reopen the global persistent dictionary
168       xsetbg_db = shelve.open(xsetbg_db_path, 'w')
169
170       timestamp = xsetbg_db.get(timestamp_key)
171       current_time = time()
172
173       if not force and timestamp is not None and \
174          current_time - timestamp < min_pause: # Too early to change background
175             return
176
177       # Save current time
178       xsetbg_db[timestamp_key] = current_time
179
180       # Select a random image and check if we've seen it recently;
181       # loop until we can find a new image (never seen before) or old enough.
182       for i in xrange(len(images)): # ensure the loop is not infinite
183          image_name = random.choice(images)
184          if xsetbg_db.has_key(image_name):
185             image_time = xsetbg_db[image_name]
186             if current_time - image_time > min_delay:
187                break
188          else:
189             break
190       xsetbg_db[image_name] = current_time
191
192       # Save filename
193       if xsetbg_db.has_key(filename_key):
194          xsetbg_db[old_filename_key] = xsetbg_db[filename_key]
195       xsetbg_db[filename_key] = image_name
196
197       program_options = ["xli", "-onroot", "-quiet"] + \
198          ["-center", "-border", random.choice(borders), "-zoom", "auto",
199             image_name]
200
201       rc = subprocess.call(program_options)
202       if rc:
203          error("cannot execute xli!")
204
205    finally:
206       # Unlock and close the lock file
207       flock(lock_file, LOCK_UN)
208       lock_file.close()
209       # Flush and close the global persistent dictionary
210       if xsetbg_db: xsetbg_db.close()