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
8 import subprocess
, sys
, os
, os
.path
, time
15 __all__
= ("init", "set_image")
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
)
27 return subprocess
.getstatusoutput(cmd
)[0] == 0
29 def init(*args
, **kwargs
):
30 """Desktop Changer factory"""
34 if sys
.platform
== "win32":
35 classes
.append(WIN32Changer
)
38 logging
.debug("Testing for OSX (NonX11)")
39 if check_cmd("ps ax -o command -c|grep -q WindowServer"):
40 classes
.append(OSXChanger
)
42 if 'DISPLAY' not in os
.environ
or os
.environ
['DISPLAY'].startswith('/tmp/launch'):
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
52 logging
.debug("Testing for XFCE4")
53 if check_cmd("xwininfo -name 'xfce4-session'"):
54 classes
.append(Xfce4Changer
)
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
)
61 classes
.append(Gnome2Changer
)
63 logging
.debug("Testing for xloadimage")
64 if check_cmd("which xloadimage"):
65 classes
.append(XLoadImageChanger
)
68 raise Exception("Unknown window manager")
71 changers
.append(klass(*args
, **kwargs
))
74 class BaseChanger(object):
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
82 def _exec_cmd(self
, cmd
):
84 return subprocess
.Popen(cmd
, stdout
=sys
.stdout
, stderr
=sys
.stderr
, stdin
=None).wait()
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
)
92 def set_image(self
, filename
):
93 raise NotImplementedError()
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
101 logger
.warn('PIL could not be found, not converting image format')
102 return filename
, False
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
)
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
)
113 return output_name
, True
116 class XLoadImageChanger(BaseChanger
):
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
))
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
)
137 def set_image(self
, filename
):
139 filename
, convert_status
= self
.convert_image_format(filename
)
141 logging
.debug('Convert failed')
149 logging
.debug('''WMaker bgset command: "'%s'"''', "' '".join(cmd
))
150 return not self
._exec
_cmd
(cmd
)
152 class OSXChanger(BaseChanger
):
154 _ConvertedWallpaperLocation
= '/tmp/wallpapers/'
155 _DesktopPlistLocation
= os
.path
.expanduser('~/Library/Preferences/com.apple.desktop.plist')
157 def __init__(self
, *args
, **kwargs
):
158 BaseChanger
.__init
__(self
, *args
, **kwargs
)
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
))
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())
175 return super(OSXChanger
, self
).convert_image_format(filename
, format
='PNG', extension
='.png')
177 logging
.debug('Could not load PIL, going to try just copying the image')
179 output_name
= os
.path
.join(self
._ConvertedWallpaperLocation
, os
.path
.basename(filename
))
180 shutil
.copyfile(filename
, output_name
)
181 return output_name
, True
183 def fix_desktop_plist(self
):
184 """Removes the entry in the desktop plist file that specifies the wallpaper for each monitor"""
187 desktop_plist
= Foundation
.NSMutableDictionary
.dictionaryWithContentsOfFile_(self
._DesktopPlistLocation
)
188 # Remove all but the 'default' entry
189 for k
in desktop_plist
['Background'].keys():
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)
196 logging
.debug('Could not import the Foundation module, you may have problems with dual screens')
198 def set_image(self
, filename
):
199 self
.fix_desktop_plist()
201 filename
, ret
= self
.convert_image_format(filename
)
203 logging
.debug("Convert failed")
205 cmd
= """osascript -e 'tell application "finder" to set desktop picture to posix file "%s"'""" % filename
207 return not subprocess
.getstatusoutput(cmd
)[0]
209 class WIN32Changer(BaseChanger
):
211 _ConvertedWallpaperLocation
= os
.path
.join(os
.environ
.get('APPDATA', os
.path
.expanduser('~')), 'wallchanger')
213 def __init__(self
, *args
, **kwargs
):
214 BaseChanger
.__init
__(self
, *args
, **kwargs
)
216 logging
.warn('Running on windows, but convert is not set')
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
))
228 def set_image(self
, filename
):
230 user32
= ctypes
.windll
.user32
232 # Taken from the Platform SDK
233 SPI_SETDESKWALLPAPER
= 20
234 SPIF_SENDWININICHANGE
= 2
237 filename
, ret
= self
.convert_image_format(filename
)
239 logging
.debug("Convert failed")
242 # Parameters for SystemParametersInfoA are:
243 # (UINT uiAction, UINT uiParam, PVOID pvParam, UINT fWinIni)
244 user32
.SystemParametersInfoA(
245 SPI_SETDESKWALLPAPER
,
248 SPIF_SENDWININICHANGE
,
252 class Gnome2Changer(BaseChanger
):
254 def set_image(self
, filename
):
255 cmd
= ['gconftool-2', '--type', 'string', '--set', '/desktop/gnome/background/picture_filename', filename
]
257 return not self
._exec
_cmd
(cmd
)
259 class Gnome3Changer(BaseChanger
):
261 def set_image(self
, filename
):
262 cmd
= ['gsettings', 'set', 'org.gnome.desktop.background', 'picture-uri', 'file://'+filename
]
264 return not self
._exec
_cmd
(cmd
)
266 class Xfce4Changer(BaseChanger
):
268 def set_image(self
, filename
):
271 "-c", "xfce4-desktop",
272 "-p", "/backdrop/screen0/monitor0/image-path",
276 return not self
._exec
_cmd
(cmd
)
279 logging
.basicConfig(level
=logging
.DEBUG
, format
="%(levelname)s: %(message)s")
283 if __name__
== "__main__":
285 filename
= sys
.argv
[1]
287 print("Usage: %s filename" % sys
.argv
[0], file=sys
.stderr
)