diff --git a/CMakeLists.txt b/CMakeLists.txt index 8315e69..9087f6c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -31,6 +31,9 @@ add_executable(${APP_NAME} ${SOURCE_DIR}/backend/x11/event.c ${SOURCE_DIR}/backend/x11/window.c + ${SOURCE_DIR}/bar/cell.c + ${SOURCE_DIR}/bar/workspaces.c + ${SOURCE_DIR}/base/color.c ${SOURCE_DIR}/base/log.c ${SOURCE_DIR}/base/process.c diff --git a/include/zdwm/bar.h b/include/zdwm/bar.h new file mode 100644 index 0000000..de973f8 --- /dev/null +++ b/include/zdwm/bar.h @@ -0,0 +1,75 @@ +#ifndef ZDWM_BAR_H +#define ZDWM_BAR_H + +#include +#include +#include +#include + +#if defined(__cplusplus) +extern "C" { +#endif + +typedef struct zdwm_bar_config_t { + int32_t height; + bool show_top; /* 如果为 false 则 bar 显示在屏幕底部 */ + uint8_t padding_x; + uint16_t fps; + const char *font_family; + uint32_t font_size; + uint32_t dpi; + + const char *bg; + const char *fg; + const char *layout_bg; + const char *layout_fg; + const char *binding_mode_bg; + const char *binding_mode_fg; + const char *window_bg; + const char *window_fg; + const char *window_focused_bg; + const char *window_focused_fg; +} zdwm_bar_config_t; + +typedef struct zdwm_bar_item_t zdwm_bar_item_t; + +typedef struct zdwm_bar_cell_api_t { + size_t (*get_cell_count)(zdwm_bar_item_t *item); + void (*set_cell_count)(zdwm_bar_item_t *item, size_t count); + + void (*cell_set_text)(zdwm_bar_item_t *item, size_t index, const char *text); + void (*cell_set_bg)(zdwm_bar_item_t *item, size_t index, const char *color); + void (*cell_set_fg)(zdwm_bar_item_t *item, size_t index, const char *color); + void (*cell_set_icon)(zdwm_bar_item_t *item, size_t index, zdwm_icon_t icon); + void (*cell_set_indicator)(zdwm_bar_item_t *item, size_t index, bool show); +} zdwm_bar_cell_api_t; + +typedef struct zdwm_bar_item_type_t { + void *(*create_state)(zdwm_output_id_t output_id, void *config); + void (*update)( + zdwm_bar_item_t *item, + const zdwm_bar_cell_api_t *cells, + void *state + ); + zdwm_action_t (*on_click)( + zdwm_bar_item_t *item, + size_t cell_index, + int32_t x, + void *state + ); + void (*destroy_state)(void *state); + uint32_t update_interval_ms; +} zdwm_bar_item_type_t; + +typedef enum zdwm_bar_side_type_t { + ZDWM_BAR_SIDE_LEFT, + ZDWM_BAR_SIDE_RIGHT, + ZDWM_BAR_SIDE_CENTER_REST, /* 中央剩余部分,用于显示窗口列表 */ + ZDWM_BAR_SIDE_COUNT, +} zdwm_bar_side_type_t; + +#if defined(__cplusplus) +} +#endif + +#endif /* ZDWM_BAR_H */ diff --git a/include/zdwm/types.h b/include/zdwm/types.h index 3a2bbea..c208fbe 100644 --- a/include/zdwm/types.h +++ b/include/zdwm/types.h @@ -30,6 +30,19 @@ typedef struct zdwm_output_info_t { zdwm_rect_t geometry; } zdwm_output_info_t; +typedef enum zdwm_icon_type_t { + ZDWM_ICON_TEXT, + ZDWM_ICON_IMAGE, +} zdwm_icon_type_t; + +typedef struct zdwm_icon_t { + zdwm_icon_type_t type; + union { + const char *text; + const char *image_path; + } as; +} zdwm_icon_t; + #if defined(__cplusplus) } #endif diff --git a/src/bar/.clang-format b/src/bar/.clang-format new file mode 120000 index 0000000..2d11237 --- /dev/null +++ b/src/bar/.clang-format @@ -0,0 +1 @@ +../../.clang-format \ No newline at end of file diff --git a/src/bar/bar.h b/src/bar/bar.h new file mode 100644 index 0000000..e3b642e --- /dev/null +++ b/src/bar/bar.h @@ -0,0 +1,22 @@ +#pragma once + +#include +#include +#include + +#include "bar/types.h" + +typedef struct bar_output_t { + zdwm_window_id_t window_id; + zdwm_output_id_t output_id; + bar_side_t sides[ZDWM_BAR_SIDE_COUNT]; +} bar_output_t; + +typedef struct bar_t { + bar_output_t *bars; + size_t count; + + bool visible; + + zdwm_bar_config_t config; +} bar_t; diff --git a/src/bar/cell.c b/src/bar/cell.c new file mode 100644 index 0000000..90819f7 --- /dev/null +++ b/src/bar/cell.c @@ -0,0 +1,142 @@ +#include "bar/cell.h" + +#include +#include +#include +#include + +#include "bar/types.h" +#include "base/array.h" +#include "base/color.h" +#include "base/memory.h" + +typedef struct color_cache_item_t { + const char *text; + color_t color; +} color_cache_item_t; + +typedef struct color_cache_t { + color_cache_item_t *items; + size_t count; + size_t capacity; +} color_cache_t; + +static color_cache_t cache = {}; + +static color_t *find_or_insert_color(const char *text) { + assert(text); + for (size_t i = 0; i < cache.count; ++i) { + auto item = &cache.items[i]; + if (text == item->text || strcasecmp(text, item->text) == 0) { + return &item->color; + } + } + + auto slot = array_push(cache.items, cache.count, cache.capacity); + slot->text = p_strdup(text); + color_parse(text, &slot->color); + return &slot->color; +} + +void bar_cell_reset_color_cache(void) { + for (size_t i = 0; i < cache.count; ++i) { + auto item = &cache.items[i]; + p_delete(&item->text); + } + p_clear(cache.items, cache.count); + cache.count = 0; +} + +void bar_cell_clean_color_cache(void) { + bar_cell_reset_color_cache(); + p_delete(&cache.items); + cache.capacity = 0; +} + +size_t bar_cell_get_count(zdwm_bar_item_t *item) { return item->count; } + +void bar_cell_set_count(zdwm_bar_item_t *item, size_t count) { + if (item->count == count) return; + + item->count = count; + p_realloc(&item->cells, count); + p_clear(item->cells, count); + item->dirty = true; +} + +void bar_cell_set_text(zdwm_bar_item_t *item, size_t index, const char *text) { + if (index >= item->count) return; + + auto cell = &item->cells[index]; + if (cell->text == text || strcmp(cell->text, text) == 0) return; + + cell->text = text; + + cell->dirty = true; + item->dirty = true; +} + +void bar_cell_set_bg(zdwm_bar_item_t *item, size_t index, const char *color) { + if (index >= item->count) return; + + auto cell = &item->cells[index]; + auto cached_color = find_or_insert_color(color); + if (cell->bg == cached_color) return; + + cell->bg = cached_color; + + cell->dirty = true; + item->dirty = true; +} + +void bar_cell_set_fg(zdwm_bar_item_t *item, size_t index, const char *color) { + if (index >= item->count) return; + + auto cell = &item->cells[index]; + auto cached_color = find_or_insert_color(color); + if (cell->fg == cached_color) return; + + cell->fg = cached_color; + + cell->dirty = true; + item->dirty = true; +} + +void bar_cell_set_icon(zdwm_bar_item_t *item, size_t index, zdwm_icon_t icon) { + if (index >= item->count) return; + + auto cell = &item->cells[index]; + auto icon_ptr = &cell->icon; + + if (icon_ptr->type == icon.type) { + switch (icon.type) { + case ZDWM_ICON_TEXT: + if (icon_ptr->as.text == icon.as.text || + strcmp(icon_ptr->as.text, icon.as.text) == 0) { + return; + } + case ZDWM_ICON_IMAGE: + if (icon_ptr->as.image_path == icon.as.image_path || + strcmp(icon_ptr->as.image_path, icon.as.image_path) == 0) { + return; + } + } + } + + *icon_ptr = icon; + + cell->dirty = true; + item->dirty = true; +} + +void bar_cell_set_indicator(zdwm_bar_item_t *item, size_t index, bool show) { + if (index >= item->count) return; + + auto cell = &item->cells[index]; + if (cell->show_indicator == show) return; + + cell->show_indicator = show; + + cell->dirty = true; + item->dirty = true; +} diff --git a/src/bar/cell.h b/src/bar/cell.h new file mode 100644 index 0000000..d2e781c --- /dev/null +++ b/src/bar/cell.h @@ -0,0 +1,14 @@ +#pragma once + +#include +#include +#include + +size_t bar_cell_get_count(zdwm_bar_item_t *item); +void bar_cell_set_count(zdwm_bar_item_t *item, size_t count); + +void bar_cell_set_text(zdwm_bar_item_t *item, size_t index, const char *text); +void bar_cell_set_bg(zdwm_bar_item_t *item, size_t index, const char *color); +void bar_cell_set_fg(zdwm_bar_item_t *item, size_t index, const char *color); +void bar_cell_set_icon(zdwm_bar_item_t *item, size_t index, zdwm_icon_t icon); +void bar_cell_set_indicator(zdwm_bar_item_t *item, size_t index, bool show); diff --git a/src/bar/types.h b/src/bar/types.h new file mode 100644 index 0000000..1073b65 --- /dev/null +++ b/src/bar/types.h @@ -0,0 +1,50 @@ +#pragma once + +#include +#include +#include +#include + +#include "base/color.h" + +typedef struct bar_x_region_t { + int32_t start; + int32_t end; +} bar_x_region_t; + +typedef struct bar_cell_t { + zdwm_icon_t icon; + const char *text; + const color_t *fg; + const color_t *bg; + bar_x_region_t region; + bool show_indicator; + bool dirty; +} bar_cell_t; + +struct zdwm_bar_item_t { + bar_cell_t *cells; + size_t count; + bar_x_region_t region; + void *state; + uint8_t cell_padding; + uint8_t indicator_width; + bool dirty; +}; + +typedef struct bar_side_t { + zdwm_bar_side_type_t type; + zdwm_bar_item_t *items; + size_t count; + size_t capacity; + bar_x_region_t region; +} bar_side_t; + +typedef struct bars_t { + size_t count; + int32_t height; + bool show_top; /* 如果为 false 则 bar 显示屏幕底部 */ + bool visible; + + zdwm_bar_config_t config; +} bars_t; diff --git a/src/bar/workspaces.c b/src/bar/workspaces.c new file mode 100644 index 0000000..17b6dd0 --- /dev/null +++ b/src/bar/workspaces.c @@ -0,0 +1,224 @@ +#include "bar/workspaces.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "base/array.h" +#include "base/memory.h" + +static size_t state_workspace_get_urgent_window_count( + const bar_workspace_state_t *state, + zdwm_workspace_id_t workspace_id +) { + size_t count = 0; + + for (size_t i = 0; i < state->window_count; ++i) { + auto window = &state->windows[i]; + if (window->workspace == workspace_id && window->urgent) { + count++; + } + } + + return count; +} + +static size_t state_workspace_get_window_count( + const bar_workspace_state_t *state, + zdwm_workspace_id_t workspace_id +) { + size_t count = 0; + + for (size_t i = 0; i < state->window_count; ++i) { + auto window = &state->windows[i]; + if (window->workspace == workspace_id && !window->skip_taskbar) { + count++; + } + } + + return count; +} + +static zdwm_window_t *state_get_window( + const bar_workspace_state_t *state, + zdwm_window_id_t window_id +) { + for (size_t i = 0; i < state->window_count; ++i) { + auto window = &state->windows[i]; + if (window->id == window_id) return window; + } + + return nullptr; +} + +void *bar_workspaces_create_state(zdwm_output_id_t output_id, void *config) { + const bar_workspace_palette_t *palette = config; + assert(palette); + + auto state = p_new(bar_workspace_state_t, 1); + state->output_id = output_id; + state->palette = *palette; + + return state; +} + +void bar_workspaces_update( + zdwm_bar_item_t *item, + const zdwm_bar_cell_api_t *cell_api, + void *state +) { + auto data = (bar_workspace_state_t *)state; + if (!data->list_inited || !data->active_inited || !data->dirty) return; + + if (data->count != cell_api->get_cell_count(item)) { + cell_api->set_cell_count(item, data->count); + } + + auto palette = &data->palette; + for (size_t i = 0; i < data->count; ++i) { + auto workspace = &data->workspaces[i]; + auto info = &workspace->info; + + workspace->window_count = state_workspace_get_window_count(state, info->id); + workspace->urgent_window_count = + state_workspace_get_urgent_window_count(state, info->id); + + bool show_indicator = workspace->window_count; + cell_api->cell_set_indicator(item, i, show_indicator); + + cell_api->cell_set_text(item, i, info->name); + if (info->id == data->workspace_id) { + cell_api->cell_set_bg(item, i, palette->active_bg); + cell_api->cell_set_fg(item, i, palette->active_fg); + continue; + } + + if (workspace->urgent_window_count > 0) { + cell_api->cell_set_bg(item, i, palette->urgent_bg); + cell_api->cell_set_fg(item, i, palette->urgent_fg); + continue; + } + + cell_api->cell_set_bg(item, i, palette->bg); + cell_api->cell_set_fg(item, i, palette->fg); + } + + data->dirty = false; +} + +zdwm_action_t bar_workspace_on_click( + zdwm_bar_item_t *item, + size_t cell_index, + int32_t x, + void *state +) { + auto data = (bar_workspace_state_t *)state; + auto workspace = &data->workspaces[cell_index]; + + if (workspace->info.id == data->workspace_id) { + return (zdwm_action_t){.type = ZDWM_ACTION_NONE}; + } + + return (zdwm_action_t){ + .type = ZDWM_ACTION_WORKSPACE_SWITCH, + .as.switch_workspace.workspace = workspace->info.id, + }; +} + +void bar_workspace_destroy_state(void *state) { + auto data = (bar_workspace_state_t *)state; + p_delete(&data->workspaces); + p_clear(data, 1); +} + +void bar_workspace_list_filter( + const zdwm_workspace_t *list, + size_t count, + void *user_data +) { + auto state = (bar_workspace_state_t *)user_data; + + for (size_t i = 0; i < count; ++i) { + auto workspace = &list[i]; + if (workspace->output_id != state->output_id) continue; + + auto item = array_push(state->workspaces, state->count, state->capacity); + p_clear(item, 1); + item->info = *workspace; + } + + state->list_inited = true; + state->dirty = true; +} + +void bar_workspace_active_updated( + zdwm_output_id_t output_id, + zdwm_workspace_id_t workspace_id, + void *user_data +) { + auto state = (bar_workspace_state_t *)user_data; + if (state->output_id != output_id) return; + if (state->workspace_id == workspace_id) return; + + state->workspace_id = workspace_id; + + state->active_inited = true; + state->dirty = true; +} + +void bar_workspace_initial_windows( + const zdwm_window_t *list, + size_t count, + void *user_data +) { + for (size_t i = 0; i < count; ++i) { + auto window = &list[i]; + bar_workspace_window_added(window, user_data); + } +} + +void bar_workspace_window_added(const zdwm_window_t *window, void *user_data) { + auto state = (bar_workspace_state_t *)user_data; + + auto win = + array_push(state->windows, state->window_count, state->window_capacity); + *win = *window; + + state->dirty = true; +} + +void bar_workspace_window_updated( + const zdwm_window_t *window, + void *user_data +) { + auto state = (bar_workspace_state_t *)user_data; + + auto old_window = state_get_window(state, window->id); + *old_window = *window; + state->dirty = true; +} + +void bar_workspace_window_removed(zdwm_window_id_t window_id, void *user_data) { + auto state = (bar_workspace_state_t *)user_data; + + bool found = false; + size_t index = 0; + + for (size_t i = 0; i < state->window_count; ++i) { + auto window = &state->windows[i]; + if (window->id == window_id) { + found = true; + index = i; + } + } + + if (!found) return; + + if (array_erase(state->windows, state->window_count, index)) { + state->dirty = true; + } +} diff --git a/src/bar/workspaces.h b/src/bar/workspaces.h new file mode 100644 index 0000000..066dd4b --- /dev/null +++ b/src/bar/workspaces.h @@ -0,0 +1,73 @@ +#pragma once + +#include +#include +#include +#include +#include + +typedef struct bar_workspace_palette_t { + const char *bg; + const char *fg; + const char *active_bg; + const char *active_fg; + const char *urgent_bg; + const char *urgent_fg; +} bar_workspace_palette_t; + +typedef struct bar_workspace_t { + zdwm_workspace_t info; + size_t window_count; + size_t urgent_window_count; +} bar_workspace_t; + +typedef struct bar_workspace_state_t { + zdwm_output_id_t output_id; + zdwm_workspace_id_t workspace_id; + bar_workspace_t *workspaces; + size_t count; + size_t capacity; + + zdwm_window_t *windows; + size_t window_count; + size_t window_capacity; + + bar_workspace_palette_t palette; + + bool list_inited; + bool active_inited; + bool dirty; +} bar_workspace_state_t; + +void *bar_workspaces_create_state(zdwm_output_id_t output_id, void *config); +void bar_workspaces_update( + zdwm_bar_item_t *item, + const zdwm_bar_cell_api_t *cell_api, + void *state +); +zdwm_action_t bar_workspace_on_click( + zdwm_bar_item_t *item, + size_t cell_index, + int32_t x, + void *state +); +void bar_workspace_destroy_state(void *state); + +void bar_workspace_list_filter( + const zdwm_workspace_t *list, + size_t count, + void *user_data +); +void bar_workspace_active_updated( + zdwm_output_id_t output_id, + zdwm_workspace_id_t workspace_id, + void *user_data +); +void bar_workspace_initial_windows( + const zdwm_window_t *list, + size_t count, + void *user_data +); +void bar_workspace_window_added(const zdwm_window_t *window, void *user_data); +void bar_workspace_window_updated(const zdwm_window_t *window, void *user_data); +void bar_workspace_window_removed(zdwm_window_id_t window_id, void *user_data);