// 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 #include #include #include #include #include #include #include #include #include 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"); }