Communities

Writing
Writing
Codidact Meta
Codidact Meta
The Great Outdoors
The Great Outdoors
Photography & Video
Photography & Video
Scientific Speculation
Scientific Speculation
Cooking
Cooking
Electrical Engineering
Electrical Engineering
Judaism
Judaism
Languages & Linguistics
Languages & Linguistics
Software Development
Software Development
Mathematics
Mathematics
Christianity
Christianity
Code Golf
Code Golf
Music
Music
Physics
Physics
Linux Systems
Linux Systems
Power Users
Power Users
Tabletop RPGs
Tabletop RPGs
Community Proposals
Community Proposals
tag:snake search within a tag
answers:0 unanswered questions
user:xxxx search by author id
score:0.5 posts with 0.5+ score
"snake oil" exact phrase
votes:4 posts with 4+ votes
created:<1w created < 1 week ago
post_type:xxxx type of post
Search help
Notifications
Mark all as read See all your notifications »
Q&A

Welcome to Software Development on Codidact!

Will you help us build our independent community of developers helping developers? We're small and trying to grow. We welcome questions about all aspects of software development, from design to code to QA and more. Got questions? Got answers? Got code you'd like someone to review? Please join us.

Comments on How to programmatically get current wallpaper file path?

Parent

How to programmatically get current wallpaper file path?

+4
−0

I have written a screensaver program that, in part, uses the current wallpaper for some visual effects. However, finding the current wallpaper in different operating systems and environments is less than straightforward.

I've made some progress, but what I've found during searches has not been very helpful[1]. So, how can I find the file system path to the current wallpaper in the following OS/environments?

GNU+Linux

  1. KDE/Plasma
  2. Gnome
  3. Unity (TBD)
  4. etc?

Microsoft

  1. Win 7
  2. Win 10
  3. Win 11 (TBD)

Apple

  1. MacOS (TBD)
  2. ?

Remarks

Please note that I don't have direct access to all of the above (e.g. no Windows 11, no Unity, no Apple systems), but I'd like this program to work on those OS/environments if a user were to run it there, so answers that have been tested to work prior to posting would be greatly appreciated.

Also, I'm not sure of what the best way to make/add a canonical answer here is, but I'd like to try that here with my answer by accumulating what has been tested/verified to work.


  1. It includes abandoned libraries that no longer work, explicitly state they don't support certain OS/environments, and out-of-date recommendations and/or commands that don't (seem to?) work anymore. For example, the command gsettings get org.gnome.desktop.background picture-uri has showed up multiple times, but in Gnome, it does not give me the path to the current wallpaper; it just points to another image. ↩︎

History
Why does this post require moderator attention?
You might want to add some details to your flag.
Why should this post be closed?

0 comment threads

Post
+2
−0

This answer will use Python 3 for code examples. I encourage people to post other answers for other languages. I'll continue to update this answer, as I'm able, based on comments with tested/verified recommendations/code/etc.

Checking OS

import platform

sys = platform.system().lower()
if sys == 'linux':
    # do Linux stuff here
elif sys == 'windows':
    # do Windows stuff here
else:
    # ...

GNU+Linux

To identify which desktop environment is being used, I'm using the DESKTOP_SESSION environment variable. I get the following string values:

  1. KDE/Plasma: plasma
  2. Gnome: ubuntu
  3. Unity: (TBD)

If there're other synonyms and/or strings for different versions of the same environment, that'd also be good to know so it can be added above.

Code:

from os import environ as env

kde = ('plasma',)
gnome = ('ubuntu',)
session = env['DESKTOP_SESSION']

if session in kde:
    # KDE/Plasma implementation here
elif session in gnome:
    # Gnome implementation here
else:
    # ...

NOTE: The GNU+Linux distro being used here is Ubuntu 22.10.

1. KDE/Plasma

Find the following file/line:

  1. File: ~/.config/plasma-org.kde.plasma.desktop-appletsrc
  2. Line: Image=file://<abs-path>

Note that:

  1. <abs-path> is the absolute path to the file in the file system (e.g. /home/<you>/Pictures/your-wallpaper.jpg);
  2. you may have multiple Image= entries (e.g. due to multiple plasma activities; I have 2 activities and 2 entries; entry 1 is for activity 1, etc.);
  3. we're only grabbing the 1st entry, which is the wallpaper for the 1st activity;
  4. you can look at the kscreenlockerrc file if interested in the image used by the screen locker.

Code:

from os.path import join, expanduser

wallpaper_path = None
configpath = join(
    expanduser('~'),
    '.config',
    'plasma-org.kde.plasma.desktop-appletsrc'
)
with open(configpath) as config:
    for line in config.readlines():
        if line.startswith('Image='):
            wallpaper_path = line.split('=')[1].removeprefix('file://').strip()
            break

