30f1400087288ee2e888f6fed9a16ca7cd6084bd
[bg-scripts] / wallchanger.py
1 #!/usr/bin/env python3
2
3 # Copyright 2008 Greg Darke <greg@tsukasa.net.au>
4 # Copyright 2008 James Bunton <jamesbunton@fastmail.fm>
5 # Licensed for distribution under the GPL version 2, check COPYING for details
6 # This is a cross platform/cross window manager way to change your wallpaper
7
8 import subprocess, sys, os, os.path, time
9 import logging
10 try:
11 import PIL, PIL.Image
12 except ImportError:
13 PIL = None
14
15 __all__ = ("init", "set_image")
16
17
18 changers = []
19
20 def set_image(filename):
21 logging.info("Setting image: %s", filename)
22 for changer in changers:
23 if not changer.set_image(filename):
24 logging.warning("Failed to set background: wallchanger.set_image(%s), changer=%s", filename, changer)
25
26 def check_cmd(cmd):
27 return subprocess.getstatusoutput(cmd)[0] == 0
28
29 def init(*args, **kwargs):
30 """Desktop Changer factory"""
31
32 classes = []
33
34 if sys.platform == "win32":
35 classes.append(WIN32Changer)
36 return
37
38 logging.debug("Testing for OSX (NonX11)")
39 if check_cmd("ps ax -o command -c|grep -q WindowServer"):
40 classes.append(OSXChanger)
41
42 if 'DISPLAY' not in os.environ or os.environ['DISPLAY'].startswith('/tmp/launch'):
43 # X11 is not running
44 return
45 else:
46 if os.uname()[0] == 'Darwin':
47 # Try to detect if the X11 server is running on OSX
48 if check_cmd("ps ax -o command|grep -q '^/.*X11 .* %s'" % os.environ['DISPLAY']):
49 # X11 is not running for this display
50 return
51
52 logging.debug("Testing for XFCE4")
53 if check_cmd("xwininfo -name 'xfce4-session'"):
54 classes.append(Xfce4Changer)
55
56 logging.debug("Testing for Gnome")
57 if check_cmd("xwininfo -name 'gnome-settings-daemon'"):
58 if check_cmd("gsettings get org.gnome.desktop.background picture-uri"):
59 classes.append(Gnome3Changer)
60 else:
61 classes.append(Gnome2Changer)
62
63 logging.debug("Testing for xloadimage")
64 if check_cmd("which xloadimage"):
65 classes.append(XLoadImageChanger)
66
67 if len(classes) == 0:
68 raise Exception("Unknown window manager")
69
70 for klass in classes:
71 changers.append(klass(*args, **kwargs))
72
73
74 class BaseChanger(object):
75 name = "undefined"
76 def __init__(self, background_color='black', convert=False):
77 logging.info('Determined the window manager is "%s"', self.name)
78 self.background_color = background_color
79 self.convert = convert
80
81 try:
82 def _exec_cmd(self, cmd):
83 import subprocess
84 return subprocess.Popen(cmd, stdout=sys.stdout, stderr=sys.stderr, stdin=None).wait()
85
86 except ImportError:
87 # A simple implementation of subprocess for python2.4
88 def _exec_cmd(self, cmd):
89 """Runs a program given in cmd"""
90 return os.spawnvp(os.P_WAIT, cmd[0], cmd)
91
92 def set_image(self, filename):
93 raise NotImplementedError()
94
95 def convert_image_format(self, filename, format='BMP', allowAlpha=False, extension='.bmp'):
96 """Convert the image to another format, and store it in a local place"""
97 if not os.path.exists(filename):
98 logger.warn('The input file "%s" does not exist, so it will not be converted', filename)
99 return filename, False
100 if PIL is None:
101 logger.warn('PIL could not be found, not converting image format')
102 return filename, False
103
104 self.remove_old_image_cache()
105 output_name = os.path.join(self._ConvertedWallpaperLocation, '%s%s' % (time.time(), extension))
106 img = PIL.Image.open(filename)
107
108 # Remove the alpha channel if the user doens't want it
109 if not allowAlpha and img.mode == 'RGBA':
110 img = img.convert('RGB')
111 img.save(output_name, format)
112
113 return output_name, True
114
115
116 class XLoadImageChanger(BaseChanger):
117 name = "xloadimage"
118 _ConvertedWallpaperLocation = '/tmp/wallpapers_xloadimage/'
119 def remove_old_image_cache(self):
120 """Cleans up any old temp images"""
121 if not os.path.isdir(self._ConvertedWallpaperLocation):
122 os.mkdir(self._ConvertedWallpaperLocation)
123 for fullpath, filenames, dirnames in os.walk(self._ConvertedWallpaperLocation, topdown=False):
124 for filename in filenames:
125 os.unlink(os.path.join(fullpath, filename))
126 for dirname in dirnames:
127 os.unlink(os.path.join(fullpath, dirname))
128
129 def convert_image_format(self, filename):
130 """Convert the image to a png, and store it in a local place"""
131 self.remove_old_image_cache()
132 output_name = os.path.join(self._ConvertedWallpaperLocation, '%s.png' % time.time())
133 cmd = ["convert", filename, output_name]
134 logging.debug("""Convert command: '"%s"'""", '" "'.join(cmd))
135 return output_name, self._exec_cmd(cmd)
136
137 def set_image(self, filename):
138 if self.convert:
139 filename, convert_status = self.convert_image_format(filename)
140 if convert_status:
141 logging.debug('Convert failed')
142 cmd = [
143 "xloadimage",
144 "-onroot",
145 "-fullscreen",
146 "-border", "black",
147 filename,
148 ]
149 logging.debug('''WMaker bgset command: "'%s'"''', "' '".join(cmd))
150 return not self._exec_cmd(cmd)
151
152 class OSXChanger(BaseChanger):
153 name = "Mac OS X"
154 _ConvertedWallpaperLocation = '/tmp/wallpapers/'
155 _DesktopPlistLocation = os.path.expanduser('~/Library/Preferences/com.apple.desktop.plist')
156
157 def __init__(self, *args, **kwargs):
158 BaseChanger.__init__(self, *args, **kwargs)
159
160 def remove_old_image_cache(self):
161 """Cleans up any old temp images"""
162 if not os.path.isdir(self._ConvertedWallpaperLocation):
163 os.mkdir(self._ConvertedWallpaperLocation)
164 for fullpath, filenames, dirnames in os.walk(self._ConvertedWallpaperLocation, topdown=False):
165 for filename in filenames:
166 os.unlink(os.path.join(fullpath, filename))
167 for dirname in dirnames:
168 os.unlink(os.path.join(fullpath, dirname))
169
170 def convert_image_format(self, filename):
171 """Convert the image to a png, and store it in a local place"""
172 self.remove_old_image_cache()
173 output_name = os.path.join(self._ConvertedWallpaperLocation, '%s.png' % time.time())
174 try:
175 return super(OSXChanger, self).convert_image_format(filename, format='PNG', extension='.png')
176 except ImportError:
177 logging.debug('Could not load PIL, going to try just copying the image')
178 import shutil
179 output_name = os.path.join(self._ConvertedWallpaperLocation, os.path.basename(filename))
180 shutil.copyfile(filename, output_name)
181 return output_name, True
182
183 def fix_desktop_plist(self):
184 """Removes the entry in the desktop plist file that specifies the wallpaper for each monitor"""
185 try:
186 import Foundation
187 desktop_plist = Foundation.NSMutableDictionary.dictionaryWithContentsOfFile_(self._DesktopPlistLocation)
188 # Remove all but the 'default' entry
189 for k in desktop_plist['Background'].keys():
190 if k == 'default':
191 continue
192 desktop_plist['Background'].removeObjectForKey_(k)
193 # Store the plist again (Make sure we write it out atomically -- Don't want to break finder)
194 desktop_plist.writeToFile_atomically_(self._DesktopPlistLocation, True)
195 except ImportError:
196 logging.debug('Could not import the Foundation module, you may have problems with dual screens')
197
198 def set_image(self, filename):
199 self.fix_desktop_plist()
200 if self.convert:
201 filename, ret = self.convert_image_format(filename)
202 if not ret:
203 logging.debug("Convert failed")
204 return False
205 cmd = """osascript -e 'tell application "finder" to set desktop picture to posix file "%s"'""" % filename
206 logging.debug(cmd)
207 return not subprocess.getstatusoutput(cmd)[0]
208
209 class WIN32Changer(BaseChanger):
210 name = "Windows"
211 _ConvertedWallpaperLocation = os.path.join(os.environ.get('APPDATA', os.path.expanduser('~')), 'wallchanger')
212
213 def __init__(self, *args, **kwargs):
214 BaseChanger.__init__(self, *args, **kwargs)
215 if not self.convert:
216 logging.warn('Running on windows, but convert is not set')
217
218 def remove_old_image_cache(self):
219 """Cleans up any old temp images"""
220 if not os.path.isdir(self._ConvertedWallpaperLocation):
221 os.mkdir(self._ConvertedWallpaperLocation)
222 for fullpath, filenames, dirnames in os.walk(self._ConvertedWallpaperLocation, topdown=False):
223 for filename in filenames:
224 os.unlink(os.path.join(fullpath, filename))
225 for dirname in dirnames:
226 os.unlink(os.path.join(fullpath, dirname))
227
228 def set_image(self, filename):
229 import ctypes
230 user32 = ctypes.windll.user32
231
232 # Taken from the Platform SDK
233 SPI_SETDESKWALLPAPER = 20
234 SPIF_SENDWININICHANGE = 2
235
236 if self.convert:
237 filename, ret = self.convert_image_format(filename)
238 if not ret:
239 logging.debug("Convert failed")
240 return False
241
242 # Parameters for SystemParametersInfoA are:
243 # (UINT uiAction, UINT uiParam, PVOID pvParam, UINT fWinIni)
244 user32.SystemParametersInfoA(
245 SPI_SETDESKWALLPAPER,
246 0,
247 filename,
248 SPIF_SENDWININICHANGE,
249 )
250 return True
251
252 class Gnome2Changer(BaseChanger):
253 name = "Gnome"
254 def set_image(self, filename):
255 cmd = ['gconftool-2', '--type', 'string', '--set', '/desktop/gnome/background/picture_filename', filename]
256 logging.debug(cmd)
257 return not self._exec_cmd(cmd)
258
259 class Gnome3Changer(BaseChanger):
260 name = "Gnome3"
261 def set_image(self, filename):
262 cmd = ['gsettings', 'set', 'org.gnome.desktop.background', 'picture-uri', 'file://'+filename]
263 logging.debug(cmd)
264 return not self._exec_cmd(cmd)
265
266 class Xfce4Changer(BaseChanger):
267 name = "XFCE4"
268 def set_image(self, filename):
269 cmd = [
270 "xfconf-query",
271 "-c", "xfce4-desktop",
272 "-p", "/backdrop/screen0/monitor0/image-path",
273 "-s", filename,
274 ]
275 logging.debug(cmd)
276 return not self._exec_cmd(cmd)
277
278 def main(filename):
279 logging.basicConfig(level=logging.DEBUG, format="%(levelname)s: %(message)s")
280 init()
281 set_image(filename)
282
283 if __name__ == "__main__":
284 try:
285 filename = sys.argv[1]
286 except:
287 print("Usage: %s filename" % sys.argv[0], file=sys.stderr)
288 sys.exit(1)
289
290 main(filename)
291
292