]> git.phdru.name Git - xsetbg.git/blob - xsetbg.py
Rename db_file to xsetbg_db_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__ = ['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
96 # DB keys
97 timestamp_key = "timestamp"
98 filename_key = "filename"
99 old_filename_key = "old_filename"
100
101
102 # Create the database if it is not exists yet.
103
104 os.umask(0066) # octal; -rw-------; make the global persistent dictionary
105                # readable only by the user
106
107 try:
108    xsetbg_db = shelve.open(xsetbg_db_path, flag='c')
109 except anydbm.error, msg:
110    if str(msg) == "db type could not be determined":
111       os.remove(xsetbg_db_path)
112       xsetbg_db = shelve.open(xsetbg_db_path, flag='c')
113
114 # Remove old filenames
115 old_time = time() - min_delay
116
117 to_delete = [timestamp_key]
118 for key in xsetbg_db.keys():
119    if key.startswith('/') and xsetbg_db[key] < old_time:
120       to_delete.append(key)
121
122 for key in to_delete:
123    try:
124       del xsetbg_db[key]
125    except KeyError:
126       pass
127
128 xsetbg_db.close() # Close DB in the parent process
129
130
131 images = []
132
133 for image_dir in image_dirs:
134    # List images in all subdirectories
135    for dirpath, dirs, files in os.walk(image_dir):
136       images.extend([os.path.join(dirpath, file) for file in files])
137
138 if not images:
139    error("No images found. Abort.")
140
141
142 def change(force=False):
143    # Use the program's file as the lock file:
144    # lock it to prevent two processes run in parallel.
145    lock_file = open(os.path.join(xsetbg_dir, 'xsetbg.py'), 'r')
146
147    try:
148       flock(lock_file, LOCK_EX|LOCK_NB)
149    except IOError: # already locked
150       lock_file.close()
151       return
152
153
154    xsetbg_db = None
155    try:
156       # Reopen the global persistent dictionary
157       xsetbg_db = shelve.open(xsetbg_db_path, 'w')
158
159       timestamp = xsetbg_db.get(timestamp_key)
160       current_time = time()
161
162       if not force and timestamp is not None and \
163          current_time - timestamp < min_pause: # Too early to change background
164             return
165
166       # Save current time
167       xsetbg_db[timestamp_key] = current_time
168
169       # Select a random image and check if we've seen it recently;
170       # loop until we can find a new image (never seen before) or old enough.
171       for i in xrange(len(images)): # ensure the loop is not infinite
172          image_name = random.choice(images)
173          if xsetbg_db.has_key(image_name):
174             image_time = xsetbg_db[image_name]
175             if current_time - image_time > min_delay:
176                break
177          else:
178             break
179       xsetbg_db[image_name] = current_time
180
181       # Save filename
182       if xsetbg_db.has_key(filename_key):
183          xsetbg_db[old_filename_key] = xsetbg_db[filename_key]
184       xsetbg_db[filename_key] = image_name
185
186       program_options = ["xli", "-onroot", "-quiet"] + \
187          ["-center", "-border", random.choice(borders), "-zoom", "auto",
188             image_name]
189
190       rc = subprocess.call(program_options)
191       if rc:
192          error("cannot execute xli!")
193
194    finally:
195       # Unlock and close the lock file
196       flock(lock_file, LOCK_UN)
197       lock_file.close()
198       # Flush and close the global persistent dictionary
199       if xsetbg_db: xsetbg_db.close()