2. Gnome

The process under Gnome boils down to the following steps:

  1. Find out what the user's theme is, and
  2. Use the theme-specific command to get the wallpaper path.

Theme

On a clean installation (e.g. Ubuntu 22.10), the user has the following theme options:

  1. Default
  2. Dark

Use this command to find out the theme currently set by the user:

gsettings get org.gnome.desktop.interface color-scheme

The possible results are below:

Theme Value
Default 'default'
Dark 'prefer-dark'

You can use the subprocess module for this:

theme = subprocess.run(
    'gsettings get org.gnome.desktop.interface color-scheme',
    capture_output=True,
    shell=True,
    universal_newlines=True
).stdout.strip().replace("'", '')

The returned string must have newlines and extra ' characters removed, hence the strip and replace calls.

Path

The command to get the wallpaper path is:

gsettings get org.gnome.desktop.background <key>

where <key> is one of the following theme-dependent values:

Theme Key
default picture-uri
prefer-dark picture-uri-dark

NOTE: Correct key selection depends on the result from the previous section. Using the wrong key will still give you a file system path. However, that path is not necessarily the current wallpaper (you could get lucky). In fact, the path may not even point to an existing file (e.g. running the command under KDE).

For example, if the user is using the Dark theme, then the correct command to get the path is:

gsettings get org.gnome.desktop.background picture-uri-dark

This is the reason the commands I had found hadn't been helpful, because I'm always using the dark theme, but the info/commands posted made no mention of these details.

Assuming the theme variable from the previous section, you can, again, use the subprocess module:

theme_key = 'picture-uri-dark' if theme == 'prefer-dark' else 'picture-uri'
wallpaper_path = subprocess.run(
    f'gsettings get org.gnome.desktop.background {theme_key}',
    capture_output=True,
    shell=True,
    universal_newlines=True
).stdout.strip().replace("'", '').removeprefix('file://')

3. Unity (TBD)

Microsoft

In Windows, there're at least two ways to go about this:

  1. Using a PowerShell command, such as Get-ItemPropertyValue -Path "Registry::HKEY_CURRENT_USER\Control Panel\Desktop" -Name Wallpaper via the subprocess module;
  2. Using the built-in winreg module, which is part of the standard library (though only available in Windows).

I'm recommending Python's winreg for the following reasons:

  1. The implementation is more consistent across different versions of Windows;
  2. The PowerShell command Get-ItemPropertyValue is not implemented in Windows 7;
  3. It should perform better anyway, since it doesn't need to spawn a new process.

Windows 7/10

Code for Windows:

import winreg

hkey = winreg.HKEY_CURRENT_USER
subkey = 'Control Panel\\Desktop'
wallpaper_path = None
with winreg.OpenKeyEx(hkey, subkey) as regkey:          # type: PyHKEY
    value = winreg.QueryValueEx(regkey, 'Wallpaper')    # type: Tuple[str, int]
    wallpaper_path = value[0]
    try:
        with open(wallpaper_path): pass                 # see caveats below
    except Exception:
        # good luck

The structure returned by the QueryValueEx function is a tuple that looks like this:

('C:\\The\\Path To\\Your\\Wallpaper\\Image.jpg', 1)

We're interested in the first tuple element, hence value[0].

Caveats

The above code has the following version-dependent caveats:

  1. Windows 7 (Python 3.7):
    1. Python 3.7 is the most recent version that will work in this Windows version, so this is how it was tested.
    2. When the user sets the wallpaper image, the OS seems to copy the file to a pre-determined location.
    3. This location seems to always remain valid, regardless of subsequent user actions.
  2. Windows 10 (Python 3.11):
    1. When the user sets the wallpaper image, the OS simply stores the path to that image at the time it was set. (The OS does not seem to copy the original image to any other location like Win7 does, which is bad for us.)
    2. Subsequent user actions on the original image (e.g. rename, delete, etc) will invalidate the registry value, which still points to the original location.
    3. This implies the path returned by QueryValueEx may not even exist at the time we query for it, which means you need a fallback.
    4. We use open(wallpaper_path) as a safe way to verify the path is valid and can be accessed.
    5. The PowerShell command returns the same path as QueryValueEx, which means the PowerShell code may be using the same function internally.

Windows 11 (TBD?)

The code above might work, but I don't have Windows 11 and cannot test it here.

History
Why does this post require moderator attention?
You might want to add some details to your flag.

2 comment threads

Use `os.path.exists` instead of open (2 comments)
Windows 10 Enterprise, Home, and Win32 API (9 comments)
Windows 10 Enterprise, Home, and Win32 API
ghost-in-the-zsh‭ wrote about 1 year ago

