Skip to content

Instantly share code, notes, and snippets.

@Speykious
Last active July 2, 2024 09:47
Show Gist options
  • Save Speykious/58911fb1c029e17f982b6f76ff8003f5 to your computer and use it in GitHub Desktop.
Save Speykious/58911fb1c029e17f982b6f76ff8003f5 to your computer and use it in GitHub Desktop.
Smooth resizing window on X11 (needs more tests)
// Smooth resizing on X11 using the XSync extension API.
//
// Note: Andrew Harter is dedicating a lot of work into making a solid, well-tested,
// thorougly documented example of a native X11+EGL window with custom decorations,
// and he notably helped me understand how XSync works which is how this gist got here.
//
// This is confirmed to work mostly as expected on KDE, remains to be tested for other DEs and WMs using X11.
// Compile: gcc -o smooth-resize smooth-resize.c -lX11 -lXext
#include <X11/Xatom.h>
#include <X11/Xcursor/Xcursor.h>
#include <X11/Xlib.h>
#include <X11/extensions/sync.h>
#include <stdint.h>
#define TITLEBAR_HEIGHT 30
#define WINDOW_TITLE "Smooth resizing"
#define WINDOW_TITLE_LEN (sizeof(WINDOW_TITLE) - 1)
void draw_window(Display* display, Window window, GC gc, int32_t win_width, int32_t win_height)
{
XSetForeground(display, gc, WhitePixel(display, DefaultScreen(display)));
XFillRectangle(display, window, gc, 0, 0, win_width, win_height);
XSetForeground(display, gc, BlackPixel(display, DefaultScreen(display)));
XFillRectangle(display, window, gc, 10, 10, win_width - 20, win_height - 20);
XSetForeground(display, gc, WhitePixel(display, DefaultScreen(display)));
XFillRectangle(display, window, gc, 0, 0, win_width, TITLEBAR_HEIGHT);
XSetForeground(display, gc, BlackPixel(display, DefaultScreen(display)));
XDrawString(display, window, gc, 10, 20, WINDOW_TITLE, WINDOW_TITLE_LEN);
}
int main(void)
{
Display* display = XOpenDisplay(NULL);
Atom atom_wm_delete_window = XInternAtom(display, "WM_DELETE_WINDOW", False);
Atom atom_net_wm_sync_request = XInternAtom(display, "_NET_WM_SYNC_REQUEST", False);
Atom atom_net_wm_sync_request_counter = XInternAtom(display, "_NET_WM_SYNC_REQUEST_COUNTER", False);
Atom atom_wm_protocols = XInternAtom(display, "WM_PROTOCOLS", False);
int32_t win_width = 1280;
int32_t win_height = 720;
Window root_window = XDefaultRootWindow(display);
Window window = XCreateWindow(display, root_window, 0, 0, win_width, win_height, 0, 0, InputOutput, NULL, 0, NULL);
GC gc = XCreateGC(display, window, 0, NULL);
XStoreName(display, window, WINDOW_TITLE);
// https://fishsoup.net/misc/wm-spec-synchronization.html
//
// "The goal of basic synchronization is limited to coordinating redraws during interactive resizing.
// A client indicates that it is willing to participate in basic synchronization by listing
// _NET_WM_SYNC_REQUEST in the WM_PROTOCOLS property of the client window..."
Atom protocols[] = {atom_wm_delete_window, atom_net_wm_sync_request};
XSetWMProtocols(display, window, protocols, 2);
XSyncValue sync_value;
XSyncIntToValue(&sync_value, 0);
XSyncCounter sync_counter = XSyncCreateCounter(display, sync_value);
// "...and storing the XID of a XSync counter in the property _NET_WM_SYNC_REQUEST_COUNTER.
// This counter is known as the basic frame counter."
//
// Note: XA_CARDINAL is the type of the counter's value (unsigned integer, here of 32 bits)
XChangeProperty(display, window, atom_net_wm_sync_request_counter, XA_CARDINAL, 32, 0, (uint8_t*)&sync_counter, 1);
long event_mask = ExposureMask | StructureNotifyMask | PropertyChangeMask;
XSelectInput(display, window, event_mask);
XMapWindow(display, window);
for (;;)
{
int32_t count = XPending(display);
count = count < 1 ? 1 : count;
int64_t sync_request_count = 0;
for (int32_t i = 0; i < count; i++)
{
XEvent xevent = {0};
XNextEvent(display, &xevent);
switch (xevent.type)
{
case ConfigureNotify:
{
win_width = xevent.xconfigure.width;
win_height = xevent.xconfigure.height;
break;
}
case ClientMessage:
{
if (xevent.xclient.message_type == atom_wm_protocols)
{
Atom* msg_data = (Atom*)xevent.xclient.data.l;
if (msg_data[0] == atom_wm_delete_window)
{
printf("request to quit\n");
return 0;
}
if (msg_data[0] == atom_net_wm_sync_request)
{
sync_request_count |= msg_data[2] | msg_data[3] << 32;
printf("sync request %ld\n", sync_request_count);
}
}
break;
}
default:
break;
}
}
draw_window(display, window, gc, win_width, win_height);
XSyncValue request_sync_value;
XSyncIntToValue(&request_sync_value, sync_request_count);
XSyncSetCounter(display, sync_counter, request_sync_value);
}
XFreeGC(display, gc);
XDestroyWindow(display, window);
XCloseDisplay(display);
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment