Compare commits
9 Commits
9fbd7f45e9
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
a5d31cccdd
|
|||
|
86a89452a7
|
|||
| 123fe77bbb | |||
| 1ec92ec3e2 | |||
|
cdcafa1216
|
|||
|
d8cd91ea48
|
|||
|
35f0ea9680
|
|||
|
4ccdc9c23d
|
|||
|
ac4cfc1ed3
|
@@ -44,6 +44,11 @@ add_executable(${APP_NAME}
|
||||
${SOURCE_DIR}/status.c
|
||||
${SOURCE_DIR}/audio.c
|
||||
${SOURCE_DIR}/rect.c
|
||||
${SOURCE_DIR}/wallpaper.c
|
||||
${SOURCE_DIR}/config.c
|
||||
${SOURCE_DIR}/renderer.c
|
||||
${SOURCE_DIR}/image.c
|
||||
${SOURCE_DIR}/tray.c
|
||||
)
|
||||
|
||||
find_package(PkgConfig REQUIRED)
|
||||
@@ -68,6 +73,7 @@ pkg_check_modules(deps REQUIRED
|
||||
cairo-xcb
|
||||
pango
|
||||
pangocairo
|
||||
imlib2
|
||||
|
||||
libpulse-mainloop-glib
|
||||
)
|
||||
|
||||
20
src/action.c
20
src/action.c
@@ -8,6 +8,7 @@
|
||||
#include "monitor.h"
|
||||
#include "types.h"
|
||||
#include "wm.h"
|
||||
#include "xcursor.h"
|
||||
|
||||
void focus_client_in_same_tag(const user_action_arg_t *arg) {
|
||||
bool next = arg->b;
|
||||
@@ -43,6 +44,16 @@ void send_client_to_next_monitor(const user_action_arg_t *arg) {
|
||||
client_send_to_monitor(wm.client_focused, next_monitor);
|
||||
}
|
||||
|
||||
void focus_next_monitor(const user_action_arg_t *arg) {
|
||||
monitor_t *next_monitor = wm_get_next_monitor(wm.current_monitor);
|
||||
if (next_monitor == wm.current_monitor) return;
|
||||
|
||||
wm_set_current_monitor(next_monitor, true);
|
||||
if (wm.current_monitor->selected_tag->task_list) {
|
||||
monitor_deal_focus(wm.current_monitor);
|
||||
}
|
||||
}
|
||||
|
||||
void spawn(const user_action_arg_t *arg) {
|
||||
const char *cmd = (const char *)arg->ptr;
|
||||
if (cmd == nullptr || strlen(cmd) == 0) return;
|
||||
@@ -62,9 +73,16 @@ void quit(const user_action_arg_t *arg) {
|
||||
void raise_or_run(const user_action_arg_t *arg) {
|
||||
const char *class = ((const char **)arg->ptr)[0];
|
||||
client_t *client = client_get_next_by_class(wm.client_focused, class);
|
||||
if (client && client == wm.client_focused) return;
|
||||
|
||||
if (client) {
|
||||
bool monitor_changed = client->monitor != wm.current_monitor;
|
||||
bool tag_changed = client->tags != client->monitor->selected_tag->mask;
|
||||
if (monitor_changed || tag_changed) {
|
||||
point_t point = monitor_changed ? monitor_get_restore_cursor_point(
|
||||
client->monitor)
|
||||
: xcursor_query_pointer_position();
|
||||
wm_ignore_enter_notify_at_point(point);
|
||||
}
|
||||
wm_set_current_monitor(client->monitor, true);
|
||||
monitor_select_tag(client->monitor, client->tags);
|
||||
client_stack_raise(client);
|
||||
|
||||
@@ -53,6 +53,7 @@ void focus_client_in_same_tag(const user_action_arg_t *arg);
|
||||
void select_tag_of_current_monitor(const user_action_arg_t *arg);
|
||||
void send_client_to_tag(const user_action_arg_t *arg);
|
||||
void send_client_to_next_monitor(const user_action_arg_t *arg);
|
||||
void focus_next_monitor(const user_action_arg_t *arg);
|
||||
void spawn(const user_action_arg_t *arg);
|
||||
void quit(const user_action_arg_t *arg);
|
||||
void raise_or_run(const user_action_arg_t *arg);
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
_XKB_RULES_NAMES
|
||||
|
||||
_XROOTPMAP_ID
|
||||
ESETROOT_PMAP_ID
|
||||
|
||||
WM_CHANGE_STATE
|
||||
WM_DELETE_WINDOW
|
||||
WM_PROTOCOLS
|
||||
@@ -9,15 +12,22 @@ WM_WINDOW_ROLE
|
||||
|
||||
UTF8_STRING
|
||||
COMPOUND_TEXT
|
||||
MANAGER
|
||||
_XEMBED
|
||||
_XEMBED_INFO
|
||||
|
||||
_NET_ACTIVE_WINDOW
|
||||
_NET_CLIENT_LIST
|
||||
_NET_SYSTEM_TRAY_COLORS
|
||||
_NET_SYSTEM_TRAY_OPCODE
|
||||
_NET_SYSTEM_TRAY_ORIENTATION
|
||||
_NET_WM_DESKTOP
|
||||
_NET_WM_NAME
|
||||
_NET_WM_STATE
|
||||
_NET_WM_STATE_FULLSCREEN
|
||||
_NET_WM_STATE_MAXIMIZED_HORZ
|
||||
_NET_WM_STATE_MAXIMIZED_VERT
|
||||
_NET_WM_STATE_SKIP_TASKBAR
|
||||
_NET_WM_WINDOW_TYPE
|
||||
_NET_WM_WINDOW_TYPE_DIALOG
|
||||
_NET_SUPPORTED
|
||||
|
||||
@@ -366,6 +366,7 @@ void client_update_window_type(client_t *client) {
|
||||
|
||||
if (state_reply) {
|
||||
xcb_atom_t state = *(xcb_atom_t *)xcb_get_property_value(state_reply);
|
||||
client->skip_taskbar = state == _NET_WM_STATE_SKIP_TASKBAR;
|
||||
if (state == _NET_WM_STATE_FULLSCREEN) {
|
||||
client_set_fullscreen(client, true);
|
||||
}
|
||||
|
||||
81
src/config.c
Normal file
81
src/config.c
Normal file
@@ -0,0 +1,81 @@
|
||||
#include "config.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include "default_config.h"
|
||||
#include "utils.h"
|
||||
|
||||
void config_status_set_gap(config_status_t *status_config, uint32_t gap,
|
||||
uint32_t item_gap) {
|
||||
if (!status_config) fatal("no valid status config");
|
||||
status_config->status_gap = gap;
|
||||
status_config->status_item_gap = item_gap;
|
||||
}
|
||||
|
||||
void config_status_add_item(config_status_t **status_config,
|
||||
config_status_item_t status_item) {
|
||||
const auto item_count = (*status_config)->status_count + 1;
|
||||
const auto new_size =
|
||||
sizeof(config_status_t) + item_count * sizeof(config_status_item_t);
|
||||
xrealloc((void **)status_config, (ssize_t)new_size);
|
||||
(*status_config)->list[item_count - 1] = status_item;
|
||||
(*status_config)->status_count = item_count;
|
||||
}
|
||||
|
||||
void config_status_filter_item(config_status_t **status_config,
|
||||
status_item_filter filter) {
|
||||
if (!status_config || !*status_config) fatal("no valid status config");
|
||||
if (!filter) return;
|
||||
|
||||
config_status_t *config = *status_config;
|
||||
size_t write_idx = 0;
|
||||
size_t item_count = config->status_count;
|
||||
|
||||
for (size_t read_idx = 0; read_idx < item_count; read_idx++) {
|
||||
config_status_item_t *item = &config->list[read_idx];
|
||||
if (!filter(item)) continue;
|
||||
if (write_idx != read_idx) {
|
||||
config->list[write_idx] = *item;
|
||||
}
|
||||
write_idx++;
|
||||
}
|
||||
|
||||
if (write_idx == item_count) return;
|
||||
|
||||
config->status_count = write_idx;
|
||||
size_t new_size =
|
||||
sizeof(config_status_t) + write_idx * sizeof(config_status_item_t);
|
||||
xrealloc((void **)status_config, (ssize_t)new_size);
|
||||
}
|
||||
|
||||
static config_status_t *config_create_default_status(void) {
|
||||
size_t item_count = countof(status_list);
|
||||
size_t size = sizeof(config_status_t) + item_count * sizeof(status_list[0]);
|
||||
config_status_t *status = xmalloc((ssize_t)size);
|
||||
|
||||
status->status_gap = status_config.status_gap;
|
||||
status->status_item_gap = status_config.status_item_gap;
|
||||
status->status_count = item_count;
|
||||
memcpy(status->list, status_list, item_count * sizeof(status_list[0]));
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
static config_t runtime_config = {};
|
||||
|
||||
const config_t *init_config(void) {
|
||||
if (!runtime_config.status) {
|
||||
runtime_config.status = config_create_default_status();
|
||||
}
|
||||
if (!runtime_config.status_renderer) {
|
||||
runtime_config.status_renderer = renderer_render_status;
|
||||
}
|
||||
return &runtime_config;
|
||||
}
|
||||
|
||||
void config_set_custom_status_renderer(config_t *config,
|
||||
status_renderer_t status_renderer) {
|
||||
if (!config) fatal("no valid config");
|
||||
config->status_renderer =
|
||||
status_renderer ? status_renderer : renderer_render_status;
|
||||
}
|
||||
57
src/config.h
Normal file
57
src/config.h
Normal file
@@ -0,0 +1,57 @@
|
||||
#pragma once
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "renderer.h"
|
||||
#include "status.h"
|
||||
|
||||
typedef struct config_status_item_t {
|
||||
status_type_t type;
|
||||
icon_type_t icon_type;
|
||||
const char *icon;
|
||||
const char *color;
|
||||
} config_status_item_t;
|
||||
|
||||
typedef struct config_status_t {
|
||||
uint32_t status_gap;
|
||||
uint32_t status_item_gap;
|
||||
size_t status_count;
|
||||
config_status_item_t list[];
|
||||
} config_status_t;
|
||||
|
||||
void config_status_set_gap(config_status_t *status_config, uint32_t gap,
|
||||
uint32_t item_gap);
|
||||
|
||||
void config_status_add_item(config_status_t **status_config,
|
||||
config_status_item_t status_item);
|
||||
|
||||
typedef bool (*status_item_filter)(config_status_item_t *item);
|
||||
/**
|
||||
* @brief 按过滤条件原地筛选 status 项并保持原有顺序
|
||||
*
|
||||
* 当 filter(item) 返回 true 时保留该项,返回 false 时删除该项。
|
||||
* 筛选后会压缩 list、更新 status_count,并收缩配置对象内存。
|
||||
*
|
||||
* @param status_config 配置指针地址(函数内部会重分配内存)
|
||||
* @param filter 过滤函数
|
||||
*/
|
||||
void config_status_filter_item(config_status_t **status_config,
|
||||
status_item_filter filter);
|
||||
|
||||
typedef void (*status_renderer_t)(config_status_t *config, size_t config_count,
|
||||
status_t *status, renderer_status_t *output);
|
||||
|
||||
typedef struct config_t {
|
||||
config_status_t *status;
|
||||
status_renderer_t status_renderer;
|
||||
} config_t;
|
||||
|
||||
/**
|
||||
* @brief 初始化配置对象
|
||||
* @return 返回静态变量指针,不需要释放
|
||||
*/
|
||||
const config_t *init_config(void);
|
||||
|
||||
void config_set_custom_status_renderer(config_t *config,
|
||||
status_renderer_t status_renderer);
|
||||
@@ -5,7 +5,10 @@
|
||||
#include <stdint.h>
|
||||
|
||||
#include "action.h"
|
||||
#include "config.h"
|
||||
#include "layout.h"
|
||||
#include "renderer.h"
|
||||
#include "status.h"
|
||||
#include "types.h"
|
||||
|
||||
static const char *const font_family = "monospace";
|
||||
@@ -27,6 +30,55 @@ static const char *const active_tag_color = "#eeeeee";
|
||||
static const char *const border_color = "#444444";
|
||||
static const char *const active_border_color = "#005577";
|
||||
|
||||
static const config_status_item_t status_list[] = {
|
||||
{
|
||||
.type = status_net_down,
|
||||
.icon_type = icon_type_image,
|
||||
.icon = "/home/zedhugh/develop/zswm/src/resources/icons/"
|
||||
"corner-left-down-line.png",
|
||||
.color = "#87af5f",
|
||||
},
|
||||
{
|
||||
.type = status_net_up,
|
||||
.icon_type = icon_type_image,
|
||||
.icon =
|
||||
"/home/zedhugh/develop/zswm/src/resources/icons/corner-right-up-line.png",
|
||||
.color = "#e54c62",
|
||||
},
|
||||
{
|
||||
.type = status_audio,
|
||||
.icon_type = icon_type_image,
|
||||
.icon = "/home/zedhugh/develop/zswm/src/resources/icons/volume-up-fill.png",
|
||||
.color = "#7493d2",
|
||||
},
|
||||
{
|
||||
.type = status_memory,
|
||||
.icon_type = icon_type_image,
|
||||
.icon = "/home/zedhugh/develop/zswm/src/resources/icons/ram-line.png",
|
||||
.color = "#e0da37",
|
||||
},
|
||||
{
|
||||
.type = status_cpu,
|
||||
.icon_type = icon_type_image,
|
||||
.icon = "/home/zedhugh/develop/zswm/src/resources/icons/cpu-line.png",
|
||||
.color = "#e33a6e",
|
||||
},
|
||||
{
|
||||
.type = status_time,
|
||||
.icon_type = icon_type_image,
|
||||
.icon = "/home/zedhugh/develop/zswm/src/resources/icons/time-line.png",
|
||||
.color = "#7788af",
|
||||
},
|
||||
};
|
||||
static const config_status_t status_config = {
|
||||
.status_gap = 10,
|
||||
.status_item_gap = 5,
|
||||
};
|
||||
|
||||
static const config_t config = {
|
||||
.status = (config_status_t *)&status_config,
|
||||
};
|
||||
|
||||
static const button_t button_list[] = {
|
||||
{click_tag, modifier_none, button_left, select_tag_of_current_monitor, {0}},
|
||||
{
|
||||
@@ -39,9 +91,9 @@ static const button_t button_list[] = {
|
||||
};
|
||||
|
||||
#define TAGKEYS(KEY, TAG) \
|
||||
{modifier_super, KEY, select_tag_of_current_monitor, {.ui = TAG}}, { \
|
||||
modifier_super | modifier_shift, KEY, send_client_to_tag, {.ui = TAG}, \
|
||||
}
|
||||
{modifier_super, KEY, select_tag_of_current_monitor, {.ui = TAG}}, { \
|
||||
modifier_super | modifier_shift, KEY, send_client_to_tag, {.ui = TAG}, \
|
||||
}
|
||||
|
||||
static const char launcher[] =
|
||||
"rofi -show combi -modes combi -combi-modes window,drun,run,ssh,windowcd";
|
||||
@@ -53,6 +105,8 @@ static const char browser[] = "firefox-bin";
|
||||
static const char browser_class[] = "firefox";
|
||||
static const char chrome[] = "google-chrome-stable";
|
||||
static const char chrome_class[] = "Google-chrome";
|
||||
static const char mpv[] = "mpv";
|
||||
static const char mpv_class[] = "mpv";
|
||||
static const keyboard_t key_list[] = {
|
||||
{modifier_super, XK_r, spawn, {.ptr = launcher}},
|
||||
{modifier_super, XK_Return, spawn, {.ptr = terminal}},
|
||||
@@ -80,6 +134,12 @@ static const keyboard_t key_list[] = {
|
||||
raise_or_run,
|
||||
{.ptr = (const char *[]){chrome_class, chrome}},
|
||||
},
|
||||
{
|
||||
modifier_super,
|
||||
XK_v,
|
||||
raise_or_run,
|
||||
{.ptr = (const char *[]){mpv_class, mpv}},
|
||||
},
|
||||
{modifier_super | modifier_shift, XK_q, quit, {.b = false}},
|
||||
{modifier_super | modifier_control, XK_r, quit, {.b = true}},
|
||||
{modifier_super | modifier_shift,
|
||||
@@ -103,6 +163,7 @@ static const keyboard_t key_list[] = {
|
||||
|
||||
{modifier_super, XK_o, send_client_to_next_monitor, {0}},
|
||||
|
||||
{modifier_super | modifier_control, XK_j, focus_next_monitor, {0}},
|
||||
{modifier_super | modifier_control, XK_space, toggle_client_floating, {0}},
|
||||
{modifier_super, XK_f, toggle_client_fullscreen, {0}},
|
||||
{modifier_super, XK_m, toggle_client_maximize, {0}},
|
||||
@@ -138,3 +199,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 = 0;
|
||||
|
||||
24
src/event.c
24
src/event.c
@@ -15,6 +15,7 @@
|
||||
#include "client.h"
|
||||
#include "default_config.h"
|
||||
#include "monitor.h"
|
||||
#include "tray.h"
|
||||
#include "types.h"
|
||||
#include "utils.h"
|
||||
#include "wm.h"
|
||||
@@ -73,6 +74,8 @@ static void button_press(xcb_button_press_event_t *ev) {
|
||||
}
|
||||
|
||||
static void configure_request(xcb_configure_request_event_t *ev) {
|
||||
if (tray_handle_configure_request(ev)) return;
|
||||
|
||||
client_t *client = client_get_by_window(ev->window);
|
||||
if (client) {
|
||||
if (ev->value_mask & XCB_CONFIG_WINDOW_BORDER_WIDTH) {
|
||||
@@ -131,6 +134,8 @@ static void key_press(xcb_key_press_event_t *ev) {
|
||||
}
|
||||
|
||||
static void map_request(xcb_map_request_event_t *ev) {
|
||||
if (tray_handle_map_request(ev)) return;
|
||||
|
||||
xcb_get_window_attributes_reply_t *wa_reply =
|
||||
xwindow_get_attributes_reply(ev->window);
|
||||
|
||||
@@ -183,6 +188,8 @@ static inline bool get_bool_property(bool init_property,
|
||||
}
|
||||
|
||||
static void client_message(xcb_client_message_event_t *ev) {
|
||||
if (tray_handle_client_message(ev)) return;
|
||||
|
||||
client_t *c = client_get_by_window(ev->window);
|
||||
if (c == nullptr) return;
|
||||
|
||||
@@ -246,6 +253,8 @@ static void client_message(xcb_client_message_event_t *ev) {
|
||||
}
|
||||
|
||||
static void property_notify(xcb_property_notify_event_t *ev) {
|
||||
if (tray_handle_property_notify(ev)) return;
|
||||
|
||||
client_t *client = client_get_by_window(ev->window);
|
||||
if (client == nullptr) return;
|
||||
|
||||
@@ -265,11 +274,15 @@ static void property_notify(xcb_property_notify_event_t *ev) {
|
||||
}
|
||||
|
||||
static void destroy_notify(xcb_destroy_notify_event_t *ev) {
|
||||
if (tray_handle_destroy_notify(ev)) return;
|
||||
|
||||
client_t *client = client_get_by_window(ev->window);
|
||||
if (client) client_unmanage(client, true);
|
||||
}
|
||||
|
||||
static void unmap_notify(xcb_unmap_notify_event_t *ev) {
|
||||
if (tray_handle_unmap_notify(ev)) return;
|
||||
|
||||
client_t *client = client_get_by_window(ev->window);
|
||||
if (!client || client->minimize) return;
|
||||
|
||||
@@ -300,16 +313,17 @@ static void expose(xcb_expose_event_t *ev) {
|
||||
}
|
||||
|
||||
static void enter_notify(xcb_enter_notify_event_t *ev) {
|
||||
if (wm_should_ignore_enter_notify(ev)) return;
|
||||
|
||||
client_t *client = client_get_by_window(ev->event);
|
||||
if (!client || wm.client_focused == client) return;
|
||||
|
||||
client_focus(client);
|
||||
wm_set_current_monitor(client->monitor, false);
|
||||
}
|
||||
|
||||
static void focus_in(xcb_focus_in_event_t *ev) {
|
||||
client_t *client = client_get_by_window(ev->event);
|
||||
if (!client) return;
|
||||
wm_set_current_monitor(client->monitor, ev->mode != XCB_NOTIFY_MODE_NORMAL);
|
||||
static void selection_clear(xcb_selection_clear_event_t *ev) {
|
||||
tray_handle_selection_clear(ev);
|
||||
}
|
||||
|
||||
static void handle_xcb_event(xcb_generic_event_t *event) {
|
||||
@@ -336,7 +350,7 @@ static void handle_xcb_event(xcb_generic_event_t *event) {
|
||||
EVENT(XCB_UNMAP_NOTIFY, unmap_notify);
|
||||
EVENT(XCB_DESTROY_NOTIFY, destroy_notify);
|
||||
EVENT(XCB_ENTER_NOTIFY, enter_notify);
|
||||
EVENT(XCB_FOCUS_IN, focus_in);
|
||||
EVENT(XCB_SELECTION_CLEAR, selection_clear);
|
||||
|
||||
#undef EVENT
|
||||
}
|
||||
|
||||
170
src/image.c
Normal file
170
src/image.c
Normal file
@@ -0,0 +1,170 @@
|
||||
#include "image.h"
|
||||
|
||||
#include <Imlib2.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "utils.h"
|
||||
|
||||
typedef struct image_cache_entry_t {
|
||||
char *path;
|
||||
cairo_surface_t *surface;
|
||||
int width;
|
||||
int height;
|
||||
} image_cache_entry_t;
|
||||
|
||||
static image_cache_entry_t *image_cache = nullptr;
|
||||
static size_t image_cache_count = 0;
|
||||
static cairo_user_data_key_t image_surface_data_key;
|
||||
|
||||
static inline uint32_t premultiply_argb(uint32_t pixel) {
|
||||
uint8_t a = (pixel >> 24) & 0xff;
|
||||
uint8_t r = (pixel >> 16) & 0xff;
|
||||
uint8_t g = (pixel >> 8) & 0xff;
|
||||
uint8_t b = pixel & 0xff;
|
||||
r = (uint8_t)((r * a + 127) / 255);
|
||||
g = (uint8_t)((g * a + 127) / 255);
|
||||
b = (uint8_t)((b * a + 127) / 255);
|
||||
return ((uint32_t)a << 24) | ((uint32_t)r << 16) | ((uint32_t)g << 8) |
|
||||
(uint32_t)b;
|
||||
}
|
||||
|
||||
static bool image_load_surface(const char *path, cairo_surface_t **surface,
|
||||
int *width, int *height) {
|
||||
if (!path || !*path || !surface || !width || !height) return false;
|
||||
|
||||
Imlib_Image image = imlib_load_image(path);
|
||||
if (!image) return false;
|
||||
|
||||
imlib_context_set_image(image);
|
||||
int img_width = imlib_image_get_width();
|
||||
int img_height = imlib_image_get_height();
|
||||
if (img_width <= 0 || img_height <= 0) {
|
||||
imlib_free_image();
|
||||
return false;
|
||||
}
|
||||
|
||||
DATA32 *src = imlib_image_get_data_for_reading_only();
|
||||
if (!src) {
|
||||
imlib_free_image();
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t pixel_count = (size_t)img_width * (size_t)img_height;
|
||||
uint32_t *dst = xmalloc((ssize_t)(pixel_count * sizeof(uint32_t)));
|
||||
for (size_t i = 0; i < pixel_count; i++) {
|
||||
dst[i] = premultiply_argb(src[i]);
|
||||
}
|
||||
|
||||
cairo_surface_t *img_surface = cairo_image_surface_create_for_data(
|
||||
(unsigned char *)dst, CAIRO_FORMAT_ARGB32, img_width, img_height,
|
||||
img_width * (int)sizeof(uint32_t));
|
||||
cairo_status_t status = cairo_surface_status(img_surface);
|
||||
if (status != CAIRO_STATUS_SUCCESS) {
|
||||
cairo_surface_destroy(img_surface);
|
||||
p_delete(&dst);
|
||||
imlib_free_image();
|
||||
return false;
|
||||
}
|
||||
|
||||
cairo_surface_set_user_data(img_surface, &image_surface_data_key, dst, free);
|
||||
*surface = img_surface;
|
||||
*width = img_width;
|
||||
*height = img_height;
|
||||
|
||||
imlib_free_image();
|
||||
return true;
|
||||
}
|
||||
|
||||
static image_cache_entry_t *image_cache_find(const char *path) {
|
||||
if (!path || !*path) return nullptr;
|
||||
for (size_t i = 0; i < image_cache_count; i++) {
|
||||
image_cache_entry_t *entry = &image_cache[i];
|
||||
if (!entry->path) continue;
|
||||
if (strcmp(entry->path, path) == 0) return entry;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static image_cache_entry_t *image_cache_add(const char *path) {
|
||||
if (!path || !*path) return nullptr;
|
||||
|
||||
size_t new_count = image_cache_count + 1;
|
||||
xrealloc((void **)&image_cache,
|
||||
(ssize_t)(new_count * sizeof(image_cache_entry_t)));
|
||||
image_cache_entry_t *entry = &image_cache[image_cache_count];
|
||||
entry->path = strdup(path);
|
||||
entry->surface = nullptr;
|
||||
entry->width = 0;
|
||||
entry->height = 0;
|
||||
image_cache_count = new_count;
|
||||
return entry;
|
||||
}
|
||||
|
||||
static image_cache_entry_t *image_cache_get(const char *path) {
|
||||
image_cache_entry_t *entry = image_cache_find(path);
|
||||
if (entry && entry->surface) return entry;
|
||||
if (!entry) entry = image_cache_add(path);
|
||||
if (!entry) return nullptr;
|
||||
|
||||
if (entry->surface) {
|
||||
cairo_surface_destroy(entry->surface);
|
||||
entry->surface = nullptr;
|
||||
}
|
||||
|
||||
if (!image_load_surface(path, &entry->surface, &entry->width, &entry->height)) {
|
||||
return nullptr;
|
||||
}
|
||||
return entry;
|
||||
}
|
||||
|
||||
bool image_get_scaled_size(const char *path, int32_t target_height, int *width,
|
||||
int *height) {
|
||||
if (!width || !height || target_height <= 0) return false;
|
||||
image_cache_entry_t *entry = image_cache_get(path);
|
||||
if (!entry || !entry->surface || entry->width <= 0 || entry->height <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
*height = target_height;
|
||||
*width = (entry->width * target_height + entry->height / 2) / entry->height;
|
||||
if (*width <= 0) *width = 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool image_draw(cairo_t *cr, const char *path, int32_t x, int32_t y,
|
||||
int32_t width, int32_t height) {
|
||||
if (!cr || width <= 0 || height <= 0) return false;
|
||||
image_cache_entry_t *entry = image_cache_get(path);
|
||||
if (!entry || !entry->surface || entry->width <= 0 || entry->height <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
cairo_save(cr);
|
||||
cairo_translate(cr, (double)x, (double)y);
|
||||
cairo_scale(cr, (double)width / (double)entry->width,
|
||||
(double)height / (double)entry->height);
|
||||
cairo_set_source_surface(cr, entry->surface, 0, 0);
|
||||
cairo_paint(cr);
|
||||
cairo_restore(cr);
|
||||
return true;
|
||||
}
|
||||
|
||||
void image_cache_clean(void) {
|
||||
if (!image_cache) return;
|
||||
|
||||
for (size_t i = 0; i < image_cache_count; i++) {
|
||||
image_cache_entry_t *entry = &image_cache[i];
|
||||
if (entry->surface) {
|
||||
cairo_surface_destroy(entry->surface);
|
||||
entry->surface = nullptr;
|
||||
}
|
||||
p_delete(&entry->path);
|
||||
entry->width = 0;
|
||||
entry->height = 0;
|
||||
}
|
||||
|
||||
p_delete(&image_cache);
|
||||
image_cache_count = 0;
|
||||
}
|
||||
36
src/image.h
Normal file
36
src/image.h
Normal file
@@ -0,0 +1,36 @@
|
||||
#pragma once
|
||||
|
||||
#include <cairo.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
/**
|
||||
* @brief 获取图片按目标高度缩放后的尺寸
|
||||
*
|
||||
* @param path 图片路径
|
||||
* @param target_height 目标高度(像素)
|
||||
* @param width 返回缩放后的宽度
|
||||
* @param height 返回缩放后的高度
|
||||
* @return 加载并计算成功返回 true
|
||||
*/
|
||||
bool image_get_scaled_size(const char *path, int32_t target_height, int *width,
|
||||
int *height);
|
||||
|
||||
/**
|
||||
* @brief 在 cairo 上下文中绘制图片
|
||||
*
|
||||
* @param cr cairo 上下文
|
||||
* @param path 图片路径
|
||||
* @param x 绘制起始 x
|
||||
* @param y 绘制起始 y
|
||||
* @param width 绘制宽度
|
||||
* @param height 绘制高度
|
||||
* @return 绘制成功返回 true
|
||||
*/
|
||||
bool image_draw(cairo_t *cr, const char *path, int32_t x, int32_t y,
|
||||
int32_t width, int32_t height);
|
||||
|
||||
/**
|
||||
* @brief 释放图片缓存
|
||||
*/
|
||||
void image_cache_clean(void);
|
||||
157
src/monitor.c
157
src/monitor.c
@@ -12,8 +12,12 @@
|
||||
#include "base.h"
|
||||
#include "client.h"
|
||||
#include "color.h"
|
||||
#include "config.h"
|
||||
#include "image.h"
|
||||
#include "renderer.h"
|
||||
#include "status.h"
|
||||
#include "text.h"
|
||||
#include "tray.h"
|
||||
#include "types.h"
|
||||
#include "utils.h"
|
||||
#include "wm.h"
|
||||
@@ -236,64 +240,132 @@ static void monitor_draw_layout_symbol(monitor_t *monitor) {
|
||||
}
|
||||
}
|
||||
|
||||
static void monitor_draw_status(monitor_t *monitor, status_t *status) {
|
||||
monitor->status_extent.end = monitor->geometry.width;
|
||||
monitor->status_extent.start = monitor->geometry.width;
|
||||
int16_t end = monitor->layout_symbol_extent.end;
|
||||
static void monitor_draw_status(monitor_t *monitor, status_t *status,
|
||||
int16_t right_edge, int16_t left_edge) {
|
||||
static renderer_status_t *rendered_status = nullptr;
|
||||
static size_t rendered_status_capacity = 0;
|
||||
|
||||
if (status == nullptr) return;
|
||||
monitor->status_extent.end = right_edge;
|
||||
monitor->status_extent.start = right_edge;
|
||||
int32_t end = left_edge;
|
||||
|
||||
char cpu[16], mem[64], volume[300];
|
||||
snprintf(cpu, sizeof(cpu), "%.1lf", status->cpu_usage_percent);
|
||||
snprintf(mem, sizeof(mem), "%s(%.1f%%)", status->mem_usage.mem_used_text,
|
||||
status->mem_usage.mem_percent);
|
||||
if (status->pulse) {
|
||||
snprintf(volume, sizeof(volume), "%d%%%s%s%s",
|
||||
status->pulse->volume_percent, status->pulse->mute ? "M" : "",
|
||||
strlen(status->pulse->device_name) ? "|" : "",
|
||||
status->pulse->device_name);
|
||||
} else {
|
||||
snprintf(volume, sizeof(volume), "0%%");
|
||||
if (status == nullptr || !wm.config || !wm.config->status) return;
|
||||
|
||||
config_status_t *status_config = wm.config->status;
|
||||
size_t item_count = status_config->status_count;
|
||||
if (item_count == 0) return;
|
||||
|
||||
if (rendered_status_capacity < item_count) {
|
||||
size_t size =
|
||||
sizeof(renderer_status_t) + item_count * sizeof(renderer_status_item_t);
|
||||
xrealloc((void **)&rendered_status, (ssize_t)size);
|
||||
rendered_status_capacity = item_count;
|
||||
}
|
||||
|
||||
char *status_items[] = {
|
||||
status->net_speed.down, status->net_speed.up, volume, mem, cpu,
|
||||
status->time,
|
||||
};
|
||||
int16_t start = monitor->status_extent.start;
|
||||
for (int i = countof(status_items) - 1; i >= 0; i--) {
|
||||
const char *text = status_items[i];
|
||||
if (!text || !strlen(text)) continue;
|
||||
status_renderer_t renderer = wm.config->status_renderer;
|
||||
if (!renderer) renderer = renderer_render_status;
|
||||
|
||||
renderer(status_config, item_count, status, rendered_status);
|
||||
item_count = MIN(item_count, rendered_status->item_count);
|
||||
|
||||
int32_t start = monitor->status_extent.start;
|
||||
int32_t icon_target_height =
|
||||
(int32_t)wm.bar_height - (int32_t)wm.padding.bar_y * 2;
|
||||
if (icon_target_height <= 0) icon_target_height = wm.bar_height;
|
||||
|
||||
for (size_t ri = item_count; ri > 0; ri--) {
|
||||
size_t i = ri - 1;
|
||||
renderer_status_item_t *item = &rendered_status->item_list[i];
|
||||
bool has_text = item->text[0] != '\0';
|
||||
int text_width = 0;
|
||||
if (has_text) text_get_size(item->text, &text_width, nullptr);
|
||||
|
||||
bool has_text_icon = false;
|
||||
int icon_text_width = 0;
|
||||
if (item->icon_type == icon_type_text && item->icon_text &&
|
||||
item->icon_text[0]) {
|
||||
has_text_icon = true;
|
||||
text_get_size(item->icon_text, &icon_text_width, nullptr);
|
||||
}
|
||||
|
||||
bool has_image_icon = false;
|
||||
int icon_width = 0;
|
||||
int icon_height = 0;
|
||||
if (item->icon_type == icon_type_image && item->icon_path &&
|
||||
item->icon_path[0]) {
|
||||
has_image_icon = image_get_scaled_size(
|
||||
item->icon_path, icon_target_height, &icon_width, &icon_height);
|
||||
}
|
||||
bool has_icon = has_text_icon || has_image_icon;
|
||||
|
||||
int width = 0;
|
||||
text_get_size(text, &width, nullptr);
|
||||
if (has_text_icon) width += icon_text_width;
|
||||
if (has_image_icon) width += icon_width;
|
||||
if (has_text) width += text_width;
|
||||
if (has_icon && has_text) width += (int)rendered_status->item_gap;
|
||||
if (!width) continue;
|
||||
|
||||
start -= width;
|
||||
if (start <= end) return;
|
||||
color_t *color = &wm.color_set.tag_color;
|
||||
area_t rect = {.x = start, .y = 0, .width = width, .height = wm.bar_height};
|
||||
draw_text(monitor->bar_cr, text, color, rect, true);
|
||||
monitor->status_extent.start = start;
|
||||
|
||||
start -= wm.padding.tag_x;
|
||||
if (start < end) return;
|
||||
color_t *color = item->color ? item->color : &wm.color_set.tag_color;
|
||||
int32_t draw_x = start;
|
||||
if (has_text_icon && icon_text_width > 0) {
|
||||
area_t icon_rect = {
|
||||
.x = (int16_t)draw_x,
|
||||
.y = 0,
|
||||
.width = (uint16_t)icon_text_width,
|
||||
.height = wm.bar_height,
|
||||
};
|
||||
draw_text(monitor->bar_cr, item->icon_text, color, icon_rect, true);
|
||||
draw_x += icon_text_width;
|
||||
} else if (has_image_icon) {
|
||||
int32_t icon_y = ((int32_t)wm.bar_height - icon_height) / 2;
|
||||
image_draw(monitor->bar_cr, item->icon_path, draw_x, icon_y, icon_width,
|
||||
icon_height);
|
||||
draw_x += icon_width;
|
||||
}
|
||||
if (has_icon && has_text) draw_x += (int32_t)rendered_status->item_gap;
|
||||
|
||||
if (has_text && text_width > 0) {
|
||||
area_t rect = {
|
||||
.x = (int16_t)draw_x,
|
||||
.y = 0,
|
||||
.width = (uint16_t)text_width,
|
||||
.height = wm.bar_height,
|
||||
};
|
||||
draw_text(monitor->bar_cr, item->text, color, rect, true);
|
||||
}
|
||||
monitor->status_extent.start = (int16_t)start;
|
||||
|
||||
if (ri > 1) {
|
||||
start -= (int32_t)rendered_status->gap;
|
||||
if (start < end) return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void monitor_draw_tasks(monitor_t *monitor) {
|
||||
static void monitor_draw_tasks(monitor_t *monitor, int16_t right_edge) {
|
||||
task_in_tag_t *task_list = monitor->selected_tag->task_list;
|
||||
if (!task_list) return;
|
||||
|
||||
uint16_t task_count = 0;
|
||||
for (task_in_tag_t *task = task_list; task; task = task->next) ++task_count;
|
||||
for (task_in_tag_t *task = task_list; task; task = task->next) {
|
||||
if (task->client->skip_taskbar) continue;
|
||||
++task_count;
|
||||
};
|
||||
if (task_count == 0) return;
|
||||
|
||||
int16_t x = monitor->layout_symbol_extent.end;
|
||||
uint16_t task_width = (monitor->status_extent.start - x) / task_count;
|
||||
int32_t available_width = (int32_t)right_edge - x;
|
||||
if (available_width <= 0) return;
|
||||
|
||||
uint16_t task_width = (uint16_t)(available_width / task_count);
|
||||
if (task_width < wm.font_size) return;
|
||||
|
||||
for (task_in_tag_t *task = task_list; task; task = task->next) {
|
||||
if (task->client->skip_taskbar) continue;
|
||||
|
||||
task->bar_extent.start = x;
|
||||
task->bar_extent.end = x + task_width;
|
||||
x += task_width;
|
||||
@@ -314,6 +386,9 @@ static void monitor_draw_tasks(monitor_t *monitor) {
|
||||
void monitor_draw_bar(monitor_t *monitor) {
|
||||
cairo_t *cr = monitor->bar_cr;
|
||||
uint16_t height = wm.bar_height;
|
||||
uint16_t tray_width = tray_get_width(monitor);
|
||||
int16_t status_right = (int16_t)monitor->geometry.width - tray_width;
|
||||
if (status_right < 0) status_right = 0;
|
||||
area_t bar_area = {
|
||||
.x = 0,
|
||||
.y = 0,
|
||||
@@ -324,8 +399,10 @@ void monitor_draw_bar(monitor_t *monitor) {
|
||||
|
||||
monitor_draw_tags(monitor);
|
||||
monitor_draw_layout_symbol(monitor);
|
||||
monitor_draw_status(monitor, wm.status);
|
||||
monitor_draw_tasks(monitor);
|
||||
monitor_draw_status(monitor, wm.status, status_right,
|
||||
monitor->layout_symbol_extent.end);
|
||||
monitor_draw_tasks(monitor, monitor->status_extent.start);
|
||||
tray_place(monitor, monitor->geometry.width);
|
||||
}
|
||||
|
||||
void monitor_arrange(monitor_t *monitor) {
|
||||
@@ -360,7 +437,7 @@ void monitor_save_cursor_point(monitor_t *monitor) {
|
||||
monitor->position_inited = true;
|
||||
}
|
||||
|
||||
void monitor_restore_cursor_point(monitor_t *monitor) {
|
||||
point_t monitor_get_restore_cursor_point(monitor_t *monitor) {
|
||||
if (!monitor->position_inited) {
|
||||
point_t point = xcursor_query_pointer_position();
|
||||
monitor_t *m = wm_get_monitor_by_point(point);
|
||||
@@ -369,5 +446,9 @@ void monitor_restore_cursor_point(monitor_t *monitor) {
|
||||
monitor->position_inited = true;
|
||||
}
|
||||
|
||||
xcursor_set_pointer_position(monitor->cursor_position);
|
||||
return monitor->cursor_position;
|
||||
}
|
||||
|
||||
void monitor_restore_cursor_point(monitor_t *monitor) {
|
||||
xcursor_set_pointer_position(monitor_get_restore_cursor_point(monitor));
|
||||
}
|
||||
|
||||
@@ -13,4 +13,5 @@ void monitor_init_bar(monitor_t *monitor);
|
||||
void monitor_draw_bar(monitor_t *monitor);
|
||||
void monitor_arrange(monitor_t *monitor);
|
||||
void monitor_save_cursor_point(monitor_t *monitor);
|
||||
point_t monitor_get_restore_cursor_point(monitor_t *monitor);
|
||||
void monitor_restore_cursor_point(monitor_t *monitor);
|
||||
|
||||
90
src/renderer.c
Normal file
90
src/renderer.c
Normal file
@@ -0,0 +1,90 @@
|
||||
#include "renderer.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "color.h"
|
||||
#include "config.h"
|
||||
#include "status.h"
|
||||
#include "utils.h"
|
||||
|
||||
static void status_text_from_data(status_type_t type, const status_t *status,
|
||||
char *text, size_t text_size) {
|
||||
if (!text || text_size == 0) return;
|
||||
text[0] = '\0';
|
||||
if (!status) return;
|
||||
|
||||
switch (type) {
|
||||
case status_net_down:
|
||||
snprintf(text, text_size, "%s", status->net_speed.down);
|
||||
break;
|
||||
case status_net_up:
|
||||
snprintf(text, text_size, "%s", status->net_speed.up);
|
||||
break;
|
||||
case status_audio:
|
||||
if (status->pulse) {
|
||||
snprintf(text, text_size, "%d%%%s%s%s", status->pulse->volume_percent,
|
||||
status->pulse->mute ? "M" : "",
|
||||
strlen(status->pulse->device_name) ? "|" : "",
|
||||
status->pulse->device_name);
|
||||
} else {
|
||||
snprintf(text, text_size, "0%%");
|
||||
}
|
||||
break;
|
||||
case status_memory:
|
||||
snprintf(text, text_size, "%s(%.1f%%)", status->mem_usage.mem_used_text,
|
||||
status->mem_usage.mem_percent);
|
||||
break;
|
||||
case status_cpu:
|
||||
snprintf(text, text_size, "%.1lf", status->cpu_usage_percent);
|
||||
break;
|
||||
case status_time:
|
||||
snprintf(text, text_size, "%s", status->time);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void renderer_render_status(config_status_t *config, size_t config_count,
|
||||
status_t *status, renderer_status_t *output) {
|
||||
static color_t *color_cache = nullptr;
|
||||
static size_t color_cache_count = 0;
|
||||
|
||||
if (!config || !output) return;
|
||||
|
||||
size_t item_count = config->status_count;
|
||||
if (config_count && config_count < item_count) item_count = config_count;
|
||||
|
||||
output->gap = config->status_gap;
|
||||
output->item_gap = config->status_item_gap;
|
||||
output->item_count = item_count;
|
||||
|
||||
if (item_count == 0) return;
|
||||
|
||||
if (color_cache_count < item_count) {
|
||||
xrealloc((void **)&color_cache, (ssize_t)(sizeof(color_t) * item_count));
|
||||
color_cache_count = item_count;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < item_count; i++) {
|
||||
const config_status_item_t *config_item = &config->list[i];
|
||||
renderer_status_item_t *output_item = &output->item_list[i];
|
||||
memset(output_item, 0, sizeof(*output_item));
|
||||
|
||||
output_item->icon_type = config_item->icon_type;
|
||||
if (config_item->icon_type == icon_type_text) {
|
||||
output_item->icon_text = config_item->icon;
|
||||
} else if (config_item->icon_type == icon_type_image) {
|
||||
output_item->icon_path = config_item->icon;
|
||||
}
|
||||
|
||||
status_text_from_data(config_item->type, status, output_item->text,
|
||||
sizeof(output_item->text));
|
||||
|
||||
if (config_item->color && *config_item->color) {
|
||||
color_parse(config_item->color, &color_cache[i]);
|
||||
output_item->color = &color_cache[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
41
src/renderer.h
Normal file
41
src/renderer.h
Normal file
@@ -0,0 +1,41 @@
|
||||
#pragma once
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "color.h"
|
||||
|
||||
typedef struct config_status_t config_status_t;
|
||||
typedef struct status_t status_t;
|
||||
|
||||
typedef enum icon_type_t {
|
||||
icon_type_none,
|
||||
icon_type_text,
|
||||
icon_type_image,
|
||||
} icon_type_t;
|
||||
|
||||
typedef struct renderer_status_item_t {
|
||||
icon_type_t icon_type;
|
||||
char text[124];
|
||||
const char *icon_text;
|
||||
const char *icon_path;
|
||||
color_t *color;
|
||||
} renderer_status_item_t;
|
||||
|
||||
typedef struct renderer_status_t {
|
||||
uint32_t gap;
|
||||
uint32_t item_gap;
|
||||
size_t item_count;
|
||||
renderer_status_item_t item_list[];
|
||||
} renderer_status_t;
|
||||
|
||||
/**
|
||||
* @brief 根据 status 配置和实时状态生成可绘制的 status 数据
|
||||
*
|
||||
* @param config status 配置
|
||||
* @param config_count 最大输出项数,传 0 表示使用 config->status_count
|
||||
* @param status 实时状态数据
|
||||
* @param output 渲染输出缓冲
|
||||
*/
|
||||
void renderer_render_status(config_status_t *config, size_t config_count,
|
||||
status_t *status, renderer_status_t *output);
|
||||
@@ -30,6 +30,15 @@ typedef struct net_speed_t {
|
||||
char down[64];
|
||||
} net_speed_t;
|
||||
|
||||
typedef enum status_type_t {
|
||||
status_net_down,
|
||||
status_net_up,
|
||||
status_audio,
|
||||
status_memory,
|
||||
status_cpu,
|
||||
status_time,
|
||||
} status_type_t;
|
||||
|
||||
typedef struct status_t {
|
||||
net_speed_t net_speed;
|
||||
memory_usage_t mem_usage;
|
||||
|
||||
602
src/tray.c
Normal file
602
src/tray.c
Normal file
@@ -0,0 +1,602 @@
|
||||
#include "tray.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <xcb/xcb.h>
|
||||
#include <xcb/xcb_aux.h>
|
||||
#include <xcb/xproto.h>
|
||||
|
||||
#include "atoms-extern.h"
|
||||
#include "base.h"
|
||||
#include "monitor.h"
|
||||
#include "utils.h"
|
||||
#include "wm.h"
|
||||
#include "xwindow.h"
|
||||
|
||||
typedef enum tray_opcode_t {
|
||||
TRAY_REQUEST_DOCK = 0,
|
||||
TRAY_BEGIN_MESSAGE = 1,
|
||||
TRAY_CANCEL_MESSAGE = 2,
|
||||
} tray_opcode_t;
|
||||
|
||||
typedef enum tray_orientation_t {
|
||||
TRAY_ORIENTATION_HORIZONTAL = 0,
|
||||
} tray_orientation_t;
|
||||
|
||||
typedef enum xembed_message_t {
|
||||
XEMBED_EMBEDDED_NOTIFY = 0,
|
||||
} xembed_message_t;
|
||||
|
||||
enum {
|
||||
XEMBED_VERSION = 0,
|
||||
};
|
||||
|
||||
typedef struct tray_icon_t {
|
||||
xcb_window_t window;
|
||||
uint16_t natural_width;
|
||||
uint16_t natural_height;
|
||||
uint8_t ignore_unmaps;
|
||||
bool mapped;
|
||||
} tray_icon_t;
|
||||
|
||||
typedef struct tray_t {
|
||||
bool initialized;
|
||||
xcb_atom_t selection_atom;
|
||||
xcb_window_t window;
|
||||
monitor_t *host_monitor;
|
||||
|
||||
tray_icon_t *icons;
|
||||
size_t icon_count;
|
||||
size_t icon_capacity;
|
||||
|
||||
uint16_t spacing;
|
||||
uint16_t side_padding;
|
||||
} tray_t;
|
||||
|
||||
static tray_t tray = {
|
||||
.selection_atom = XCB_ATOM_NONE,
|
||||
.window = XCB_WINDOW_NONE,
|
||||
};
|
||||
|
||||
static xcb_atom_t tray_intern_atom(const char *name) {
|
||||
if (!name || !name[0]) return XCB_ATOM_NONE;
|
||||
|
||||
xcb_intern_atom_cookie_t cookie =
|
||||
xcb_intern_atom(wm.xcb_conn, false, (uint16_t)strlen(name), name);
|
||||
xcb_intern_atom_reply_t *reply =
|
||||
xcb_intern_atom_reply(wm.xcb_conn, cookie, nullptr);
|
||||
if (!reply) return XCB_ATOM_NONE;
|
||||
|
||||
xcb_atom_t atom = reply->atom;
|
||||
p_delete(&reply);
|
||||
return atom;
|
||||
}
|
||||
|
||||
static tray_icon_t *tray_get_icon(xcb_window_t window) {
|
||||
for (size_t i = 0; i < tray.icon_count; i++) {
|
||||
tray_icon_t *icon = &tray.icons[i];
|
||||
if (icon->window == window) return icon;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static size_t tray_find_icon_index(xcb_window_t window) {
|
||||
for (size_t i = 0; i < tray.icon_count; i++) {
|
||||
if (tray.icons[i].window == window) return i;
|
||||
}
|
||||
|
||||
return SIZE_MAX;
|
||||
}
|
||||
|
||||
static uint16_t tray_target_icon_height(void) {
|
||||
return MAX((uint16_t)1, wm.bar_height);
|
||||
}
|
||||
|
||||
static uint32_t tray_color_component_u16(double channel) {
|
||||
if (channel < 0.0) channel = 0.0;
|
||||
if (channel > 1.0) channel = 1.0;
|
||||
|
||||
uint32_t value = (uint32_t)(channel * 65535.0 + 0.5);
|
||||
return MIN(value, 65535u);
|
||||
}
|
||||
|
||||
static void tray_get_icon_layout(const tray_icon_t *icon, uint16_t *width,
|
||||
uint16_t *height) {
|
||||
uint32_t target_height = tray_target_icon_height();
|
||||
uint32_t natural_width = icon->natural_width;
|
||||
uint32_t natural_height = icon->natural_height;
|
||||
|
||||
if (natural_width == 0) natural_width = target_height;
|
||||
if (natural_height == 0) natural_height = target_height;
|
||||
if (natural_height == 0) natural_height = 1;
|
||||
|
||||
uint32_t scaled_width =
|
||||
(natural_width * target_height + natural_height / 2) / natural_height;
|
||||
if (scaled_width == 0) scaled_width = 1;
|
||||
|
||||
if (width) *width = (uint16_t)MIN((uint32_t)UINT16_MAX, scaled_width);
|
||||
if (height) *height = (uint16_t)MIN((uint32_t)UINT16_MAX, target_height);
|
||||
}
|
||||
|
||||
static uint16_t tray_compute_width(void) {
|
||||
if (!tray.initialized || tray.host_monitor == nullptr || tray.icon_count == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint32_t width = (uint32_t)tray.side_padding * 2;
|
||||
for (size_t i = 0; i < tray.icon_count; i++) {
|
||||
uint16_t icon_width = 0;
|
||||
tray_get_icon_layout(&tray.icons[i], &icon_width, nullptr);
|
||||
width += icon_width;
|
||||
}
|
||||
if (tray.icon_count > 1) {
|
||||
width += (uint32_t)(tray.icon_count - 1) * tray.spacing;
|
||||
}
|
||||
|
||||
return (uint16_t)MIN((uint32_t)UINT16_MAX, width);
|
||||
}
|
||||
|
||||
static void tray_send_xembed_message(xcb_window_t window, uint32_t message,
|
||||
uint32_t detail, uint32_t data1,
|
||||
uint32_t data2) {
|
||||
xcb_client_message_event_t ev;
|
||||
p_clear(&ev, 1);
|
||||
ev.response_type = XCB_CLIENT_MESSAGE;
|
||||
ev.format = 32;
|
||||
ev.window = window;
|
||||
ev.type = _XEMBED;
|
||||
ev.data.data32[0] = XCB_CURRENT_TIME;
|
||||
ev.data.data32[1] = message;
|
||||
ev.data.data32[2] = detail;
|
||||
ev.data.data32[3] = data1;
|
||||
ev.data.data32[4] = data2;
|
||||
|
||||
xcb_send_event(wm.xcb_conn, false, window, XCB_EVENT_MASK_NO_EVENT,
|
||||
(char *)&ev);
|
||||
}
|
||||
|
||||
static void tray_select_icon_events(xcb_window_t window) {
|
||||
xcb_params_cw_t params = {
|
||||
.back_pixel = wm.color_set.bar_bg.argb,
|
||||
.border_pixel = 0,
|
||||
.event_mask = XCB_EVENT_MASK_PROPERTY_CHANGE |
|
||||
XCB_EVENT_MASK_STRUCTURE_NOTIFY,
|
||||
};
|
||||
uint32_t mask =
|
||||
XCB_CW_BACK_PIXEL | XCB_CW_BORDER_PIXEL | XCB_CW_EVENT_MASK;
|
||||
xcb_aux_change_window_attributes(wm.xcb_conn, window, mask, ¶ms);
|
||||
xcb_clear_area(wm.xcb_conn, false, window, 0, 0, 0, 0);
|
||||
}
|
||||
|
||||
static void tray_release_icon_window(tray_icon_t *icon) {
|
||||
if (!icon || icon->window == XCB_WINDOW_NONE) return;
|
||||
|
||||
xcb_change_save_set(wm.xcb_conn, XCB_SET_MODE_DELETE, icon->window);
|
||||
xcb_params_cw_t params = {
|
||||
.event_mask = XCB_EVENT_MASK_NO_EVENT,
|
||||
.border_pixel = 0,
|
||||
};
|
||||
xcb_aux_change_window_attributes(
|
||||
wm.xcb_conn, icon->window, XCB_CW_EVENT_MASK | XCB_CW_BORDER_PIXEL,
|
||||
¶ms);
|
||||
xcb_reparent_window(wm.xcb_conn, icon->window, wm.screen->root, 0, 0);
|
||||
}
|
||||
|
||||
static void tray_layout_icons(int16_t right_edge) {
|
||||
if (!tray.initialized || tray.window == XCB_WINDOW_NONE ||
|
||||
tray.host_monitor == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint16_t tray_width = tray_compute_width();
|
||||
if (tray_width == 0) {
|
||||
xcb_unmap_window(wm.xcb_conn, tray.window);
|
||||
return;
|
||||
}
|
||||
|
||||
int32_t tray_x = (int32_t)right_edge - tray_width;
|
||||
if (tray_x < 0) tray_x = 0;
|
||||
|
||||
xcb_configure_window_value_list_t tray_values = {
|
||||
.x = (int16_t)tray_x,
|
||||
.y = 0,
|
||||
.width = tray_width,
|
||||
.height = wm.bar_height,
|
||||
};
|
||||
uint16_t tray_mask = XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y |
|
||||
XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT;
|
||||
xcb_configure_window_aux(wm.xcb_conn, tray.window, tray_mask, &tray_values);
|
||||
xcb_map_window(wm.xcb_conn, tray.window);
|
||||
|
||||
int32_t x = tray.side_padding;
|
||||
for (size_t i = 0; i < tray.icon_count; i++) {
|
||||
tray_icon_t *icon = &tray.icons[i];
|
||||
uint16_t icon_width = 0;
|
||||
uint16_t icon_height = 0;
|
||||
tray_get_icon_layout(icon, &icon_width, &icon_height);
|
||||
|
||||
int32_t y = ((int32_t)wm.bar_height - icon_height) / 2;
|
||||
if (y < 0) y = 0;
|
||||
|
||||
xcb_configure_window_value_list_t icon_values = {
|
||||
.x = (int16_t)x,
|
||||
.y = (int16_t)y,
|
||||
.width = icon_width,
|
||||
.height = icon_height,
|
||||
.border_width = 0,
|
||||
};
|
||||
uint16_t icon_mask = XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y |
|
||||
XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT |
|
||||
XCB_CONFIG_WINDOW_BORDER_WIDTH;
|
||||
xcb_configure_window_aux(wm.xcb_conn, icon->window, icon_mask,
|
||||
&icon_values);
|
||||
xcb_map_window(wm.xcb_conn, icon->window);
|
||||
icon->mapped = true;
|
||||
|
||||
x += icon_width + tray.spacing;
|
||||
}
|
||||
}
|
||||
|
||||
static void tray_request_redraw(void) {
|
||||
if (!tray.initialized || tray.host_monitor == nullptr) return;
|
||||
|
||||
monitor_draw_bar(tray.host_monitor);
|
||||
xcb_flush(wm.xcb_conn);
|
||||
}
|
||||
|
||||
static void tray_remove_icon_by_index(size_t index, bool release_window) {
|
||||
if (index >= tray.icon_count) return;
|
||||
|
||||
tray_icon_t icon = tray.icons[index];
|
||||
if (release_window) tray_release_icon_window(&icon);
|
||||
|
||||
if (index + 1 < tray.icon_count) {
|
||||
memmove(&tray.icons[index], &tray.icons[index + 1],
|
||||
sizeof(*tray.icons) * (tray.icon_count - index - 1));
|
||||
}
|
||||
tray.icon_count--;
|
||||
|
||||
tray_request_redraw();
|
||||
}
|
||||
|
||||
static void tray_add_icon(xcb_window_t window) {
|
||||
if (!tray.initialized || window == XCB_WINDOW_NONE ||
|
||||
tray_get_icon(window) != nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (tray.icon_count == tray.icon_capacity) {
|
||||
tray.icon_capacity = (size_t)p_alloc_nr(tray.icon_capacity);
|
||||
p_realloc(&tray.icons, tray.icon_capacity);
|
||||
}
|
||||
|
||||
xcb_get_geometry_reply_t *geometry = xwindow_get_geometry_reply(window);
|
||||
if (!geometry) return;
|
||||
|
||||
tray_icon_t *icon = &tray.icons[tray.icon_count++];
|
||||
*icon = (tray_icon_t){
|
||||
.window = window,
|
||||
.natural_width = geometry->width,
|
||||
.natural_height = geometry->height,
|
||||
.ignore_unmaps = 1,
|
||||
.mapped = true,
|
||||
};
|
||||
p_delete(&geometry);
|
||||
|
||||
xcb_change_save_set(wm.xcb_conn, XCB_SET_MODE_INSERT, window);
|
||||
xcb_reparent_window(wm.xcb_conn, window, tray.window, 0, 0);
|
||||
tray_select_icon_events(window);
|
||||
tray_send_xembed_message(window, XEMBED_EMBEDDED_NOTIFY, 0, tray.window,
|
||||
XEMBED_VERSION);
|
||||
xcb_map_window(wm.xcb_conn, window);
|
||||
|
||||
tray_request_redraw();
|
||||
}
|
||||
|
||||
static bool tray_create_window(void) {
|
||||
static xcb_colormap_t colormap = XCB_NONE;
|
||||
if (tray.host_monitor == nullptr || tray.host_monitor->bar_window == XCB_NONE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
visual_t *visual = xwindow_get_xcb_visual(false);
|
||||
if (visual == nullptr) return false;
|
||||
|
||||
xcb_visualid_t visual_id = visual->visual->visual_id;
|
||||
if (colormap == XCB_NONE) {
|
||||
colormap = xcb_generate_id(wm.xcb_conn);
|
||||
xcb_create_colormap(wm.xcb_conn, XCB_COLORMAP_ALLOC_NONE, colormap,
|
||||
wm.screen->root, visual_id);
|
||||
}
|
||||
|
||||
xcb_window_t window = xcb_generate_id(wm.xcb_conn);
|
||||
uint32_t value_mask = XCB_CW_BACK_PIXEL | XCB_CW_BORDER_PIXEL |
|
||||
XCB_CW_EVENT_MASK | XCB_CW_COLORMAP;
|
||||
const xcb_create_window_value_list_t value_list = {
|
||||
.background_pixel = wm.color_set.bar_bg.argb,
|
||||
.border_pixel = 0,
|
||||
.event_mask = XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT |
|
||||
XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY,
|
||||
.colormap = colormap,
|
||||
};
|
||||
|
||||
xcb_void_cookie_t cookie = xcb_create_window_aux_checked(
|
||||
wm.xcb_conn, visual->depth, window, tray.host_monitor->bar_window, 0, 0, 1,
|
||||
wm.bar_height, 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, visual_id, value_mask,
|
||||
&value_list);
|
||||
p_delete(&visual);
|
||||
if (xcb_request_check(wm.xcb_conn, cookie)) {
|
||||
logger("cannot create system tray window\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
tray.window = window;
|
||||
xwindow_set_class_instance(window);
|
||||
#define NAME APP_NAME "_tray"
|
||||
xwindow_set_name_static(window, NAME);
|
||||
xcb_change_property(wm.xcb_conn, XCB_PROP_MODE_REPLACE, window, _NET_WM_NAME,
|
||||
UTF8_STRING, 8, sizeof(NAME) - 1, NAME);
|
||||
#undef NAME
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool tray_take_selection_owner(void) {
|
||||
xcb_get_selection_owner_cookie_t owner_cookie =
|
||||
xcb_get_selection_owner(wm.xcb_conn, tray.selection_atom);
|
||||
xcb_get_selection_owner_reply_t *owner_reply =
|
||||
xcb_get_selection_owner_reply(wm.xcb_conn, owner_cookie, nullptr);
|
||||
if (owner_reply && owner_reply->owner != XCB_WINDOW_NONE) {
|
||||
logger("system tray selection is already owned by window %u\n",
|
||||
owner_reply->owner);
|
||||
p_delete(&owner_reply);
|
||||
return false;
|
||||
}
|
||||
p_delete(&owner_reply);
|
||||
|
||||
xcb_set_selection_owner(wm.xcb_conn, tray.window, tray.selection_atom,
|
||||
XCB_CURRENT_TIME);
|
||||
|
||||
owner_cookie = xcb_get_selection_owner(wm.xcb_conn, tray.selection_atom);
|
||||
owner_reply = xcb_get_selection_owner_reply(wm.xcb_conn, owner_cookie, nullptr);
|
||||
bool success = owner_reply && owner_reply->owner == tray.window;
|
||||
p_delete(&owner_reply);
|
||||
|
||||
if (!success) logger("cannot acquire system tray selection\n");
|
||||
return success;
|
||||
}
|
||||
|
||||
static void tray_set_properties(void) {
|
||||
uint32_t orientation = TRAY_ORIENTATION_HORIZONTAL;
|
||||
xcb_change_property(wm.xcb_conn, XCB_PROP_MODE_REPLACE, tray.window,
|
||||
_NET_SYSTEM_TRAY_ORIENTATION, XCB_ATOM_CARDINAL, 32, 1,
|
||||
&orientation);
|
||||
|
||||
const color_t *fg = &wm.color_set.tag_color;
|
||||
uint32_t colors[12] = {
|
||||
tray_color_component_u16(fg->red),
|
||||
tray_color_component_u16(fg->green),
|
||||
tray_color_component_u16(fg->blue),
|
||||
tray_color_component_u16(fg->red),
|
||||
tray_color_component_u16(fg->green),
|
||||
tray_color_component_u16(fg->blue),
|
||||
tray_color_component_u16(fg->red),
|
||||
tray_color_component_u16(fg->green),
|
||||
tray_color_component_u16(fg->blue),
|
||||
tray_color_component_u16(fg->red),
|
||||
tray_color_component_u16(fg->green),
|
||||
tray_color_component_u16(fg->blue),
|
||||
};
|
||||
xcb_change_property(wm.xcb_conn, XCB_PROP_MODE_REPLACE, tray.window,
|
||||
_NET_SYSTEM_TRAY_COLORS, XCB_ATOM_CARDINAL, 32,
|
||||
countof(colors), colors);
|
||||
}
|
||||
|
||||
static void tray_announce_manager(void) {
|
||||
xcb_client_message_event_t ev;
|
||||
p_clear(&ev, 1);
|
||||
ev.response_type = XCB_CLIENT_MESSAGE;
|
||||
ev.window = wm.screen->root;
|
||||
ev.format = 32;
|
||||
ev.type = MANAGER;
|
||||
ev.data.data32[0] = XCB_CURRENT_TIME;
|
||||
ev.data.data32[1] = tray.selection_atom;
|
||||
ev.data.data32[2] = tray.window;
|
||||
|
||||
xcb_send_event(wm.xcb_conn, false, wm.screen->root,
|
||||
XCB_EVENT_MASK_STRUCTURE_NOTIFY, (char *)&ev);
|
||||
}
|
||||
|
||||
void tray_init(void) {
|
||||
if (tray.initialized) return;
|
||||
|
||||
tray.host_monitor = wm.monitor_list;
|
||||
if (tray.host_monitor == nullptr) return;
|
||||
|
||||
tray.side_padding = 0;
|
||||
tray.spacing = 0;
|
||||
|
||||
char selection_name[64];
|
||||
snprintf(selection_name, sizeof(selection_name), "_NET_SYSTEM_TRAY_S%d",
|
||||
wm.default_screen);
|
||||
tray.selection_atom = tray_intern_atom(selection_name);
|
||||
if (tray.selection_atom == XCB_ATOM_NONE) {
|
||||
logger("cannot intern tray selection atom %s\n", selection_name);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!tray_create_window()) return;
|
||||
if (!tray_take_selection_owner()) {
|
||||
tray_cleanup();
|
||||
return;
|
||||
}
|
||||
|
||||
tray.initialized = true;
|
||||
tray_set_properties();
|
||||
tray_announce_manager();
|
||||
tray_layout_icons((int16_t)tray.host_monitor->geometry.width);
|
||||
xcb_flush(wm.xcb_conn);
|
||||
}
|
||||
|
||||
void tray_cleanup(void) {
|
||||
for (size_t i = 0; i < tray.icon_count; i++) {
|
||||
tray_release_icon_window(&tray.icons[i]);
|
||||
}
|
||||
p_delete(&tray.icons);
|
||||
tray.icon_count = 0;
|
||||
tray.icon_capacity = 0;
|
||||
|
||||
if (tray.selection_atom != XCB_ATOM_NONE) {
|
||||
xcb_set_selection_owner(wm.xcb_conn, XCB_WINDOW_NONE, tray.selection_atom,
|
||||
XCB_CURRENT_TIME);
|
||||
}
|
||||
|
||||
if (tray.window != XCB_WINDOW_NONE) {
|
||||
xcb_destroy_window(wm.xcb_conn, tray.window);
|
||||
}
|
||||
|
||||
tray = (tray_t){
|
||||
.selection_atom = XCB_ATOM_NONE,
|
||||
.window = XCB_WINDOW_NONE,
|
||||
};
|
||||
}
|
||||
|
||||
bool tray_handle_client_message(xcb_client_message_event_t *ev) {
|
||||
if (!tray.initialized) return false;
|
||||
|
||||
if (ev->type != _NET_SYSTEM_TRAY_OPCODE) return false;
|
||||
if (ev->window != tray.window && ev->window != wm.screen->root) return false;
|
||||
|
||||
switch (ev->data.data32[1]) {
|
||||
case TRAY_REQUEST_DOCK:
|
||||
tray_add_icon((xcb_window_t)ev->data.data32[2]);
|
||||
break;
|
||||
case TRAY_BEGIN_MESSAGE:
|
||||
case TRAY_CANCEL_MESSAGE:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
xcb_flush(wm.xcb_conn);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool tray_handle_configure_request(xcb_configure_request_event_t *ev) {
|
||||
if (!tray.initialized) return false;
|
||||
|
||||
if (ev->window == tray.window) {
|
||||
tray_layout_icons((int16_t)tray.host_monitor->geometry.width);
|
||||
xcb_flush(wm.xcb_conn);
|
||||
return true;
|
||||
}
|
||||
|
||||
tray_icon_t *icon = tray_get_icon(ev->window);
|
||||
if (!icon) return false;
|
||||
|
||||
if ((ev->value_mask & XCB_CONFIG_WINDOW_WIDTH) && ev->width > 0) {
|
||||
icon->natural_width = ev->width;
|
||||
}
|
||||
if ((ev->value_mask & XCB_CONFIG_WINDOW_HEIGHT) && ev->height > 0) {
|
||||
icon->natural_height = ev->height;
|
||||
}
|
||||
|
||||
tray_request_redraw();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool tray_handle_map_request(xcb_map_request_event_t *ev) {
|
||||
if (!tray.initialized) return false;
|
||||
|
||||
if (ev->window == tray.window) {
|
||||
tray_layout_icons((int16_t)tray.host_monitor->geometry.width);
|
||||
xcb_flush(wm.xcb_conn);
|
||||
return true;
|
||||
}
|
||||
|
||||
tray_icon_t *icon = tray_get_icon(ev->window);
|
||||
if (!icon && ev->parent != tray.window) return false;
|
||||
|
||||
if (icon) icon->mapped = true;
|
||||
xcb_map_window(wm.xcb_conn, ev->window);
|
||||
tray_layout_icons((int16_t)tray.host_monitor->geometry.width);
|
||||
xcb_flush(wm.xcb_conn);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool tray_handle_destroy_notify(xcb_destroy_notify_event_t *ev) {
|
||||
if (!tray.initialized) return false;
|
||||
|
||||
if (ev->window == tray.window) {
|
||||
tray.window = XCB_WINDOW_NONE;
|
||||
tray.initialized = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
size_t index = tray_find_icon_index(ev->window);
|
||||
if (index == SIZE_MAX) return false;
|
||||
|
||||
tray_remove_icon_by_index(index, false);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool tray_handle_unmap_notify(xcb_unmap_notify_event_t *ev) {
|
||||
if (!tray.initialized) return false;
|
||||
|
||||
tray_icon_t *icon = tray_get_icon(ev->window);
|
||||
if (!icon) return ev->window == tray.window;
|
||||
|
||||
if (icon->ignore_unmaps > 0) {
|
||||
icon->ignore_unmaps--;
|
||||
return true;
|
||||
}
|
||||
|
||||
size_t index = tray_find_icon_index(ev->window);
|
||||
if (index != SIZE_MAX) tray_remove_icon_by_index(index, true);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool tray_handle_property_notify(xcb_property_notify_event_t *ev) {
|
||||
if (!tray.initialized) return false;
|
||||
return tray_is_window(ev->window);
|
||||
}
|
||||
|
||||
bool tray_handle_selection_clear(xcb_selection_clear_event_t *ev) {
|
||||
if (!tray.initialized) return false;
|
||||
if (ev->owner != tray.window) return false;
|
||||
if (ev->selection != tray.selection_atom) return false;
|
||||
|
||||
monitor_t *host_monitor = tray.host_monitor;
|
||||
logger("system tray selection lost\n");
|
||||
tray_cleanup();
|
||||
|
||||
if (host_monitor != nullptr) monitor_draw_bar(host_monitor);
|
||||
xcb_flush(wm.xcb_conn);
|
||||
return true;
|
||||
}
|
||||
|
||||
uint16_t tray_get_width(monitor_t *monitor) {
|
||||
if (!tray.initialized || monitor == nullptr || monitor != tray.host_monitor) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return tray_compute_width();
|
||||
}
|
||||
|
||||
void tray_place(monitor_t *monitor, int16_t right_edge) {
|
||||
if (!tray.initialized || monitor == nullptr || monitor != tray.host_monitor) {
|
||||
return;
|
||||
}
|
||||
|
||||
tray_layout_icons(right_edge);
|
||||
}
|
||||
|
||||
bool tray_is_window(xcb_window_t window) {
|
||||
if (window == XCB_WINDOW_NONE) return false;
|
||||
if (tray.window == window) return true;
|
||||
return tray_get_icon(window) != nullptr;
|
||||
}
|
||||
21
src/tray.h
Normal file
21
src/tray.h
Normal file
@@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <xcb/xproto.h>
|
||||
|
||||
#include "types.h"
|
||||
|
||||
void tray_init(void);
|
||||
void tray_cleanup(void);
|
||||
|
||||
bool tray_handle_client_message(xcb_client_message_event_t *ev);
|
||||
bool tray_handle_configure_request(xcb_configure_request_event_t *ev);
|
||||
bool tray_handle_map_request(xcb_map_request_event_t *ev);
|
||||
bool tray_handle_destroy_notify(xcb_destroy_notify_event_t *ev);
|
||||
bool tray_handle_unmap_notify(xcb_unmap_notify_event_t *ev);
|
||||
bool tray_handle_property_notify(xcb_property_notify_event_t *ev);
|
||||
bool tray_handle_selection_clear(xcb_selection_clear_event_t *ev);
|
||||
|
||||
uint16_t tray_get_width(monitor_t *monitor);
|
||||
void tray_place(monitor_t *monitor, int16_t right_edge);
|
||||
bool tray_is_window(xcb_window_t window);
|
||||
@@ -38,6 +38,7 @@ struct client_t {
|
||||
bool minimize;
|
||||
bool sticky;
|
||||
bool urgent;
|
||||
bool skip_taskbar;
|
||||
bool size_freeze; /* 尺寸冻结窗口一定是浮动窗口 */
|
||||
|
||||
char *class, *instance;
|
||||
|
||||
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);
|
||||
67
src/wm.c
67
src/wm.c
@@ -26,13 +26,17 @@
|
||||
#include "buffer.h"
|
||||
#include "client.h"
|
||||
#include "color.h"
|
||||
#include "config.h"
|
||||
#include "default_config.h"
|
||||
#include "event.h"
|
||||
#include "image.h"
|
||||
#include "monitor.h"
|
||||
#include "status.h"
|
||||
#include "text.h"
|
||||
#include "tray.h"
|
||||
#include "types.h"
|
||||
#include "utils.h"
|
||||
#include "wallpaper.h"
|
||||
#include "xcursor.h"
|
||||
#include "xkb.h"
|
||||
#include "xres.h"
|
||||
@@ -45,6 +49,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);
|
||||
@@ -52,6 +57,7 @@ static void wm_update_status(status_t *status);
|
||||
static void wm_run_autostart(const char *const commands[]);
|
||||
static void run_once(const char *command);
|
||||
static bool command_already_running(const char *command);
|
||||
static gboolean clear_ignore_enter_notify(gpointer data);
|
||||
|
||||
wm_t wm;
|
||||
|
||||
@@ -76,6 +82,39 @@ static void signal_fatal(int signal_number) {
|
||||
|
||||
static guint sources[3] = {0};
|
||||
|
||||
static gboolean clear_ignore_enter_notify(gpointer data) {
|
||||
wm.ignore_enter_notify = false;
|
||||
wm.ignored_enter_notify_point = (point_t){0};
|
||||
wm.clear_ignore_enter_notify_source = 0;
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
|
||||
void wm_ignore_enter_notify_at_point(point_t point) {
|
||||
wm.ignore_enter_notify = true;
|
||||
wm.ignored_enter_notify_point = point;
|
||||
if (wm.clear_ignore_enter_notify_source) {
|
||||
g_source_remove(wm.clear_ignore_enter_notify_source);
|
||||
}
|
||||
wm.clear_ignore_enter_notify_source =
|
||||
g_idle_add(clear_ignore_enter_notify, nullptr);
|
||||
}
|
||||
|
||||
bool wm_should_ignore_enter_notify(const xcb_enter_notify_event_t *ev) {
|
||||
if (!wm.ignore_enter_notify) return false;
|
||||
if (ev->root_x != wm.ignored_enter_notify_point.x ||
|
||||
ev->root_y != wm.ignored_enter_notify_point.y) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (wm.clear_ignore_enter_notify_source) {
|
||||
g_source_remove(wm.clear_ignore_enter_notify_source);
|
||||
wm.clear_ignore_enter_notify_source = 0;
|
||||
}
|
||||
wm.ignore_enter_notify = false;
|
||||
wm.ignored_enter_notify_point = (point_t){0};
|
||||
return true;
|
||||
}
|
||||
|
||||
void wm_setup_signal(void) {
|
||||
sources[0] = g_unix_signal_add(SIGINT, exit_on_signal, nullptr);
|
||||
sources[1] = g_unix_signal_add(SIGTERM, exit_on_signal, nullptr);
|
||||
@@ -377,8 +416,13 @@ void wm_scan_clients(void) {
|
||||
}
|
||||
|
||||
void wm_clean(void) {
|
||||
wallpaper_clean(wm.wallpaper);
|
||||
wm.wallpaper = nullptr;
|
||||
|
||||
clean_status();
|
||||
text_clean_pango_layout();
|
||||
image_cache_clean();
|
||||
tray_cleanup();
|
||||
|
||||
{
|
||||
client_t *c = wm.client_stack_list;
|
||||
@@ -395,6 +439,12 @@ void wm_clean(void) {
|
||||
guint source_id = sources[i];
|
||||
if (source_id) g_source_remove(source_id);
|
||||
}
|
||||
if (wm.clear_ignore_enter_notify_source) {
|
||||
g_source_remove(wm.clear_ignore_enter_notify_source);
|
||||
wm.clear_ignore_enter_notify_source = 0;
|
||||
}
|
||||
wm.ignore_enter_notify = false;
|
||||
wm.ignored_enter_notify_point = (point_t){0};
|
||||
|
||||
xcb_delete_property(wm.xcb_conn, wm.screen->root, _NET_ACTIVE_WINDOW);
|
||||
xcb_delete_property(wm.xcb_conn, wm.screen->root, _NET_SUPPORTING_WM_CHECK);
|
||||
@@ -412,6 +462,9 @@ void wm_clean(void) {
|
||||
}
|
||||
|
||||
static void wm_setup(void) {
|
||||
wm.config = init_config();
|
||||
if (!wm.config) fatal("cannot init config");
|
||||
|
||||
wm.layout_list = layout_list;
|
||||
wm.layout_count = (uint16_t)countof(layout_list);
|
||||
|
||||
@@ -446,6 +499,7 @@ static void wm_setup(void) {
|
||||
|
||||
wm_setup_keybindings();
|
||||
atoms_init(wm.xcb_conn);
|
||||
tray_init();
|
||||
|
||||
{
|
||||
wm.wm_check_window = xcb_generate_id(wm.xcb_conn);
|
||||
@@ -471,6 +525,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 +748,7 @@ int main(int argc, char *argv[]) {
|
||||
|
||||
wm_detect_monitor();
|
||||
wm_setup();
|
||||
wm_init_wallpaper();
|
||||
setup_event_loop();
|
||||
wm_scan_clients();
|
||||
|
||||
|
||||
11
src/wm.h
11
src/wm.h
@@ -8,6 +8,9 @@
|
||||
#include "base.h"
|
||||
#include "status.h"
|
||||
#include "types.h"
|
||||
#include "wallpaper.h"
|
||||
|
||||
typedef struct config_t config_t;
|
||||
|
||||
typedef struct wm_t {
|
||||
padding_t padding;
|
||||
@@ -18,12 +21,16 @@ typedef struct wm_t {
|
||||
bool need_restart;
|
||||
GMainLoop *loop;
|
||||
status_t *status;
|
||||
const config_t *config;
|
||||
|
||||
client_t *client_list;
|
||||
client_t *client_stack_list;
|
||||
client_t *client_focused;
|
||||
monitor_t *monitor_list;
|
||||
monitor_t *current_monitor;
|
||||
bool ignore_enter_notify;
|
||||
point_t ignored_enter_notify_point;
|
||||
guint clear_ignore_enter_notify_source;
|
||||
const layout_t *layout_list;
|
||||
|
||||
char *font_family;
|
||||
@@ -49,6 +56,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;
|
||||
|
||||
@@ -57,6 +66,8 @@ extern wm_t wm;
|
||||
void wm_restart(void);
|
||||
void wm_quit(void);
|
||||
void wm_restack_clients(void);
|
||||
void wm_ignore_enter_notify_at_point(point_t point);
|
||||
bool wm_should_ignore_enter_notify(const xcb_enter_notify_event_t *ev);
|
||||
void wm_set_current_monitor(monitor_t *monitor, bool restore_cursor);
|
||||
monitor_t *__attribute__((returns_nonnull)) wm_get_monitor_by_area(area_t area);
|
||||
monitor_t *__attribute__((returns_nonnull)) wm_get_monitor_by_point(
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "xcursor.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <xcb/xcb.h>
|
||||
#include <xcb/xcb_cursor.h>
|
||||
#include <xcb/xproto.h>
|
||||
|
||||
@@ -143,4 +144,5 @@ void xcursor_set_pointer_position(point_t point) {
|
||||
xcb_window_t window = XCB_WINDOW_NONE;
|
||||
xcb_window_t root = wm.screen->root;
|
||||
xcb_warp_pointer(wm.xcb_conn, window, root, 0, 0, 1, 1, point.x, point.y);
|
||||
xcb_flush(wm.xcb_conn);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user