One of the simplest ways to make a multi-monitor system look more impressive is to set a desktop background which spans across all of the monitors. Just scale the image up to the smallest size that fills all of the visible desktop space, then cut out monitor-shaped pieces to display.
Every system (even lightweight, older Linux desktops) supports this… every system except one. When KDE went from 3.5.x to 4.x, they dropped support for spanning a background across all desktops and, to this day, they still haven’t brought it back.
…and since everything more lightweight has bugs with the desktop layout I’m currently dealing with while I wait for the new monitor bracket I ordered, that’s a problem. (Even worse, there’s no API I could find to programmatically set a background either!)
The solution I settled on was to write a little Python script which uses maybe half a dozen lines of PyQt 4.x/5.x calls (before line-wrapping and boilerplate) to do what KDE should have, then spits out image files to be set as per-monitor backgrounds.
I also implemented a --randomize option which can be used with cron to produce input for KDE’s slideshow mode. (Basically, you set each monitor background to be a single-entry slideshow that the script updates)
Just give it an image (or --randomize and a list of files and/or folders) and an output directory. (See --help for more details)
UPDATE: It now also gives more control over how the background is matched to the desktop’s aspect ratio via --gravity and I included, as an example, the .desktop file I use to integrate it with Geeqie via Zenity. (KDialog’s equivalent to zenity --list is inferior.)
Nice effort, I tested this code, and it works decent for simple rectangular monitor configurations but isn’t robust enough to handle non-rectangular monitor configurations(KDE 4.13 clearly isn’t either as while I was testing your code, after switching my monitor orientation multiple times during testing, plasma finally got confused and all monitors were stuck in a state where nothing was responsive, no problem though as my mighty reboot powers thwarted the problem).
Here are some suggestions:
1) The images and background folder simply do not exist on my system, your code assumes that they do and raises an exception. I’m not exactly sure why images/background was chosen but if it needs to exist due to FreeDesktop specs or something of the like then, check if they exist and are writable first (don’t rely on exceptions, prevent them from ever occurring, because in this particular case, there is nothing exceptional about missing data, and your code will be more portable and robust as a result).
IOError: [Errno 13] Destination directory not writable: %s: '/home/user/.local/share/images/background'
2) Since your .desktop file is specific to geeqie, I would suggest mentioning that it needs to be installed in the /usr/share/geeqie/applications directory(I have no previous experience with geeqie). Furthermore, since you took the time to integrate it with geeqie to that extent, you really shouldn’t be looking in the users directory for the script.
3) Rather than assuming the monitors will be in a rectangular configuration, you should be researching how to grab the entire multi-monitor resolution from KDE’s “Display Configuration” (It’s likely stored in rc config somewhere in ~/home/user/.kde/share/config/ , likely in plasma-desktoprc judging from a quick peek) and then scale the wallpaper accordingly and crop out the “dead zones” as you mentioned on the KDE forums( https://forum.kde.org/viewtopic.php?f=67&t=93190&sid=376c69f95bc29e2228305fabd886087d&start=15 ). I.E. all monitors are of equal resolution, top-left and bottom-right monitors in vertical orientation, top-right and bottom-left monitors are in horizontal orientation, thus leaving a “dead zone” in the center.
4a) Your code needs to take into account monitor bezel width. KDE 4.13 “Display Configuration” does takes this into account but not in physical units, but in pixels. Unfortunately it does not allow you to manually specify what the pixel-bezel gap is between the monitors, but I’m sure this could be found and modified in the plasma-desktoprc file mentioned above. I.E. take the 4-monitor configuration from above and make it 5 by adding a monitor filling the “dead zone” (note that the resolution of this monitor would affect the “bezel” width. Based on the image cropping algorithm and monitor orientation listed in suggestion 3 above for how to crop images properly, the monitor in the “dead zone” should be set at whatever the maximum resolution of the “dead zone” is minus the center monitor’s bezel width). Grok that.
4b) Now understand that every monitor can be set at a different resolution. For this next example, we are going to show the monitor configuration. See the attached image depicting the configuration: ( https://drive.google.com/file/d/0B8EHPTGJzAQ5ZV9XNll1TEdHWFE/view?usp=sharing ) The four outer monitors are drawn at a 800X600 size and the center monitor is drawn at a 180X180 size, all with a bezel size of 10(making a 20 gap between). In this configuration, the outer monitors are at a 4:3 ratio and the center monitor at a 1:1 ratio. While it’s possible that the center monitor would actually be a 1:1 ratio monitor with 180×180 resolution, it’s highly unlikely in almost any other user’s configuration other than this example.
4c) Lets say that for the example the center monitor is set at a higher resolution capability than the outer monitors (1920×1920 center and 800×600 outer). What should the total background wallpaper resolution be? You should include bezel width since the center monitor bezel width cannot be ignored. 800+600+20=1420 virtual pixel width.
4d) You finally have a desktop that handles a rectangle properly(or so I think). I’m not aware fully of how Wayland will work compared to X11, but I think this would be a step in the right direction for KDE. I’m a bit surprised that they consider this a low priority, but I’m sure it will elevate to a higher priority as they understand the problem better.
I hope you and others have found this educational (if not essential) and improves KDE in the long term.
Trust me, I wrote it specifically because I have a non-rectangular desktop and, based on what I know about the APIs I’m using, it should work for anything. If it’s failing for you, it’s probably a misconfiguration or bug outside my script but I’d be happy to investigate.
I’ve posted an update to add log.debug() output so, when explaining your symptoms, please run it with -vv and paste the output.
As for your points:
1.~/.local/ mirrors /usr/ and I mirrored /usr/share/images/ because that’s where Lubuntu was putting custom theming elements like panel backgrounds back when I started customizing my desktop’s appearance.
(In other words, it’s XDG-inspired but, in this particular case, there’s no explicit technical reason for it)
As for “Destination directory not writable”, that’s just it falling back to the general-case error message I wrote because I was too tired to decide whether there were cases where an os.makedirs() call would be unwelcome (given the potential for problems if there are typos and that no other command-line utility just blindly creates missing directories, with ancestors, like you suggest) and I then forgot about it the next morning.
I’ve made the following changes:
I suppressed the traceback and fed it through log.error() to make it more clear that it’s an expected error caused by bad input rather than an unexpected error caused by bad code.
I added clearer error messages for “destination directory does not exist” and “destination path is not a directory”
I’ve added screaming bold formatting to the part where it says that the .desktop file is intended as an example.
I’ve added an all-caps disclaimer to the .desktop file indicating that it’s intended as an example and may require changes to work on your system.
(Also, thanks for drawing attention to that formatting bug I introduced when I replaced a logging call (with lazy string formatting evaulation) as an IOError (which doesn’t support lazy string formatting evaluation but does have a three-argument form that prevented it from erroring out).
2. The whole point of Geeqie using .desktop files to populate the Edit menu is that it does not need them to be in a special location. The .desktop file just specifies OnlyShowIn=X-Geeqie and anywhere on the normal .desktop lookup path will do. (That’s why I just used xdg-desktop-menu --install to drop it into ~/.local/share/applications/ and update the cache on my system.)
However, I did accidentally post an old version which still had an accidental Hidden=true line, so I apologize for that. I’ve also added instructions to the .desktop file.
3. The whole reason I wrote this is for non-rectangular desktops. If I had a rectangular desktop, I’d still be using PCManFM for desktop icons and LXPanel for my panels. (LXPanel doesn’t like non-rectangular desktops but lets me span a single panel and taskbar widget across all monitors.)
Also, the monitor configuration is stored in the X11 server itself and accessed via the XRandR protocol extension. That’s why you can query and set it using non-KDE tools like the command-line xrandr utility or the nvidia-settings GUI.
If you look at get_desktop_geom() (lines , you’ll see that it looks up the entire desktop’s rectangle (for scaling) and the geometry of each monitor within that rectangle (for copying out the individual monitor images).
If you look at write_backgrounds(), you’ll see that it takes that geometry and uses it to determine where each individual image should be grabbed from within the desktop rectangle. I don’t know why that’s going wrong on your system, but my code is simple enough and works properly and reliably enough on my system (tested on both PyQt 4.x and PyQt 5.x) that it’s much more likely that a bug in X11 or Qt is causing your system to lie to my script about the monitor geometry.
Also, there’s no need to quote “dead zones” when not talking about the term itself. It makes it sound like you’re saying it while making air quotes with your fingers but “dead space”, “dead areas”, or “dead zones” are the actual technical terms used.
4a, 4b, 4c. If you look at something less primitive than the KDE display control panel (I use nvidia-settings and then copy the generated MetaModes line into my xorg.conf so it’s not DE-specific), you’ll see that you can set monitor positions without needing to fill the gaps. Under the hood, it’s just setting integer coordinates for rectangles within a larger rectangle. In fact, you can even overlap monitors. (Useful for things like hacking together a presentation where you want to mirror just one window on your desktop to the projector.)
My script works as follows:
Ask X11 for the desktop rectangle (the smallest rectangle which encompasses all monitors).
Use aspect ratio-preserving scaling to fill the desktop (this makes it bigger than the desktop in one dimension unless the aspect ratios match perfectly)
Crop the scaled image to the desktop shape to attain the alignment requested via --gravity
Ask X11 for the (x, y, width, height) rectangles representing all defined monitors and then crop out and save each one as a background.
(Note: All querying X11 is actually done at the beginning but, for simplicity’s sake, I haven’t written it that way.)
Everything you’re saying I have to do is already done by X11 itself. In fact, the only thing I didn’t bother to do is provide an option to scale pieces of the background to compensate for varying physical pixel sizes. (Because it’s a lot of complexity and testing for a situation I’d never find myself in. it’d drive me crazy to have monitors of different DPIs on my system and I was too lazy to temporarily borrow a 17″ 1280×1024 monitor to set up next to my 19″ 1280×1024 monitors.)
4d. Given that the APIs I use are used by things like games to figure out what window geometry to request, it’s quite likely that XWayland will expose them. As I’m on the nVidia binary drivers and, as a KDE user, wary of libinput’s “have one good set of defaults” mentality, Wayland hasn’t been a priority for me. (You’re talking to someone who hacked together a replacement for the update manager because they removed the ability to disable the “Reboot to update your kernel” nags. It’s entirely possible I’ll end up maintaining a fork of libinput to get some behaviour they decided wasn’t worth supporting.)
By submitting a comment here you grant this site a perpetual license to reproduce your words and name/web site in attribution under the same terms as the associated post.
All comments are moderated. If your comment is generic enough to apply to any post, it will be assumed to be spam. Borderline comments will have their URL field erased before being approved.
About the Author
I'm Stephan, a Linux user with a passion for open-source, UI/UX design, and exploring what makes fiction work.
In my spare time, I focus on (and write about):
Programming (mainly in Python and Rust)
Retrocomputing (mostly DOS but, as of January 2023, I also own a machine running Mac OS 9.2)
Reading and Reviewing Fiction
The odd bit of UI/UX design or literary theory
For notification of significant updates to existing posts, consider following me on Mastodon.
Nice effort, I tested this code, and it works decent for simple rectangular monitor configurations but isn’t robust enough to handle non-rectangular monitor configurations(KDE 4.13 clearly isn’t either as while I was testing your code, after switching my monitor orientation multiple times during testing, plasma finally got confused and all monitors were stuck in a state where nothing was responsive, no problem though as my mighty reboot powers thwarted the problem).
Here are some suggestions:
1) The images and background folder simply do not exist on my system, your code assumes that they do and raises an exception. I’m not exactly sure why images/background was chosen but if it needs to exist due to FreeDesktop specs or something of the like then, check if they exist and are writable first (don’t rely on exceptions, prevent them from ever occurring, because in this particular case, there is nothing exceptional about missing data, and your code will be more portable and robust as a result).
IOError: [Errno 13] Destination directory not writable: %s: '/home/user/.local/share/images/background'
2) Since your .desktop file is specific to geeqie, I would suggest mentioning that it needs to be installed in the /usr/share/geeqie/applications directory(I have no previous experience with geeqie). Furthermore, since you took the time to integrate it with geeqie to that extent, you really shouldn’t be looking in the users directory for the script.
3) Rather than assuming the monitors will be in a rectangular configuration, you should be researching how to grab the entire multi-monitor resolution from KDE’s “Display Configuration” (It’s likely stored in rc config somewhere in ~/home/user/.kde/share/config/ , likely in plasma-desktoprc judging from a quick peek) and then scale the wallpaper accordingly and crop out the “dead zones” as you mentioned on the KDE forums( https://forum.kde.org/viewtopic.php?f=67&t=93190&sid=376c69f95bc29e2228305fabd886087d&start=15 ). I.E. all monitors are of equal resolution, top-left and bottom-right monitors in vertical orientation, top-right and bottom-left monitors are in horizontal orientation, thus leaving a “dead zone” in the center.
4a) Your code needs to take into account monitor bezel width. KDE 4.13 “Display Configuration” does takes this into account but not in physical units, but in pixels. Unfortunately it does not allow you to manually specify what the pixel-bezel gap is between the monitors, but I’m sure this could be found and modified in the plasma-desktoprc file mentioned above. I.E. take the 4-monitor configuration from above and make it 5 by adding a monitor filling the “dead zone” (note that the resolution of this monitor would affect the “bezel” width. Based on the image cropping algorithm and monitor orientation listed in suggestion 3 above for how to crop images properly, the monitor in the “dead zone” should be set at whatever the maximum resolution of the “dead zone” is minus the center monitor’s bezel width). Grok that.
4b) Now understand that every monitor can be set at a different resolution. For this next example, we are going to show the monitor configuration. See the attached image depicting the configuration: ( https://drive.google.com/file/d/0B8EHPTGJzAQ5ZV9XNll1TEdHWFE/view?usp=sharing ) The four outer monitors are drawn at a 800X600 size and the center monitor is drawn at a 180X180 size, all with a bezel size of 10(making a 20 gap between). In this configuration, the outer monitors are at a 4:3 ratio and the center monitor at a 1:1 ratio. While it’s possible that the center monitor would actually be a 1:1 ratio monitor with 180×180 resolution, it’s highly unlikely in almost any other user’s configuration other than this example.
4c) Lets say that for the example the center monitor is set at a higher resolution capability than the outer monitors (1920×1920 center and 800×600 outer). What should the total background wallpaper resolution be? You should include bezel width since the center monitor bezel width cannot be ignored. 800+600+20=1420 virtual pixel width.
4d) You finally have a desktop that handles a rectangle properly(or so I think). I’m not aware fully of how Wayland will work compared to X11, but I think this would be a step in the right direction for KDE. I’m a bit surprised that they consider this a low priority, but I’m sure it will elevate to a higher priority as they understand the problem better.
I hope you and others have found this educational (if not essential) and improves KDE in the long term.
Cheers,
Jonathan
Disclosure: I’m one of the developers for Embroidermodder 2 and working on a KDE4 thumbnailer for embroidery files FWIW. I’m a Life Member at Digital Blasphemy who was researching this problem a day after Stephan posted this.
Trust me, I wrote it specifically because I have a non-rectangular desktop and, based on what I know about the APIs I’m using, it should work for anything. If it’s failing for you, it’s probably a misconfiguration or bug outside my script but I’d be happy to investigate.
I’ve posted an update to add
log.debug()
output so, when explaining your symptoms, please run it with-vv
and paste the output.As for your points:
1.
~/.local/
mirrors/usr/
and I mirrored/usr/share/images/
because that’s where Lubuntu was putting custom theming elements like panel backgrounds back when I started customizing my desktop’s appearance.(In other words, it’s XDG-inspired but, in this particular case, there’s no explicit technical reason for it)
As for “Destination directory not writable”, that’s just it falling back to the general-case error message I wrote because I was too tired to decide whether there were cases where an
os.makedirs()
call would be unwelcome (given the potential for problems if there are typos and that no other command-line utility just blindly creates missing directories, with ancestors, like you suggest) and I then forgot about it the next morning.I’ve made the following changes:
log.error()
to make it more clear that it’s an expected error caused by bad input rather than an unexpected error caused by bad code..desktop
file is intended as an example..desktop
file indicating that it’s intended as an example and may require changes to work on your system.(Also, thanks for drawing attention to that formatting bug I introduced when I replaced a logging call (with lazy string formatting evaulation) as an
IOError
(which doesn’t support lazy string formatting evaluation but does have a three-argument form that prevented it from erroring out).2. The whole point of Geeqie using
.desktop
files to populate the Edit menu is that it does not need them to be in a special location. The.desktop
file just specifiesOnlyShowIn=X-Geeqie
and anywhere on the normal.desktop
lookup path will do. (That’s why I just usedxdg-desktop-menu --install
to drop it into~/.local/share/applications/
and update the cache on my system.)However, I did accidentally post an old version which still had an accidental
Hidden=true
line, so I apologize for that. I’ve also added instructions to the.desktop
file.3. The whole reason I wrote this is for non-rectangular desktops. If I had a rectangular desktop, I’d still be using PCManFM for desktop icons and LXPanel for my panels. (LXPanel doesn’t like non-rectangular desktops but lets me span a single panel and taskbar widget across all monitors.)
In fact, when I wrote this, I hadn’t yet dug up a monitor raiser and my left-hand monitor only had about 2/3rds vertical overlap with the other two monitors. I have no clue why it isn’t working for you.
Also, the monitor configuration is stored in the X11 server itself and accessed via the XRandR protocol extension. That’s why you can query and set it using non-KDE tools like the command-line
xrandr
utility or thenvidia-settings
GUI.If you look at
get_desktop_geom()
(lines , you’ll see that it looks up the entire desktop’s rectangle (for scaling) and the geometry of each monitor within that rectangle (for copying out the individual monitor images).If you look at
write_backgrounds()
, you’ll see that it takes that geometry and uses it to determine where each individual image should be grabbed from within the desktop rectangle. I don’t know why that’s going wrong on your system, but my code is simple enough and works properly and reliably enough on my system (tested on both PyQt 4.x and PyQt 5.x) that it’s much more likely that a bug in X11 or Qt is causing your system to lie to my script about the monitor geometry.Also, there’s no need to quote “dead zones” when not talking about the term itself. It makes it sound like you’re saying it while making air quotes with your fingers but “dead space”, “dead areas”, or “dead zones” are the actual technical terms used.
4a, 4b, 4c. If you look at something less primitive than the KDE display control panel (I use
nvidia-settings
and then copy the generatedMetaModes
line into myxorg.conf
so it’s not DE-specific), you’ll see that you can set monitor positions without needing to fill the gaps. Under the hood, it’s just setting integer coordinates for rectangles within a larger rectangle. In fact, you can even overlap monitors. (Useful for things like hacking together a presentation where you want to mirror just one window on your desktop to the projector.)My script works as follows:
--gravity
(x, y, width, height)
rectangles representing all defined monitors and then crop out and save each one as a background.(Note: All querying X11 is actually done at the beginning but, for simplicity’s sake, I haven’t written it that way.)
Everything you’re saying I have to do is already done by X11 itself. In fact, the only thing I didn’t bother to do is provide an option to scale pieces of the background to compensate for varying physical pixel sizes. (Because it’s a lot of complexity and testing for a situation I’d never find myself in. it’d drive me crazy to have monitors of different DPIs on my system and I was too lazy to temporarily borrow a 17″ 1280×1024 monitor to set up next to my 19″ 1280×1024 monitors.)
4d. Given that the APIs I use are used by things like games to figure out what window geometry to request, it’s quite likely that XWayland will expose them. As I’m on the nVidia binary drivers and, as a KDE user, wary of libinput’s “have one good set of defaults” mentality, Wayland hasn’t been a priority for me. (You’re talking to someone who hacked together a replacement for the update manager because they removed the ability to disable the “Reboot to update your kernel” nags. It’s entirely possible I’ll end up maintaining a fork of libinput to get some behaviour they decided wasn’t worth supporting.)