]>
code.delx.au - bg-scripts/blob - randombg.py
13 from optparse
import OptionParser
16 logging
.basicConfig(format
="%(levelname)s: %(message)s")
18 # Python 2.3's logging.basicConfig does not support parameters
22 import cPickle
as pickle
30 except ImportError, e
:
31 logging
.critical("Missing libraries! Exiting...", exc_info
=1)
37 def filter_images(filenames
):
38 extensions
= ('.jpg', '.jpe', '.jpeg', '.png', '.gif', '.bmp')
39 for filename
in filenames
:
40 _
, ext
= os
.path
.splitext(filename
)
41 if ext
.lower() in extensions
:
44 class BaseFileList(object):
45 """Base file list implementation"""
50 def add_path(self
, path
):
51 self
.paths
.append(path
)
53 def store_cache(self
, filename
):
55 logging
.debug("Attempting to store cache")
56 fd
= open(filename
, 'wb')
57 pickle
.dump(self
, fd
, 2)
58 logging
.debug("Cache successfully stored")
60 warning("Storing cache: %s" % e
)
62 def load_cache(self
, filename
):
64 logging
.debug("Attempting to load cache from: %s" % filename
)
67 fd
= open(filename
, 'rb')
70 if tmp
.__class
__ != self
.__class
__:
71 raise ValueError("Using different file list type")
74 if self
.paths
!= tmp
.paths
:
75 raise ValueError, "Path list changed"
77 # Overwrite this object with the other
78 for attr
, value
in tmp
.__dict
__.items():
79 setattr(self
, attr
, value
)
84 logging
.warning("Loading cache: %s" % e
)
87 def add_to_favourites(self
):
88 '''Adds the current image to the list of favourites'''
89 self
.favourites
.append(self
.get_current_image())
92 raise NotImplementedError()
94 def get_next_image(self
):
95 raise NotImplementedError()
97 def get_prev_image(self
):
98 raise NotImplementedError()
100 def get_current_image(self
):
101 raise NotImplementedError()
107 class RandomFileList(BaseFileList
):
109 super(RandomFileList
, self
).__init
__()
111 self
.last_image
= None
113 def scan_paths(self
):
114 for path
in self
.paths
:
115 for dirpath
, dirsnames
, filenames
in os
.walk(path
):
116 for filename
in filter_images(filenames
):
117 self
.list.append(os
.path
.join(dirpath
, filename
))
119 def add_path(self
, path
):
120 self
.paths
.append(path
)
121 logging
.debug('Added path "%s" to the list' % path
)
123 def get_next_image(self
):
124 n
= random
.randint(0, len(self
.list)-1)
125 self
.last_image
= self
.list[n
]
126 logging
.debug("Picked file '%s' from list" % self
.last_image
)
127 return self
.last_image
129 def get_current_image(self
):
131 return self
.last_image
133 return self
.get_next_image()
136 return len(self
.list) == 0
139 class AllRandomFileList(BaseFileList
):
141 super(AllRandomFileList
, self
).__init
__()
143 self
.imagePointer
= 0
145 # Scan the input directory, and then randomize the file list
146 def scan_paths(self
):
147 logging
.debug("Scanning paths")
150 for path
in self
.paths
:
151 logging
.debug('Scanning "%s"' % path
)
152 for dirpath
, dirsnames
, filenames
in os
.walk(path
):
153 for filename
in filter_images(filenames
):
154 logging
.debug('Adding file "%s"' % filename
)
155 self
.list.append(os
.path
.join(dirpath
, filename
))
157 random
.shuffle(self
.list)
159 def add_path(self
, path
):
160 self
.paths
.append(path
)
161 logging
.debug('Added path "%s" to the list' % path
)
163 def store_cache(self
, filename
):
165 fd
= open(filename
, 'wb')
166 pickle
.dump(self
, fd
, 2)
167 logging
.debug("Cache successfully stored")
169 logging
.warning("Storing cache", exc_info
=1)
171 def get_current_image(self
):
172 return self
.list[self
.imagePointer
]
174 def __inc_in_range(self
, n
, amount
= 1, rangeMax
= None, rangeMin
= 0):
175 if rangeMax
== None: rangeMax
= len(self
.list)
177 return (n
+ amount
) % rangeMax
179 def get_next_image(self
):
180 self
.imagePointer
= self
.__inc
_in
_range
(self
.imagePointer
)
181 imageName
= self
.list[self
.imagePointer
]
182 logging
.debug("Picked file '%s' (pointer=%d) from list" % (imageName
, self
.imagePointer
))
185 def get_prev_image(self
):
186 self
.imagePointer
= self
.__inc
_in
_range
(self
.imagePointer
, amount
=-1)
187 imageName
= self
.list[self
.imagePointer
]
188 logging
.debug("Picked file '%s' (pointer=%d) from list" % (imageName
, self
.imagePointer
))
192 return len(self
.list) == 0
195 class FolderRandomFileList(BaseFileList
):
196 """A file list that will pick a file randomly within a directory. Each
197 directory has the same chance of being chosen."""
199 super(FolderRandomFileList
, self
).__init
__()
200 self
.directories
= {}
201 self
.last_image
= None
203 def scan_paths(self
):
206 def add_path(self
, path
):
207 logging
.debug('Added path "%s" to the list' % path
)
208 for dirpath
, dirs
, filenames
in os
.walk(path
):
209 logging
.debug('Scanning "%s" for images' % dirpath
)
210 if self
.directories
.has_key(dirpath
):
212 filenames
= list(filter_images(filenames
))
214 self
.directories
[dirpath
] = filenames
215 logging
.debug('Adding "%s" to "%s"' % (filenames
, dirpath
))
217 logging
.debug("No images found in '%s'" % dirpath
)
219 def get_next_image(self
):
220 directory
= random
.choice(self
.directories
.keys())
221 logging
.debug('directory: "%s"' % directory
)
222 filename
= random
.choice(self
.directories
[directory
])
223 logging
.debug('filename: "%s"' % filename
)
224 return os
.path
.join(directory
, filename
)
226 def get_current_image(self
):
228 return self
.last_image
230 return self
.get_next_image()
233 return len(self
.directories
.values()) == 0
236 class Cycler(object):
237 def init(self
, options
, paths
, oneshot
=False):
238 self
.cycle_time
= options
.cycle_time
239 self
.cache_filename
= options
.cache_filename
241 logging
.debug("Initialising wallchanger")
242 wallchanger
.init(options
.background_colour
, options
.convert
)
244 logging
.debug("Initialising file list")
245 if options
.all_random
:
246 self
.filelist
= AllRandomFileList()
247 elif options
.folder_random
:
248 self
.filelist
= FolderRandomFileList()
250 self
.filelist
= RandomFileList()
253 self
.filelist
.add_path(path
)
255 if self
.filelist
.load_cache(self
.cache_filename
):
256 logging
.debug("Loaded cache successfully")
258 logging
.debug("Could not load cache")
259 self
.filelist
.scan_paths()
261 if self
.filelist
.is_empty():
262 logging
.error("No images were found. Exiting...")
272 self
.filelist
.store_cache(self
.cache_filename
)
274 def find_files(self
, options
, paths
):
279 image
= self
.filelist
.get_next_image()
280 wallchanger
.set_image(image
)
284 if self
.task
is not None:
286 self
.task
= asyncsched
.schedule(self
.cycle_time
, next
)
287 logging
.debug("Reset timer for %s seconds" % self
.cycle_time
)
288 self
.filelist
.store_cache(self
.cache_filename
)
290 def cmd_reload(self
):
291 image
= self
.filelist
.get_current_image()
292 wallchanger
.set_image(image
)
296 image
= self
.filelist
.get_next_image()
297 wallchanger
.set_image(image
)
301 image
= self
.filelist
.get_prev_image()
302 wallchanger
.set_image(image
)
305 def cmd_rescan(self
):
306 self
.filelist
.scan_paths()
309 if self
.task
is not None:
316 def cmd_favourite(self
):
317 self
.filelist
.add_to_favourites()
319 class Server(asynchat
.async_chat
):
320 def __init__(self
, cycler
, sock
):
321 asynchat
.async_chat
.__init
__(self
, sock
)
324 self
.set_terminator("\n")
326 def collect_incoming_data(self
, data
):
327 self
.ibuffer
.append(data
)
329 def found_terminator(self
):
330 line
= "".join(self
.ibuffer
).lower()
332 prefix
, cmd
= line
.split(None, 1)
334 logging
.debug('Bad line received "%s"' % line
)
336 if hasattr(self
.cycler
, "cmd_" + cmd
):
337 logging
.debug('Executing command "%s"' % cmd
)
338 getattr(self
.cycler
, "cmd_" + cmd
)()
340 logging
.debug('Unknown command received "%s"' % cmd
)
343 class SockHackWrap(object):
344 def __init__(self
, sock
, addr
):
347 def getpeername(self
):
349 def __getattr__(self
, key
):
350 return getattr(self
.__sock
, key
)
352 class Listener(asyncore
.dispatcher
):
353 def __init__(self
, socket_filename
, cycler
):
354 asyncore
.dispatcher
.__init
__(self
)
356 self
.create_socket(socket
.AF_UNIX
, socket
.SOCK_STREAM
)
357 self
.bind(socket_filename
)
358 self
.listen(2) # Backlog = 2
360 def handle_accept(self
):
361 sock
, addr
= self
.accept()
362 Server(self
.cycler
, SockHackWrap(sock
, addr
))
368 def do_server(options
, paths
):
370 sock
= socket
.socket(socket
.AF_UNIX
, socket
.SOCK_STREAM
)
371 sock
.connect(options
.socket_filename
)
372 print >>sys
.stderr
, "Server is already running! Sending exit command."
373 sock
= sock
.makefile()
374 sock
.write("cmd exit\n")
380 os
.unlink(options
.socket_filename
)
385 listener
= Listener(options
.socket_filename
, cycler
)
386 # Initialisation of Cycler delayed so we grab the socket quickly
387 cycler
.init(options
, paths
)
390 except KeyboardInterrupt:
394 def do_client(options
, args
):
397 sock
= socket
.socket(socket
.AF_UNIX
, socket
.SOCK_STREAM
)
398 sock
.connect(options
.socket_filename
)
399 sock
= sock
.makefile()
400 for i
, cmd
in enumerate(args
):
401 sock
.write("cmd %s\n" % cmd
)
402 if i
< len(args
) - 1:
403 time
.sleep(options
.cycle_time
)
406 def do_oneshot(options
, paths
):
408 cycler
.init(options
, paths
, oneshot
=True)
411 parser
= OptionParser(version
="%prog " + VERSION
,
412 description
= "Cycles through random background images.",
414 "\n(server) %prog [options] dir [dir2 ...]"
415 "\n(client) %prog [options] [next|prev|rescan|reload|pause] [...]"
416 "\nThe first instance to be run will be the server.\n"
418 parser
.add_option("-v", '-d', "--verbose", "--debug",
419 action
="count", dest
="verbose", default
=0,
420 help="Make the louder (good for debugging, or those who are curious)")
421 parser
.add_option("-b", "--background-colour",
422 action
="store", type="string", dest
="background_colour", default
="black",
423 help="Change the default background colour that is displayed if the image is not in the correct aspect ratio [Default: %default]")
424 parser
.add_option("--all-random",
425 action
="store_true", dest
="all_random", default
=False,
426 help="Make sure that all images have been displayed before repeating an image")
427 parser
.add_option("-1", "--oneshot",
428 action
="store_true", dest
="oneshot", default
=False,
429 help="Set one random image and terminate immediately.")
430 parser
.add_option("--folder-random",
431 action
="store_true", dest
="folder_random", default
=False,
432 help="Give each folder an equal chance of having an image selected from it")
433 parser
.add_option("--convert",
434 action
="store_true", dest
="convert", default
=False,
435 help="Do conversions using ImageMagick or PIL, don't rely on the window manager")
436 parser
.add_option("--cycle-time",
437 action
="store", type="int", default
=1800, dest
="cycle_time",
438 help="Cause the image to cycle every X seconds")
439 parser
.add_option("--socket",
440 action
="store", type="string", dest
="socket_filename", default
=os
.path
.expanduser('~/.randombg_socket'),
441 help="Location of the command/control socket.")
442 parser
.add_option("--cache-file",
443 action
="store", type="string", dest
="cache_filename", default
=os
.path
.expanduser('~/.randombg_cache'),
444 help="Stores the location of the last image to be loaded.")
445 parser
.add_option("--server",
446 action
="store_true", dest
="server", default
=False,
447 help="Run in server mode to listen for clients.")
451 parser
= build_parser()
452 options
, args
= parser
.parse_args(sys
.argv
[1:])
454 if options
.verbose
== 1:
455 logging
.getLogger().setLevel(logging
.INFO
)
456 elif options
.verbose
>= 2:
457 logging
.getLogger().setLevel(logging
.DEBUG
)
460 do_server(options
, args
)
464 do_oneshot(options
, args
)
468 do_client(options
, args
)
471 print >>sys
.stderr
, "Failed to connect to server:", e
474 if __name__
== "__main__":