diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 9fd169f..e70fa69 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -27,6 +27,9 @@ add_executable(${APP_NAME} monitor.c client.c text.c + color.c + xcursor.c + xwindow.c ) # use C23 @@ -37,12 +40,16 @@ find_package(PkgConfig REQUIRED) pkg_check_modules(deps REQUIRED glib-2.0 + x11 xcb xcb-aux + xcb-cursor xcb-randr xcb-xfixes xcb-xinerama + cairo + cairo-xcb pango pangocairo ) diff --git a/src/color.c b/src/color.c new file mode 100644 index 0000000..d0d3f36 --- /dev/null +++ b/src/color.c @@ -0,0 +1,87 @@ +#include "color.h" + +#include "utils.h" + +/** + * @brief 将 16 进制的颜色配置转换成 uint32 格式的 rgba数据 + * + * @param hex 16 进制的颜色表示,支持的格式有 + * RGB/RGBA/RRGGBB/RRGGBBAA/#RGB/#RGBA/#RRGGBB/#RRGGBBAA + * @param rgba 返回数据的指针,解析结果会放到这个指针指向的内存中 + * @return 如果 hex 是一个能正常解析的颜色,返回 true ,否则返回 false + */ +static bool hex_color_to_rgba(const char *hex, uint32_t *rgba); +/** + * @brief 将 rgba 通道顺序的颜色转换成 argb 顺序 + * @description xcb 库用到的颜色需要是 argb 通道顺序 + */ +static uint32_t rgba_to_argb(uint32_t rgba); +/** + * @brief 提取颜色中的各个颜色通道数值 + * @param rgba uint32 表示的颜色 + * @param red red channel, range 0~1 + * @param green green channel, range 0~1 + * @param blue blue channel, range 0~1 + * @param alpha alpha channel, range 0~1 + */ +static void extract_color_channel(const uint32_t rgba, double *red, + double *green, double *blue, double *alpha); + +void color_parse(const char *hex, color_t *const color) { + bool success = hex_color_to_rgba(hex, &color->rgba); + if (!success) fatal("invalid hex color: %s", hex); + + color->argb = rgba_to_argb(color->rgba); + extract_color_channel(color->rgba, &color->red, &color->green, &color->blue, + &color->alpha); +} + +bool hex_color_to_rgba(const char *hex, uint32_t *rgba) { + if (hex[0] == '#') hex++; + + size_t len = strlen(hex); + char extended[9] = {0}; + uint32_t color = 0x00000000; + + if (len == 3 || len == 4) { + extended[0] = extended[1] = hex[0]; + extended[2] = extended[3] = hex[1]; + extended[4] = extended[5] = hex[2]; + if (len == 3) { + extended[6] = extended[7] = 'F'; + } else { + extended[6] = extended[7] = hex[3]; + } + } else if (len == 6) { + strncpy(extended, hex, len); + extended[6] = extended[7] = 'F'; + } else if (len == 8) { + strncpy(extended, hex, len); + } else { + return false; + } + + color = strtoul(extended, NULL, 16) & 0xffffffff; + *rgba = color; + return true; +} + +#define EXTRACE_COLOR_BIT(C, R) (((C) >> (R)) & 0xff) +uint32_t rgba_to_argb(uint32_t rgba) { + uint32_t r = EXTRACE_COLOR_BIT(rgba, 24); + uint32_t g = EXTRACE_COLOR_BIT(rgba, 16); + uint32_t b = EXTRACE_COLOR_BIT(rgba, 8); + uint32_t a = EXTRACE_COLOR_BIT(rgba, 0); + + uint32_t argb = (a << 24) | (r << 16) | (g << 8) | b; + return argb; +} + +#define COLOR_SPLIT(C, R) (EXTRACE_COLOR_BIT((C), (R)) / (double)0xff) +void extract_color_channel(const uint32_t rgba, double *red, double *green, + double *blue, double *alpha) { + *red = COLOR_SPLIT(rgba, 24); + *green = COLOR_SPLIT(rgba, 16); + *blue = COLOR_SPLIT(rgba, 8); + *alpha = COLOR_SPLIT(rgba, 0); +} diff --git a/src/color.h b/src/color.h index 746261c..ec7f0d7 100644 --- a/src/color.h +++ b/src/color.h @@ -19,4 +19,4 @@ typedef struct color_t { have: RGB/RGBA/RRGGBB/RRGGBBAA/#RGB/#RGBA/#RRGGBB/#RRGGBBAA * @param color return the parsed color */ -void parse_color(const char *hex, color_t *const color); +void color_parse(const char *hex, color_t *const color); diff --git a/src/default_config.h b/src/default_config.h index 7c414a4..33bba19 100644 --- a/src/default_config.h +++ b/src/default_config.h @@ -1,6 +1,17 @@ #include +const char *font_family = "monospace"; +const int font_size = 10; +const int default_dpi = 144; + static const char *const tags[] = { "1", "2", "3", "4", "5", "6", "7", "8", "9", nullptr, }; -static const uint32_t bar_y_padding = 1; +static const uint16_t bar_y_padding = 1; +static const uint16_t tag_x_padding = 10; + +static const char *const bar_bg = "#222222"; +static const char *const tag_bg = "#222222"; +static const char *const active_tag_bg = "#005577"; +static const char *const tag_color = "#bbbbbb"; +static const char *const active_tag_color = "#eeeeee"; diff --git a/src/monitor.c b/src/monitor.c index 2d3898d..f552e8f 100644 --- a/src/monitor.c +++ b/src/monitor.c @@ -1,11 +1,23 @@ #include "monitor.h" +#include +#include +#include #include +#include +#include "base.h" +#include "color.h" +#include "text.h" #include "types.h" #include "utils.h" +#include "wm.h" +#include "xcursor.h" +#include "xwindow.h" -void monitor_initialize_tag(monitor_t *monitor, const char **tags) { +uint32_t monitor_initialize_tag(monitor_t *monitor, const char **tags, + uint32_t tag_index_start_at) { + uint32_t tag_count = 0; int i = 0; const char *tag_name = nullptr; tag_t *prev_tag = nullptr; @@ -13,6 +25,7 @@ void monitor_initialize_tag(monitor_t *monitor, const char **tags) { tag_t *tag = p_new(tag_t, 1); tag->name = strdup(tag_name); tag->mask = 1u << i; + tag->index = tag_index_start_at + tag_count; if (prev_tag) { prev_tag->next = tag; @@ -21,9 +34,11 @@ void monitor_initialize_tag(monitor_t *monitor, const char **tags) { } prev_tag = tag; + tag_count++; } monitor->selected_tag = monitor->tag_list; + return tag_count; } static void tag_clean(tag_t *tag) { @@ -45,7 +60,117 @@ void monitor_clean(monitor_t *monitor) { m->selected_tag = nullptr; m->tag_list = nullptr; + cairo_destroy(m->bar_cr); + m->bar_cr = nullptr; + next_monitor = m->next; p_delete(&m); } } + +void monitor_init_bar(monitor_t *monitor) { + static xcb_colormap_t colormap = XCB_NONE; + visual_t *visual = xwindow_get_xcb_visual(true); + xcb_visualid_t visual_id = visual->visual->visual_id; + xcb_connection_t *conn = wm.xcb_conn; + if (colormap == XCB_NONE) { + colormap = xcb_generate_id(conn); + uint8_t alloc = XCB_COLORMAP_ALLOC_NONE; + xcb_create_colormap(conn, alloc, colormap, wm.screen->root, visual_id); + } + + xcb_window_t window = xcb_generate_id(conn); + uint32_t value_mask = XCB_CW_OVERRIDE_REDIRECT | XCB_CW_BACK_PIXEL | + XCB_CW_BORDER_PIXEL | XCB_CW_EVENT_MASK | + XCB_CW_CURSOR | XCB_CW_COLORMAP; + const xcb_create_window_value_list_t value_list = { + .override_redirect = true, + .background_pixel = wm.color_set.bar_bg.argb, + .border_pixel = 0, + .event_mask = XCB_EVENT_MASK_BUTTON_PRESS | XCB_EVENT_MASK_EXPOSURE, + .cursor = xcursor_get_xcb_cursor(cursor_normal), + .colormap = colormap, + }; + xcb_void_cookie_t cookie; + cookie = xcb_create_window_aux_checked( + conn, visual->depth, window, wm.screen->root, monitor->geometry.x, + monitor->geometry.y, monitor->geometry.width, wm.bar_height, 0, + XCB_WINDOW_CLASS_INPUT_OUTPUT, visual_id, value_mask, &value_list); + if (xcb_request_check(conn, cookie)) fatal("cannot create bar window"); + + cookie = xcb_map_window(conn, window); + if (xcb_request_check(conn, cookie)) fatal("cannot map bar window:"); + + xcb_aux_sync(conn); + + uint16_t width = monitor->geometry.width; + monitor->workarea.x = monitor->geometry.x; + monitor->workarea.y = monitor->geometry.y; + monitor->workarea.width = width; + monitor->workarea.height = monitor->geometry.height - wm.bar_height; + monitor->bar_window = window; + cairo_surface_t *surface = cairo_xcb_surface_create( + wm.xcb_conn, window, visual->visual, width, wm.bar_height); + cairo_t *cr = cairo_create(surface); + cairo_surface_destroy(surface); + p_delete(&visual); + + cairo_status_t status = cairo_status(cr); + if (status != CAIRO_STATUS_SUCCESS) { + fatal("cannot create cairo context: %s", cairo_status_to_string(status)); + } + monitor->bar_cr = cr; +} + +static void monitor_draw_tags(monitor_t *monitor) { + int16_t x = 0; + for (tag_t *tag = monitor->tag_list; tag; tag = tag->next) { + bool selected = monitor->selected_tag == tag; + bool has_client = tag->task_list; + color_t *bg = nullptr; + color_t *color = nullptr; + if (selected) { + bg = &wm.color_set.active_tag_bg; + color = &wm.color_set.active_tag_color; + } else { + bg = &wm.color_set.tag_bg; + color = &wm.color_set.tag_color; + } + int width = 0, height = 0; + text_get_size(tag->name, &width, &height); + width += 2 * wm.padding.tag_x; + tag->bar_extent.start = x; + tag->bar_extent.end = x + width; + + area_t tag_rect = {.x = x, .y = 0, .width = width, .height = wm.bar_height}; + if (bg->rgba != wm.color_set.bar_bg.rgba) { + draw_background(monitor->bar_cr, bg, tag_rect); + } + if (has_client) { + area_t rect = {.x = x, .y = 0, .width = 4, .height = 4}; + draw_rect(monitor->bar_cr, rect, selected, color, 1); + } + area_t text_rect = tag_rect; + tag_rect.y = (int16_t)((int)wm.bar_height - height) / 2; + tag_rect.height = (int16_t)height; + draw_text(monitor->bar_cr, tag->name, color, text_rect, true); + x += width; + } + + monitor->tag_extent.start = 0; + monitor->tag_extent.end = x; +} + +void monitor_draw_bar(monitor_t *monitor) { + cairo_t *cr = monitor->bar_cr; + uint16_t height = wm.bar_height; + area_t bar_area = { + .x = 0, + .y = 0, + .width = monitor->geometry.width, + .height = height, + }; + draw_background(cr, &wm.color_set.bar_bg, bar_area); + + monitor_draw_tags(monitor); +} diff --git a/src/monitor.h b/src/monitor.h index 6ad12b1..31b6b01 100644 --- a/src/monitor.h +++ b/src/monitor.h @@ -4,6 +4,9 @@ #include "types.h" -void monitor_initialize_tag(monitor_t *monitor, const char **tags); +uint32_t monitor_initialize_tag(monitor_t *monitor, const char **tags, + uint32_t tag_index_start_at); void monitor_select_tag(monitor_t *monitor, uint32_t tag_mask); void monitor_clean(monitor_t *monitor); +void monitor_init_bar(monitor_t *monitor); +void monitor_draw_bar(monitor_t *monitor); diff --git a/src/text.c b/src/text.c index a4b81ae..19480b7 100644 --- a/src/text.c +++ b/src/text.c @@ -1,9 +1,11 @@ #include "text.h" +#include #include #include #include #include +#include #include #include #include @@ -12,6 +14,9 @@ #include #include +#include "base.h" +#include "color.h" + static PangoContext *context = nullptr; static PangoLayout *layout = nullptr; static PangoAttrList *attr_list = nullptr; @@ -24,6 +29,8 @@ static PangoAttrList *attr_list = nullptr; * @return layout 高度,可用于确定 bar 高度 */ int text_init_pango_layout(const char *family, uint32_t size, uint32_t dpi) { + printf("family: %s, size: %u, dpi: %u\n", family, size, dpi); + static const char *layout_family = nullptr; static uint32_t layout_size = 0; static uint32_t layout_dpi = 0; @@ -55,6 +62,10 @@ int text_init_pango_layout(const char *family, uint32_t size, uint32_t dpi) { PangoAttribute *attr = pango_attr_font_desc_new(desc); attr_list = pango_attr_list_new(); pango_attr_list_insert(attr_list, attr); + pango_layout_set_attributes(layout, attr_list); + + pango_layout_set_wrap(layout, PANGO_WRAP_NONE); + pango_layout_set_ellipsize(layout, PANGO_ELLIPSIZE_END); int height = pango_font_metrics_get_height(metrics); int ascent = pango_font_metrics_get_ascent(metrics); @@ -90,3 +101,82 @@ void text_clean_pango_layout(void) { context = nullptr; } } + +static void get_layout_size(int *width, int *height) { + PangoRectangle logical_rect; + pango_layout_get_pixel_extents(layout, nullptr, &logical_rect); + if (width) *width = logical_rect.width; + if (height) *height = logical_rect.height; +} + +void text_get_size(const char *text, int *width, int *height) { + pango_layout_set_width(layout, -1); + pango_layout_set_text(layout, text, -1); + get_layout_size(width, height); +} + +static void text_layout_prepare(bool align_center) { + if (layout == nullptr) { + layout = pango_layout_new(context); + pango_layout_set_attributes(layout, attr_list); + pango_layout_set_wrap(layout, PANGO_WRAP_NONE); + pango_layout_set_ellipsize(layout, PANGO_ELLIPSIZE_END); + } + + PangoAlignment align = align_center ? PANGO_ALIGN_CENTER : PANGO_ALIGN_LEFT; + pango_layout_set_alignment(layout, align); +} + +void draw_text(cairo_t *cr, const char *text, color_t *color, area_t area, + bool align_center) { + text_layout_prepare(align_center); + pango_layout_set_width(layout, (int)area.width * PANGO_SCALE); + pango_layout_set_text(layout, text, -1); + cairo_set_source_rgba(cr, (double)color->red, (double)color->green, + (double)color->blue, (double)color->alpha); + int height = 0; + get_layout_size(nullptr, &height); + + double offset_y = ((double)area.height - (double)height) / 2; + pango_cairo_update_layout(cr, layout); + cairo_move_to(cr, (double)area.x, offset_y + (double)area.y); + pango_cairo_show_layout(cr, layout); +} + +void draw_rect(cairo_t *cr, area_t area, bool fill, color_t *color, + uint16_t line_width) { + cairo_set_line_width(cr, (double)line_width); + double x = (double)area.x; + double y = (double)area.y; + double width = (double)area.width; + double height = (double)area.height; + if (fill) { + cairo_rectangle(cr, x, y, width, height); + cairo_set_source_rgba(cr, (double)color->red, (double)color->green, + (double)color->blue, (double)color->alpha); + cairo_fill(cr); + } else { + double half_line_width = (double)line_width / 2; + x += half_line_width; + y += half_line_width; + width -= half_line_width; + height -= half_line_width; + + cairo_rectangle(cr, x, y, width, height); + cairo_set_source_rgba(cr, (double)color->red, (double)color->green, + (double)color->blue, (double)color->alpha); + cairo_stroke(cr); + } +} + +void draw_background(cairo_t *cr, color_t *color, area_t area) { + double x = (double)area.x; + double y = (double)area.y; + double width = (double)area.width; + double height = (double)area.height; + cairo_move_to(cr, x, y); + cairo_set_source_rgba(cr, (double)color->red, (double)color->green, + (double)color->blue, (double)color->alpha); + cairo_rectangle(cr, x, y, width, height); + cairo_fill(cr); +} diff --git a/src/text.h b/src/text.h index 9c3fda7..4be7238 100644 --- a/src/text.h +++ b/src/text.h @@ -1,6 +1,16 @@ #pragma once +#include #include +#include "base.h" +#include "color.h" + int text_init_pango_layout(const char *family, uint32_t size, uint32_t dpi); void text_clean_pango_layout(void); +void text_get_size(const char *text, int *width, int *height); +void draw_text(cairo_t *cr, const char *text, color_t *color, area_t area, + bool align_center); +void draw_rect(cairo_t *cr, area_t area, bool fill, color_t *color, + uint16_t line_width); +void draw_background(cairo_t *cr, color_t *color, area_t area); diff --git a/src/types.h b/src/types.h index 41d3d97..122abfc 100644 --- a/src/types.h +++ b/src/types.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include #include @@ -54,6 +55,9 @@ struct monitor_t { tag_t *selected_tag; char *name; + cairo_t *bar_cr; + + extent_in_bar_t tag_extent; xcb_window_t bar_window; }; @@ -74,3 +78,15 @@ struct task_in_tag_t { area_t geometry; extent_in_bar_t bar_extent; }; + +typedef struct color_set_t { + color_t bar_bg; + color_t tag_bg; + color_t active_tag_bg; + color_t tag_color; + color_t active_tag_color; +} color_set_t; + +typedef struct padding_t { + uint16_t tag_x; +} padding_t; diff --git a/src/wm.c b/src/wm.c index 849510d..4545c54 100644 --- a/src/wm.c +++ b/src/wm.c @@ -18,11 +18,13 @@ #include "backtrace.h" #include "buffer.h" +#include "color.h" #include "default_config.h" #include "monitor.h" #include "text.h" #include "types.h" #include "utils.h" +#include "xcursor.h" static void wm_setup_signal(void); static void wm_check_other_wm(void); @@ -31,6 +33,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_color_set(void); wm_t wm; @@ -255,17 +258,37 @@ void wm_clean(void) { guint source_id = sources[i]; if (source_id) g_source_remove(source_id); } + + xcursor_clean(); + xcb_disconnect(wm.xcb_conn); } static void wm_setup(void) { - int font_height = text_init_pango_layout("Terminus, Emacs Simsun", 10, 144); - wm.bar_height = (uint32_t)font_height + 2 * bar_y_padding; + wm.padding.tag_x = tag_x_padding; + + wm_init_color_set(); + + int font_height = text_init_pango_layout(font_family, font_size, default_dpi); + wm.bar_height = (uint16_t)font_height + 2 * bar_y_padding; printf("font height: %d, bar height: %u\n", font_height, wm.bar_height); + uint32_t tag_count = 0; for (monitor_t *m = wm.monitor_list; m; m = m->next) { - monitor_initialize_tag(m, (const char **)tags); + tag_count += monitor_initialize_tag(m, (const char **)tags, tag_count); + monitor_init_bar(m); + monitor_draw_bar(m); } + + xcb_flush(wm.xcb_conn); +} + +void wm_init_color_set(void) { + color_parse(bar_bg, &wm.color_set.bar_bg); + color_parse(tag_bg, &wm.color_set.tag_bg); + color_parse(active_tag_bg, &wm.color_set.active_tag_bg); + color_parse(tag_color, &wm.color_set.tag_color); + color_parse(active_tag_color, &wm.color_set.active_tag_color); } void wm_restart(void) { @@ -286,8 +309,13 @@ static void debug_show_monitor_list(void) { printf("x: %4d, y: %4d, width: %4u, height: %4u -> monitor[%s]\n", m->geometry.x, m->geometry.y, m->geometry.width, m->geometry.height, m->name); + printf("x: %4d, y: %4d, width: %4u, height: %4u -> workarea[%s]: %u\n", + m->workarea.x, m->workarea.y, m->workarea.width, m->workarea.height, + m->name, wm.bar_height); + printf("tag extend: [%d, %d]\n", m->tag_extent.start, m->tag_extent.end); for (const tag_t *tag = m->tag_list; tag; tag = tag->next) { - printf("tag[\"%s\"]: 0b%09b\n", tag->name, tag->mask); + printf("tag[%u]: 0b%09b, \"%s\", [%d, %d]\n", tag->index, tag->mask, + tag->name, tag->bar_extent.start, tag->bar_extent.end); } } } diff --git a/src/wm.h b/src/wm.h index 3378292..74e26ee 100644 --- a/src/wm.h +++ b/src/wm.h @@ -5,7 +5,8 @@ #include "types.h" typedef struct wm_t { - uint32_t bar_height; + padding_t padding; + uint16_t bar_height; bool need_restart; GMainLoop *loop; @@ -22,6 +23,8 @@ typedef struct wm_t { bool have_xfixes; bool have_xinerama; bool have_randr; + + color_set_t color_set; } wm_t; extern wm_t wm; diff --git a/src/xcursor.c b/src/xcursor.c new file mode 100644 index 0000000..74b4f61 --- /dev/null +++ b/src/xcursor.c @@ -0,0 +1,128 @@ +#include "xcursor.h" + +#include +#include + +#include "utils.h" +#include "wm.h" + +static const char *const xcursor_font[] = { + [XC_X_cursor] = "X_cursor", + [XC_arrow] = "arrow", + [XC_based_arrow_down] = "based_arrow_down", + [XC_based_arrow_up] = "based_arrow_up", + [XC_boat] = "boat", + [XC_bogosity] = "bogosity", + [XC_bottom_left_corner] = "bottom_left_corner", + [XC_bottom_right_corner] = "bottom_right_corner", + [XC_bottom_side] = "bottom_side", + [XC_bottom_tee] = "bottom_tee", + [XC_box_spiral] = "box_spiral", + [XC_center_ptr] = "center_ptr", + [XC_circle] = "circle", + [XC_clock] = "clock", + [XC_coffee_mug] = "coffee_mug", + [XC_cross] = "cross", + [XC_cross_reverse] = "cross_reverse", + [XC_crosshair] = "crosshair", + [XC_diamond_cross] = "diamond_cross", + [XC_dot] = "dot", + [XC_dotbox] = "dotbox", + [XC_double_arrow] = "double_arrow", + [XC_draft_large] = "draft_large", + [XC_draft_small] = "draft_small", + [XC_draped_box] = "draped_box", + [XC_exchange] = "exchange", + [XC_fleur] = "fleur", + [XC_gobbler] = "gobbler", + [XC_gumby] = "gumby", + [XC_hand1] = "hand1", + [XC_hand2] = "hand2", + [XC_heart] = "heart", + [XC_icon] = "icon", + [XC_iron_cross] = "iron_cross", + [XC_left_ptr] = "left_ptr", + [XC_left_side] = "left_side", + [XC_left_tee] = "left_tee", + [XC_leftbutton] = "leftbutton", + [XC_ll_angle] = "ll_angle", + [XC_lr_angle] = "lr_angle", + [XC_man] = "man", + [XC_middlebutton] = "middlebutton", + [XC_mouse] = "mouse", + [XC_pencil] = "pencil", + [XC_pirate] = "pirate", + [XC_plus] = "plus", + [XC_question_arrow] = "question_arrow", + [XC_right_ptr] = "right_ptr", + [XC_right_side] = "right_side", + [XC_right_tee] = "right_tee", + [XC_rightbutton] = "rightbutton", + [XC_rtl_logo] = "rtl_logo", + [XC_sailboat] = "sailboat", + [XC_sb_down_arrow] = "sb_down_arrow", + [XC_sb_h_double_arrow] = "sb_h_double_arrow", + [XC_sb_left_arrow] = "sb_left_arrow", + [XC_sb_right_arrow] = "sb_right_arrow", + [XC_sb_up_arrow] = "sb_up_arrow", + [XC_sb_v_double_arrow] = "sb_v_double_arrow", + [XC_shuttle] = "shuttle", + [XC_sizing] = "sizing", + [XC_spider] = "spider", + [XC_spraycan] = "spraycan", + [XC_star] = "star", + [XC_target] = "target", + [XC_tcross] = "tcross", + [XC_top_left_arrow] = "top_left_arrow", + [XC_top_left_corner] = "top_left_corner", + [XC_top_right_corner] = "top_right_corner", + [XC_top_side] = "top_side", + [XC_top_tee] = "top_tee", + [XC_trek] = "trek", + [XC_ul_angle] = "ul_angle", + [XC_umbrella] = "umbrella", + [XC_ur_angle] = "ur_angle", + [XC_watch] = "watch", + [XC_xterm] = "xterm", +}; + +static xcb_cursor_context_t *get_xcb_cursor_context(void) { + static xcb_cursor_context_t *ctx = nullptr; + if (ctx) return ctx; + + if (xcb_cursor_context_new(wm.xcb_conn, wm.screen, &ctx) < 0) { + return nullptr; + } + + return ctx; +} + +static const char *xcursor_font_tostr(uint16_t c) { + if (c < (uint16_t)countof(xcursor_font)) { + return xcursor_font[c]; + } + return nullptr; +} + +static xcb_cursor_t cursor_list[countof(xcursor_font)]; +static xcb_cursor_context_t *cursor_ctx = nullptr; + +xcb_cursor_t xcursor_get_xcb_cursor(cursor_t cursor) { + if (!cursor_ctx) cursor_ctx = get_xcb_cursor_context(); + if (!cursor_ctx) fatal("cannot get cursor"); + + if (!cursor_list[cursor]) { + const char *name = xcursor_font_tostr(cursor); + cursor_list[cursor] = xcb_cursor_load_cursor(cursor_ctx, name); + } + + return cursor_list[cursor]; +} + +void xcursor_clean(void) { + for (int i = 0; i < countof(xcursor_font); i++) { + xcb_cursor_t cursor = cursor_list[i]; + if (cursor) xcb_free_cursor(wm.xcb_conn, cursor); + } + if (cursor_ctx) xcb_cursor_context_free(cursor_ctx); +} diff --git a/src/xcursor.h b/src/xcursor.h new file mode 100644 index 0000000..a03aedb --- /dev/null +++ b/src/xcursor.h @@ -0,0 +1,13 @@ +#pragma once + +#include +#include + +typedef enum cursor_t { + cursor_normal = XC_left_ptr, + cursor_resize = XC_bottom_right_corner, + cursor_move = XC_fleur, +} cursor_t; + +xcb_cursor_t xcursor_get_xcb_cursor(cursor_t cursor); +void xcursor_clean(void); diff --git a/src/xwindow.c b/src/xwindow.c new file mode 100644 index 0000000..5a4b23b --- /dev/null +++ b/src/xwindow.c @@ -0,0 +1,102 @@ +#include "xwindow.h" + +#include + +#include "utils.h" +#include "wm.h" +#include "xcursor.h" + +xcb_window_t xwindow_create_bar_window(area_t area, uint32_t background_argb, + cursor_t cursor) { + static xcb_colormap_t colormap = XCB_NONE; + visual_t *visual = xwindow_get_xcb_visual(true); + xcb_visualid_t visual_id = visual->visual->visual_id; + xcb_connection_t *conn = wm.xcb_conn; + if (colormap == XCB_NONE) { + colormap = xcb_generate_id(conn); + uint8_t alloc = XCB_COLORMAP_ALLOC_NONE; + xcb_create_colormap(conn, alloc, colormap, wm.screen->root, visual_id); + } + + xcb_window_t window = xcb_generate_id(conn); + uint32_t value_mask = XCB_CW_OVERRIDE_REDIRECT | XCB_CW_BACK_PIXEL | + XCB_CW_BORDER_PIXEL | XCB_CW_EVENT_MASK | + XCB_CW_CURSOR | XCB_CW_COLORMAP; + const xcb_create_window_value_list_t value_list = { + .override_redirect = true, + .background_pixel = background_argb, + .border_pixel = 0, + .event_mask = XCB_EVENT_MASK_BUTTON_PRESS | XCB_EVENT_MASK_EXPOSURE, + .cursor = xcursor_get_xcb_cursor(cursor), + .colormap = colormap, + }; + xcb_void_cookie_t cookie; + cookie = xcb_create_window_aux_checked( + conn, visual->depth, window, wm.screen->root, area.x, area.y, area.width, + area.height, 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, visual_id, value_mask, + &value_list); + p_delete(&visual); + if (xcb_request_check(conn, cookie)) fatal("cannot create bar window"); + + cookie = xcb_map_window(conn, window); + if (xcb_request_check(conn, cookie)) fatal("cannot map bar window:"); + + xcb_aux_sync(conn); + + return window; +} + +static visual_t *find_alpha_visual() { + xcb_depth_iterator_t depth_iter; + depth_iter = xcb_screen_allowed_depths_iterator(wm.screen); + for (; depth_iter.rem; xcb_depth_next(&depth_iter)) { + if (depth_iter.data->depth != 32) continue; + + xcb_visualtype_iterator_t visual_iter; + visual_iter = xcb_depth_visuals_iterator(depth_iter.data); + for (; visual_iter.rem; xcb_visualtype_next(&visual_iter)) { + if (visual_iter.data->_class == XCB_VISUAL_CLASS_TRUE_COLOR) { + visual_t *r = p_new(visual_t, 1); + r->visual = visual_iter.data; + r->depth = depth_iter.data->depth; + return r; + } + } + } + + return NULL; +} + +static visual_t *find_root_visual() { + xcb_depth_iterator_t depth_iter; + depth_iter = xcb_screen_allowed_depths_iterator(wm.screen); + xcb_visualid_t vid = wm.screen->root_visual; + for (; depth_iter.rem; xcb_depth_next(&depth_iter)) { + xcb_visualtype_iterator_t visual_iter; + visual_iter = xcb_depth_visuals_iterator(depth_iter.data); + for (; visual_iter.rem; xcb_visualtype_next(&visual_iter)) { + if (visual_iter.data->visual_id == vid) { + visual_t *r = p_new(visual_t, 1); + r->visual = visual_iter.data; + r->depth = depth_iter.data->depth; + return r; + } + } + } + + return NULL; +} + +/** + * @brief 获取 xcb 的 visual 及其对应的 depth + * @param prefer_alpha 支持 alpha 的 visual 优先 + * @returns 返回的信息使用后记得使用 free 释放 + */ +visual_t *xwindow_get_xcb_visual(bool prefer_alpha) { + if (!prefer_alpha) return find_root_visual(); + + visual_t *visual = find_alpha_visual(); + if (visual == NULL) visual = find_root_visual(); + + return visual; +} diff --git a/src/xwindow.h b/src/xwindow.h index 6f70f09..5b8051b 100644 --- a/src/xwindow.h +++ b/src/xwindow.h @@ -1 +1,11 @@ #pragma once + +#include +#include + +typedef struct visual_t { + uint8_t depth; + xcb_visualtype_t *visual; +} visual_t; + +visual_t *xwindow_get_xcb_visual(bool prefer_alpha);