51

How can I change my desktop background with python?

I want to do it in both Windows and Linux.

HDE 226868
  • 111
  • 1
  • 4
  • 7
aliva
  • 5,450
  • 1
  • 33
  • 44
  • 2
    For the linux-half of your question, (and assuming a GNOME desktop environment), you might want to take a look at http://oracle.bridgewayconsulting.com.au/~danni/misc/change-background-py.html – unutbu Dec 30 '09 at 00:17
  • Anyone know how to do this in KDE? – TheInitializer May 30 '16 at 16:11

15 Answers15

42

On Windows with python2.5 or higher, use ctypes to load user32.dll and call SystemParametersInfo() with SPI_SETDESKWALLPAPER action.

For example:

import ctypes
SPI_SETDESKWALLPAPER = 20 
ctypes.windll.user32.SystemParametersInfoA(SPI_SETDESKWALLPAPER, 0, "image.jpg" , 0)
n611x007
  • 8,952
  • 8
  • 59
  • 102
J.J.
  • 5,019
  • 2
  • 28
  • 26
  • 3
    Didn't seem to work with a .jpg, works with a .bmp tho (on xp) – Noelkd May 14 '13 at 11:19
  • 2
    I successfully used a jpg on win 7 – CrazyPenguin Jan 06 '14 at 15:39
  • 1
    doesn't seem to work on 3.4 ctypes.windll doesnt contain a user32 method/function – CularBytes Oct 22 '14 at 17:26
  • @RageCompex the API is in user32.dll; you can always load it directly. – J.J. Oct 24 '14 at 18:27
  • 2
    I would recommend using SystemParametersInfoW on both Python2 and Python3 (and making sure the path you are trying to set is properly decoded to unicode on python2). (The *W variant takes a unicode parameter - W for **W**ide char while the *A variant takes ANSI parameter). – ejm Feb 07 '19 at 10:45
  • Also, for the final parameter, you can use the value `3`, which comes from `SPIF_UPDATEINIFILE | SPIF_SENDCHANGE` (search those constants on https://learn.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-systemparametersinfow for more info on what they do; the values can be found in `win32con` module - although there is no need for a dependency on that module just to get these constant values) – ejm Feb 07 '19 at 10:48
  • Didn't work for me. I'm using win10 and python 3.6.x. I used ```SystemParametersInfoW ``` with parameter as ```u"image.jpg"``` and ```"image.jpg"``` but both didn't work. Any other solution? – theashwanisingla Dec 14 '19 at 16:06
  • Can you help with this? https://stackoverflow.com/questions/65914485/set-windows-wallpaper-fit-span-position-using-python-script – Meet Apr 07 '22 at 04:20
41

For Python3.5, SystemParametersInfoA doesn't work. Use SystemParametersInfoW.

import ctypes
ctypes.windll.user32.SystemParametersInfoW(20, 0, "absolute path" , 0)
mesksr
  • 411
  • 4
  • 5
  • Is it Python or the types of Windows used? – Johnny Nov 17 '17 at 20:01
  • 3
    Python2 uses **SystemParametersInfoA** and Python3 uses **SystemParametersInfoW** – mesksr Nov 18 '17 at 20:48
  • 3
    To clarify the difference between `SystemParametersInfoA` and `SystemParametersInfoW`; the latter takes a unicode parameter (aka **W**ide char), the former takes an ANSI parameter. You can still use the *W variant on Python2 but make sure you pass a unicode (e.g. u"path", or str.decode(...)) instead of a str (which is the same as python3 bytes). – ejm Feb 07 '19 at 10:34
20

I use the following method in one of my initial projects:

    def set_wallpaper(self,file_loc, first_run):
        # Note: There are two common Linux desktop environments where 
        # I have not been able to set the desktop background from 
        # command line: KDE, Enlightenment
        desktop_env = self.get_desktop_environment()
        try:
            if desktop_env in ["gnome", "unity", "cinnamon"]:
                uri = "'file://%s'" % file_loc
                try:
                    SCHEMA = "org.gnome.desktop.background"
                    KEY = "picture-uri"
                    gsettings = Gio.Settings.new(SCHEMA)
                    gsettings.set_string(KEY, uri)
                except:
                    args = ["gsettings", "set", "org.gnome.desktop.background", "picture-uri", uri]
                    subprocess.Popen(args)
            elif desktop_env=="mate":
                try: # MATE >= 1.6
                    # info from http://wiki.mate-desktop.org/docs:gsettings
                    args = ["gsettings", "set", "org.mate.background", "picture-filename", "'%s'" % file_loc]
                    subprocess.Popen(args)
                except: # MATE < 1.6
                    # From https://bugs.launchpad.net/variety/+bug/1033918
                    args = ["mateconftool-2","-t","string","--set","/desktop/mate/background/picture_filename",'"%s"' %file_loc]
                    subprocess.Popen(args)
            elif desktop_env=="gnome2": # Not tested
                # From https://bugs.launchpad.net/variety/+bug/1033918
                args = ["gconftool-2","-t","string","--set","/desktop/gnome/background/picture_filename", '"%s"' %file_loc]
                subprocess.Popen(args)
            ## KDE4 is difficult
            ## see http://blog.zx2c4.com/699 for a solution that might work
            elif desktop_env in ["kde3", "trinity"]:
                # From http://ubuntuforums.org/archive/index.php/t-803417.html
                args = 'dcop kdesktop KBackgroundIface setWallpaper 0 "%s" 6' % file_loc
                subprocess.Popen(args,shell=True)
            elif desktop_env=="xfce4":
                #From http://www.commandlinefu.com/commands/view/2055/change-wallpaper-for-xfce4-4.6.0
                if first_run:
                    args0 = ["xfconf-query", "-c", "xfce4-desktop", "-p", "/backdrop/screen0/monitor0/image-path", "-s", file_loc]
                    args1 = ["xfconf-query", "-c", "xfce4-desktop", "-p", "/backdrop/screen0/monitor0/image-style", "-s", "3"]
                    args2 = ["xfconf-query", "-c", "xfce4-desktop", "-p", "/backdrop/screen0/monitor0/image-show", "-s", "true"]
                    subprocess.Popen(args0)
                    subprocess.Popen(args1)
                    subprocess.Popen(args2)
                args = ["xfdesktop","--reload"]
                subprocess.Popen(args)
            elif desktop_env=="razor-qt": #TODO: implement reload of desktop when possible
                if first_run:
                    desktop_conf = configparser.ConfigParser()
                    # Development version
                    desktop_conf_file = os.path.join(self.get_config_dir("razor"),"desktop.conf") 
                    if os.path.isfile(desktop_conf_file):
                        config_option = r"screens\1\desktops\1\wallpaper"
                    else:
                        desktop_conf_file = os.path.join(self.get_home_dir(),".razor/desktop.conf")
                        config_option = r"desktops\1\wallpaper"
                    desktop_conf.read(os.path.join(desktop_conf_file))
                    try:
                        if desktop_conf.has_option("razor",config_option): #only replacing a value
                            desktop_conf.set("razor",config_option,file_loc)
                            with codecs.open(desktop_conf_file, "w", encoding="utf-8", errors="replace") as f:
                                desktop_conf.write(f)
                    except:
                        pass
                else:
                    #TODO: reload desktop when possible
                    pass 
            elif desktop_env in ["fluxbox","jwm","openbox","afterstep"]:
                #http://fluxbox-wiki.org/index.php/Howto_set_the_background
                # used fbsetbg on jwm too since I am too lazy to edit the XML configuration 
                # now where fbsetbg does the job excellent anyway. 
                # and I have not figured out how else it can be set on Openbox and AfterSTep
                # but fbsetbg works excellent here too.
                try:
                    args = ["fbsetbg", file_loc]
                    subprocess.Popen(args)
                except:
                    sys.stderr.write("ERROR: Failed to set wallpaper with fbsetbg!\n")
                    sys.stderr.write("Please make sre that You have fbsetbg installed.\n")
            elif desktop_env=="icewm":
                # command found at http://urukrama.wordpress.com/2007/12/05/desktop-backgrounds-in-window-managers/
                args = ["icewmbg", file_loc]
                subprocess.Popen(args)
            elif desktop_env=="blackbox":
                # command found at http://blackboxwm.sourceforge.net/BlackboxDocumentation/BlackboxBackground
                args = ["bsetbg", "-full", file_loc]
                subprocess.Popen(args)
            elif desktop_env=="lxde":
                args = "pcmanfm --set-wallpaper %s --wallpaper-mode=scaled" % file_loc
                subprocess.Popen(args,shell=True)
            elif desktop_env=="windowmaker":
                # From http://www.commandlinefu.com/commands/view/3857/set-wallpaper-on-windowmaker-in-one-line
                args = "wmsetbg -s -u %s" % file_loc
                subprocess.Popen(args,shell=True)
            ## NOT TESTED BELOW - don't want to mess things up ##
            #elif desktop_env=="enlightenment": # I have not been able to make it work on e17. On e16 it would have been something in this direction
            #    args = "enlightenment_remote -desktop-bg-add 0 0 0 0 %s" % file_loc
            #    subprocess.Popen(args,shell=True)
            #elif desktop_env=="windows": #Not tested since I do not run this on Windows
            #    #From https://stackoverflow.com/questions/1977694/change-desktop-background
            #    import ctypes
            #    SPI_SETDESKWALLPAPER = 20
            #    ctypes.windll.user32.SystemParametersInfoA(SPI_SETDESKWALLPAPER, 0, file_loc , 0)
            #elif desktop_env=="mac": #Not tested since I do not have a mac
            #    #From https://stackoverflow.com/questions/431205/how-can-i-programatically-change-the-background-in-mac-os-x
            #    try:
            #        from appscript import app, mactypes
            #        app('Finder').desktop_picture.set(mactypes.File(file_loc))
            #    except ImportError:
            #        #import subprocess
            #        SCRIPT = """/usr/bin/osascript<<END
            #        tell application "Finder" to
            #        set desktop picture to POSIX file "%s"
            #        end tell
            #        END"""
            #        subprocess.Popen(SCRIPT%file_loc, shell=True)
            else:
                if first_run: #don't spam the user with the same message over and over again
                    sys.stderr.write("Warning: Failed to set wallpaper. Your desktop environment is not supported.")
                    sys.stderr.write("You can try manually to set Your wallpaper to %s" % file_loc)
                return False
            return True
        except:
            sys.stderr.write("ERROR: Failed to set wallpaper. There might be a bug.\n")
            return False

    def get_config_dir(self, app_name=APP_NAME):
        if "XDG_CONFIG_HOME" in os.environ:
            confighome = os.environ['XDG_CONFIG_HOME'] 
        elif "APPDATA" in os.environ: # On Windows
            confighome = os.environ['APPDATA'] 
        else:
            try:
                from xdg import BaseDirectory   
                confighome =  BaseDirectory.xdg_config_home
            except ImportError: # Most likely a Linux/Unix system anyway
                confighome =  os.path.join(self.get_home_dir(),".config")
        configdir = os.path.join(confighome,app_name)
        return configdir

    def get_home_dir(self):
        if sys.platform == "cygwin":
            home_dir = os.getenv('HOME')
        else:
            home_dir = os.getenv('USERPROFILE') or os.getenv('HOME')
        if home_dir is not None:
            return os.path.normpath(home_dir)
        else:
            raise KeyError("Neither USERPROFILE or HOME environment variables set.")

The get_desktop_environment method has been posted in another thread.

Community
  • 1
  • 1
Martin Hansen
  • 597
  • 6
  • 5
  • 4
    Instead of the `get_home_dir()` function, you can just use `os.path.expanduser('~')` –  Apr 08 '16 at 12:21
14

On a gnome desktop, you usually do this with gconf, either directly calling gconftool or using the gconf python module. The latter is in the link given by unutbu. The first method could be done like this.

import commands
command = "gconftool-2 --set /desktop/gnome/background/picture_filename --type string '/path/to/file.jpg'"
status, output = commands.getstatusoutput(command)  # status=0 if success
Raja Selvaraj
  • 7,178
  • 3
  • 20
  • 13
  • For Ubuntu 11.04 this no longer seems to work. The gconf setting changes, but the background doesn't refresh to the new image. – hobs Oct 25 '11 at 03:00
  • I'm using 11.04, and I just wrote a script that cycles through images in a folder, and I used this snippet. Worked fine for me. However, I'm executing the command with os.system(command) – MikeVaughan Mar 16 '12 at 19:59
9

In gnome, it is probably preferable to use the python binding of gconf directly:

import gconf
conf = gconf.client_get_default()
conf.set_string('/desktop/gnome/background/picture_filename','/path/to/filename.jpg')
Alex
  • 91
  • 1
  • 1
5

On windows, you will need some trickery with pywin32, and the windows API, on 'linux' the answer will depend on which desktop is running - KDE, Gnome, or something more exotic. Under KDE (and maybe Gnome) you can probably send a message using D-Bus, which you could do without including any new libraries by using the command line tool dbus-send.

The other option would be to set the desktop wallpaper to a file which you then edit / replace from python - but this will probably only result in a change when the user logs in.

James
  • 24,676
  • 13
  • 84
  • 130
5

Firstly, import ctypes: it gives you access to windows components such as the screensaver, wallpapers, etc.

Then call

ctypes.windll.user32.SystemParametersInfoA(20, 0, the_complete_path_of_your_image, 0)

Make sure the path is the complete path of your image, not just the path from the active directory

Mechanic Pig
  • 6,756
  • 3
  • 10
  • 31
sidthekid
  • 73
  • 1
  • 7
  • 3
    For those interested, you see yourself as a wallpaper in real time if you use this answer in conjunction with opencv, it's hilarious. `source = cv2.VideoCapture(0); path = r"C:\Users\YOUR_NAME_HERE\Desktop\my_face.png";` then loop `for _ in range(50): cv2.imwrite(path, source.read()[1]); ctypes.windll.user32.SystemParametersInfoA(20, 0, path, 0); time.sleep(1/10)` then `source.release()`. – Guimoute Jan 06 '21 at 00:14
  • @Guimoute this is a crazy idea, but in a cool way – Hacker Jan 06 '22 at 10:20
4

There is a difference what SystemParametersInfo method to be called based on what if you are running on 64 bit or 32 bit OS. For 64 bit you have to use SystemParametersInfoW (Unicode) and for 32 bit SystemParametersInfoA (ANSI)

import struct
import ctypes


SPI_SETDESKWALLPAPER = 20
WALLPAPER_PATH = 'C:\\your_file_name.jpg'


def is_64_windows():
    """Find out how many bits is OS. """
    return struct.calcsize('P') * 8 == 64


def get_sys_parameters_info():
    """Based on if this is 32bit or 64bit returns correct version of SystemParametersInfo function. """
    return ctypes.windll.user32.SystemParametersInfoW if is_64_windows() \
        else ctypes.windll.user32.SystemParametersInfoA


def change_wallpaper():
    sys_parameters_info = get_sys_parameters_info()
    r = sys_parameters_info(SPI_SETDESKWALLPAPER, 0, WALLPAPER_PATH, 3)

    # When the SPI_SETDESKWALLPAPER flag is used,
    # SystemParametersInfo returns TRUE
    # unless there is an error (like when the specified file doesn't exist).
    if not r:
        print(ctypes.WinError())


change_wallpaper()
Vlad Bezden
  • 83,883
  • 25
  • 248
  • 179
  • 1
    This is nonsense. The choice of ASCII or Unicode is completely unrelated to whether you are running 32-bit or 64-bit Windows. (You had to use ASCII on 16-bit Windows, and Windows 95/98/ME, but Windows NT has always supported Unicode, both 32-bit and 64-bit versions.) – Harry Johnston Jul 02 '17 at 06:27
  • @HarryJohnston then how do you explain `SystemParametersInfoA` not working on Windows 10 64-bit? – Johnny Nov 17 '17 at 19:56
  • 1
    @Johnny, I've just tried it, and it works perfectly well for me. That's from C, mind you, so it is still *possible* that there's some Python-related weirdness happening that somehow depends on the bitness of the operating system, though that seems very unlikely. Looking at the ctypes documentation, it *should* depend only on whether you're using Python 2 or Python 3. – Harry Johnston Nov 18 '17 at 02:55
  • I find it interesting that Vlad doesn't defend his findings. – Johnny Nov 25 '17 at 08:35
  • `is_64_windows()` actually determines whether the python install is 32 or 64 bit, doesn't it? You are free to install 32 bit python on 64 bit Windows. – Andris Aug 28 '19 at 17:39
4
import ctypes,win32con

def getWallpaper():
    ubuf = ctypes.create_unicode_buffer(512)
    ctypes.windll.user32.SystemParametersInfoW(win32con.SPI_GETDESKWALLPAPER,len(ubuf),ubuf,0)
    return ubuf.value

def setWallpaper(path):
    changed = win32con.SPIF_UPDATEINIFILE | win32con.SPIF_SENDCHANGE
    ctypes.windll.user32.SystemParametersInfoW(win32con.SPI_SETDESKWALLPAPER,0,path,changed)

Alternatively: (with SystemParametersInfoA)

def getWallpaper():
    sbuf = ctypes.create_string_buffer(512) # ctypes.c_buffer(512)
    ctypes.windll.user32.SystemParametersInfoA(win32con.SPI_GETDESKWALLPAPER,len(sbuf),sbuf,0)
    return sbuf.value

def setWallpaper(path):
    changed = win32con.SPIF_UPDATEINIFILE | win32con.SPIF_SENDCHANGE
    ctypes.windll.user32.SystemParametersInfoA(win32con.SPI_SETDESKWALLPAPER,0,path.encode(),changed) # "".encode() = b""

Arguments are:
SystemParametersInfo(SetOrGet, GetBufferSize, SetBufferOrGetBuffer, SetChange)

The path has to be absolute, so if you're using something relative to your script, do:
path = os.path.abspath(path)

To see more stuff you can do with SystemParametersInfo, see the docs.
(near the bottom there's an example to change the mouse speed)

P.S. There are many answers already here, but they're leaving out the broadcasting you're supposed to do. Sure it works without it, but it's bad practice not to use it properly.

P.P.S And they only gave hard coded values, rather than the variables they come from.

Also note, i use 512 characters for the buffer size when getting the path, just to be more safe since paths might exceed 256. I doubt anyone will have paths as long as that though.

One more note. I've only tested the above examples in Python 3, but i don't think SystemParametersInfoA needs the .encode() in Python 2. (they updated strings in Python 3 to unicode i believe) The string in SystemParametersInfoW may need converting for Python 2.

Puddle
  • 2,993
  • 1
  • 19
  • 32
  • Can you help with this? https://stackoverflow.com/questions/65914485/set-windows-wallpaper-fit-span-position-using-python-script – Meet Apr 07 '22 at 04:20
3

I read all the answers and after searching for a while i found a easier solution.

Install the module named py-wallpaper.

pip install py-wallpaper

Import the module.

from wallpaper import set_wallpaper, get_wallpaper

set the wallpaper using set walpaper

set_wallpaper("location/to/image.jpg")

get the current wallpaper's path using get wallpaper

print(get_wallpaper())

thanks.

2

changing the background image of desktop

    import ctypes
    import os
    SPI_SETDESKWALLPAPER = 20 
    ctypes.windll.user32.SystemParametersInfoA(SPI_SETDESKWALLPAPER, 0, 'your image path', 3) 
    #'C:\\Users\\Public\\Pictures\\abc.jpg'

it worked fine for me. windows10, python27

Community
  • 1
  • 1
ShivaGuntuku
  • 5,274
  • 6
  • 25
  • 37
2

On Windows with python2.5 or higher, use ctypes to load user32.dll and call

import ctypes
ctypes.windll.user32.SystemParametersInfoW(20,0,"Path_wallpaper", 0) 
     speak("Background changed succesfully")
Saad
  • 3,340
  • 2
  • 10
  • 32
1

Just adding a small precision to ShivaGuntuku 's post : In python 3 you should replace the 'A' by a 'W' in SytemParametersInfoA. Small exemple to change your desktop background in windows10 with python 3 :

import ctypes
import os
SPI_SETDESKWALLPAPER = 20
ctypes.windll.user32.SystemParametersInfoW(
    SPI_SETDESKWALLPAPER, 0, 'C:\\Users\\godet\\OneDrive\\Images\\breaker_wall.jpg', 0)
Godeta
  • 21
  • 2
1

You can use this library PyWallpaper, worked for me on mac also.

To install type pip install PyWallpaper. And then to change/set your wallpaper -

from PyWallpaper import change_wallpaper
change_wallpaper("/some_path/sample.jpg")
ashraf minhaj
  • 504
  • 2
  • 14
0

this works for me

import ctypes
ctypes.windll.user32.SystemParametersInfoW(20,0,path:os.PathLike,3)
Abhimanyu Sharma
  • 858
  • 1
  • 9
  • 17