Skip to content

Instantly share code, notes, and snippets.

@AlecsFerra
Last active November 6, 2023 21:52
Show Gist options
  • Star 24 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save AlecsFerra/ef1cc008990319f3b676eb2d8aa89903 to your computer and use it in GitHub Desktop.
Save AlecsFerra/ef1cc008990319f3b676eb2d8aa89903 to your computer and use it in GitHub Desktop.
POC for simple animated wallpapers in Xorg
#define _POSIX_C_SOURCE 199309L
//#define DEBUG
#include <Imlib2.h>
#include <X11/Xatom.h>
#include <X11/Xlib.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
typedef struct {
Window root;
Pixmap pixmap;
Imlib_Context *render_context;
int width, height;
} Monitor;
void setRootAtoms(Display *display, Monitor *monitor) {
Atom atom_root, atom_eroot, type;
unsigned char *data_root, *data_eroot;
int format;
unsigned long length, after;
atom_root = XInternAtom(display, "_XROOTMAP_ID", True);
atom_eroot = XInternAtom(display, "ESETROOT_PMAP_ID", True);
// doing this to clean up after old background
if (atom_root != None && atom_eroot != None) {
XGetWindowProperty(display, monitor->root, atom_root, 0L, 1L, False,
AnyPropertyType, &type, &format, &length, &after,
&data_root);
if (type == XA_PIXMAP) {
XGetWindowProperty(display, monitor->root, atom_eroot, 0L, 1L, False,
AnyPropertyType, &type, &format, &length, &after,
&data_eroot);
if (data_root && data_eroot && type == XA_PIXMAP &&
*((Pixmap *)data_root) == *((Pixmap *)data_eroot))
XKillClient(display, *((Pixmap *)data_root));
}
}
atom_root = XInternAtom(display, "_XROOTPMAP_ID", False);
atom_eroot = XInternAtom(display, "ESETROOT_PMAP_ID", False);
// setting new background atoms
XChangeProperty(display, monitor->root, atom_root, XA_PIXMAP, 32,
PropModeReplace, (unsigned char *)&monitor->pixmap, 1);
XChangeProperty(display, monitor->root, atom_eroot, XA_PIXMAP, 32,
PropModeReplace, (unsigned char *)&monitor->pixmap, 1);
}
int main(int argc, char *argv[]) {
#ifdef DEBUG
fprintf(stdout, "Loading images");
#endif
Imlib_Image images[] = {
imlib_load_image("/home/alecs/Repos/paperview/scenes/castle/out-0.bmp"),
imlib_load_image("/home/alecs/Repos/paperview/scenes/castle/out-1.bmp"),
imlib_load_image("/home/alecs/Repos/paperview/scenes/castle/out-2.bmp"),
imlib_load_image("/home/alecs/Repos/paperview/scenes/castle/out-3.bmp"),
imlib_load_image("/home/alecs/Repos/paperview/scenes/castle/out-4.bmp"),
imlib_load_image("/home/alecs/Repos/paperview/scenes/castle/out-5.bmp"),
imlib_load_image("/home/alecs/Repos/paperview/scenes/castle/out-6.bmp"),
imlib_load_image("/home/alecs/Repos/paperview/scenes/castle/out-7.bmp"),
};
int images_count = 8;
#ifdef DEBUG
fprintf(stdout, "Loading monitors\n");
#endif
Display *display = XOpenDisplay(NULL);
if (!display) {
fprintf(stderr, "Could not open XDisplay\n");
exit(42);
}
const int screen_count = ScreenCount(display);
#ifdef DEBUG
fprintf(stdout, "Found %d screens\n", screen_count);
#endif
Monitor *monitors = malloc(sizeof(Monitor) * screen_count);
for (int current_screen = 0; current_screen < screen_count;
++current_screen) {
#ifdef DEBUG
fprintf(stdout, "Running screen %d\n", current_screen);
#endif
const int width = DisplayWidth(display, current_screen);
const int height = DisplayHeight(display, current_screen);
const int depth = DefaultDepth(display, current_screen);
Visual *vis = DefaultVisual(display, current_screen);
const int cm = DefaultColormap(display, current_screen);
#ifdef DEBUG
fprintf(stdout, "Screen %d: width: %d, height: %d, depth: %d\n",
current_screen, width, height, depth);
#endif
Window root = RootWindow(display, current_screen);
Pixmap pixmap = XCreatePixmap(display, root, width, height, depth);
monitors[current_screen].width = width;
monitors[current_screen].height = height;
monitors[current_screen].root = root;
monitors[current_screen].pixmap = pixmap;
monitors[current_screen].render_context = imlib_context_new();
imlib_context_push(monitors[current_screen].render_context);
imlib_context_set_display(display);
imlib_context_set_visual(vis);
imlib_context_set_colormap(cm);
imlib_context_set_drawable(pixmap);
imlib_context_set_color_range(imlib_create_color_range());
imlib_context_pop();
}
#ifdef DEBUG
fprintf(stdout, "Loaded %d screens\n", screen_count);
#endif
#ifdef DEBUG
fprintf(stdout, "Starting render loop");
#endif
struct timespec timeout;
timeout.tv_sec = 0;
timeout.tv_nsec = 33000000;
for (int cycle = 0; cycle < 10; ++cycle) {
Imlib_Image current = images[cycle % images_count];
for (int monitor = 0; monitor < screen_count; ++monitor) {
Monitor *c_monitor = &monitors[monitor];
imlib_context_push(c_monitor->render_context);
imlib_context_set_dither(1);
imlib_context_set_blend(1);
imlib_context_set_image(current);
imlib_render_image_on_drawable(0, 0);
setRootAtoms(display, c_monitor);
XKillClient(display, AllTemporary);
XSetCloseDownMode(display, RetainTemporary);
XSetWindowBackgroundPixmap(display, c_monitor->root, c_monitor->pixmap);
XClearWindow(display, c_monitor->root);
XFlush(display);
XSync(display, False);
imlib_context_pop();
}
nanosleep(&timeout, NULL);
}
}
@glouw
Copy link

