/* transparent.c - display a transparent image in a transparent window
** cc -lX11 transparent.c atoms.o -o transparent
** (C)opyright 2020 "Pegasus Epsilon" <pegasus@pimpninjas.org>
** Distribute Unmodified - https://pegasus.pimpninjas.org/license
**
**345678901234567890123456789012345678901234567890123456789012345678901234567890
*/
#define _POSIX_C_SOURCE 200112L // struct sigaction
#include <X11/Xutil.h> // XTextProperty, XStringListToTextProperty(),
// XSetWMName(), XCreateImage()
#include <sys/select.h> // fd_set, pipe(), FD_SET(), FD_ZERO(), select()
#include <stdio.h> // printf(), fflush(), stdout, puts(), perror(),
// fprintf(), stderr(), FILE, fopen(), fread(), fclose()
#include <stdlib.h> // exit(), malloc()
#include <stdint.h> // uint32_t, uint8_t
#include <signal.h> // struct sigaction, sigaction(), sigemptyset(),
// SIGINT, sig_atomic_t
#include <string.h> // strlen(), strncmp()
#include <unistd.h> // getpid()
#include "atoms.h"
#define FILENAME "pegasus.data"
#define WIDTH 256
#define HEIGHT 256
#define BPP 32
static Display *display;
static GC gc;
static Window win;
static int myself[2];
#define BUFFER_LENGTH 512
char buffer[BUFFER_LENGTH];
int death_proof (Display *display, XErrorEvent *error) {
XGetErrorText(display, error->error_code, buffer, BUFFER_LENGTH-1);
printf("%d: X Error %d/%d.%d: %s\n", error->serial, error->error_code,
error->request_code, error->minor_code, buffer);
fflush(stdout);
return 0;
}
void finish (int signal) {
if (signal) puts("");
write(myself[1], &signal, sizeof(signal));
}
void hook_signals (void) {
struct sigaction act = { 0 };
sigemptyset(&act.sa_mask);
act.sa_handler = &finish;
if (sigaction(SIGINT, &act, NULL) < 0) {
perror("Failed to hook INT signal");
exit(1);
}
}
Window create_window (
Display *display, XVisualInfo visualinfo, char *title, char *class
) {
Window root = DefaultRootWindow(display);
Window win = XCreateWindow(display, root, 0, 0, WIDTH, HEIGHT, 0,
visualinfo.depth, InputOutput, visualinfo.visual,
CWColormap | CWEventMask | CWBackPixmap | CWBorderPixel,
&(XSetWindowAttributes){
.colormap = XCreateColormap(display, root, visualinfo.visual,
AllocNone),
.event_mask = ExposureMask | ButtonPressMask,
.background_pixmap = None, .border_pixel = 0
}
);
pid_t pid = getpid();
XSetWMProtocols(display, win, &atom[WM_DELETE_WINDOW], 1);
XSetWMNormalHints(display, win, &(XSizeHints){
.flags = PMinSize | PMaxSize, .min_width = WIDTH, .max_width = WIDTH,
.min_height = HEIGHT, .max_height = HEIGHT
});
XChangeProperty(display, win, XInternAtom(display, "_NET_WM_PID", False),
atom[CARDINAL], 32, PropModeReplace, (void *)&pid, 1);
XChangeProperty(display, win, XInternAtom(display, "WM_CLASS", False),
atom[UTF8_STRING], 8, PropModeReplace, (void *)class, 4);
XChangeProperty(display, win, XInternAtom(display, "_NET_WM_NAME", False),
atom[UTF8_STRING], 8, PropModeReplace, (void *)title, strlen(title));
XStoreName(display, win, title); // WM_NAME type STRING
return win;
}
typedef union {
uint32_t packed;
struct { uint8_t red, grn, blu, alpha; } channels;
} __attribute__((packed)) PIXEL;
XImage *load_image (Display *display, int screen, Visual *visual) {
PIXEL *data = malloc(WIDTH * HEIGHT * sizeof(PIXEL));
// slurp icon
FILE *icon = fopen(FILENAME, "r");
if (!icon) {
perror("open " FILENAME);
exit(1);
}
fread(data, sizeof(PIXEL), WIDTH * HEIGHT, icon);
fclose(icon);
for (uint32_t i = 0; i < WIDTH * HEIGHT; i++) {
// fix the byte order
data[i].channels.red ^= data[i].channels.blu;
data[i].channels.blu ^= data[i].channels.red;
data[i].channels.red ^= data[i].channels.blu;
// premultiply pixels for proper alpha
data[i].channels.red *= data[i].channels.alpha / 0xFF;
data[i].channels.grn *= data[i].channels.alpha / 0xFF;
data[i].channels.blu *= data[i].channels.alpha / 0xFF;
}
// create an actual XImage
return XCreateImage(display, visual, BPP, ZPixmap, 0,
(void *)data, WIDTH, HEIGHT, BPP, WIDTH * sizeof(PIXEL));
}
int main (int argc, char **argv, char **envp) {
// become death proof
hook_signals();
XSetErrorHandler(death_proof);
// play stupid X games
if (!(display = XOpenDisplay(NULL))) {
unsigned i = 0;
while (envp[i] && strncmp(envp[i], "DISPLAY=:", 9)) i++;
fprintf(stderr, "Can't open display \"%s\"\n", envp[i] + 9);
return 1;
}
int screen = DefaultScreen(display);
XVisualInfo visualinfo = { 0 };
XMatchVisualInfo(display, screen, BPP, TrueColor, &visualinfo);
// cache atoms
initialize_atom_cache(display, envp);
// create our window
win = create_window(display, visualinfo, "Test Window", "test");
// set up drawing the power button
gc = XCreateGC(display, win, 0, 0);
XImage *img = load_image(display, screen, visualinfo.visual);
// actually show the window
XMapWindow(display, win);
//printf("line %d request %d\n", __LINE__, XNextRequest(display)); fflush(stdout);
pipe(myself);
int Xfd = ConnectionNumber(display);
fd_set listen, pending;
FD_ZERO(&listen);
FD_SET(Xfd, &listen);
FD_SET(*myself, &listen);
int max_fd = 1 + (Xfd > *myself ? Xfd : *myself);
XEvent event;
do {
XNextEvent(display, &event);
switch (event.type) {
case Expose:
XPutImage(display, win, gc, img, 0, 0, 0, 0, WIDTH, HEIGHT);
XSync(display, False);
break;
case ButtonPress:
puts("click!"); fflush(stdout);
break;
case ClientMessage:
if (event.xclient.message_type == atom[WM_PROTOCOLS] &&
*event.xclient.data.l == atom[WM_DELETE_WINDOW]) {
finish(0);
}
break;
}
// wait for next event
pending = listen;
select(max_fd, &pending, NULL, NULL, NULL);
} while (XPending(display));
XDestroyImage(img);
XFreeGC(display, gc);
XDestroyWindow(display, win);
XCloseDisplay(display);
shutdown_atom_cache();
puts("Clean exit.");
return 0;
}