wl-overlay/wl-overlay.c

499 lines
15 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <time.h>
#include <unistd.h>
#include <getopt.h>
#include <wayland-client.h>
#include <cairo/cairo.h>
#include <pango/pangocairo.h>
#include "wlr-layer-shell-protocol.h"
#define STR_EQUAL 0
#define DEFAULT_BORDER_RADIUS 15
#define DEFAULT_PADDING 10
#define DEFAULT_WIDTH 256
#define DEFAULT_HEIGHT 256
#define DEFAULT_FONT "Fira Sans 17"
#define DEFAULT_BACKDROP_COLOR \
((struct color_argb){0.85, 0x11 / 255.0, 0x11 / 255.0, 0x11 / 255.0})
/* Shared memory support code */
static void
randname(char *buf)
{
struct timespec ts;
clock_gettime(CLOCK_REALTIME, &ts);
long r = ts.tv_nsec;
for (int i = 0; i < 6; ++i) {
buf[i] = 'A'+(r&15)+(r&16)*2;
r >>= 5;
}
}
static int
create_shm_file(void)
{
int retries = 100;
do {
char name[] = "/wl_shm-XXXXXX";
randname(name + sizeof(name) - 7);
--retries;
int fd = shm_open(name, O_RDWR | O_CREAT | O_EXCL, 0600);
if (fd >= 0) {
shm_unlink(name);
return fd;
}
} while (retries > 0 && errno == EEXIST);
return -1;
}
static int
allocate_shm_file(size_t size)
{
int fd = create_shm_file();
if (fd < 0)
return -1;
int ret;
do {
ret = ftruncate(fd, size);
} while (ret < 0 && errno == EINTR);
if (ret < 0) {
close(fd);
return -1;
}
return fd;
}
/* Wayland code */
struct color_argb {
double a;
double r;
double g;
double b;
};
static struct color_argb color_transparent_black = { 0.0, 0.0, 0.0, 0.0 };
static bool
color_argb_equal(struct color_argb *a, struct color_argb *b)
{
return a->a == b->a && a->r == b->r && a->g == b->g && a->b == b->b;
}
static bool
color_argb_invalid(struct color_argb *a)
{
return !(
(0.0 <= a->a) && (a->a <= 1.0) &&
(0.0 <= a->r) && (a->r <= 1.0) &&
(0.0 <= a->g) && (a->g <= 1.0) &&
(0.0 <= a->b) && (a->b <= 1.0)
);
}
static void
set_source_argb_cairo(cairo_t *cairo, struct color_argb *color) {
cairo_set_source_rgba(cairo, color->r, color->g, color->b, color->a);
}
struct user_request {
char *graphics_filename;
char *text;
char *font;
struct color_argb backdrop;
struct color_argb text_color;
int border_radius;
int width;
int height;
int padding;
};
struct client_state {
struct user_request user_request;
bool stop;
/* Globals */
struct wl_registry *wl_registry;
struct wl_display *wl_display;
struct wl_shm *wl_shm;
struct wl_compositor *wl_compositor;
struct zwlr_layer_shell_v1 *layer_shell;
struct wl_seat *wl_seat;
/* Objects */
struct wl_surface *wl_surface;
struct zwlr_layer_surface_v1 *zwlr_surface;
};
static void
wl_buffer_release(void *data, struct wl_buffer *wl_buffer)
{
/* Sent by the compositor when it's no longer using this buffer */
wl_buffer_destroy(wl_buffer);
}
static const struct wl_buffer_listener wl_buffer_listener = {
.release = wl_buffer_release,
};
static void
draw_rounded_rectangle(cairo_t *cr, int x, int y, int width, int height, int radius)
{
cairo_new_sub_path(cr);
cairo_arc(cr, x + radius, y + radius, radius, M_PI, 3*M_PI/2);
cairo_arc(cr, x + width - radius, y + radius, radius, 3*M_PI/2, 0);
cairo_arc(cr, x + width - radius, y + height - radius, radius, 0, M_PI/2);
cairo_arc(cr, x + radius, y + height - radius, radius, M_PI/2, M_PI);
cairo_close_path(cr);
}
static struct wl_buffer *
draw_frame(struct client_state *state, const int width, const int height)
{
int stride = width * 4;
int size = stride * height;
int fd = allocate_shm_file(size);
if (fd == -1) {
return NULL;
}
uint32_t *data = mmap(NULL, size,
PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (data == MAP_FAILED) {
close(fd);
return NULL;
}
struct wl_shm_pool *pool = wl_shm_create_pool(state->wl_shm, fd, size);
struct wl_buffer *buffer = wl_shm_pool_create_buffer(pool, 0,
width, height, stride, WL_SHM_FORMAT_ARGB8888);
wl_shm_pool_destroy(pool);
close(fd);
cairo_surface_t *cairo_target_surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height);
cairo_t *cairo = cairo_create(cairo_target_surface);
memset(cairo_image_surface_get_data(cairo_target_surface), 0, size);
/* Draw background */
if (!color_argb_equal(&state->user_request.backdrop, &color_transparent_black)) {
set_source_argb_cairo(cairo, &state->user_request.backdrop);
draw_rounded_rectangle(cairo, 0, 0, width, height, state->user_request.border_radius);
cairo_fill(cairo);
}
/* Load and draw PNG */
if (strcmp("", state->user_request.graphics_filename) != STR_EQUAL) {
cairo_save(cairo);
cairo_surface_t *cairo_png_surface = cairo_image_surface_create_from_png(state->user_request.graphics_filename);
if (cairo_surface_status(cairo_png_surface) != CAIRO_STATUS_SUCCESS) {
fprintf(stderr, "Failed to open PNG image.\n");
exit(1);
}
/* Set transformations to fit image in box */
int img_width = cairo_image_surface_get_width(cairo_png_surface);
int img_height = cairo_image_surface_get_height(cairo_png_surface);
double width_scale = ((double)width - 2*state->user_request.padding) / img_width;
double height_scale = (((double)height - 2*state->user_request.padding) * 3 / 4.0) / img_height;
double scale = MIN(width_scale, height_scale);
if (scale > 1.0) scale = 1.0;
cairo_translate(cairo,
(width - img_width *scale)/2.0,
(3*height/4.0 - img_height*scale)/2.0);
cairo_scale(cairo, scale, scale);
cairo_set_source_surface(cairo, cairo_png_surface, 0.0, 0.0);
cairo_paint(cairo);
cairo_surface_destroy(cairo_png_surface);
cairo_restore(cairo);
}
/* Draw text */
if (state->user_request.text) {
PangoLayout *layout = pango_cairo_create_layout(cairo);
pango_layout_set_text(layout, state->user_request.text, -1);
pango_layout_set_alignment(layout, PANGO_ALIGN_CENTER);
pango_layout_set_width(layout, (width - 2*state->user_request.padding) * PANGO_SCALE);
PangoFontDescription *desc = pango_font_description_from_string(
state->user_request.font != NULL ? state->user_request.font : DEFAULT_FONT);
pango_layout_set_font_description(layout, desc);
pango_font_description_free(desc);
set_source_argb_cairo(cairo, &state->user_request.text_color);
int text_width = 0, text_height = 0;
pango_layout_get_size(layout, &text_width, &text_height);
cairo_move_to(cairo, state->user_request.padding, 3*height/4.0);
pango_cairo_show_layout(cairo, layout);
g_object_unref(layout);
}
memcpy(data, cairo_image_surface_get_data(cairo_target_surface), size);
cairo_surface_destroy(cairo_target_surface);
cairo_destroy(cairo);
munmap(data, size);
wl_buffer_add_listener(buffer, &wl_buffer_listener, NULL);
return buffer;
}
static void
zwlr_layer_surface_configure(void *data,
struct zwlr_layer_surface_v1 *zwlr_layer_surface_v1,
uint32_t serial, uint32_t width, uint32_t height)
{
struct client_state *state = data;
zwlr_layer_surface_v1_ack_configure(zwlr_layer_surface_v1, serial);
struct wl_buffer *buffer = draw_frame(state, width, height);
wl_surface_attach(state->wl_surface, buffer, 0, 0);
wl_surface_commit(state->wl_surface);
}
static const struct zwlr_layer_surface_v1_listener zwlr_layer_surface_listener = {
.configure = zwlr_layer_surface_configure,
};
static void
wl_pointer_button(void *data, struct wl_pointer *wl_pointer,
uint32_t serial, uint32_t time, uint32_t button, uint32_t button_state)
{
struct client_state *state = data;
state->stop = true;
}
void wl_pointer_enter(void *data, struct wl_pointer *wl_pointer,
uint32_t serial, struct wl_surface *surface, wl_fixed_t surface_x, wl_fixed_t surface_y) {}
void wl_pointer_leave(void *data, struct wl_pointer *wl_pointer,
uint32_t serial, struct wl_surface *surface) {}
void wl_pointer_motion(void *data, struct wl_pointer *wl_pointer,
uint32_t time, wl_fixed_t surface_x, wl_fixed_t surface_y) {}
void wl_pointer_axis(void *data, struct wl_pointer *wl_pointer,
uint32_t time, uint32_t axis, wl_fixed_t value) {}
void wl_pointer_frame(void *data, struct wl_pointer *wl_pointer) {}
void wl_pointer_axis_source(void *data, struct wl_pointer *wl_pointer,
uint32_t axis_source) {}
void wl_pointer_axis_stop(void *data, struct wl_pointer *wl_pointer,
uint32_t time, uint32_t axis) {}
void wl_pointer_axis_discrete(void *data, struct wl_pointer *wl_pointer,
uint32_t axis, int32_t discrete) {}
void wl_pointer_axis_value120(void *data, struct wl_pointer *wl_pointer,
uint32_t axis, int32_t value120) {}
static const struct wl_pointer_listener wl_pointer_listener = {
.button = wl_pointer_button,
.enter = wl_pointer_enter,
.leave = wl_pointer_leave,
.motion = wl_pointer_motion,
.axis = wl_pointer_axis,
.frame = wl_pointer_frame,
.axis_source = wl_pointer_axis_source,
.axis_stop = wl_pointer_axis_stop,
.axis_discrete = wl_pointer_axis_discrete,
.axis_value120 = wl_pointer_axis_value120,
};
static void
registry_global(void *data, struct wl_registry *wl_registry,
uint32_t name, const char *interface, uint32_t version)
{
struct client_state *state = data;
if (strcmp(interface, wl_shm_interface.name) == STR_EQUAL) {
state->wl_shm = wl_registry_bind(
wl_registry, name, &wl_shm_interface, 1);
} else if (strcmp(interface, wl_compositor_interface.name) == STR_EQUAL) {
state->wl_compositor = wl_registry_bind(
wl_registry, name, &wl_compositor_interface, 4);
} else if (strcmp(interface, zwlr_layer_shell_v1_interface.name) == STR_EQUAL) {
state->layer_shell = wl_registry_bind(
wl_registry, name, &zwlr_layer_shell_v1_interface, 1);
} else if (strcmp(interface, wl_seat_interface.name) == STR_EQUAL) {
state->wl_seat = wl_registry_bind(
wl_registry, name, &wl_seat_interface, 7);
}
}
static void
registry_global_remove(void *data,
struct wl_registry *wl_registry, uint32_t name)
{
/* This space deliberately left blank */
}
static const struct wl_registry_listener wl_registry_listener = {
.global = registry_global,
.global_remove = registry_global_remove,
};
static void
parse_hex(char *hex, struct color_argb *result_color) {
if (strlen(hex) != 4 * 2) {
// Invalid
result_color->a = -1.0;
return;
}
int a, r, g, b;
sscanf(hex, "%02x%02x%02x%02x", &a, &r, &g, &b);
result_color->a = a / 255.0;
result_color->r = r / 255.0;
result_color->g = g / 255.0;
result_color->b = b / 255.0;
}
static void
print_usage()
{
fprintf(stderr, "Usage: wl-overlay [options...] <image.png> [text]\n\n"
"image.png must be the path to a valid PNG image or may be the empty string for "
"no image. text is the text to show.\n\n"
"Options:\n"
" -h --help Show this help text and exit\n"
" --backdrop=<aarrggbb> Set backdrop colour (default d9111111, set to 00000000 to disable backdrop)\n"
" --color=<aarrggbb> Set text colour (default ffffffff)\n"
" --border-radius=<int> Give the backdrop rounded corners with specified radius (default %d)\n"
" --font=<str> Set font of text (default " DEFAULT_FONT ")\n"
" --width=<int> \n"
" --height=<int> Set width and height (default %d×%d)\n"
" --padding=<int> Set padding around image and text (default %d)\n",
DEFAULT_BORDER_RADIUS,
DEFAULT_WIDTH, DEFAULT_HEIGHT,
DEFAULT_PADDING);
}
int
main(int argc, char *argv[])
{
struct client_state state = { 0 };
state.user_request.text_color = (struct color_argb){1.0, 1.0, 1.0, 1.0};
state.user_request.border_radius = DEFAULT_BORDER_RADIUS;
state.user_request.backdrop = DEFAULT_BACKDROP_COLOR;
state.user_request.width = DEFAULT_WIDTH;
state.user_request.height = DEFAULT_HEIGHT;
state.user_request.padding = DEFAULT_PADDING;
int c;
while (1) {
int option_index = 0;
static struct option long_options[] = {
{"help", no_argument, 0, 'h'},
{"backdrop", required_argument, 0, 0 },
{"color", required_argument, 0, 0 },
{"colour", required_argument, 0, 0 },
{"border-radius", required_argument, 0, 0 },
{"font", required_argument, 0, 0 },
{"width", required_argument, 0, 0 },
{"height", required_argument, 0, 0 },
{"padding", required_argument, 0, 0 },
{0, 0, 0, 0 }
};
c = getopt_long(argc, argv, "h", long_options, &option_index);
if (c == -1)
break;
switch (c) {
case 0:
switch (option_index) {
case 1: parse_hex(optarg, &state.user_request.backdrop); break;
case 2: case 3: parse_hex(optarg, &state.user_request.text_color); break;
case 4: state.user_request.border_radius = atoi(optarg); break;
case 5: state.user_request.font = strdup(optarg); break;
case 6: state.user_request.width = atoi(optarg); break;
case 7: state.user_request.height = atoi(optarg); break;
case 8: state.user_request.padding = atoi(optarg); break;
default: break;
}
break;
case 'h':
print_usage();
exit(0);
break;
default: break;
}
}
if (optind < argc) {
state.user_request.graphics_filename = strdup(argv[optind]);
} else {
print_usage();
exit(1);
}
optind++;
if (optind < argc) {
state.user_request.text = strdup(argv[optind]);
} else {
state.user_request.text = NULL;
}
if (color_argb_invalid(&state.user_request.backdrop)) {
fprintf(stderr, "Invalid --backdrop color, must be hexadecimal aarrggbb\n");
exit(1);
}
if (color_argb_invalid(&state.user_request.text_color)) {
fprintf(stderr, "Invalid --font color, must be hexadecimal aarrggbb\n");
exit(1);
}
state.wl_display = wl_display_connect(NULL);
if (!state.wl_display) {
fprintf(stderr, "Could not connect to Wayland server\n");
exit(1);
}
state.wl_registry = wl_display_get_registry(state.wl_display);
wl_registry_add_listener(state.wl_registry, &wl_registry_listener, &state);
wl_display_roundtrip(state.wl_display);
if (state.layer_shell == NULL) {
fprintf(stderr, "Wayland compositor does not support wlr-layer-shell protocol\n");
exit(1);
}
state.wl_surface = wl_compositor_create_surface(state.wl_compositor);
state.zwlr_surface = zwlr_layer_shell_v1_get_layer_surface(
state.layer_shell, state.wl_surface, NULL, ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY, "wl-overlay");
zwlr_layer_surface_v1_set_size(state.zwlr_surface, state.user_request.width, state.user_request.height);
zwlr_layer_surface_v1_set_anchor(state.zwlr_surface, ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM);
zwlr_layer_surface_v1_set_margin(state.zwlr_surface, 50, 50, 50, 50);
zwlr_layer_surface_v1_add_listener(state.zwlr_surface, &zwlr_layer_surface_listener, &state);
struct wl_pointer *pointer = wl_seat_get_pointer(state.wl_seat);
wl_pointer_add_listener(pointer, &wl_pointer_listener, &state);
wl_surface_commit(state.wl_surface);
while (wl_display_dispatch(state.wl_display) && !state.stop) {
/* This space deliberately left blank */
}
if (state.wl_surface != NULL) {
wl_surface_destroy(state.wl_surface);
state.wl_surface = NULL;
}
free(state.user_request.graphics_filename);
if (state.user_request.text != NULL) {
free(state.user_request.text);
}
if (state.user_request.font != NULL) {
free(state.user_request.font);
}
return 0;
}