diff --git a/CMakeLists.txt b/CMakeLists.txt index d2dd388..af38cf6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -44,6 +44,7 @@ add_executable(${APP_NAME} ${SOURCE_DIR}/status.c ${SOURCE_DIR}/audio.c ${SOURCE_DIR}/rect.c + ${SOURCE_DIR}/wallpaper.c ) find_package(PkgConfig REQUIRED) @@ -68,6 +69,7 @@ pkg_check_modules(deps REQUIRED cairo-xcb pango pangocairo + imlib2 libpulse-mainloop-glib ) diff --git a/src/atoms-list.txt b/src/atoms-list.txt index 2832a0b..17d444a 100644 --- a/src/atoms-list.txt +++ b/src/atoms-list.txt @@ -1,5 +1,8 @@ _XKB_RULES_NAMES +_XROOTPMAP_ID +ESETROOT_PMAP_ID + WM_CHANGE_STATE WM_DELETE_WINDOW WM_PROTOCOLS diff --git a/src/default_config.h b/src/default_config.h index 2ca04de..8611316 100644 --- a/src/default_config.h +++ b/src/default_config.h @@ -139,3 +139,7 @@ static const rule_t rules[] = { {.class = "mpv", .tag_index = 11, .switch_to_tag = true, .fullscreen = true}, {.class = "Emacs", .tag_index = 10, .switch_to_tag = true, .maximize = true}, }; + +/* static const char *const wallpapers[] = {"~/bg/\*", "~/Downloads/bg/\*"}; */ +static const char *const wallpapers[] = {}; +static const uint32_t wallpaper_interval = 10; diff --git a/src/wallpaper.c b/src/wallpaper.c new file mode 100644 index 0000000..2361ac7 --- /dev/null +++ b/src/wallpaper.c @@ -0,0 +1,314 @@ +#include "wallpaper.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "atoms-extern.h" +#include "base.h" +#include "types.h" +#include "utils.h" +#include "xwindow.h" + +struct wallpaper_context_t { + uint32_t interval; + uint32_t image_count; + char **image_path_list; + + xcb_screen_t *screen; + xcb_connection_t *conn; + + area_t *monitor_geometries; + uint32_t monitor_count; + + xcb_pixmap_t pixmap; + cairo_t *cr; + + uint32_t index; + + guint timer; +}; + +static inline bool file_exist(const char *path) { + if (!path || !*path) return false; + return access(path, R_OK) == 0; +} + +static char **parse_wallpaper_paths(char **images, uint32_t image_count) { + GStrvBuilder *builder = g_strv_builder_new(); + for (uint32_t i = 0; i < image_count; i++) { + wordexp_t p; + if (wordexp(images[i], &p, 0)) continue; + + for (size_t i = 0; i < p.we_wordc; i++) { + size_t len = strlen(p.we_wordv[i]) + 1; + char *realpath = p_new(char, len); + strncpy(realpath, p.we_wordv[i], len); + g_strstrip(realpath); + if (file_exist(realpath)) g_strv_builder_add(builder, realpath); + + p_delete(&realpath); + } + wordfree(&p); + } + + return g_strv_builder_unref_to_strv(builder); +} + +static uint32_t *generate_wallpaper(wallpaper_context_t *context) { + int width = context->screen->width_in_pixels; + int height = context->screen->height_in_pixels; + + Imlib_Image final_image = imlib_create_image(width, height); + if (!final_image) return nullptr; + + imlib_context_set_image(final_image); + imlib_image_set_has_alpha(true); + + /* 重置像素数据 */ + imlib_image_clear(); + for (uint32_t i = 0; i < context->monitor_count; i++) { + uint32_t img_index = (context->index + i) % context->image_count; + const char *path = context->image_path_list[img_index]; + logger("== wallpaper[%u]: %s\n", img_index, path); + Imlib_Image wallpaper_img = imlib_load_image(path); + if (!wallpaper_img) continue; + + uint32_t dst_width = context->monitor_geometries[i].width; + uint32_t dst_height = context->monitor_geometries[i].height; + + /* 获取图片原始尺寸 */ + imlib_context_set_image(wallpaper_img); + uint32_t img_width = imlib_image_get_width(); + uint32_t img_height = imlib_image_get_height(); + + /* 计算缩放比例,保持原始宽高比 */ + float width_ratio = (float)dst_width / img_width; + float height_ratio = (float)dst_height / img_height; + float scale = MAX(width_ratio, height_ratio); + + /* 图片居中显示,计算原图绘制区域 */ + int src_width = dst_width / scale; + int src_height = dst_height / scale; + int src_x = (img_width - src_width) / 2; + int src_y = (img_height - src_height) / 2; + + /* 绘制图片到最终图像上 */ + imlib_context_set_image(final_image); + imlib_blend_image_onto_image(wallpaper_img, true, src_x, src_y, src_width, + src_height, context->monitor_geometries[i].x, + context->monitor_geometries[i].y, dst_width, + dst_height); + + /* 释放图片资源 */ + imlib_context_set_image(wallpaper_img); + imlib_free_image(); + } + + /* 获取最终图像数据 */ + imlib_context_set_image(final_image); + uint32_t *data = imlib_image_get_data_for_reading_only(); + + /* 复制数据到自己分配的内存中(原数据会在图像释放后无效) */ + int count = width * height; + uint32_t *result = p_new(uint32_t, count); + if (result) memcpy(result, data, count * sizeof(uint32_t)); + + /* 释放图像 */ + imlib_context_set_image(final_image); + imlib_free_image(); + + return result; +} + +double get_time() { + struct timeval tv; + gettimeofday(&tv, nullptr); + return tv.tv_sec + tv.tv_usec / 1000000.0; +} + +static void cairo_set_wallpaper(wallpaper_context_t *context, + uint8_t *wallpaper_data) { + xcb_connection_t *conn = context->conn; + xcb_window_t root = context->screen->root; + xcb_pixmap_t pixmap = context->pixmap; + uint32_t width = context->screen->width_in_pixels; + uint32_t height = context->screen->height_in_pixels; + int stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, width); + cairo_surface_t *s = cairo_image_surface_create_for_data( + wallpaper_data, CAIRO_FORMAT_ARGB32, width, height, stride); + cairo_set_source_surface(context->cr, s, 0, 0); + cairo_paint(context->cr); + + xcb_change_window_attributes_value_list_t value_list = { + .background_pixmap = pixmap, + }; + xcb_change_window_attributes_aux(conn, root, XCB_CW_BACK_PIXMAP, &value_list); + + /* 即使壁纸对应的 pixmap 未变更,也强制通知合成器壁纸已更改 */ + uint8_t mode = XCB_PROP_MODE_REPLACE; + xcb_pixmap_t none = XCB_PIXMAP_NONE; + xcb_atom_t atom = _XROOTPMAP_ID; + xcb_change_property(conn, mode, root, atom, XCB_ATOM_PIXMAP, 32, 1, &none); + xcb_change_property(conn, mode, root, atom, XCB_ATOM_PIXMAP, 32, 1, &pixmap); + atom = ESETROOT_PMAP_ID; + xcb_change_property(conn, mode, root, atom, XCB_ATOM_PIXMAP, 32, 1, &none); + xcb_change_property(conn, mode, root, atom, XCB_ATOM_PIXMAP, 32, 1, &pixmap); + + xcb_clear_area(conn, 0, root, 0, 0, 0, 0); + xcb_flush(conn); + cairo_surface_destroy(s); +} + +static void set_wallpaper(wallpaper_context_t *context) { + double start = get_time(); + uint32_t *wallpaper = generate_wallpaper(context); + logger("== generate wallpaper cost: %lf, %p\n", get_time() - start, + wallpaper); + if (!wallpaper) return; + + uint8_t *data = (uint8_t *)wallpaper; + + cairo_set_wallpaper(context, data); + + p_delete(&wallpaper); + + context->index += context->monitor_count; +} + +static gboolean update_wallpaper(gpointer user_data) { + if (!user_data) return FALSE; + + wallpaper_context_t *wc = user_data; + set_wallpaper(wc); + + /* 屏幕比壁纸多则不动态切换 */ + if (wc->monitor_count >= wc->image_count) return FALSE; + + return TRUE; +} + +static bool prepare_wallpaper_surface(wallpaper_context_t *context) { + uint16_t width = context->screen->width_in_pixels; + uint16_t height = context->screen->height_in_pixels; + xcb_window_t root = context->screen->root; + xcb_connection_t *conn = context->conn; + xcb_pixmap_t pixmap = context->pixmap; + + visual_t *visual = xwindow_get_xcb_visual(false); + + if (pixmap != XCB_PIXMAP_NONE) { + xcb_kill_client(conn, pixmap); + xcb_free_pixmap(conn, pixmap); + } + + pixmap = xcb_generate_id(conn); + xcb_create_pixmap(conn, visual->depth, pixmap, root, width, height); + xcb_gcontext_t gc = xcb_generate_id(conn); + xcb_create_gc(conn, gc, pixmap, 0, nullptr); + xcb_change_window_attributes_value_list_t value_list = { + .background_pixmap = pixmap, + }; + xcb_change_window_attributes_aux(conn, root, XCB_CW_BACK_PIXMAP, &value_list); + + /* 兼容合成器,不设置若开启合成器壁纸会不显示 */ + uint8_t mode = XCB_PROP_MODE_REPLACE; + xcb_atom_t atom = _XROOTPMAP_ID; + xcb_change_property(conn, mode, root, atom, XCB_ATOM_PIXMAP, 32, 1, &pixmap); + atom = ESETROOT_PMAP_ID; + xcb_change_property(conn, mode, root, atom, XCB_ATOM_PIXMAP, 32, 1, &pixmap); + + xcb_clear_area(conn, 0, root, 0, 0, width, height); + xcb_free_gc(conn, gc); + xcb_flush(conn); + + context->pixmap = pixmap; + + if (context->cr) cairo_destroy(context->cr); + cairo_surface_t *surface = + cairo_xcb_surface_create(conn, pixmap, visual->visual, width, height); + p_delete(&visual); + if (cairo_surface_status(surface) != CAIRO_STATUS_SUCCESS) { + cairo_surface_destroy(surface); + return false; + } + + cairo_t *cr = cairo_create(surface); + if (cairo_status(cr) != CAIRO_STATUS_SUCCESS) { + cairo_destroy(cr); + cairo_surface_destroy(surface); + return false; + } + + context->cr = cr; + + cairo_surface_destroy(surface); + cairo_set_source_rgba(cr, 0, 0, 0, 1); + cairo_paint(cr); + xcb_clear_area(conn, 0, root, 0, 0, 0, 0); + xcb_flush(context->conn); + + return true; +} + +wallpaper_context_t *wallpaper_init(wallpaper_config_t config) { + if (!config.path_count || !config.paths) return nullptr; + if (!config.monitors) return nullptr; + + char **images = parse_wallpaper_paths(config.paths, config.path_count); + if (!images || !g_strv_length(images)) return nullptr; + + wallpaper_context_t *ctx = p_new(wallpaper_context_t, 1); + ctx->interval = config.interval; + ctx->image_path_list = images; + ctx->image_count = g_strv_length(images); + + ctx->conn = config.conn; + ctx->screen = config.screen; + + { + uint32_t i = 0; + monitor_t *m = nullptr; + for (m = config.monitors; m; m = m->next) ctx->monitor_count++; + + ctx->monitor_geometries = p_new(area_t, ctx->monitor_count); + for (m = config.monitors; m; m = m->next) { + ctx->monitor_geometries[i++] = m->geometry; + } + } + + if (!prepare_wallpaper_surface(ctx)) { + wallpaper_clean(ctx); + return nullptr; + } + + if (!update_wallpaper(ctx) || !ctx->interval) { + wallpaper_clean(ctx); + return nullptr; + } + + ctx->timer = g_timeout_add_seconds(ctx->interval, update_wallpaper, ctx); + + return ctx; +} + +void wallpaper_clean(wallpaper_context_t *context) { + if (!context) return; + + if (context->timer) g_source_remove(context->timer); + if (context->cr) cairo_destroy(context->cr); + p_delete(&context->monitor_geometries); + if (context->image_path_list) g_strfreev(context->image_path_list); + + p_delete(&context); +} diff --git a/src/wallpaper.h b/src/wallpaper.h new file mode 100644 index 0000000..130d4cc --- /dev/null +++ b/src/wallpaper.h @@ -0,0 +1,19 @@ +#pragma once + +#include +#include +#include "types.h" + +typedef struct wallpaper_context_t wallpaper_context_t; + +typedef struct wallpaper_config_t { + uint32_t interval; + uint32_t path_count; + char **paths; + xcb_screen_t *screen; + xcb_connection_t *conn; + monitor_t *monitors; +} wallpaper_config_t; + +wallpaper_context_t *wallpaper_init(wallpaper_config_t config); +void wallpaper_clean(wallpaper_context_t *context); diff --git a/src/wm.c b/src/wm.c index 14655fa..0441f36 100644 --- a/src/wm.c +++ b/src/wm.c @@ -33,6 +33,7 @@ #include "text.h" #include "types.h" #include "utils.h" +#include "wallpaper.h" #include "xcursor.h" #include "xkb.h" #include "xres.h" @@ -45,6 +46,7 @@ static void wm_detect_monitor(void); static void wm_scan_clients(void); static void wm_clean(void); static void wm_setup(void); +static void wm_init_wallpaper(void); static void wm_get_xres_config(void); static void wm_init_color_set(void); static void wm_setup_keybindings(void); @@ -377,6 +379,9 @@ void wm_scan_clients(void) { } void wm_clean(void) { + wallpaper_clean(wm.wallpaper); + wm.wallpaper = nullptr; + clean_status(); text_clean_pango_layout(); @@ -471,6 +476,18 @@ static void wm_setup(void) { xcb_flush(wm.xcb_conn); } +static void wm_init_wallpaper(void) { + wallpaper_config_t config = { + .interval = wallpaper_interval, + .path_count = countof(wallpapers), + .paths = (char **)wallpapers, + .screen = wm.screen, + .conn = wm.xcb_conn, + .monitors = wm.monitor_list, + }; + wm.wallpaper = wallpaper_init(config); +} + static void wm_get_xres_config(void) { xres_init_xrm_db(); @@ -682,6 +699,7 @@ int main(int argc, char *argv[]) { wm_detect_monitor(); wm_setup(); + wm_init_wallpaper(); setup_event_loop(); wm_scan_clients(); diff --git a/src/wm.h b/src/wm.h index 114df34..5c0cf20 100644 --- a/src/wm.h +++ b/src/wm.h @@ -8,6 +8,7 @@ #include "base.h" #include "status.h" #include "types.h" +#include "wallpaper.h" typedef struct wm_t { padding_t padding; @@ -49,6 +50,8 @@ typedef struct wm_t { struct xkb_context *xkb_ctx; struct xkb_state *xkb_state; + wallpaper_context_t *wallpaper; + color_set_t color_set; } wm_t;