Last active
July 2, 2024 09:47
-
-
Save Speykious/58911fb1c029e17f982b6f76ff8003f5 to your computer and use it in GitHub Desktop.
Smooth resizing window on X11 (needs more tests)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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