NEDM/message.c

348 lines
9.8 KiB
C

// Copyright 2020 - 2025, project-repo and the NEDM contributors
// SPDX-License-Identifier: MIT
#include <cairo/cairo.h>
#include <drm_fourcc.h>
#include <pango/pangocairo.h>
#include <sys/mman.h>
#include <unistd.h>
#include <wayland-client.h>
#include <wlr/backend.h>
#include <wlr/interfaces/wlr_buffer.h>
#include <wlr/render/allocator.h>
#include <wlr/render/drm_format_set.h>
#include <wlr/render/wlr_renderer.h>
#include <wlr/types/wlr_output_layout.h>
#include <wlr/types/wlr_scene.h>
#include <wlr/util/log.h>
#include "message.h"
#include "output.h"
#include "pango.h"
#include "server.h"
#include "util.h"
struct msg_buffer {
struct wlr_buffer base;
void *data;
uint32_t format;
size_t stride;
};
static void
msg_buffer_destroy(struct wlr_buffer *wlr_buffer) {
struct msg_buffer *buffer = wl_container_of(wlr_buffer, buffer, base);
free(buffer->data);
free(buffer);
}
static bool
msg_buffer_begin_data_ptr_access(struct wlr_buffer *wlr_buffer,
__attribute__((unused)) uint32_t flags,
void **data, uint32_t *format,
size_t *stride) {
struct msg_buffer *buffer = wl_container_of(wlr_buffer, buffer, base);
if(data != NULL) {
*data = (void *)buffer->data;
}
if(format != NULL) {
*format = buffer->format;
}
if(stride != NULL) {
*stride = buffer->stride;
}
return true;
}
static void
msg_buffer_end_data_ptr_access(
__attribute__((unused)) struct wlr_buffer *wlr_buffer) {
// This space is intentionally left blank
}
static const struct wlr_buffer_impl msg_buffer_impl = {
.destroy = msg_buffer_destroy,
.begin_data_ptr_access = msg_buffer_begin_data_ptr_access,
.end_data_ptr_access = msg_buffer_end_data_ptr_access,
};
static struct msg_buffer *
msg_buffer_create(uint32_t width, uint32_t height, uint32_t stride) {
struct msg_buffer *buffer = calloc(1, sizeof(*buffer));
if(buffer == NULL) {
return NULL;
}
wlr_buffer_init(&buffer->base, &msg_buffer_impl, width, height);
buffer->format = DRM_FORMAT_ARGB8888;
buffer->stride = stride;
buffer->data = malloc(buffer->stride * height);
if(buffer->data == NULL) {
free(buffer);
return NULL;
}
return buffer;
}
cairo_subpixel_order_t
to_cairo_subpixel_order(const enum wl_output_subpixel subpixel) {
switch(subpixel) {
case WL_OUTPUT_SUBPIXEL_HORIZONTAL_RGB:
return CAIRO_SUBPIXEL_ORDER_RGB;
case WL_OUTPUT_SUBPIXEL_HORIZONTAL_BGR:
return CAIRO_SUBPIXEL_ORDER_BGR;
case WL_OUTPUT_SUBPIXEL_VERTICAL_RGB:
return CAIRO_SUBPIXEL_ORDER_VRGB;
case WL_OUTPUT_SUBPIXEL_VERTICAL_BGR:
return CAIRO_SUBPIXEL_ORDER_VBGR;
default:
return CAIRO_SUBPIXEL_ORDER_DEFAULT;
}
return CAIRO_SUBPIXEL_ORDER_DEFAULT;
}
struct msg_buffer *
create_message_texture(const char *string, const struct nedm_output *output) {
const int WIDTH_PADDING = 8;
const int HEIGHT_PADDING = 2;
double scale = output->wlr_output->scale;
int width = 0;
int height = 0;
// We must use a non-nil cairo_t for cairo_set_font_options to work.
// Therefore, we cannot use cairo_create(NULL).
cairo_surface_t *dummy_surface =
cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 0, 0);
// This occurs when we are fuzzing. In that case, do nothing
if(dummy_surface == NULL) {
return NULL;
}
cairo_t *c = cairo_create(dummy_surface);
cairo_set_antialias(c, CAIRO_ANTIALIAS_BEST);
cairo_font_options_t *fo = cairo_font_options_create();
cairo_font_options_set_hint_style(fo, CAIRO_HINT_STYLE_FULL);
cairo_font_options_set_antialias(fo, CAIRO_ANTIALIAS_SUBPIXEL);
cairo_font_options_set_subpixel_order(
fo, to_cairo_subpixel_order(output->wlr_output->subpixel));
cairo_set_font_options(c, fo);
get_text_size(c, output->server->message_config.font, &width, &height, NULL,
scale, "%s", string);
width += 2 * WIDTH_PADDING;
height += 2 * HEIGHT_PADDING;
cairo_surface_destroy(dummy_surface);
cairo_destroy(c);
cairo_surface_t *surface =
cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height);
cairo_t *cairo = cairo_create(surface);
cairo_set_antialias(cairo, CAIRO_ANTIALIAS_BEST);
cairo_set_font_options(cairo, fo);
cairo_font_options_destroy(fo);
float *bg_col = output->server->message_config.bg_color;
cairo_set_source_rgba(cairo, bg_col[0], bg_col[1], bg_col[2], bg_col[3]);
cairo_paint(cairo);
float *fg_col = output->server->message_config.fg_color;
cairo_set_source_rgba(cairo, fg_col[0], fg_col[1], fg_col[2], fg_col[3]);
cairo_set_line_width(cairo, 2);
cairo_rectangle(cairo, 0, 0, width, height);
cairo_stroke(cairo);
cairo_move_to(cairo, WIDTH_PADDING, HEIGHT_PADDING);
pango_printf(cairo, output->server->message_config.font, scale, "%s",
string);
cairo_surface_flush(surface);
unsigned char *data = cairo_image_surface_get_data(surface);
int stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, width);
struct msg_buffer *buf = msg_buffer_create(width, height, stride);
void *data_ptr;
if(!wlr_buffer_begin_data_ptr_access(&buf->base,
WLR_BUFFER_DATA_PTR_ACCESS_WRITE,
&data_ptr, NULL, NULL)) {
wlr_log(WLR_ERROR, "Failed to get pointer access to message buffer");
return NULL;
}
memcpy(data_ptr, data, stride * height);
wlr_buffer_end_data_ptr_access(&buf->base);
cairo_surface_destroy(surface);
cairo_destroy(cairo);
return buf;
}
void
message_set_output(struct nedm_output *output, const char *string,
struct wlr_box *box, enum nedm_message_anchor anchor) {
struct nedm_message *message = malloc(sizeof(struct nedm_message));
if(!message) {
wlr_log(WLR_ERROR, "Error allocating message structure");
free(box);
return;
}
struct msg_buffer *buf = create_message_texture(string, output);
if(!buf) {
wlr_log(WLR_ERROR, "Could not create message texture");
free(box);
free(message);
return;
}
message->position = box;
wl_list_insert(&output->messages, &message->link);
double scale = output->wlr_output->scale;
int width = buf->base.width / scale;
int height = buf->base.height / scale;
message->position->width = width;
message->position->height = height;
switch(anchor) {
case NEDM_MESSAGE_TOP_LEFT:
message->position->x = 0;
message->position->y = 0;
break;
case NEDM_MESSAGE_TOP_CENTER:
message->position->x -= width / 2;
message->position->y = 0;
break;
case NEDM_MESSAGE_TOP_RIGHT:
message->position->x -= width;
message->position->y = 0;
break;
case NEDM_MESSAGE_BOTTOM_LEFT:
message->position->x = 0;
message->position->y -= height;
break;
case NEDM_MESSAGE_BOTTOM_CENTER:
message->position->x -= width / 2;
message->position->y -= height;
break;
case NEDM_MESSAGE_BOTTOM_RIGHT:
message->position->x -= width;
message->position->y -= height;
break;
case NEDM_MESSAGE_CENTER:
message->position->x -= width / 2;
message->position->y -= height / 2;
break;
default:
break;
}
struct wlr_scene_output *scene_output =
wlr_scene_get_scene_output(output->server->scene, output->wlr_output);
if(scene_output == NULL) {
return;
}
message->message =
wlr_scene_buffer_create(&scene_output->scene->tree, &buf->base);
message->buf = buf;
wlr_scene_node_raise_to_top(&message->message->node);
wlr_scene_node_set_enabled(&message->message->node, true);
wlr_scene_buffer_set_dest_size(message->message, width, height);
wlr_scene_node_set_position(
&message->message->node,
message->position->x + output_get_layout_box(output).x,
message->position->y + output_get_layout_box(output).y);
}
void
message_printf(struct nedm_output *output, const char *fmt, ...) {
if(output->destroyed || output->server->message_config.enabled == 0) {
return;
}
va_list ap;
va_start(ap, fmt);
char *buffer = malloc_vsprintf_va_list(fmt, ap);
va_end(ap);
if(buffer == NULL) {
wlr_log(WLR_ERROR, "Failed to allocate buffer in message_printf");
return;
}
struct wlr_box *box = malloc(sizeof(struct wlr_box));
if(box == NULL) {
wlr_log(WLR_ERROR, "Failed to allocate box in message_printf");
free(buffer);
return;
}
struct wlr_box output_box = output_get_layout_box(output);
box->width = 0;
box->height = 0;
switch(output->server->message_config.anchor) {
case NEDM_MESSAGE_TOP_LEFT:
box->x = 0;
box->y = 0;
break;
case NEDM_MESSAGE_TOP_CENTER:
box->x = output_box.width / 2;
box->y = 0;
break;
case NEDM_MESSAGE_TOP_RIGHT:
box->x = output_box.width;
box->y = 0;
break;
case NEDM_MESSAGE_BOTTOM_LEFT:
box->x = 0;
box->y = output_box.height;
break;
case NEDM_MESSAGE_BOTTOM_CENTER:
box->x = output_box.width / 2;
box->y = output_box.height;
break;
case NEDM_MESSAGE_BOTTOM_RIGHT:
box->x = output_box.width;
box->y = output_box.height;
break;
case NEDM_MESSAGE_CENTER:
box->x = output_box.width / 2;
box->y = output_box.height / 2;
break;
default:
break;
}
message_set_output(output, buffer, box,
output->server->message_config.anchor);
free(buffer);
alarm(output->server->message_config.display_time);
}
void
message_printf_pos(struct nedm_output *output, struct wlr_box *position,
const enum nedm_message_anchor anchor, const char *fmt, ...) {
if(output->destroyed || output->server->message_config.enabled == 0) {
free(position);
return;
}
uint16_t buf_len = 256;
char *buffer = (char *)malloc(buf_len * sizeof(char));
va_list ap;
va_start(ap, fmt);
vsnprintf(buffer, buf_len, fmt, ap);
va_end(ap);
message_set_output(output, buffer, position, anchor);
free(buffer);
alarm(output->server->message_config.display_time);
}
void
message_clear(struct nedm_output *output) {
struct nedm_message *message, *tmp;
wl_list_for_each_safe(message, tmp, &output->messages, link) {
wl_list_remove(&message->link);
wlr_scene_node_destroy(&message->message->node);
free(message->position);
if(message->buf != NULL) {
msg_buffer_destroy(&message->buf->base);
}
free(message);
}
}