glouw commented Aug 2, 2020

Very pretty. I like how you got rid of SDL2 entirely and made it purely X11. This will be very portable. I'll link to this in my README if you are okay with that?

@AlecsFerra
Copy link
Author

Very pretty. I like how you got rid of SDL2 entirely and made it purely X11. This will be very portable. I'll link to this in my README if you are okay with that?

For sure but performance wise it's worst I'm trying to figure out a way to reintroduce SDL

@glouw
Copy link

glouw commented Aug 2, 2020

ah okay. I assume most of the work is being done by the CPU with X11 libraries. SDL2 stores its SDL_Texture images on GPU memory, which makes image copying and image resizing to the screen very cheap

@jmi2k
Copy link

jmi2k commented Aug 3, 2020

Would one of Xorg's extensions allow you to upload the textures to the GPU instead? No problem with SDL though, just thinking that a pure Xorg solution would also be desirable.

@glouw
Copy link

glouw commented Aug 3, 2020

Yes I agree on that. Almost all my projects rely on SDL2, so it feels at home. Alec's solution hits very close to home though.

@sdhand
Copy link

sdhand commented Aug 3, 2020

You could almost certainly use the XRender extension to do this efficiently, but the API isn't especially nice or well documented.

@heapslip
Copy link

heapslip commented Aug 3, 2020

