Tiling Windowmanager for Wayland
Files | Log | Commits | Refs | README
Size: 15181 bytes
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/select.h>
#include <sys/mman.h>
#include <wayland-client.h>
#include <cairo/cairo.h>
#include <pango/pangocairo.h>
#include <fcntl.h>
#include <sys/stat.h>
#include "protocols/wlr-layer-shell-unstable-v1-client-protocol.h"
#include "config.h"
#include "snot.h"
#include "dbus.h"
static struct wl_display *display;
static struct wl_registry *registry;
static struct wl_compositor *compositor;
static struct zwlr_layer_shell_v1 *layer_shell;
static struct wl_shm *shm;
static Notification notifications[MAX_NOTIFICATIONS];
static int notification_count = 0;
static void draw_notification(Notification *n);
static void remove_notification(int index);
static void
die(const char *msg) {
fprintf(stderr, "snot: %s\n", msg);
exit(1);
}
static void
registry_global(void *data, struct wl_registry *registry,
uint32_t name, const char *interface, uint32_t version) {
if (strcmp(interface, wl_compositor_interface.name) == 0) {
compositor = wl_registry_bind(registry, name,
&wl_compositor_interface, 4);
} else if (strcmp(interface, zwlr_layer_shell_v1_interface.name) == 0) {
layer_shell = wl_registry_bind(registry, name,
&zwlr_layer_shell_v1_interface, 1);
} else if (strcmp(interface, wl_shm_interface.name) == 0) {
shm = wl_registry_bind(registry, name,
&wl_shm_interface, 1);
}
}
static void
registry_global_remove(void *data, struct wl_registry *registry,
uint32_t name) {
}
static const struct wl_registry_listener registry_listener = {
.global = registry_global,
.global_remove = registry_global_remove,
};
static void
layer_surface_configure(void *data, struct zwlr_layer_surface_v1 *surface,
uint32_t serial, uint32_t width, uint32_t height) {
Notification *n = data;
printf("Configuring surface with %dx%d\n", width, height);
zwlr_layer_surface_v1_ack_configure(surface, serial);
if (!n->configured) {
n->configured = true;
draw_notification(n);
}
}
static void
layer_surface_closed(void *data, struct zwlr_layer_surface_v1 *surface) {
Notification *n = data;
int index = n - notifications;
if (index >= 0 && index < notification_count) {
remove_notification(index);
}
}
static const struct zwlr_layer_surface_v1_listener layer_surface_listener = {
.configure = layer_surface_configure,
.closed = layer_surface_closed,
};
static void
create_notification_surface(Notification *n) {
printf("Creating notification for: '%s' - '%s'\n", n->summary, n->body);
int width = NOTIFICATION_WIDTH;
int height = NOTIFICATION_HEIGHT;
n->surface = wl_compositor_create_surface(compositor);
if (!n->surface) {
fprintf(stderr, "Failed to create surface\n");
return;
}
n->layer_surface = zwlr_layer_shell_v1_get_layer_surface(
layer_shell, n->surface, NULL,
ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY, "notification");
if (!n->layer_surface) {
fprintf(stderr, "Failed to create layer surface\n");
wl_surface_destroy(n->surface);
return;
}
n->cairo_surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height);
if (cairo_surface_status(n->cairo_surface) != CAIRO_STATUS_SUCCESS) {
fprintf(stderr, "Failed to create Cairo surface\n");
zwlr_layer_surface_v1_destroy(n->layer_surface);
wl_surface_destroy(n->surface);
return;
}
n->cairo = cairo_create(n->cairo_surface);
if (cairo_status(n->cairo) != CAIRO_STATUS_SUCCESS) {
fprintf(stderr, "Failed to create Cairo context\n");
cairo_surface_destroy(n->cairo_surface);
zwlr_layer_surface_v1_destroy(n->layer_surface);
wl_surface_destroy(n->surface);
return;
}
PangoLayout *layout = pango_cairo_create_layout(n->cairo);
PangoFontDescription *desc = pango_font_description_from_string(FONT);
pango_layout_set_font_description(layout, desc);
pango_font_description_free(desc);
int max_text_width = NOTIFICATION_WIDTH - (2 * PADDING);
pango_layout_set_width(layout, max_text_width * PANGO_SCALE);
pango_layout_set_wrap(layout, PANGO_WRAP_WORD_CHAR);
int text_width, text_height;
int total_height = 0;
if (n->summary) {
pango_layout_set_text(layout, n->summary, -1);
pango_layout_get_pixel_size(layout, &text_width, &text_height);
total_height = text_height;
width = MAX(width, text_width + (2 * PADDING));
}
if (n->body) {
pango_layout_set_text(layout, n->body, -1);
pango_layout_get_pixel_size(layout, &text_width, &text_height);
total_height += text_height + PADDING;
width = MAX(width, text_width + (2 * PADDING));
}
g_object_unref(layout);
height = MAX(NOTIFICATION_HEIGHT, total_height + (2 * PADDING));
width = MIN(MAX(NOTIFICATION_WIDTH, width), NOTIFICATION_MAX_WIDTH);
if (width != NOTIFICATION_WIDTH || height != NOTIFICATION_HEIGHT) {
cairo_destroy(n->cairo);
cairo_surface_destroy(n->cairo_surface);
n->cairo_surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height);
n->cairo = cairo_create(n->cairo_surface);
}
n->width = width;
n->height = height;
zwlr_layer_surface_v1_add_listener(n->layer_surface,
&layer_surface_listener, n);
zwlr_layer_surface_v1_set_size(n->layer_surface, width, height);
uint32_t anchor = 0;
int margin_top = 0;
int margin_right = 0;
int margin_bottom = 0;
int margin_left = 0;
if (POSITION == 0) { // top
anchor |= ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP;
margin_top = SPACING;
} else { // bottom
anchor |= ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM;
margin_bottom = SPACING;
}
switch (ALIGNMENT) {
case 0: // left
anchor |= ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT;
margin_left = SPACING;
break;
case 1: // center
anchor |= ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT |
ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT;
break;
case 2: // right
anchor |= ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT;
margin_right = SPACING;
break;
}
int stack_offset = notification_count * (height + SPACING);
if (POSITION == 0) {
margin_top += stack_offset;
} else {
margin_bottom += stack_offset;
}
zwlr_layer_surface_v1_set_anchor(n->layer_surface, anchor);
zwlr_layer_surface_v1_set_margin(n->layer_surface,
margin_top,
margin_right,
margin_bottom,
margin_left);
zwlr_layer_surface_v1_set_exclusive_zone(n->layer_surface, -1);
n->configured = false;
wl_surface_commit(n->surface);
printf("Notification surface created: pos=%s align=%s size=%dx%d offset=%d\n",
POSITION == 0 ? "top" : "bottom",
ALIGNMENT == 0 ? "left" : (ALIGNMENT == 1 ? "center" : "right"),
width, height, stack_offset);
}
static void
draw_notification(Notification *n) {
if (!n->cairo || !n->cairo_surface) {
fprintf(stderr, "No cairo surface available\n");
return;
}
cairo_t *cr = n->cairo;
cairo_save(cr);
cairo_set_operator(cr, CAIRO_OPERATOR_CLEAR);
cairo_paint(cr);
cairo_restore(cr);
cairo_set_source_rgba(cr, 0.133, 0.133, 0.133, 0.9);
cairo_paint(cr);
cairo_set_source_rgb(cr, 0.0, 0.337, 0.467);
cairo_set_line_width(cr, BORDER_WIDTH);
cairo_rectangle(cr, 0, 0, n->width, n->height);
cairo_stroke(cr);
PangoLayout *layout = pango_cairo_create_layout(cr);
PangoFontDescription *desc = pango_font_description_from_string(FONT);
pango_layout_set_font_description(layout, desc);
pango_font_description_free(desc);
pango_layout_set_alignment(layout, PANGO_ALIGN_CENTER);
int total_height = 0;
int summary_height = 0;
int body_height = 0;
int text_width;
pango_layout_set_width(layout, (n->width - 2 * PADDING) * PANGO_SCALE);
pango_layout_set_wrap(layout, PANGO_WRAP_WORD_CHAR);
if (n->summary) {
pango_layout_set_text(layout, n->summary, -1);
pango_layout_get_pixel_size(layout, &text_width, &summary_height);
total_height += summary_height;
}
if (n->body) {
pango_layout_set_text(layout, n->body, -1);
pango_layout_get_pixel_size(layout, &text_width, &body_height);
total_height += body_height + (PADDING/2);
}
int y_offset = (n->height - total_height) / 2;
if (n->summary) {
printf("Drawing summary: %s\n", n->summary);
cairo_set_source_rgb(cr, 0.733, 0.733, 0.733);
pango_layout_set_text(layout, n->summary, -1);
cairo_move_to(cr, PADDING, y_offset);
pango_cairo_show_layout(cr, layout);
}
if (n->body) {
printf("Drawing body: %s\n", n->body);
pango_layout_set_text(layout, n->body, -1);
cairo_move_to(cr, PADDING, y_offset + summary_height + (PADDING/2));
pango_cairo_show_layout(cr, layout);
}
g_object_unref(layout);
int stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, n->width);
int size = stride * n->height;
char tmp[] = "/tmp/snot-XXXXXX";
int fd = mkstemp(tmp);
if (fd < 0) {
fprintf(stderr, "Failed to create temporary file\n");
return;
}
unlink(tmp);
if (ftruncate(fd, size) < 0) {
fprintf(stderr, "Failed to set file size\n");
close(fd);
return;
}
void *data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (data == MAP_FAILED) {
fprintf(stderr, "Failed to mmap\n");
close(fd);
return;
}
memcpy(data, cairo_image_surface_get_data(n->cairo_surface), size);
struct wl_shm_pool *pool = wl_shm_create_pool(shm, fd, size);
struct wl_buffer *buffer = wl_shm_pool_create_buffer(pool, 0,
n->width, n->height,
stride,
WL_SHM_FORMAT_ARGB8888);
wl_shm_pool_destroy(pool);
close(fd);
wl_surface_attach(n->surface, buffer, 0, 0);
wl_surface_damage_buffer(n->surface, 0, 0, n->width, n->height);
wl_surface_commit(n->surface);
wl_buffer_destroy(buffer);
munmap(data, size);
printf("Drawing complete\n");
}
void
add_notification(const char *summary, const char *body,
const char *app_name, uint32_t replaces_id,
uint32_t expire_timeout) {
Notification *n;
printf("Received notification: '%s' - '%s'\n", summary, body);
/* Handle replacement if applicable */
if (replaces_id > 0) {
for (int i = 0; i < notification_count; i++) {
if (notifications[i].replaces_id == replaces_id) {
n = ¬ifications[i];
free(n->summary);
free(n->body);
free(n->app_name);
goto replace;
}
}
}
if (notification_count >= MAX_NOTIFICATIONS)
return;
n = ¬ifications[notification_count++];
n->surface = NULL;
n->layer_surface = NULL;
n->cairo_surface = NULL;
n->cairo = NULL;
n->configured = false;
n->width = NOTIFICATION_WIDTH;
n->height = NOTIFICATION_HEIGHT;
replace:
n->summary = summary ? strdup(summary) : NULL;
n->body = body ? strdup(body) : NULL;
n->app_name = app_name ? strdup(app_name) : NULL;
n->replaces_id = replaces_id;
n->expire_timeout = expire_timeout;
n->start_time = time(NULL) * 1000;
n->opacity = 1.0;
create_notification_surface(n);
}
static void
remove_notification(int index) {
if (index < 0 || index >= notification_count)
return;
Notification *n = ¬ifications[index];
if (n->layer_surface) {
zwlr_layer_surface_v1_destroy(n->layer_surface);
n->layer_surface = NULL;
}
if (n->surface) {
wl_surface_destroy(n->surface);
n->surface = NULL;
}
if (n->cairo) {
cairo_destroy(n->cairo);
n->cairo = NULL;
}
if (n->cairo_surface) {
cairo_surface_destroy(n->cairo_surface);
n->cairo_surface = NULL;
}
free(n->summary);
free(n->body);
free(n->app_name);
for (int i = index; i < notification_count - 1; i++) {
notifications[i] = notifications[i + 1];
}
notification_count--;
}
int
main(void) {
display = wl_display_connect(NULL);
if (!display)
die("Cannot connect to Wayland display");
printf("Connected to Wayland display\n");
registry = wl_display_get_registry(display);
wl_registry_add_listener(registry, ®istry_listener, NULL);
wl_display_roundtrip(display);
if (!compositor || !layer_shell || !shm)
die("Missing required Wayland protocols");
printf("Wayland protocols initialized\n");
if (dbus_init() < 0)
die("Failed to initialize D-Bus");
printf("D-Bus initialized\n");
while (1) {
while (wl_display_prepare_read(display) != 0) {
wl_display_dispatch_pending(display);
}
wl_display_flush(display);
int wayland_fd = wl_display_get_fd(display);
fd_set read_fds;
FD_ZERO(&read_fds);
FD_SET(wayland_fd, &read_fds);
struct timeval tv = {
.tv_sec = 0,
.tv_usec = 16666
};
int ret = select(wayland_fd + 1, &read_fds, NULL, NULL, &tv);
if (ret < 0 && errno != EINTR) {
fprintf(stderr, "select failed: %s\n", strerror(errno));
break;
}
if (ret > 0 && FD_ISSET(wayland_fd, &read_fds)) {
if (wl_display_read_events(display) < 0) {
fprintf(stderr, "Failed to read Wayland events\n");
break;
}
} else {
wl_display_cancel_read(display);
}
if (wl_display_dispatch_pending(display) < 0) {
fprintf(stderr, "Failed to dispatch Wayland events\n");
break;
}
if (dbus_dispatch() < 0) {
fprintf(stderr, "Failed to dispatch D-Bus events\n");
break;
}
unsigned long current_time = time(NULL) * 1000;
for (int i = 0; i < notification_count; i++) {
Notification *n = ¬ifications[i];
if (!n->configured)
continue;
unsigned long display_time = (n->expire_timeout == -1) ?
DURATION : n->expire_timeout;
if (display_time > 0 &&
current_time - n->start_time >= display_time) {
printf("Removing expired notification %d\n", i);
remove_notification(i--);
}
}
}}