NEDM/wallpaper.c

339 lines
9.9 KiB
C

// Copyright 2020 - 2025, project-repo and the NEDM contributors
// SPDX-License-Identifier: MIT
#include "wallpaper.h"
#include "output.h"
#include "server.h"
#include "util.h"
#include <wlr/types/wlr_output.h>
#include <wlr/types/wlr_scene.h>
#include <wlr/util/log.h>
#include <cairo.h>
#include <cairo/cairo.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <wlr/interfaces/wlr_buffer.h>
#include <drm_fourcc.h>
struct wallpaper_buffer {
struct wlr_buffer base;
void *data;
uint32_t format;
size_t stride;
};
static void
wallpaper_buffer_destroy(struct wlr_buffer *wlr_buffer) {
struct wallpaper_buffer *buffer = wl_container_of(wlr_buffer, buffer, base);
free(buffer->data);
free(buffer);
}
static bool
wallpaper_buffer_begin_data_ptr_access(struct wlr_buffer *wlr_buffer,
__attribute__((unused)) uint32_t flags,
void **data, uint32_t *format,
size_t *stride) {
struct wallpaper_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
wallpaper_buffer_end_data_ptr_access(
__attribute__((unused)) struct wlr_buffer *wlr_buffer) {
// This space is intentionally left blank
}
static const struct wlr_buffer_impl wallpaper_buffer_impl = {
.destroy = wallpaper_buffer_destroy,
.begin_data_ptr_access = wallpaper_buffer_begin_data_ptr_access,
.end_data_ptr_access = wallpaper_buffer_end_data_ptr_access,
};
static struct wallpaper_buffer *
wallpaper_buffer_create(uint32_t width, uint32_t height, uint32_t stride) {
struct wallpaper_buffer *buffer = calloc(1, sizeof(*buffer));
if(buffer == NULL) {
return NULL;
}
size_t size = stride * height;
buffer->data = malloc(size);
if(buffer->data == NULL) {
free(buffer);
return NULL;
}
buffer->format = DRM_FORMAT_XRGB8888;
buffer->stride = stride;
wlr_buffer_init(&buffer->base, &wallpaper_buffer_impl, width, height);
return buffer;
}
static void wallpaper_calculate_scaling(struct nedm_wallpaper *wallpaper,
double *scale_x, double *scale_y, double *offset_x, double *offset_y) {
double img_w = wallpaper->image_width;
double img_h = wallpaper->image_height;
double out_w = wallpaper->output_width;
double out_h = wallpaper->output_height;
*scale_x = 1.0;
*scale_y = 1.0;
*offset_x = 0.0;
*offset_y = 0.0;
switch (wallpaper->mode) {
case NEDM_WALLPAPER_FILL: {
// Scale to fill the entire output, cropping if necessary
double scale = fmax(out_w / img_w, out_h / img_h);
*scale_x = scale;
*scale_y = scale;
*offset_x = (out_w - img_w * scale) / 2.0;
*offset_y = (out_h - img_h * scale) / 2.0;
break;
}
case NEDM_WALLPAPER_FIT: {
// Scale to fit entirely within the output, maintaining aspect ratio
double scale = fmin(out_w / img_w, out_h / img_h);
*scale_x = scale;
*scale_y = scale;
*offset_x = (out_w - img_w * scale) / 2.0;
*offset_y = (out_h - img_h * scale) / 2.0;
break;
}
case NEDM_WALLPAPER_STRETCH: {
// Stretch to fill the entire output, ignoring aspect ratio
*scale_x = out_w / img_w;
*scale_y = out_h / img_h;
*offset_x = 0.0;
*offset_y = 0.0;
break;
}
case NEDM_WALLPAPER_CENTER: {
// Center the image without scaling
*scale_x = 1.0;
*scale_y = 1.0;
*offset_x = (out_w - img_w) / 2.0;
*offset_y = (out_h - img_h) / 2.0;
break;
}
case NEDM_WALLPAPER_TILE: {
// Tile the image (no scaling, repeat pattern)
*scale_x = 1.0;
*scale_y = 1.0;
*offset_x = 0.0;
*offset_y = 0.0;
break;
}
}
}
bool nedm_wallpaper_load_image(struct nedm_wallpaper *wallpaper, const char *path) {
if (!path) {
wlr_log(WLR_ERROR, "No wallpaper path provided");
return false;
}
// Load the image using Cairo
wallpaper->image_surface = cairo_image_surface_create_from_png(path);
if (cairo_surface_status(wallpaper->image_surface) != CAIRO_STATUS_SUCCESS) {
wlr_log(WLR_ERROR, "Failed to load wallpaper image: %s", path);
if (wallpaper->image_surface) {
cairo_surface_destroy(wallpaper->image_surface);
wallpaper->image_surface = NULL;
}
return false;
}
// Get image dimensions
wallpaper->image_width = cairo_image_surface_get_width(wallpaper->image_surface);
wallpaper->image_height = cairo_image_surface_get_height(wallpaper->image_surface);
wlr_log(WLR_INFO, "Loaded wallpaper: %s (%dx%d)", path,
wallpaper->image_width, wallpaper->image_height);
wallpaper->loaded = true;
return true;
}
void nedm_wallpaper_render(struct nedm_wallpaper *wallpaper) {
if (!wallpaper->loaded || !wallpaper->image_surface || !wallpaper->render_surface) {
return;
}
struct nedm_wallpaper_config *config = &wallpaper->output->server->wallpaper_config;
// Clear the render surface with configured background color
cairo_set_source_rgba(wallpaper->cairo,
config->bg_color[0], config->bg_color[1], config->bg_color[2], config->bg_color[3]);
cairo_paint(wallpaper->cairo);
// Calculate scaling and positioning
double scale_x, scale_y, offset_x, offset_y;
wallpaper_calculate_scaling(wallpaper, &scale_x, &scale_y, &offset_x, &offset_y);
if (wallpaper->mode == NEDM_WALLPAPER_TILE) {
// Special handling for tile mode
cairo_pattern_t *pattern = cairo_pattern_create_for_surface(wallpaper->image_surface);
cairo_pattern_set_extend(pattern, CAIRO_EXTEND_REPEAT);
cairo_set_source(wallpaper->cairo, pattern);
cairo_paint(wallpaper->cairo);
cairo_pattern_destroy(pattern);
} else {
// Apply transformation
cairo_save(wallpaper->cairo);
cairo_translate(wallpaper->cairo, offset_x, offset_y);
cairo_scale(wallpaper->cairo, scale_x, scale_y);
// Draw the image
cairo_set_source_surface(wallpaper->cairo, wallpaper->image_surface, 0, 0);
cairo_paint(wallpaper->cairo);
cairo_restore(wallpaper->cairo);
}
// Flush the surface
cairo_surface_flush(wallpaper->render_surface);
}
static void wallpaper_handle_output_destroy(struct wl_listener *listener, void *data) {
(void)data;
struct nedm_wallpaper *wallpaper = wl_container_of(listener, wallpaper, output_destroy);
nedm_wallpaper_destroy(wallpaper);
}
void nedm_wallpaper_create_for_output(struct nedm_output *output) {
if (!output || !output->server) {
wlr_log(WLR_ERROR, "Invalid output or server for wallpaper creation");
return;
}
struct nedm_wallpaper *wallpaper = calloc(1, sizeof(struct nedm_wallpaper));
if (!wallpaper) {
wlr_log(WLR_ERROR, "Failed to allocate wallpaper");
return;
}
wallpaper->output = output;
output->wallpaper = wallpaper;
struct nedm_wallpaper_config *config = &output->server->wallpaper_config;
// Set output dimensions
wallpaper->output_width = output->wlr_output->width;
wallpaper->output_height = output->wlr_output->height;
// Use configured wallpaper mode
wallpaper->mode = config->mode;
// Store the image path
wallpaper->image_path = strdup(config->image_path ? config->image_path : "assets/nedm.png");
// Create render surface
wallpaper->render_surface = cairo_image_surface_create(CAIRO_FORMAT_RGB24,
wallpaper->output_width, wallpaper->output_height);
wallpaper->cairo = cairo_create(wallpaper->render_surface);
// Load the wallpaper image
if (!nedm_wallpaper_load_image(wallpaper, wallpaper->image_path)) {
wlr_log(WLR_ERROR, "Failed to load wallpaper image");
nedm_wallpaper_destroy(wallpaper);
return;
}
// Render the wallpaper first
nedm_wallpaper_render(wallpaper);
// Create a scene buffer from the rendered wallpaper
wallpaper->scene_buffer = wlr_scene_buffer_create(output->layers[0], NULL);
if (!wallpaper->scene_buffer) {
wlr_log(WLR_ERROR, "Failed to create scene buffer for wallpaper");
nedm_wallpaper_destroy(wallpaper);
return;
}
if (wallpaper->render_surface) {
cairo_surface_flush(wallpaper->render_surface);
unsigned char *data = cairo_image_surface_get_data(wallpaper->render_surface);
int stride = cairo_image_surface_get_stride(wallpaper->render_surface);
struct wallpaper_buffer *buf = wallpaper_buffer_create(wallpaper->output_width, wallpaper->output_height, stride);
if (buf) {
void *data_ptr;
if(wlr_buffer_begin_data_ptr_access(&buf->base,
WLR_BUFFER_DATA_PTR_ACCESS_WRITE,
&data_ptr, NULL, NULL)) {
memcpy(data_ptr, data, stride * wallpaper->output_height);
wlr_buffer_end_data_ptr_access(&buf->base);
wlr_scene_buffer_set_buffer(wallpaper->scene_buffer, &buf->base);
wlr_buffer_drop(&buf->base);
}
}
}
// Position the wallpaper at (0, 0) to cover the entire output
wlr_scene_node_set_position(&wallpaper->scene_buffer->node, 0, 0);
// Set up event listeners
wallpaper->output_destroy.notify = wallpaper_handle_output_destroy;
wl_signal_add(&output->events.destroy, &wallpaper->output_destroy);
wlr_log(WLR_INFO, "Created wallpaper for output %s (%dx%d) with image %s",
output->wlr_output->name, wallpaper->output_width, wallpaper->output_height,
wallpaper->image_path);
}
void nedm_wallpaper_destroy(struct nedm_wallpaper *wallpaper) {
if (!wallpaper) {
return;
}
if (wallpaper->scene_buffer) {
wlr_scene_node_destroy(&wallpaper->scene_buffer->node);
}
if (wallpaper->cairo) {
cairo_destroy(wallpaper->cairo);
}
if (wallpaper->render_surface) {
cairo_surface_destroy(wallpaper->render_surface);
}
if (wallpaper->image_surface) {
cairo_surface_destroy(wallpaper->image_surface);
}
if (wallpaper->image_path) {
free(wallpaper->image_path);
}
if (wallpaper->output_destroy.notify) {
wl_list_remove(&wallpaper->output_destroy.link);
}
if (wallpaper->output) {
wallpaper->output->wallpaper = NULL;
}
free(wallpaper);
}
void nedm_wallpaper_init(struct nedm_server *server) {
(void)server;
// Wallpapers are created per-output, so nothing to initialize globally
wlr_log(WLR_INFO, "Wallpaper subsystem initialized");
}