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.