Code Examples
Complete, working examples to get you started. From simple windows to GPU-accelerated rendering.
Building Examples
All examples can be compiled with a single command using pkg-config:
# Compile any example
gcc -o my_app my_app.c $(pkg-config --cflags --libs plexy)
# Or manually
gcc -o my_app my_app.c -I/usr/local/include -L/usr/local/lib -lplexy
Minimal Window
The simplest possible PlexyDesk application. Creates a window, fills it with a solid color, and waits for close.
/*
* hello_plexy.c - Minimal PlexyDesk window
* Compile: gcc -o hello_plexy hello_plexy.c $(pkg-config --cflags --libs plexy)
*/
#include <plexy.h>
#include <stdio.h>
#include <string.h>
static bool running = true;
static void on_close(PlexyWindow* win, void* data) {
running = false;
}
int main(void) {
// Connect to compositor
PlexyConnection* conn = plexy_connect(NULL);
if (!conn) {
fprintf(stderr, "Failed to connect to compositor\n");
return 1;
}
// Create 400x300 window
PlexyWindow* win = plexy_create_window(conn, -1, -1, 400, 300, "Hello PlexyDesk");
if (!win) {
fprintf(stderr, "Failed to create window\n");
plexy_disconnect(conn);
return 1;
}
// Set close callback
PlexyWindowCallbacks callbacks = { .close = on_close };
plexy_window_set_callbacks(win, &callbacks, NULL);
// Create buffer and fill with blue
PlexyBuffer* buf = plexy_create_buffer(conn, 400, 300, PLEXY_FORMAT_ARGB8888);
uint32_t* pixels = (uint32_t*)plexy_buffer_get_data(buf);
for (int i = 0; i < 400 * 300; i++) {
pixels[i] = 0xFF3584E4; // GNOME blue
}
// Display the buffer
plexy_window_attach(win, buf);
plexy_window_commit(win);
plexy_flush(conn);
// Event loop
while (running) {
if (plexy_dispatch(conn) < 0) {
break;
}
}
// Cleanup
plexy_destroy_buffer(buf);
plexy_destroy_window(win);
plexy_disconnect(conn);
return 0;
}
What this demonstrates
- • Connection to the compositor
- • Window creation with automatic positioning
- • Creating and filling a shared memory buffer
- • Basic event loop with close handling
Drawing Shapes
Software rendering with basic shapes and transparency.
/*
* shapes.c - Drawing basic shapes
*/
#include <plexy.h>
#include <string.h>
#include <math.h>
// Helper to pack ARGB color
static inline uint32_t rgba(uint8_t r, uint8_t g, uint8_t b, uint8_t a) {
return ((uint32_t)a << 24) | ((uint32_t)r << 16) |
((uint32_t)g << 8) | b;
}
// Fill rectangle
static void fill_rect(uint32_t* pixels, int stride,
int x, int y, int w, int h, uint32_t color) {
for (int row = y; row < y + h; row++) {
for (int col = x; col < x + w; col++) {
pixels[row * stride + col] = color;
}
}
}
// Draw filled circle
static void fill_circle(uint32_t* pixels, int stride, int width, int height,
int cx, int cy, int radius, uint32_t color) {
int r2 = radius * radius;
for (int y = cy - radius; y <= cy + radius; y++) {
if (y < 0 || y >= height) continue;
for (int x = cx - radius; x <= cx + radius; x++) {
if (x < 0 || x >= width) continue;
int dx = x - cx, dy = y - cy;
if (dx*dx + dy*dy <= r2) {
pixels[y * stride + x] = color;
}
}
}
}
// Draw line (Bresenham's algorithm)
static void draw_line(uint32_t* pixels, int stride, int width, int height,
int x0, int y0, int x1, int y1, uint32_t color) {
int dx = abs(x1 - x0), sx = x0 < x1 ? 1 : -1;
int dy = -abs(y1 - y0), sy = y0 < y1 ? 1 : -1;
int err = dx + dy;
while (1) {
if (x0 >= 0 && x0 < width && y0 >= 0 && y0 < height)
pixels[y0 * stride + x0] = color;
if (x0 == x1 && y0 == y1) break;
int e2 = 2 * err;
if (e2 >= dy) { err += dy; x0 += sx; }
if (e2 <= dx) { err += dx; y0 += sy; }
}
}
int main(void) {
PlexyConnection* conn = plexy_connect(NULL);
PlexyWindow* win = plexy_create_window(conn, -1, -1, 640, 480, "Shapes");
// Create buffer with alpha support
PlexyBuffer* buf = plexy_create_buffer(conn, 640, 480, PLEXY_FORMAT_ARGB8888);
uint32_t* pixels = (uint32_t*)plexy_buffer_get_data(buf);
int stride = 640;
// Clear to white
for (int i = 0; i < 640 * 480; i++)
pixels[i] = rgba(255, 255, 255, 255);
// Draw shapes
fill_rect(pixels, stride, 50, 50, 200, 150, rgba(53, 132, 228, 255)); // Blue rect
fill_rect(pixels, stride, 100, 100, 200, 150, rgba(255, 120, 0, 180)); // Orange (translucent)
fill_circle(pixels, stride, 640, 480, 400, 200, 80, rgba(143, 89, 2, 255));
draw_line(pixels, stride, 640, 480, 10, 400, 630, 300, rgba(30, 30, 30, 255));
plexy_window_attach(win, buf);
plexy_window_commit(win);
plexy_flush(conn);
// Wait for events
bool running = true;
PlexyWindowCallbacks cbs = { .close = (void*)&(running = false) };
plexy_window_set_callbacks(win, &cbs, &running);
while (running && plexy_dispatch(conn) >= 0);
plexy_destroy_buffer(buf);
plexy_destroy_window(win);
plexy_disconnect(conn);
return 0;
}
Input Handling
Handle mouse and keyboard events with callbacks.
/*
* input_demo.c - Mouse and keyboard handling
*/
#include <plexy.h>
#include <plexy_event_loop.h>
#include <stdio.h>
typedef struct {
PlexyWindow* window;
PlexyBuffer* buffer;
PlexyEventLoop* loop;
int32_t mouse_x, mouse_y;
bool left_pressed;
uint32_t modifiers;
} AppState;
static void on_pointer_motion(PlexyWindow* win, int32_t x, int32_t y, void* data) {
AppState* app = (AppState*)data;
app->mouse_x = x;
app->mouse_y = y;
// Draw at cursor position when button held
if (app->left_pressed) {
uint32_t* pixels = (uint32_t*)plexy_buffer_get_data(app->buffer);
uint32_t w, h;
plexy_window_get_size(win, &w, &h);
// Draw small circle at cursor
for (int dy = -3; dy <= 3; dy++) {
for (int dx = -3; dx <= 3; dx++) {
int px = x + dx, py = y + dy;
if (px >= 0 && px < w && py >= 0 && py < h) {
if (dx*dx + dy*dy <= 9) {
pixels[py * w + px] = 0xFF1A5FB4; // Blue
}
}
}
}
plexy_window_commit(win);
}
}
static void on_pointer_button(PlexyWindow* win, uint32_t button,
bool pressed, int32_t x, int32_t y, void* data) {
AppState* app = (AppState*)data;
if (button == PLEXY_BTN_LEFT) {
app->left_pressed = pressed;
printf("Left button %s at (%d, %d)\n", pressed ? "pressed" : "released", x, y);
} else if (button == PLEXY_BTN_RIGHT && pressed) {
// Clear canvas on right click
uint32_t* pixels = (uint32_t*)plexy_buffer_get_data(app->buffer);
for (int i = 0; i < 800 * 600; i++) {
pixels[i] = 0xFFFFFFFF; // White
}
plexy_window_commit(win);
printf("Canvas cleared\n");
}
}
static void on_key(PlexyWindow* win, uint32_t keycode, bool pressed,
uint32_t modifiers, void* data) {
AppState* app = (AppState*)data;
app->modifiers = modifiers;
if (pressed) {
printf("Key %u pressed", keycode);
if (modifiers & PLEXY_MOD_CTRL) printf(" +Ctrl");
if (modifiers & PLEXY_MOD_SHIFT) printf(" +Shift");
if (modifiers & PLEXY_MOD_ALT) printf(" +Alt");
printf("\n");
// Escape to quit
if (keycode == 1) { // KEY_ESC
plexy_event_loop_quit(app->loop);
}
}
}
static void on_close(PlexyWindow* win, void* data) {
AppState* app = (AppState*)data;
plexy_event_loop_quit(app->loop);
}
int main(void) {
AppState app = {0};
PlexyConnection* conn = plexy_connect(NULL);
app.window = plexy_create_window(conn, -1, -1, 800, 600, "Drawing Canvas");
app.buffer = plexy_create_buffer(conn, 800, 600, PLEXY_FORMAT_ARGB8888);
app.loop = plexy_event_loop_create(conn);
// Clear to white
uint32_t* pixels = (uint32_t*)plexy_buffer_get_data(app.buffer);
for (int i = 0; i < 800 * 600; i++) pixels[i] = 0xFFFFFFFF;
plexy_window_attach(app.window, app.buffer);
plexy_window_commit(app.window);
// Set all callbacks
PlexyWindowCallbacks callbacks = {
.pointer_motion = on_pointer_motion,
.pointer_button = on_pointer_button,
.key = on_key,
.close = on_close,
};
plexy_window_set_callbacks(app.window, &callbacks, &app);
printf("Draw with left mouse button. Right-click to clear. ESC to quit.\n");
// Run event loop
plexy_event_loop_run(app.loop);
// Cleanup
plexy_event_loop_destroy(app.loop);
plexy_destroy_buffer(app.buffer);
plexy_destroy_window(app.window);
plexy_disconnect(conn);
return 0;
}
Key concepts
- • Callbacks receive the user_data pointer for state access
- • Button events include the position at press/release time
- • Key events include current modifier state
- • Using the event loop for clean event handling
Dock Panel
Create a dock panel using layer surfaces. Demonstrates anchoring, exclusive zones, and hover effects.
/*
* dock.c - Bottom dock panel with layer surface
*/
#include <plexy.h>
#include <plexy_event_loop.h>
#include <string.h>
#define DOCK_HEIGHT 64
#define ICON_SIZE 48
#define ICON_COUNT 5
typedef struct {
PlexyConnection* conn;
PlexyLayerSurface* dock;
PlexyBuffer* buffer;
PlexyEventLoop* loop;
uint32_t width;
int hovered_icon;
} DockState;
static uint32_t icon_colors[ICON_COUNT] = {
0xFF3584E4, // Blue (Files)
0xFFFF7800, // Orange (Firefox)
0xFF33D17A, // Green (Terminal)
0xFF9141AC, // Purple (Settings)
0xFFC01C28, // Red (Power)
};
static void draw_dock(DockState* dock) {
uint32_t* pixels = (uint32_t*)plexy_buffer_get_data(dock->buffer);
uint32_t stride = dock->width;
// Semi-transparent dark background
for (uint32_t y = 0; y < DOCK_HEIGHT; y++) {
for (uint32_t x = 0; x < dock->width; x++) {
pixels[y * stride + x] = 0xE0303030;
}
}
// Draw icons centered
int total_width = ICON_COUNT * (ICON_SIZE + 16) - 16;
int start_x = (dock->width - total_width) / 2;
int icon_y = (DOCK_HEIGHT - ICON_SIZE) / 2;
for (int i = 0; i < ICON_COUNT; i++) {
int icon_x = start_x + i * (ICON_SIZE + 16);
uint32_t color = icon_colors[i];
// Hover effect: brighten
if (i == dock->hovered_icon) {
uint8_t r = ((color >> 16) & 0xFF) + 30;
uint8_t g = ((color >> 8) & 0xFF) + 30;
uint8_t b = (color & 0xFF) + 30;
color = 0xFF000000 | (r << 16) | (g << 8) | b;
}
// Draw rounded rectangle (simplified as filled rect)
for (int y = icon_y; y < icon_y + ICON_SIZE; y++) {
for (int x = icon_x; x < icon_x + ICON_SIZE; x++) {
pixels[y * stride + x] = color;
}
}
}
plexy_layer_surface_commit(dock->dock);
}
static void on_motion(PlexyWindow* w, int32_t x, int32_t y, void* data) {
DockState* dock = (DockState*)data;
int total_width = ICON_COUNT * (ICON_SIZE + 16) - 16;
int start_x = (dock->width - total_width) / 2;
int old_hover = dock->hovered_icon;
dock->hovered_icon = -1;
for (int i = 0; i < ICON_COUNT; i++) {
int icon_x = start_x + i * (ICON_SIZE + 16);
if (x >= icon_x && x < icon_x + ICON_SIZE) {
dock->hovered_icon = i;
break;
}
}
if (dock->hovered_icon != old_hover) {
draw_dock(dock);
}
}
static void on_leave(PlexyWindow* w, void* data) {
DockState* dock = (DockState*)data;
dock->hovered_icon = -1;
draw_dock(dock);
}
int main(void) {
DockState dock = { .hovered_icon = -1 };
dock.conn = plexy_connect(NULL);
// Get screen width
plexy_get_screen_size(dock.conn, &dock.width, NULL);
// Create layer surface anchored to bottom edge
dock.dock = plexy_create_layer_surface(
dock.conn,
PLEXY_LAYER_TOP,
PLEXY_ANCHOR_BOTTOM | PLEXY_ANCHOR_LEFT | PLEXY_ANCHOR_RIGHT,
0, // width: stretch to anchors
DOCK_HEIGHT,
DOCK_HEIGHT, // exclusive zone: reserve this space
0, 0 // margins
);
// Create buffer for dock
dock.buffer = plexy_create_buffer(dock.conn, dock.width, DOCK_HEIGHT,
PLEXY_FORMAT_ARGB8888);
plexy_layer_surface_attach(dock.dock, dock.buffer);
// Set callbacks for hover effects
PlexyLayerSurfaceCallbacks callbacks = {
.pointer_motion = on_motion,
.pointer_leave = on_leave,
};
plexy_layer_surface_set_callbacks(dock.dock, &callbacks, &dock);
// Initial draw
draw_dock(&dock);
plexy_flush(dock.conn);
// Run
dock.loop = plexy_event_loop_create(dock.conn);
plexy_event_loop_run(dock.loop);
// Cleanup
plexy_event_loop_destroy(dock.loop);
plexy_destroy_buffer(dock.buffer);
plexy_destroy_layer_surface(dock.dock);
plexy_disconnect(dock.conn);
return 0;
}
Layer Types
BACKGROUND— Desktop wallpaperBOTTOM— Below windowsTOP— Panels, docksOVERLAY— Lock screens, notifications
Anchor Flags
TOP | LEFT | RIGHT— Top barBOTTOM | LEFT | RIGHT— Bottom dockLEFT | TOP | BOTTOM— Left sidebar
Smooth Animation
Use the event loop with timers for smooth 60fps animation.
/*
* animation.c - Smooth 60fps animation with event loop timers
*/
#include <plexy.h>
#include <plexy_event_loop.h>
#include <math.h>
#define WIDTH 640
#define HEIGHT 480
#define FPS 60
typedef struct {
PlexyWindow* window;
PlexyBuffer* buffer;
PlexyEventLoop* loop;
double time;
} AnimState;
static int render_frame(void* data) {
AnimState* anim = (AnimState*)data;
uint32_t* pixels = (uint32_t*)plexy_buffer_get_data(anim->buffer);
anim->time += 1.0 / FPS;
// Clear
for (int i = 0; i < WIDTH * HEIGHT; i++) {
pixels[i] = 0xFF1E1E1E; // Dark gray
}
// Animated circle
double cx = WIDTH / 2 + cos(anim->time * 2.0) * 150;
double cy = HEIGHT / 2 + sin(anim->time * 3.0) * 100;
int radius = 40 + sin(anim->time * 5.0) * 15;
// Hue cycling
double hue = fmod(anim->time * 60, 360);
uint8_t r, g, b;
// HSV to RGB (simplified)
int hi = (int)(hue / 60) % 6;
double f = hue / 60 - hi;
uint8_t v = 230, p = 50, q = v * (1 - f), t = v * f;
switch (hi) {
case 0: r = v; g = t; b = p; break;
case 1: r = q; g = v; b = p; break;
case 2: r = p; g = v; b = t; break;
case 3: r = p; g = q; b = v; break;
case 4: r = t; g = p; b = v; break;
default: r = v; g = p; b = q; break;
}
uint32_t color = 0xFF000000 | (r << 16) | (g << 8) | b;
// Draw filled circle
for (int y = (int)cy - radius; y <= (int)cy + radius; y++) {
if (y < 0 || y >= HEIGHT) continue;
for (int x = (int)cx - radius; x <= (int)cx + radius; x++) {
if (x < 0 || x >= WIDTH) continue;
double dx = x - cx, dy = y - cy;
if (dx*dx + dy*dy <= radius*radius) {
pixels[y * WIDTH + x] = color;
}
}
}
plexy_window_commit(anim->window);
return 0; // Keep timer running
}
static void on_close(PlexyWindow* win, void* data) {
AnimState* anim = (AnimState*)data;
plexy_event_loop_quit(anim->loop);
}
int main(void) {
AnimState anim = {0};
PlexyConnection* conn = plexy_connect(NULL);
anim.window = plexy_create_window(conn, -1, -1, WIDTH, HEIGHT, "Animation");
anim.buffer = plexy_create_buffer(conn, WIDTH, HEIGHT, PLEXY_FORMAT_ARGB8888);
anim.loop = plexy_event_loop_create(conn);
plexy_window_attach(anim.window, anim.buffer);
PlexyWindowCallbacks callbacks = { .close = on_close };
plexy_window_set_callbacks(anim.window, &callbacks, &anim);
// Add 60fps timer (~16ms interval)
plexy_event_loop_add_timer(anim.loop, 1000 / FPS, render_frame, &anim);
// Initial frame
render_frame(&anim);
// Run
plexy_event_loop_run(anim.loop);
plexy_event_loop_destroy(anim.loop);
plexy_destroy_buffer(anim.buffer);
plexy_destroy_window(anim.window);
plexy_disconnect(conn);
return 0;
}
Timer callback return values
return 0;— Keep timer runningreturn 1;— Remove timer (one-shot)
GPU Rendering with DMA-BUF
Zero-copy GPU sharing using DMA-BUF for maximum performance. This example uses GBM for buffer allocation and OpenGL ES for rendering.
/*
* gpu_render.c - OpenGL ES rendering with DMA-BUF
* Compile: gcc -o gpu_render gpu_render.c $(pkg-config --cflags --libs plexy gbm egl glesv2)
*/
#include <plexy.h>
#include <gbm.h>
#include <EGL/egl.h>
#include <EGL/eglext.h>
#include <GLES2/gl2.h>
#include <drm_fourcc.h>
#include <fcntl.h>
#include <unistd.h>
#define WIDTH 800
#define HEIGHT 600
typedef struct {
int drm_fd;
struct gbm_device* gbm;
struct gbm_bo* bo;
EGLDisplay egl_display;
EGLContext egl_context;
EGLImageKHR egl_image;
GLuint fbo, texture;
} GPUState;
static bool init_gpu(GPUState* gpu) {
// Open render node
gpu->drm_fd = open("/dev/dri/renderD128", O_RDWR);
if (gpu->drm_fd < 0) return false;
// Create GBM device
gpu->gbm = gbm_create_device(gpu->drm_fd);
if (!gpu->gbm) return false;
// Create buffer object
gpu->bo = gbm_bo_create(gpu->gbm, WIDTH, HEIGHT,
GBM_FORMAT_ARGB8888,
GBM_BO_USE_RENDERING | GBM_BO_USE_LINEAR);
if (!gpu->bo) return false;
// Initialize EGL
gpu->egl_display = eglGetPlatformDisplay(EGL_PLATFORM_GBM_KHR, gpu->gbm, NULL);
eglInitialize(gpu->egl_display, NULL, NULL);
eglBindAPI(EGL_OPENGL_ES_API);
static const EGLint ctx_attribs[] = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE };
static const EGLint cfg_attribs[] = {
EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
EGL_NONE
};
EGLConfig config;
EGLint num_configs;
eglChooseConfig(gpu->egl_display, cfg_attribs, &config, 1, &num_configs);
gpu->egl_context = eglCreateContext(gpu->egl_display, config,
EGL_NO_CONTEXT, ctx_attribs);
eglMakeCurrent(gpu->egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE,
gpu->egl_context);
// Create EGL image from GBM buffer
static const EGLAttrib image_attribs[] = {
EGL_WIDTH, WIDTH,
EGL_HEIGHT, HEIGHT,
EGL_LINUX_DRM_FOURCC_EXT, DRM_FORMAT_ARGB8888,
EGL_DMA_BUF_PLANE0_FD_EXT, gbm_bo_get_fd(gpu->bo),
EGL_DMA_BUF_PLANE0_OFFSET_EXT, 0,
EGL_DMA_BUF_PLANE0_PITCH_EXT, gbm_bo_get_stride(gpu->bo),
EGL_NONE
};
PFNEGLCREATEIMAGEKHRPROC eglCreateImageKHR =
(void*)eglGetProcAddress("eglCreateImageKHR");
gpu->egl_image = eglCreateImageKHR(gpu->egl_display, EGL_NO_CONTEXT,
EGL_LINUX_DMA_BUF_EXT, NULL, image_attribs);
// Create texture and FBO
glGenTextures(1, &gpu->texture);
glBindTexture(GL_TEXTURE_2D, gpu->texture);
PFNGLEGLIMAGETARGETTEXTURE2DOESPROC glEGLImageTargetTexture2DOES =
(void*)eglGetProcAddress("glEGLImageTargetTexture2DOES");
glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, gpu->egl_image);
glGenFramebuffers(1, &gpu->fbo);
glBindFramebuffer(GL_FRAMEBUFFER, gpu->fbo);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
GL_TEXTURE_2D, gpu->texture, 0);
glViewport(0, 0, WIDTH, HEIGHT);
return true;
}
int main(void) {
GPUState gpu = {0};
if (!init_gpu(&gpu)) {
fprintf(stderr, "Failed to initialize GPU\n");
return 1;
}
PlexyConnection* conn = plexy_connect(NULL);
PlexyWindow* window = plexy_create_window(conn, -1, -1, WIDTH, HEIGHT,
"GPU Rendering");
// Create PlexyBuffer from DMA-BUF
int dmabuf_fd = gbm_bo_get_fd(gpu.bo);
PlexyBuffer* buffer = plexy_create_buffer_from_dmabuf(
conn, dmabuf_fd,
WIDTH, HEIGHT,
gbm_bo_get_stride(gpu.bo),
DRM_FORMAT_ARGB8888,
DRM_FORMAT_MOD_LINEAR
);
plexy_window_attach(window, buffer);
// Render with OpenGL
glClearColor(0.1f, 0.1f, 0.2f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
// ... your OpenGL rendering here ...
glFinish(); // Ensure GPU completes before commit
plexy_window_commit(window);
plexy_flush(conn);
// Event loop...
bool running = true;
while (running && plexy_dispatch(conn) >= 0);
// Cleanup
plexy_destroy_buffer(buffer);
plexy_destroy_window(window);
plexy_disconnect(conn);
glDeleteFramebuffers(1, &gpu.fbo);
glDeleteTextures(1, &gpu.texture);
eglDestroyImageKHR(gpu.egl_display, gpu.egl_image);
eglDestroyContext(gpu.egl_display, gpu.egl_context);
eglTerminate(gpu.egl_display);
gbm_bo_destroy(gpu.bo);
gbm_device_destroy(gpu.gbm);
close(gpu.drm_fd);
return 0;
}
Requirements
DMA-BUF requires a GPU with proper DRM/KMS drivers. Most modern Intel, AMD, and NVIDIA GPUs are supported.
Dependencies: libgbm, libegl, libglesv2, libdrm