aboutsummaryrefslogtreecommitdiff
path: root/src/xbouncing.c
diff options
context:
space:
mode:
authorDavid Timber <dxdt@dev.snart.me>2024-08-28 14:08:09 +0200
committerDavid Timber <dxdt@dev.snart.me>2024-08-28 14:10:16 +0200
commit1e0496b6676b30642305e09f6f7dbe7966f87f0e (patch)
tree1de4fa0052963cbea242275cc261ae4105a8ebcb /src/xbouncing.c
Initial commit
Diffstat (limited to 'src/xbouncing.c')
-rw-r--r--src/xbouncing.c413
1 files changed, 413 insertions, 0 deletions
diff --git a/src/xbouncing.c b/src/xbouncing.c
new file mode 100644
index 0000000..7ab5d89
--- /dev/null
+++ b/src/xbouncing.c
@@ -0,0 +1,413 @@
+#include <stdlib.h>
+#include <stdbool.h>
+#include <string.h>
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+#include <X11/extensions/Xrandr.h>
+#include <X11/extensions/shape.h>
+#include <time.h>
+#include <math.h>
+
+#include "img/dvdlogo.xbm"
+#include "img/dvdlogo_mask.xbm"
+_Static_assert(dvdlogo_width == dvdlogo_mask_width);
+_Static_assert(dvdlogo_height == dvdlogo_mask_height);
+
+#ifndef M_PI
+#define M_PI 3.14159265358979323846 /* pi */
+#endif
+
+static unsigned long FILL_COLORS[] = {
+ 0xbe00ffff,
+ 0x00feffff,
+ 0xff8300ff,
+ 0x0026ffff,
+ 0xfffa01ff,
+ 0xff2600ff,
+ 0xff008bff,
+ 0x25ff01ff
+};
+#define NB_FILL_COLORS (sizeof(FILL_COLORS) / sizeof(unsigned long))
+
+struct {
+ Display *dpy;
+ int scr_num;
+ Screen *scr;
+ unsigned int depth;
+ unsigned long wndmask;
+ XSetWindowAttributes wndattr;
+ Window rwnd, mywnd;
+ short refresh_rate;
+ struct timespec refresh_interval;
+ Pixmap pm[NB_FILL_COLORS];
+ GC gc[NB_FILL_COLORS];
+ Pixmap mask;
+} x11;
+
+struct {
+ double vel;
+} param;
+
+struct vec2 {
+ double x, y;
+};
+
+struct {
+ struct vec2 dir;
+ struct vec2 p1, p2;
+ double w, h;
+ size_t color_ptr;
+} state;
+
+static int main_loop (void);
+static bool init_x11 (void);
+static void deinit_x11 (void);
+static Bool MakeAlwaysOnTop(Display* display, Window root, Window mywin);
+
+/*
+ * --monitor
+ * --logo
+ * --width
+ * --height
+ */
+int main (const int argc, const char **argv) {
+ int ec = 0;
+
+ param.vel = 300;
+ state.dir.x = sin(45.0 / 360.0 * M_PI * 2.0);
+ state.dir.y = cos(45.0 / 360.0 * M_PI * 2.0);
+ state.p1.x = 0;
+ state.p1.y = 0;
+ state.p2.x = state.w = dvdlogo_width;
+ state.p2.y = state.h = dvdlogo_height;
+
+ init_x11();
+
+ // - load xbm
+ // - init X11
+ // - Select monitor
+ // - do the main loop
+
+ ec = main_loop();
+
+END:
+ deinit_x11();
+ return ec;
+}
+
+static void update_screen_info (void) {
+ XRRScreenConfiguration *conf = XRRGetScreenInfo(x11.dpy, x11.mywnd);
+
+ if (conf == NULL) {
+ return;
+ }
+
+ x11.refresh_rate = XRRConfigCurrentRate(conf);
+ XRRFreeScreenConfigInfo(conf);
+
+ if (x11.refresh_rate <= 0) {
+ x11.refresh_rate = 10;
+ }
+ x11.refresh_interval.tv_sec = 0;
+ x11.refresh_interval.tv_nsec = 1000000000 / x11.refresh_rate;
+}
+
+static bool init_x11 (void) {
+ XSizeHints hints = { 0, };
+ XGCValues gcv = { 0, };
+
+ x11.dpy = XOpenDisplay(NULL);
+ x11.scr_num = DefaultScreen(x11.dpy); // TODO: parameterise
+ x11.scr = XScreenOfDisplay(x11.dpy, x11.scr_num);
+ x11.rwnd = RootWindowOfScreen(x11.scr);
+ x11.depth = DefaultDepth(x11.dpy, x11.scr_num);
+
+ x11.wndmask = CWBackingPixel | CWOverrideRedirect;
+ x11.wndattr.override_redirect = True;
+
+ hints.flags = PPosition | PSize;
+ hints.x = (int)state.p1.x;
+ hints.y = (int)state.p1.y;
+ hints.width = state.w;
+ hints.height = state.h;
+
+ x11.mywnd = XCreateWindow(
+ x11.dpy,
+ x11.rwnd,
+ 0,
+ 0,
+ state.w,
+ state.h,
+ 0,
+ x11.depth,
+ InputOutput,
+ CopyFromParent,
+ x11.wndmask,
+ &x11.wndattr);
+ XSetNormalHints(x11.dpy, x11.mywnd, &hints);
+ XSelectInput(
+ x11.dpy,
+ x11.mywnd,
+ ExposureMask | VisibilityChangeMask | KeyPressMask);
+ XMapWindow(x11.dpy, x11.mywnd);
+ MakeAlwaysOnTop(x11.dpy, x11.rwnd, x11.mywnd);
+
+ for (size_t i = 0; i < NB_FILL_COLORS; i += 1) {
+ x11.pm[i] = XCreatePixmapFromBitmapData(
+ x11.dpy,
+ x11.mywnd,
+ dvdlogo_bits,
+ dvdlogo_width,
+ dvdlogo_height,
+ 0x00000000,
+ FILL_COLORS[i],
+ x11.depth);
+
+ gcv.function = GXcopy;
+ gcv.fill_style = FillTiled;
+ gcv.ts_x_origin = 0;
+ gcv.ts_y_origin = 0;
+ gcv.tile = x11.pm[i];
+ x11.gc[i] = XCreateGC(
+ x11.dpy,
+ x11.mywnd,
+ GCFunction | GCTile | GCTileStipXOrigin | GCTileStipYOrigin | GCFillStyle,
+ &gcv);
+ }
+
+ x11.mask = XCreateBitmapFromData(
+ x11.dpy,
+ x11.mywnd,
+ dvdlogo_mask_bits,
+ dvdlogo_mask_width,
+ dvdlogo_mask_height);
+
+ XFlush(x11.dpy);
+
+ update_screen_info();
+
+ return true;
+}
+
+static void deinit_x11 (void) {
+ if (x11.dpy != NULL) {
+ XCloseDisplay(x11.dpy);
+ }
+}
+
+#define _NET_WM_STATE_REMOVE 0 // remove/unset property
+#define _NET_WM_STATE_ADD 1 // add/set property
+#define _NET_WM_STATE_TOGGLE 2 // toggle property
+
+static Bool MakeAlwaysOnTop(Display* display, Window root, Window mywin) {
+ Atom wmStateAbove = XInternAtom( display, "_NET_WM_STATE_ABOVE", 1 );
+ if( wmStateAbove == None ) {
+ return False;
+ }
+
+ Atom wmNetWmState = XInternAtom( display, "_NET_WM_STATE", 1 );
+ if( wmNetWmState == None ) {
+ return False;
+ }
+
+ // set window always on top hint
+ if( wmStateAbove != None )
+ {
+ XClientMessageEvent xclient;
+ memset( &xclient, 0, sizeof (xclient) );
+ //
+ //window = the respective client window
+ //message_type = _NET_WM_STATE
+ //format = 32
+ //data.l[0] = the action, as listed below
+ //data.l[1] = first property to alter
+ //data.l[2] = second property to alter
+ //data.l[3] = source indication (0-unk,1-normal app,2-pager)
+ //other data.l[] elements = 0
+ //
+ xclient.type = ClientMessage;
+ xclient.window = mywin; // GDK_WINDOW_XID(window);
+ xclient.message_type = wmNetWmState; //gdk_x11_get_xatom_by_name_for_display( display, "_NET_WM_STATE" );
+ xclient.format = 32;
+ xclient.data.l[0] = _NET_WM_STATE_ADD; // add ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE;
+ xclient.data.l[1] = wmStateAbove; //gdk_x11_atom_to_xatom_for_display (display, state1);
+ xclient.data.l[2] = 0; //gdk_x11_atom_to_xatom_for_display (display, state2);
+ xclient.data.l[3] = 0;
+ xclient.data.l[4] = 0;
+ //gdk_wmspec_change_state( FALSE, window,
+ // gdk_atom_intern_static_string ("_NET_WM_STATE_BELOW"),
+ // GDK_NONE );
+ XSendEvent(display,
+ //mywin - wrong, not app window, send to root window!
+ root, // <-- DefaultRootWindow( display )
+ False,
+ SubstructureRedirectMask | SubstructureNotifyMask,
+ (XEvent *)&xclient );
+
+ return True;
+ }
+
+ return False;
+}
+
+static void proc_key (XKeyEvent *evt, bool *loop_flag) {
+ int len;
+ char buf[2] = { 0, };
+
+ len = XLookupString(evt, buf, 1, NULL, NULL);
+ if (len > 0) {
+ switch (buf[0]) {
+ case 0x1B:
+ case 'q':
+ case 'Q':
+ *loop_flag = false;
+ break;
+ }
+ }
+}
+
+static void draw (void) {
+ XShapeCombineMask(
+ x11.dpy,
+ x11.mywnd,
+ ShapeBounding,
+ 0,
+ 0,
+ x11.mask,
+ ShapeSet);
+ XFillRectangle(
+ x11.dpy,
+ x11.mywnd,
+ x11.gc[state.color_ptr],
+ 0,
+ 0,
+ state.w,
+ state.h);
+}
+
+static void proc_event (bool *loop_flag, bool *redraw) {
+ XEvent evt;
+
+ while (XPending(x11.dpy)) {
+ XNextEvent(x11.dpy, &evt);
+
+ switch (evt.type) {
+ case Expose:
+ *redraw = true;
+ break;
+ case KeyPress:
+ proc_key((XKeyEvent*)&evt, loop_flag);
+ break;
+ }
+ }
+}
+
+static int get_geo_nullable (
+ Display *dpy,
+ Drawable d,
+ Window *out_wnd,
+ int *out_x,
+ int *out_y,
+ unsigned int *out_w,
+ unsigned int *out_h,
+ unsigned int *out_bw,
+ unsigned int *out_dph)
+{
+#define CHK_ASS(l, r) if ((l) != NULL) { *(l) = (r); }
+ Window wnd;
+ int x, y;
+ unsigned int w, h, bw, dph;
+ int ret;
+
+ ret = XGetGeometry(dpy, d, &wnd, &x, &y, &w, &h, &bw, &dph);
+ if (ret) {
+ CHK_ASS(out_wnd, wnd);
+ CHK_ASS(out_x, x);
+ CHK_ASS(out_y, y);
+ CHK_ASS(out_w, w);
+ CHK_ASS(out_h, h);
+ CHK_ASS(out_bw, bw);
+ CHK_ASS(out_dph, dph);
+ }
+
+ return ret;
+#undef CHK_ASS
+}
+
+static void do_move (bool *redraw) {
+ double dx, dy;
+ const unsigned int root_w = XWidthOfScreen(x11.scr);
+ const unsigned int root_h = XHeightOfScreen(x11.scr);
+
+ dx = state.dir.x * param.vel / x11.refresh_rate;
+ dy = state.dir.y * param.vel / x11.refresh_rate;
+ state.p1.x += dx;
+ state.p1.y += dy;
+ state.p2.x += dx;
+ state.p2.y += dy;
+ XMoveWindow(x11.dpy, x11.mywnd, (int)state.p1.x, (int)state.p1.y);
+
+ // north
+ if (state.p1.y < 0) {
+ state.p1.y = 0;
+ state.p2.y = state.h;
+ state.dir.y = -state.dir.y;
+ state.color_ptr = (state.color_ptr + 1) % NB_FILL_COLORS;
+ *redraw = true;
+ }
+ // east
+ if (state.p2.x > root_w) {
+ state.p2.x = root_w;
+ state.p1.x = root_w - state.w;
+ state.dir.x = -state.dir.x;
+ state.color_ptr = (state.color_ptr + 1) % NB_FILL_COLORS;
+ *redraw = true;
+ }
+ // south
+ if (state.p2.y > root_h) {
+ state.p2.y = root_h;
+ state.p1.y = root_h - state.h;
+ state.dir.y = -state.dir.y;
+ state.color_ptr = (state.color_ptr + 1) % NB_FILL_COLORS;
+ *redraw = true;
+ }
+ // west
+ if (state.p1.x < 0) {
+ state.p1.x = 0;
+ state.p2.x = state.w;
+ state.dir.x = -state.dir.x;
+ state.color_ptr = (state.color_ptr + 1) % NB_FILL_COLORS;
+ *redraw = true;
+ }
+}
+
+static int main_loop (void) {
+ bool loop_flag = true;
+ bool redraw;
+
+ while (true) {
+ redraw = false;
+
+ proc_event(&loop_flag, &redraw);
+
+ if (!loop_flag) {
+ break;
+ }
+
+ do_move(&redraw);
+
+ if (redraw) {
+ draw();
+ }
+
+ if (true) {
+ XSync(x11.dpy, False);
+ }
+ else {
+ XFlush(x11.dpy);
+ }
+ nanosleep(&x11.refresh_interval, NULL);
+ }
+
+ return 0;
+}