wl-overlay/wl-overlay.c

500 lines
15 KiB
C
Raw Normal View History

2022-09-06 21:36:03 +00:00
#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>
2022-09-07 18:10:19 +00:00
#include <getopt.h>
2022-09-06 21:36:03 +00:00
#include <wayland-client.h>
#include <cairo/cairo.h>
2022-09-07 14:28:43 +00:00
#include <pango/pangocairo.h>
2022-09-06 21:36:03 +00:00
#include "wlr-layer-shell-protocol.h"
2022-09-07 14:28:43 +00:00
#define STR_EQUAL 0
2022-09-07 18:10:19 +00:00
#define DEFAULT_BORDER_RADIUS 15
2022-09-07 19:12:46 +00:00
#define DEFAULT_PADDING 10
2022-09-07 18:10:19 +00:00
#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})
2022-09-06 21:36:03 +00:00
/* 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 */
2022-09-07 18:10:19 +00:00
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);
}
2022-09-07 14:28:43 +00:00
struct user_request {
char *graphics_filename;
char *text;
2022-09-07 18:10:19 +00:00
char *font;
struct color_argb backdrop;
struct color_argb text_color;
int border_radius;
int width;
int height;
2022-09-07 19:12:46 +00:00
int padding;
2022-09-07 14:28:43 +00:00
};
2022-09-06 21:36:03 +00:00
struct client_state {
2022-09-07 14:28:43 +00:00
struct user_request user_request;
bool stop;
2022-09-06 21:36:03 +00:00
/* Globals */
struct wl_registry *wl_registry;
2022-09-07 19:50:25 +00:00
struct wl_display *wl_display;
2022-09-06 21:36:03 +00:00
struct wl_shm *wl_shm;
struct wl_compositor *wl_compositor;
struct zwlr_layer_shell_v1 *layer_shell;
2022-09-07 19:50:25 +00:00
struct wl_seat *wl_seat;
2022-09-06 21:36:03 +00:00
/* 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,
};
2022-09-07 14:28:43 +00:00
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);
}
2022-09-06 21:36:03 +00:00
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);
2022-09-07 18:10:19 +00:00
/* 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);
2022-09-07 14:28:43 +00:00
cairo_fill(cairo);
}
/* Load and draw PNG */
if (strcmp("", state->user_request.graphics_filename) != STR_EQUAL) {
2022-09-07 18:10:19 +00:00
cairo_save(cairo);
2022-09-07 14:28:43 +00:00
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);
}
2022-09-07 18:10:19 +00:00
/* 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);
2022-09-07 19:12:46 +00:00
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;
2022-09-07 18:10:19 +00:00
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);
2022-09-07 14:28:43 +00:00
cairo_set_source_surface(cairo, cairo_png_surface, 0.0, 0.0);
cairo_paint(cairo);
cairo_surface_destroy(cairo_png_surface);
2022-09-07 18:10:19 +00:00
cairo_restore(cairo);
2022-09-06 21:36:03 +00:00
}
2022-09-07 14:28:43 +00:00
/* Draw text */
if (state->user_request.text) {
PangoLayout *layout = pango_cairo_create_layout(cairo);
pango_layout_set_text(layout, state->user_request.text, -1);
2022-09-07 18:10:19 +00:00
pango_layout_set_alignment(layout, PANGO_ALIGN_CENTER);
2022-09-07 19:12:46 +00:00
pango_layout_set_width(layout, (width - 2*state->user_request.padding) * PANGO_SCALE);
2022-09-07 18:10:19 +00:00
PangoFontDescription *desc = pango_font_description_from_string(
state->user_request.font != NULL ? state->user_request.font : DEFAULT_FONT);
2022-09-07 14:28:43 +00:00
pango_layout_set_font_description(layout, desc);
pango_font_description_free(desc);
2022-09-07 18:10:19 +00:00
set_source_argb_cairo(cairo, &state->user_request.text_color);
int text_width = 0, text_height = 0;
2022-09-07 14:28:43 +00:00
pango_layout_get_size(layout, &text_width, &text_height);
2022-09-07 19:12:46 +00:00
cairo_move_to(cairo, state->user_request.padding, 3*height/4.0);
2022-09-07 14:28:43 +00:00
pango_cairo_show_layout(cairo, layout);
g_object_unref(layout);
}
memcpy(data, cairo_image_surface_get_data(cairo_target_surface), size);
2022-09-06 21:36:03 +00:00
cairo_surface_destroy(cairo_target_surface);
cairo_destroy(cairo);
munmap(data, size);
wl_buffer_add_listener(buffer, &wl_buffer_listener, NULL);
2022-09-07 19:50:25 +00:00
2022-09-06 21:36:03 +00:00
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,
};
2022-09-07 19:50:25 +00:00
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,
};
2022-09-06 21:36:03 +00:00
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;
2022-09-07 14:28:43 +00:00
if (strcmp(interface, wl_shm_interface.name) == STR_EQUAL) {
2022-09-06 21:36:03 +00:00
state->wl_shm = wl_registry_bind(
wl_registry, name, &wl_shm_interface, 1);
2022-09-07 14:28:43 +00:00
} else if (strcmp(interface, wl_compositor_interface.name) == STR_EQUAL) {
2022-09-06 21:36:03 +00:00
state->wl_compositor = wl_registry_bind(
wl_registry, name, &wl_compositor_interface, 4);
2022-09-07 14:28:43 +00:00
} else if (strcmp(interface, zwlr_layer_shell_v1_interface.name) == STR_EQUAL) {
2022-09-06 21:36:03 +00:00
state->layer_shell = wl_registry_bind(
2022-09-07 19:50:25 +00:00
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);
2022-09-06 21:36:03 +00:00
}
}
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,
};
2022-09-07 19:12:46 +00:00
2022-09-07 19:50:25 +00:00
2022-09-07 18:10:19 +00:00
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"
2022-09-07 19:12:46 +00:00
" --padding=<int> Set padding around image and text (default %d)\n",
2022-09-07 18:10:19 +00:00
DEFAULT_BORDER_RADIUS,
DEFAULT_WIDTH, DEFAULT_HEIGHT,
2022-09-07 19:12:46 +00:00
DEFAULT_PADDING);
2022-09-07 18:10:19 +00:00
}
2022-09-06 21:36:03 +00:00
int
main(int argc, char *argv[])
{
struct client_state state = { 0 };
2022-09-07 18:10:19 +00:00
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;
2022-09-07 19:12:46 +00:00
state.user_request.padding = DEFAULT_PADDING;
2022-09-07 18:10:19 +00:00
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 },
2022-09-07 19:12:46 +00:00
{"padding", required_argument, 0, 0 },
2022-09-07 18:10:19 +00:00
{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;
2022-09-07 19:12:46 +00:00
case 8: state.user_request.padding = atoi(optarg); break;
2022-09-07 18:10:19 +00:00
default: break;
}
break;
case 'h':
print_usage();
exit(0);
break;
default: break;
}
2022-09-06 21:36:03 +00:00
}
2022-09-07 18:10:19 +00:00
if (optind < argc) {
state.user_request.graphics_filename = strdup(argv[optind]);
2022-09-07 14:28:43 +00:00
} else {
2022-09-07 18:10:19 +00:00
print_usage();
exit(1);
2022-09-07 14:28:43 +00:00
}
2022-09-07 18:10:19 +00:00
optind++;
if (optind < argc) {
state.user_request.text = strdup(argv[optind]);
2022-09-07 14:28:43 +00:00
} else {
state.user_request.text = NULL;
}
2022-09-07 18:10:19 +00:00
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);
}
2022-09-06 21:36:03 +00:00
state.wl_display = wl_display_connect(NULL);
if (!state.wl_display) {
fprintf(stderr, "Could not connect to Wayland server\n");
exit(1);
2022-09-06 21:36:03 +00:00
}
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);
}
2022-09-06 21:36:03 +00:00
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");
2022-09-07 18:10:19 +00:00
zwlr_layer_surface_v1_set_size(state.zwlr_surface, state.user_request.width, state.user_request.height);
2022-09-06 21:36:03 +00:00
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);
2022-09-07 19:50:25 +00:00
struct wl_pointer *pointer = wl_seat_get_pointer(state.wl_seat);
wl_pointer_add_listener(pointer, &wl_pointer_listener, &state);
2022-09-06 21:36:03 +00:00
wl_surface_commit(state.wl_surface);
2022-09-07 19:50:25 +00:00
while (wl_display_dispatch(state.wl_display) && !state.stop) {
2022-09-06 21:36:03 +00:00
/* This space deliberately left blank */
}
2022-09-07 19:50:25 +00:00
if (state.wl_surface != NULL) {
wl_surface_destroy(state.wl_surface);
state.wl_surface = NULL;
}
2022-09-07 14:28:43 +00:00
free(state.user_request.graphics_filename);
if (state.user_request.text != NULL) {
free(state.user_request.text);
}
2022-09-07 18:10:19 +00:00
if (state.user_request.font != NULL) {
free(state.user_request.font);
}
2022-09-06 21:36:03 +00:00
return 0;
}