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 for line
in subprocess
.check_output("xrandr").decode("utf-8").split("\n"):
29 if len(tokens
) < 2 or tokens
[1] != "connected":
34 return subprocess
.getstatusoutput(cmd
)[0] == 0
36 def init(*args
, **kwargs
):
37 """Desktop Changer factory"""
41 if sys
.platform
== "win32":
42 classes
.append(WIN32Changer
)
45 logging
.debug("Testing for OSX (NonX11)")
46 if call_shell("ps ax -o command -c|grep -q WindowServer"):
47 classes
.append(OSXChanger
)
49 if 'DISPLAY' not in os
.environ
or os
.environ
['DISPLAY'].startswith('/tmp/launch'):
53 if os
.uname()[0] == 'Darwin':
54 # Try to detect if the X11 server is running on OSX
55 if call_shell("ps ax -o command|grep -q '^/.*X11 .* %s'" % os
.environ
['DISPLAY']):
56 # X11 is not running for this display
59 logging
.debug("Testing for XFCE4")
60 if call_shell("xwininfo -name 'xfdesktop'"):
61 classes
.append(Xfce4Changer
)
63 logging
.debug("Testing for Gnome")
64 if call_shell("xwininfo -name 'gnome-settings-daemon'"):
65 if call_shell("gsettings get org.gnome.desktop.background picture-uri"):
66 classes
.append(Gnome3Changer
)
68 classes
.append(Gnome2Changer
)
70 logging
.debug("Testing for xloadimage")
71 if call_shell("which xloadimage"):
72 classes
.append(XLoadImageChanger
)
75 raise Exception("Unknown window manager")
78 changers
.append(klass(*args
, **kwargs
))
81 class BaseChanger(object):
83 def __init__(self
, background_color
='black', convert
=False):
84 logging
.info('Determined the window manager is "%s"', self
.name
)
85 self
.background_color
= background_color
86 self
.convert
= convert
88 def set_image(self
, filename
):
89 raise NotImplementedError()
91 def convert_image_format(self
, filename
, format
='BMP', allowAlpha
=False, extension
='.bmp'):
92 """Convert the image to another format, and store it in a local place"""
93 if not os
.path
.exists(filename
):
94 logger
.warn('The input file "%s" does not exist, so it will not be converted', filename
)
95 return filename
, False
97 logger
.warn('PIL could not be found, not converting image format')
98 return filename
, False
100 self
.remove_old_image_cache()
101 output_name
= os
.path
.join(self
._ConvertedWallpaperLocation
, '%s%s' % (time
.time(), extension
))
102 img
= PIL
.Image
.open(filename
)
104 # Remove the alpha channel if the user doens't want it
105 if not allowAlpha
and img
.mode
== 'RGBA':
106 img
= img
.convert('RGB')
107 img
.save(output_name
, format
)
109 return output_name
, True
112 class XLoadImageChanger(BaseChanger
):
114 _ConvertedWallpaperLocation
= '/tmp/wallpapers_xloadimage/'
115 def remove_old_image_cache(self
):
116 """Cleans up any old temp images"""
117 if not os
.path
.isdir(self
._ConvertedWallpaperLocation
):
118 os
.mkdir(self
._ConvertedWallpaperLocation
)
119 for fullpath
, filenames
, dirnames
in os
.walk(self
._ConvertedWallpaperLocation
, topdown
=False):
120 for filename
in filenames
:
121 os
.unlink(os
.path
.join(fullpath
, filename
))
122 for dirname
in dirnames
:
123 os
.unlink(os
.path
.join(fullpath
, dirname
))
125 def convert_image_format(self
, filename
):
126 """Convert the image to a png, and store it in a local place"""
127 self
.remove_old_image_cache()
128 output_name
= os
.path
.join(self
._ConvertedWallpaperLocation
, '%s.png' % time
.time())
129 cmd
= ["convert", filename
, output_name
]
130 logging
.debug("""Convert command: '"%s"'""", '" "'.join(cmd
))
131 return output_name
, subprocess
.call(cmd
) == 0
133 def set_image(self
, filename
):
135 filename
, convert_status
= self
.convert_image_format(filename
)
137 logging
.debug('Convert failed')
145 logging
.debug('''WMaker bgset command: "'%s'"''', "' '".join(cmd
))
146 return subprocess
.call(cmd
) == 0
148 class OSXChanger(BaseChanger
):
150 _ConvertedWallpaperLocation
= '/tmp/wallpapers/'
151 _DesktopPlistLocation
= os
.path
.expanduser('~/Library/Preferences/com.apple.desktop.plist')
153 def __init__(self
, *args
, **kwargs
):
154 BaseChanger
.__init
__(self
, *args
, **kwargs
)
156 def remove_old_image_cache(self
):
157 """Cleans up any old temp images"""
158 if not os
.path
.isdir(self
._ConvertedWallpaperLocation
):
159 os
.mkdir(self
._ConvertedWallpaperLocation
)
160 for fullpath
, filenames
, dirnames
in os
.walk(self
._ConvertedWallpaperLocation
, topdown
=False):
161 for filename
in filenames
:
162 os
.unlink(os
.path
.join(fullpath
, filename
))
163 for dirname
in dirnames
:
164 os
.unlink(os
.path
.join(fullpath
, dirname
))
166 def convert_image_format(self
, filename
):
167 """Convert the image to a png, and store it in a local place"""
168 self
.remove_old_image_cache()
169 output_name
= os
.path
.join(self
._ConvertedWallpaperLocation
, '%s.png' % time
.time())
171 return super(OSXChanger
, self
).convert_image_format(filename
, format
='PNG', extension
='.png')
173 logging
.debug('Could not load PIL, going to try just copying the image')
175 output_name
= os
.path
.join(self
._ConvertedWallpaperLocation
, os
.path
.basename(filename
))
176 shutil
.copyfile(filename
, output_name
)
177 return output_name
, True
179 def fix_desktop_plist(self
):
180 """Removes the entry in the desktop plist file that specifies the wallpaper for each monitor"""
183 desktop_plist
= Foundation
.NSMutableDictionary
.dictionaryWithContentsOfFile_(self
._DesktopPlistLocation
)
184 # Remove all but the 'default' entry
185 for k
in desktop_plist
['Background'].keys():
188 desktop_plist
['Background'].removeObjectForKey_(k
)
189 # Store the plist again (Make sure we write it out atomically -- Don't want to break finder)
190 desktop_plist
.writeToFile_atomically_(self
._DesktopPlistLocation
, True)
192 logging
.debug('Could not import the Foundation module, you may have problems with dual screens')
194 def set_image(self
, filename
):
195 self
.fix_desktop_plist()
197 filename
, ret
= self
.convert_image_format(filename
)
199 logging
.debug("Convert failed")
201 cmd
= """osascript -e 'tell application "finder" to set desktop picture to posix file "%s"'""" % filename
203 return not subprocess
.getstatusoutput(cmd
)[0]
205 class WIN32Changer(BaseChanger
):
207 _ConvertedWallpaperLocation
= os
.path
.join(os
.environ
.get('APPDATA', os
.path
.expanduser('~')), 'wallchanger')
209 def __init__(self
, *args
, **kwargs
):
210 BaseChanger
.__init
__(self
, *args
, **kwargs
)
212 logging
.warn('Running on windows, but convert is not set')
214 def remove_old_image_cache(self
):
215 """Cleans up any old temp images"""
216 if not os
.path
.isdir(self
._ConvertedWallpaperLocation
):
217 os
.mkdir(self
._ConvertedWallpaperLocation
)
218 for fullpath
, filenames
, dirnames
in os
.walk(self
._ConvertedWallpaperLocation
, topdown
=False):
219 for filename
in filenames
:
220 os
.unlink(os
.path
.join(fullpath
, filename
))
221 for dirname
in dirnames
:
222 os
.unlink(os
.path
.join(fullpath
, dirname
))
224 def set_image(self
, filename
):
226 user32
= ctypes
.windll
.user32
228 # Taken from the Platform SDK
229 SPI_SETDESKWALLPAPER
= 20
230 SPIF_SENDWININICHANGE
= 2
233 filename
, ret
= self
.convert_image_format(filename
)
235 logging
.debug("Convert failed")
238 # Parameters for SystemParametersInfoA are:
239 # (UINT uiAction, UINT uiParam, PVOID pvParam, UINT fWinIni)
240 user32
.SystemParametersInfoA(
241 SPI_SETDESKWALLPAPER
,
244 SPIF_SENDWININICHANGE
,
248 class Gnome2Changer(BaseChanger
):
250 def set_image(self
, filename
):
251 cmd
= ['gconftool-2', '--type', 'string', '--set', '/desktop/gnome/background/picture_filename', filename
]
253 return subprocess
.call(cmd
) == 0
255 class Gnome3Changer(BaseChanger
):
257 def set_image(self
, filename
):
258 cmd
= ['gsettings', 'set', 'org.gnome.desktop.background', 'picture-uri', 'file://'+filename
]
260 return subprocess
.call(cmd
) == 0
262 class Xfce4Changer(BaseChanger
):
264 def set_image(self
, filename
):
265 for display
in get_displays():
268 "-c", "xfce4-desktop",
269 "-p", "/backdrop/screen0/monitor%s/workspace0/last-image" % display
,
273 if subprocess
.call(cmd
) != 0:
278 logging
.basicConfig(level
=logging
.DEBUG
, format
="%(levelname)s: %(message)s")
282 if __name__
== "__main__":
284 filename
= sys
.argv
[1]
286 print("Usage: %s filename" % sys
.argv
[0], file=sys
.stderr
)