feat: 添加壁纸功能
This commit is contained in:
@@ -44,6 +44,7 @@ add_executable(${APP_NAME}
|
|||||||
${SOURCE_DIR}/status.c
|
${SOURCE_DIR}/status.c
|
||||||
${SOURCE_DIR}/audio.c
|
${SOURCE_DIR}/audio.c
|
||||||
${SOURCE_DIR}/rect.c
|
${SOURCE_DIR}/rect.c
|
||||||
|
${SOURCE_DIR}/wallpaper.c
|
||||||
)
|
)
|
||||||
|
|
||||||
find_package(PkgConfig REQUIRED)
|
find_package(PkgConfig REQUIRED)
|
||||||
@@ -68,6 +69,7 @@ pkg_check_modules(deps REQUIRED
|
|||||||
cairo-xcb
|
cairo-xcb
|
||||||
pango
|
pango
|
||||||
pangocairo
|
pangocairo
|
||||||
|
imlib2
|
||||||
|
|
||||||
libpulse-mainloop-glib
|
libpulse-mainloop-glib
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
_XKB_RULES_NAMES
|
_XKB_RULES_NAMES
|
||||||
|
|
||||||
|
_XROOTPMAP_ID
|
||||||
|
ESETROOT_PMAP_ID
|
||||||
|
|
||||||
WM_CHANGE_STATE
|
WM_CHANGE_STATE
|
||||||
WM_DELETE_WINDOW
|
WM_DELETE_WINDOW
|
||||||
WM_PROTOCOLS
|
WM_PROTOCOLS
|
||||||
|
|||||||
@@ -139,3 +139,7 @@ static const rule_t rules[] = {
|
|||||||
{.class = "mpv", .tag_index = 11, .switch_to_tag = true, .fullscreen = true},
|
{.class = "mpv", .tag_index = 11, .switch_to_tag = true, .fullscreen = true},
|
||||||
{.class = "Emacs", .tag_index = 10, .switch_to_tag = true, .maximize = 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;
|
||||||
|
|||||||
313
src/wallpaper.c
Normal file
313
src/wallpaper.c
Normal file
@@ -0,0 +1,313 @@
|
|||||||
|
#include "wallpaper.h"
|
||||||
|
|
||||||
|
#include <Imlib2.h>
|
||||||
|
#include <cairo-xcb.h>
|
||||||
|
#include <cairo.h>
|
||||||
|
#include <glib.h>
|
||||||
|
#include <glibconfig.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <sys/time.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <wordexp.h>
|
||||||
|
#include <xcb/xproto.h>
|
||||||
|
|
||||||
|
#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);
|
||||||
|
}
|
||||||
19
src/wallpaper.h
Normal file
19
src/wallpaper.h
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <xcb/xproto.h>
|
||||||
|
#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);
|
||||||
18
src/wm.c
18
src/wm.c
@@ -33,6 +33,7 @@
|
|||||||
#include "text.h"
|
#include "text.h"
|
||||||
#include "types.h"
|
#include "types.h"
|
||||||
#include "utils.h"
|
#include "utils.h"
|
||||||
|
#include "wallpaper.h"
|
||||||
#include "xcursor.h"
|
#include "xcursor.h"
|
||||||
#include "xkb.h"
|
#include "xkb.h"
|
||||||
#include "xres.h"
|
#include "xres.h"
|
||||||
@@ -45,6 +46,7 @@ static void wm_detect_monitor(void);
|
|||||||
static void wm_scan_clients(void);
|
static void wm_scan_clients(void);
|
||||||
static void wm_clean(void);
|
static void wm_clean(void);
|
||||||
static void wm_setup(void);
|
static void wm_setup(void);
|
||||||
|
static void wm_init_wallpaper(void);
|
||||||
static void wm_get_xres_config(void);
|
static void wm_get_xres_config(void);
|
||||||
static void wm_init_color_set(void);
|
static void wm_init_color_set(void);
|
||||||
static void wm_setup_keybindings(void);
|
static void wm_setup_keybindings(void);
|
||||||
@@ -377,6 +379,9 @@ void wm_scan_clients(void) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void wm_clean(void) {
|
void wm_clean(void) {
|
||||||
|
wallpaper_clean(wm.wallpaper);
|
||||||
|
wm.wallpaper = nullptr;
|
||||||
|
|
||||||
clean_status();
|
clean_status();
|
||||||
text_clean_pango_layout();
|
text_clean_pango_layout();
|
||||||
|
|
||||||
@@ -471,6 +476,18 @@ static void wm_setup(void) {
|
|||||||
xcb_flush(wm.xcb_conn);
|
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) {
|
static void wm_get_xres_config(void) {
|
||||||
xres_init_xrm_db();
|
xres_init_xrm_db();
|
||||||
|
|
||||||
@@ -682,6 +699,7 @@ int main(int argc, char *argv[]) {
|
|||||||
|
|
||||||
wm_detect_monitor();
|
wm_detect_monitor();
|
||||||
wm_setup();
|
wm_setup();
|
||||||
|
wm_init_wallpaper();
|
||||||
setup_event_loop();
|
setup_event_loop();
|
||||||
wm_scan_clients();
|
wm_scan_clients();
|
||||||
|
|
||||||
|
|||||||
3
src/wm.h
3
src/wm.h
@@ -8,6 +8,7 @@
|
|||||||
#include "base.h"
|
#include "base.h"
|
||||||
#include "status.h"
|
#include "status.h"
|
||||||
#include "types.h"
|
#include "types.h"
|
||||||
|
#include "wallpaper.h"
|
||||||
|
|
||||||
typedef struct wm_t {
|
typedef struct wm_t {
|
||||||
padding_t padding;
|
padding_t padding;
|
||||||
@@ -49,6 +50,8 @@ typedef struct wm_t {
|
|||||||
struct xkb_context *xkb_ctx;
|
struct xkb_context *xkb_ctx;
|
||||||
struct xkb_state *xkb_state;
|
struct xkb_state *xkb_state;
|
||||||
|
|
||||||
|
wallpaper_context_t *wallpaper;
|
||||||
|
|
||||||
color_set_t color_set;
|
color_set_t color_set;
|
||||||
} wm_t;
|
} wm_t;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user