53

How can I change my desktop background with python?

I want to do it in both Windows and Linux.

2
  • 2
    For the linux-half of your question, (and assuming a GNOME desktop environment), you might want to take a look at oracle.bridgewayconsulting.com.au/~danni/misc/… Commented Dec 30, 2009 at 0:17
  • Anyone know how to do this in KDE? Commented May 30, 2016 at 16:11

16 Answers 16

44

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

import ctypes
ctypes.windll.user32.SystemParametersInfoW(20, 0, "absolute path" , 0)
Sign up to request clarification or add additional context in comments.

5 Comments

Is it Python or the types of Windows used?
Python2 uses SystemParametersInfoA and Python3 uses SystemParametersInfoW
To clarify the difference between SystemParametersInfoA and SystemParametersInfoW; the latter takes a unicode parameter (aka Wide 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).
this script is working even if the user don't have privilege to change wallpaper !!
This sets the same wallpaper for all monitors. If you need to set them individually, you can use this answer: stackoverflow.com/a/74203777/3620725
43

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)

8 Comments

Didn't seem to work with a .jpg, works with a .bmp tho (on xp)
I successfully used a jpg on win 7
doesn't seem to work on 3.4 ctypes.windll doesnt contain a user32 method/function
@RageCompex the API is in user32.dll; you can always load it directly.
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 Wide char while the *A variant takes ANSI parameter).
|
21

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.

2 Comments

Instead of the get_home_dir() function, you can just use os.path.expanduser('~')
I have updated this code for Python 3, got it working across Ubuntu 22, Windows 10, and macOS 10.14, and tightened security by avoiding interpolating shell commands: github.com/1j01/textual-paint/blob/main/src/textual_paint/…
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

2 Comments

For Ubuntu 11.04 this no longer seems to work. The gconf setting changes, but the background doesn't refresh to the new image.
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)
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')

Comments

6

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

2 Comments

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 this is a crazy idea, but in a cool way
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.

Comments

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()

5 Comments

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.)
@HarryJohnston then how do you explain SystemParametersInfoA not working on Windows 10 64-bit?
@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.
I find it interesting that Vlad doesn't defend his findings.
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.
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.

1 Comment

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.

1 Comment

It doesn't support Linux (at least the descriptions says so..)
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

Comments

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")

Comments

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)

Comments

0

this works for me

import ctypes
ctypes.windll.user32.SystemParametersInfoW(20,0,path:os.PathLike,3)

Comments

0

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")

Comments

0

Combining a few answers for those who are using aliae, I created a python one liner alias for

python -c "import pathlib; import ctypes; ctypes.windll.user32.SystemParametersInfoW(20, 0, str(pathlib.Path('$1').resolve()), 0)"
alias:
  - name: setwp
    value: |
      python -c "import pathlib; import ctypes; ctypes.windll.user32.SystemParametersInfoW(20, 0, str(pathlib.Path('$1').resolve()), 0)"
    type: function
    if: eq .OS "windows"

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.