Alexei‭ Does your Windows 10 recommendation work in the Home edition or does it really require the Enterprise version of it? Does Windows 10 Home even have Powershell available? (Not something I'm able to check right at this moment.) Also, are you aware of something in the Windows 32 API that could be used here? (That might be preferable and would avoid the shell availability issue/question.)

Alexei‭ wrote about 1 year ago

ghost-in-the-zsh‭ AFAIK, all Windows versions after Windows 7 include a PS installed. This is confirmed by this article. No need for enterprise, I have mentioned it because it was the OS I was using for testing.

Ref. to Win 32 API, RegGetValueA seems to do the job.

ghost-in-the-zsh‭ wrote about 1 year ago · edited about 1 year ago

Alexei‭ Your method shows error messages when attempted via Python3's subprocess module. For example, sp.run(f'{os.environ["SystemRoot"]}\\System32\\WindowsPowerShell\\v1.0\\powershell.exe Get-ItemPropertyValue -Path "Registry::HKEY_CURRENT_USER\Control Panel\Desktop" -Name Wallpaper', capture_output=True, shell=True, universal_newlines=True) and other variations fail. I'm also having issues accessing RegGetValueA over ctypes. (I'm still trying other things to see what I can get to work.)

Also, since you've decided to edit my answer, instead of posting your own, can you make sure your posts are in Python3, as I said I'd do in my answer? As it currently stands, there're some issues getting it to work in Python3. Thanks. (I would've preferred having suggestions posted in comments rather than randomly editing my answer/post.)

ghost-in-the-zsh‭ wrote about 1 year ago · edited about 1 year ago

I found a way to make it work in Python3 (in Windows 10). I'll be updating the post later. Also, while PowerShell is installed in Windows 7, the Get-ItemPropertyValue command returns an error message, suggesting the command is not implemented.

Alexei‭ wrote about 1 year ago

@ghost-in-the-zsh‭ Sorry about that. I didn't notice the Python tag. I have rolled back to your last version until we found a way to make it work when calling it from Python.

ghost-in-the-zsh‭ wrote about 1 year ago · edited about 1 year ago

Alexei‭ No harm no foul. The way I got it to work from Python in Windows 10 was as follows (had to use a list for the commands, unlike in GNU+Linux):

command = [
    f'{env["SystemRoot"]}\\System32\\WindowsPowerShell\\v1.0\\powershell.exe',
    'Get-ItemPropertyValue',
    '-Path',
    '"Registry::HKEY_CURRENT_USER\Control Panel\Desktop"',
    '-Name',
    'Wallpaper'
]
sp.run(command, shell=True, check=True, capture_output=True, universal_newlines=True)

However, I've seen comments that the path to the powershell.exe may not always be the same, so it seems this is not a very reliable method.

Alexei‭ wrote about 1 year ago

ghost-in-the-zsh‭ I am wondering if it wouldn't be easier to read the registry key using winreg instead of doing it indirectly using Powershell.

ghost-in-the-zsh‭ wrote about 1 year ago · edited about 1 year ago

Alexei‭ It would be and that's what I was doing earlier today. Unfortunately, what I found is potentially unreliable. On Win7, had to use Python 3.7 (latest release that will work there). This code seems to work:

>>> import winreg as w
>>> key = w.OpenKeyEx(w.HKEY_CURRENT_USER, 'Control Panel\\Desktop')
>>> w.QueryValueEx(key, 'Wallpaper')
('C:\\Users\\<UserName>\\AppData\\Roaming\\Microsoft\\Windows\\Themes\\TranscodedWallpaper.jpg', 1)

The wallpaper image gets copied over to the above location with the name TranscodedWallpaper.jpg and that seems to be it. However, in Windows 10 (Python 3.11):

>>> import winreg as w
>>> key = w.OpenKeyEx(w.HKEY_CURRENT_USER, 'Control Panel\\Desktop')
>>> w.QueryValueEx(key, 'Wallpaper')
('E:\\gears_of_war.jpg', 1)

Unfortunately, the path above is not necessarily valid, though. That's where the wallpaper image was at the time it was set as the wallpaper, not where it is right now. The output's the same as PowerShell.

ghost-in-the-zsh‭ wrote about 1 year ago · edited about 1 year ago

After looking around the registry with the Registry Editor, I couldn't find anything else that seemed to indicate the current path to the current wallpaper or anything. So, it's very possible for the user to set an image as a wallpaper and then (re)move it, causing the Windows registry entry to end up pointing to a non-existent path/file, presumably until the user sets a new wallpaper, in which case, we're just back to square one.

Unless a more reliable option is available, I may have no choice other than a "best-guess/effort" implementation.