In this tutorial, we will explore how to create a transparent, borderless, and movable window using the X11 library and Cairo graphics library in C. The program draws a simple square window with a "HELLO WORLD" text centered inside, and it allows the user to move the window around the screen by dragging it.
Code Overview
This program is written in C and uses several libraries to achieve its goals:
- X11: Provides the necessary functions to create and manage windows in a Unix-like operating system.
- Cairo: A graphics library used for drawing the contents of the window.
Setting Up the Window
The program starts by opening a connection to the X server using XOpenDisplay, which returns a Display pointer. Then, a simple window is created with the XCreateSimpleWindow function. The size of the window is defined as 300x300 pixels.
// Open a connection to the X server
Display *display = XOpenDisplay(NULL);
int screen = DefaultScreen(display);
Window root = RootWindow(display, screen);
// Define the size of the window
int size = 300;
// Create a simple window
Window win = XCreateSimpleWindow(display, root, 0, 0, size, size, 0, 0, 0);
Making the Window Borderless and Transparent
To create a borderless and transparent window, the program modifies the window attributes and properties:
- Borderless: The window is set to have no borders by setting the
_NET_WM_WINDOW_TYPE property to _NET_WM_WINDOW_TYPE_DOCK.
- Transparency: The window's opacity is adjusted to 50% by setting the
_NET_WM_WINDOW_OPACITY property.
// Set window properties to make it borderless
Atom window_type = XInternAtom(display, "_NET_WM_WINDOW_TYPE", False);
Atom window_type_dock = XInternAtom(display, "_NET_WM_WINDOW_TYPE_DOCK", False);
XChangeProperty(display, win, window_type, XA_ATOM, 32, PropModeReplace, (unsigned char *) &window_type_dock, 1);
// Set window opacity to 50%
unsigned long opacity = (unsigned long)(0.5 * 0xFFFFFFFF);
Atom net_wm_opacity = XInternAtom(display, "_NET_WM_WINDOW_OPACITY", False);
XChangeProperty(display, win, net_wm_opacity, XA_CARDINAL, 32, PropModeReplace, (unsigned char *)&opacity, 1);
Rendering Content with Cairo
Cairo is used to draw the contents of the window. In this case, the program draws a red square with 50% opacity and adds white text saying "HELLO WORLD" in the center. The text is centered by calculating the position based on the text's dimensions.
// Create Cairo surface to render transparency
cairo_surface_t *surface = cairo_xlib_surface_create(display, win, DefaultVisual(display, screen), size, size);
cairo_t *cr = cairo_create(surface);
// Draw the square with 50% transparency
cairo_set_source_rgba(cr, 1, 0, 0, 0.5); // Window color (50% opacity)
cairo_rectangle(cr, 0, 0, size, size);
cairo_fill(cr);
// Set the text properties
cairo_set_source_rgba(cr, 1, 1, 1, 1); // Set text color to white
cairo_select_font_face(cr, "Sans", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_BOLD);
cairo_set_font_size(cr, 25); // Set font size
// Calculate text position to center it
const char *text = "HELLO WORLD";
cairo_text_extents_t extents;
cairo_text_extents(cr, text, &extents);
double x = (size - extents.width) / 2; // Centering the text horizontally
double y = (size + extents.height) / 2; // Centering the text vertically
// Move to the calculated position and show the text
cairo_move_to(cr, x, y);
cairo_show_text(cr, text);
Making the Window Movable
The window is made draggable by listening to mouse events using XSelectInput. The window's position is updated based on the user's mouse movements while the left mouse button is pressed.
// Variables for dragging
int dragging = 0;
int x_offset = 0;
int y_offset = 0;
// Make the window listen for mouse events
XSelectInput(display, win, ExposureMask | ButtonPressMask | ButtonReleaseMask | PointerMotionMask);
// Main loop
XEvent e;
while (1) {
XNextEvent(display, &e);
switch (e.type) {
case Expose:
cairo_paint(cr);
break;
case ButtonPress:
if (e.xbutton.button == Button1) {
dragging = 1;
x_offset = e.xbutton.x;
y_offset = e.xbutton.y;
}
break;
case ButtonRelease:
if (e.xbutton.button == Button1) {
dragging = 0;
}
break;
case MotionNotify:
if (dragging) {
int x = e.xmotion.x_root - x_offset;
int y = e.xmotion.y_root - y_offset;
XMoveWindow(display, win, x, y);
}
break;
}
}
Cleanup
Before the program exits, it properly destroys the Cairo context, the window, and closes the connection to the X server to avoid memory leaks and other issues.
cairo_destroy(cr);
cairo_surface_destroy(surface);
XDestroyWindow(display, win);
XCloseDisplay(display);
Conclusion
This program demonstrates how to create a simple graphical application with a transparent, movable window using X11 and Cairo. While this example is basic, it lays the foundation for more complex applications that require custom window shapes and advanced graphical content.