@glouw and @AlecsFerra thanks for this!
It works pretty ok when not recording (https://gist.ro/animatedwallpaper.mp4) even at 2560x1440, it would be awesome to juice out a bit more performance.

@Rodrigo-Barros
Copy link

hey anyone can give any tip about how compile this?
when I try the terminal says:
/usr/bin/ld: animated_wallpaper.c:(.text+0x6c1): undefined reference for "XClearWindow"
I'm trying compile with this command: gcc animated_wallpaper.c

@jmi2k
Copy link

jmi2k commented Aug 9, 2020

@Rodrigo-Barros Try with gcc -lX11 animated_wallpaper.c

@Rodrigo-Barros
Copy link

@jmi2k thanks , I have tried again and now its working, I only change cycle < 10 to cycle < images_count, I forget replace the imlib_load_image content, and I was receiving warnings on terminal my fault, and again thank you.

@plasmoduck
Copy link

plasmoduck commented Sep 19, 2020

How do I use this instead of the other version?

✗ doas gcc -lX11 animated_wallpaper.c
/usr/local/bin/ld: /tmp//ccbPBjeP.o: in function main': animated_wallpaper.c:(.text+0x1ff): undefined reference to imlib_load_image'
/usr/local/bin/ld: animated_wallpaper.c:(.text+0x210): undefined reference
to imlib_load_image' /usr/local/bin/ld: animated_wallpaper.c:(.text+0x221): undefined reference to imlib_load_image'
/usr/local/bin/ld: animated_wallpaper.c:(.text+0x232): undefined reference
to imlib_load_image' /usr/local/bin/ld: animated_wallpaper.c:(.text+0x243): undefined reference to imlib_load_image'
/usr/local/bin/ld: /tmp//ccbPBjeP.o:animated_wallpaper.c:(.text+0x254): more undefined references to imlib_load_image' follow /usr/local/bin/ld: /tmp//ccbPBjeP.o: in function main':
animated_wallpaper.c:(.text+0x448): undefined reference to imlib_context_new' /usr/local/bin/ld: animated_wallpaper.c:(.text+0x46b): undefined reference to imlib_context_push'
/usr/local/bin/ld: animated_wallpaper.c:(.text+0x477): undefined reference
to imlib_context_set_display' /usr/local/bin/ld: animated_wallpaper.c:(.text+0x483): undefined reference to imlib_context_set_visual'
/usr/local/bin/ld: animated_wallpaper.c:(.text+0x490): undefined reference
to imlib_context_set_colormap' /usr/local/bin/ld: animated_wallpaper.c:(.text+0x49c): undefined reference to imlib_context_set_drawable'
/usr/local/bin/ld: animated_wallpaper.c:(.text+0x4a1): undefined reference
to imlib_create_color_range' /usr/local/bin/ld: animated_wallpaper.c:(.text+0x4a9): undefined reference to imlib_context_set_color_range'
/usr/local/bin/ld: animated_wallpaper.c:(.text+0x4ae): undefined reference
to `imlib_context_pop'
/usr/local/bin/ld: animated_wallpaper.c:(.text+0x52a): undefined reference

@Rodrigo-Barros
Copy link

Rodrigo-Barros commented Sep 19, 2020

@plasmoduck First you need see if you have all libs in your system like the Imlib2, I'm using Ubuntu focal fossa, so if you running the same distro like me you can install running: $ sudo apt install libimlib2-dev .
For compiling I use this code: $ gcc animated_wallpaper.c -lX11 -lImlib2, and one last tip change the contents of line 61 ownwards to match your pc wallpaper location, I hope helps.

@neofeo
Copy link

neofeo commented May 27, 2021

it doenst work at all to me, neither the original neither this one. it doesnt display anything sadly. xfce ubuntu 20.04, arm linux.

@AlecsFerra
Copy link
Author

it doenst work at all to me, neither the original neither this one. it doesnt display anything sadly. xfce ubuntu 20.04, arm linux.

Uncomment line 2

#define DEBUG 

What is the output?

@neofeo
Copy link

neofeo commented May 31, 2021

it doenst work at all to me, neither the original neither this one. it doesnt display anything sadly. xfce ubuntu 20.04, arm linux.

Uncomment line 2

#define DEBUG 

What is the output?

the problem is the xfce compositor. it overide the program.. do you know any workaround to that? disabling it would create a lot of tearing

@jzbor
Copy link

jzbor commented Jan 4, 2022

Hi, I am sitting here trying to just figure out how to set an x11 wallpaper with imlib2. Unfortunately neither the imlib2 website nor this poc got me to understand how to do it. Maybe you can help me out? All I wanna do is load an image into imlib2 and then set it to the pixmap of the root window (no animation or anything).

@jzbor
Copy link

jzbor commented Jan 4, 2022

Nvm I got it - thanks alot!

@Unkn0wable
Copy link

Unkn0wable commented Mar 19, 2022

Wow, this is a really cool program you wrote, @AlecsFerra - thanks for sharing it!

I did get it compiled and running thanks to @Rodrigo-Barros explaining above how to get the dependency and compile. It runs fine, but looking at the source it looks like there's supposed to be some sort of feedback on what it's doing - "Found %d screens", "Running screen %d\n", and so on. When I run it, it does load the images onto monitor 0, the desktop LCD, but there's no feedback from the terminal window. I also have a projector connected, and the desktop looks weird over there, but it's not a big deal because I mainly use that display for playing music videos or whatever. If there's a simple way to use the pics on both monitors that'd be good, or a different pic on the projector (like a static pic) would be sweet, but that's a minor issue.

I know like zero about programming, but I did get what you wrote modified for my use for the most part. I'm only using two pics, but it's going back and forth between them really fast. I tried to slow it down, and what I did helps a little, but I don't know how to make it any slower.

What I did first was just mod line 61 & 62 to add my two images, then line 64 to show only 2 images. Also I want it to cycle ceaselessly, so I changed line 128 to the following:

for (int cycle = 0; ++cycle;) {

That takes out the finish line I guess, but it's working. After compiling, I then added the command to my startup programs:

/home/USERNAME/animated_wallpaper/a.out &

I don't know if the '&' is necessary, but obviously it's needed when I run it from the command line. Either way it works well. The thing is, I want it to switch between the 2 pics much slower, maybe a half second or full second between them. I changed line 126 to:

timeout.tv_nsec = 33000000000;

(added a few zeros) but it's still moving really fast. I really have no idea what I'm doing, but it's somewhat functional now. Is there an easy way to slow the program from switching between the pics?

Just to satisfy your (probable) curiosity, I've attached both pics here (in jpg format.) Oh, and if you're curious, while I didn't make these wallpapers, I do remember using this waaaaay back in the day. That's how old I am. :D If you're trying to spot the difference between the two pics, it's the cursor.

Thanks again for sharing your awesome program, Alecs - it rules.

off
on

@AlecsFerra
Copy link
Author

To enable logging you should uncomment line 2
To slow down the program try setting tv.sec to some higher value
I didn't understand the comment about the external monitor

@Unkn0wable
Copy link

Unkn0wable commented Apr 22, 2022

ACK! Wow, whatta month. Sorry about the delay in answering, Alecs. When I wrote this I had like 5 browser windows open, my machine crashed, and when it came back up it only opened one, so I lost like 40 tabs of important stuff I was doing. Took some doing, but I'm back in the game. Here's my answer:

Yes, I hadn't enabled debugging before, but that is super useful, you're right. I'll get to the rest of this when I address your last comment.

I'm betting you meant the "timeout.tv_sec = " line, which after the mods in my file is line 125 (I only have two wallpapers so I removed a few lines.) I currently have it as high as I can get it to compile:

timeout.tv_sec = 1000000000000000000;
// timeout.tv_nsec = 330000000000000;

I've tried messing with the other line (timeout.tv_nsec), but no real effect. As far as increasing the first line listed, I've got it up as far as I can and it's still blinking so fast I can barely see it. Obviously I removed the // (yes, I do know enough to understand that that comments the line out.) I tried it a couple of different ways, with one line commented out, then the other, and so on. No changes. I also tried pasting the same lines over and over where it lists the wallpapers to see if I could slow it down just a hair, still no effect:


Imlib_Image images[] = {
imlib_load_image("/home/unknowable/!everything_else/Pictures/CPM/on.bmp"),
imlib_load_image("/home/unknowable/!everything_else/Pictures/CPM/on.bmp"),
imlib_load_image("/home/unknowable/!everything_else/Pictures/CPM/on.bmp"),
imlib_load_image("/home/unknowable/!everything_else/Pictures/CPM/on.bmp"),
imlib_load_image("/home/unknowable/!everything_else/Pictures/CPM/on.bmp"),
imlib_load_image("/home/unknowable/!everything_else/Pictures/CPM/off.bmp"),
imlib_load_image("/home/unknowable/!everything_else/Pictures/CPM/off.bmp"),
imlib_load_image("/home/unknowable/!everything_else/Pictures/CPM/off.bmp"),
imlib_load_image("/home/unknowable/!everything_else/Pictures/CPM/off.bmp"),
imlib_load_image("/home/unknowable/!everything_else/Pictures/CPM/off.bmp"),
};
int images_count = 10;


It was still super fast, no real change there either.

As far as your last comment on the external monitor, what I was referring to is that I have a second monitor. It's not a desktop display though, it's a projector that shows the display on the wall almost 3 meters across. I have the desktop extended onto the projector though, not mirrored. The computer just sees it as another monitor though, so anything I can show on my desktop monitor will slide over to the projector, no big deal.

However, that brings up the debug info. Since you told me to do that, I get some pretty useful info:


unknowable@ANDROID:/animated_wallpaper$ gcc animated_wallpaper.c -lX11 -lImlib2
unknowable@ANDROID:
/animated_wallpaper$ ./a.out
Loading imagesLoading monitors
Found 1 screens
Running screen 0
Screen 0: width: 3840, height: 1080, depth: 24
Loaded 1 screens
^C
unknowable@ANDROID:~/animated_wallpaper$


Sorry for the ugly text, couldn't get the "Add code" button to work right. It seems to be removing all the line breaks and putting everything on one line.

The output above tells me that it only sees one giant screen. Both my displays are set at 1920x1080, but the script is seeing them together as only one screen showing 3840x1080. That must be why the desktop LCD shows the wallpapers while the projector's picture looks all weird and messed up. I guess if I wanted to, I could just double the width of both wallpapers - just copy and paste the image twice onto a new image same height, double the width. Hm. Think I'll try that right now. Should just take a minute...

Anyway, that's where I am, and why I disappeared. So I am really, REALLY sorry about ditching you. I think I know where I am set now, here in GitHub. :) So I'll just double the width and see if I can at least get it on both displays. In the meantime, let me know if you have any more thoughts on slowing it down.

Thanks again! This script rules and so does the guy who wrote it! :D Thanks Alecs!


EDIT 1: I've actually been awake for a couple of days and I have something important coming up tonight, so I'll actually do the double sizing (and whatever else you suggest) much later after sleeping. Night Alecs. Thanks again for dealing with us end users. I'm trying to learn I promise. :)


EDIT 2: Right after I wrote the above edit, I realized I hadn't eaten in like 5 hours. I went to go eat, and now I'm all supercharged again. :D

Made the change I described, basically copy/pasting both wallpapers from their original 1920x1080 into a double-wide 3840x1080 image. Both the ON and OFF pics appear twice in each image, one on the left side and one on the right. That makes it stretch across both displays so I have CP/M running on both monitors. grin Also, those pics are now just over 12MB in size each so it takes just a hair longer to load them up so and does look just a tiny bit slower.

Not a huge resolution on speed, but I've had wallpaper on only one screen for like a month, and now I have CP/M flashing on both my desktop LCD and the projector, so I'm going bananas. :D This script keeps getting better the more I learn how to work it.

Thanks again for authoring, then supporting your script, Alecs. You simply rule. No two ways about that one.

@CardealRusso
Copy link

CardealRusso commented Aug 21, 2023

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment