#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "x11.h" #include "log.h" // TODO: Event logic should be decoupled from Window logic #include "draw.h" struct Window { xcb_connection_t *connection; xcb_screen_t *screen; xcb_randr_screen_size_t *screen_size; xcb_xrm_database_t *database; xcb_errors_context_t *errors; xcb_drawable_t window; xcb_visualtype_t *visual_type; xcb_ewmh_connection_t ewmh; cairo_surface_t *surface; cairo_t *cr; double dpi; int x, y; int width, height; GSource *source; }; typedef struct { GSource source; Window *win; xcb_generic_event_t *event; } XcbSource; static gboolean xcb_source_check(GSource *source) { XcbSource *xsource = (XcbSource *)source; g_assert_null(xsource->event); xsource->event = xcb_poll_for_event(xsource->win->connection); return xsource->event != NULL; } static gboolean xcb_source_dispatch(GSource *source, GSourceFunc callback, gpointer data) { XcbSource *xsource = (XcbSource *)source; g_assert_nonnull(xsource->event); g_assert_nonnull(callback); g_assert_nonnull(data); Window *win = (Window *)data; do { switch (xsource->event->response_type & ~0x80) { case 0: { xcb_generic_error_t *error = (xcb_generic_error_t *)xsource->event; const char *extension; const char *name = xcb_errors_get_name_for_error(win->errors, error->error_code, &extension); const char *major = xcb_errors_get_name_for_major_code(win->errors, error->major_code); const char *minor = xcb_errors_get_name_for_minor_code(win->errors, error->major_code, error->minor_code); // TODO: Handle errors instead of aborting log_error("Xcb error '%s' [extension=%s, major=%s, minor=%s, resource=%u, sequence=%u]", name, extension ? extension : "none", major, minor ? minor : "none", (unsigned int)error->resource_id, (unsigned int)error->sequence); break; } case XCB_EXPOSE: { xcb_expose_event_t *expose = (xcb_expose_event_t *)xsource->event; log_debug("Processing event 'Expose' [type=%d]", XCB_EXPOSE); // Redraw callback(data); break; } case XCB_BUTTON_RELEASE: { log_debug("Mouse event"); break; } case XCB_PROPERTY_NOTIFY: { xcb_property_notify_event_t *property = (xcb_property_notify_event_t *)xsource->event; log_debug("Processing event 'PropertyNotify' [type=%d]", XCB_PROPERTY_NOTIFY); if (property->atom == XCB_ATOM_RESOURCE_MANAGER) { window_update_scale(data); // Redraw callback(data); } break; } default: { const char *extension; const char *name = xcb_errors_get_name_for_xcb_event(win->errors, xsource->event, &extension); log_debug("Ignoring event '%s' [extension=%s, type=%d]", name, extension ? extension : "none", xsource->event->response_type & 0x7f); } } } while ((xsource->event = xcb_poll_for_event(xsource->win->connection)) != NULL); return G_SOURCE_CONTINUE; } static void xcb_source_finalize(GSource *source) { log_debug("Xcb event loop finalized"); } static GSourceFuncs source_fns = { NULL, xcb_source_check, xcb_source_dispatch, xcb_source_finalize, }; static void attach_source(Window *win) { XcbSource *source = (XcbSource *)g_source_new(&source_fns, sizeof(XcbSource)); source->win = win; source->event = NULL; win->source = (GSource *)source; g_source_set_static_name(win->source, "XcbSource"); // TODO: Draw should be decoupled from this file g_source_set_callback(win->source, G_SOURCE_FUNC(draw), win, NULL); g_source_add_unix_fd(win->source, xcb_get_file_descriptor(win->connection), G_IO_IN | G_IO_HUP | G_IO_ERR); g_source_attach(win->source, NULL); } static bool query_xrm(Window *win, const char *res, char **value) { if (xcb_xrm_resource_get_string(win->database,res, res, value) >= 0) { log_debug("Xrm query '%s' found '%s'", res, *value); return true; } log_debug("Xrm query '%s' not found", res); return false; } static xcb_atom_t intern_atom(Window *win, const char *atom) { xcb_generic_error_t *error; xcb_intern_atom_cookie_t cookie = xcb_intern_atom(win->connection, false, strlen(atom), atom); xcb_intern_atom_reply_t *reply = xcb_intern_atom_reply(win->connection, cookie, &error); g_assert_null(error); xcb_atom_t id = reply->atom; g_free(reply); return id; } static void wm_set_size(Window *win) { xcb_size_hints_t hints; xcb_icccm_size_hints_set_size(&hints, false, win->width, win->height); xcb_icccm_size_hints_set_min_size(&hints, win->width, win->height); xcb_icccm_size_hints_set_max_size(&hints, win->width, win->height); xcb_icccm_size_hints_set_base_size(&hints, win->width, win->height); xcb_icccm_size_hints_set_position(&hints, false, win->x, win->y); xcb_icccm_set_wm_size_hints(win->connection, win->window, XCB_ATOM_WM_NORMAL_HINTS, &hints); log_debug("Xcb icccm updated size hints"); } static void wm_set_struts(Window *win) { const uint32_t end = MAX(0, win->x + win->width - 1); const uint32_t values[12] = { 0, 0, win->height, 0, // left, right, top, bottom 0, 0, 0, 0, // left y0, left y1, right y0, right y1 win->x, end, 0, 0, // top y0, top y1, bottom y0, bottom y1 }; xcb_change_property(win->connection, XCB_PROP_MODE_REPLACE, win->window, win->ewmh._NET_WM_STRUT, XCB_ATOM_CARDINAL, 32, 4, values); xcb_change_property(win->connection, XCB_PROP_MODE_REPLACE, win->window, win->ewmh._NET_WM_STRUT_PARTIAL, XCB_ATOM_CARDINAL, 32, 12, values); log_debug("Xcb ewmh struts updated"); } static void wm_setup(Window *win) { const char *title = "comet"; xcb_icccm_set_wm_name(win->connection, win->window, XCB_ATOM_STRING, 8, strlen(title), title); log_debug("Window updated title [%s]", title); const char class[] = "comet\0Comet"; xcb_icccm_set_wm_class(win->connection, win->window, strlen(class), class); log_debug("Window updated class [%s]", "comet\\0Comet"); xcb_generic_error_t *error; xcb_intern_atom_cookie_t *ewmh_cookie = xcb_ewmh_init_atoms(win->connection, &win->ewmh); g_assert_true(xcb_ewmh_init_atoms_replies(&win->ewmh, ewmh_cookie, &error)); g_assert_null(error); log_debug("Xcb ewmh connected"); xcb_ewmh_set_wm_window_type(&win->ewmh, win->window, 1, &win->ewmh._NET_WM_WINDOW_TYPE_DOCK); xcb_atom_t state[] = { win->ewmh._NET_WM_STATE_STICKY, win->ewmh._NET_WM_STATE_ABOVE }; xcb_ewmh_set_wm_state(&win->ewmh, win->window, G_N_ELEMENTS(state), state); xcb_ewmh_set_wm_desktop(&win->ewmh, win->window, 0xFFFFFFFF); xcb_ewmh_set_wm_pid(&win->ewmh, win->window, getpid()); wm_set_size(win); wm_set_struts(win); } Window *window_create(void) { Window *win = g_malloc0(sizeof(Window)); int preferred_screen = 0; win->connection = xcb_connect(NULL, &preferred_screen); g_assert_true(win->connection != NULL && !xcb_connection_has_error(win->connection)); log_debug("Xcb connected"); xcb_screen_iterator_t iter = xcb_setup_roots_iterator(xcb_get_setup(win->connection)); log_debug("Default screen %d", preferred_screen); while (preferred_screen != 0 && iter.rem) { xcb_screen_next(&iter); preferred_screen--; } win->screen = iter.data; xcb_randr_query_version_cookie_t version_cookie = xcb_randr_query_version(win->connection, XCB_RANDR_MAJOR_VERSION, XCB_RANDR_MINOR_VERSION); xcb_generic_error_t *error; xcb_randr_query_version_reply_t *randr_version = xcb_randr_query_version_reply(win->connection, version_cookie, &error); g_assert_null(error); log_debug("RandR loaded [version=%d.%d]", randr_version->major_version, randr_version->minor_version); g_assert_cmpint(randr_version->major_version, >=, 1); xcb_randr_get_screen_info_cookie_t cookie = xcb_randr_get_screen_info(win->connection, win->screen->root); xcb_randr_get_screen_info_reply_t *info_reply = xcb_randr_get_screen_info_reply(win->connection, cookie, &error); g_assert_null(error); win->screen_size = xcb_randr_get_screen_info_sizes(info_reply); g_assert_nonnull(win->screen_size); log_debug("Screen size [width=%d, height=%d]", win->screen_size->width, win->screen_size->height); win->width = win->height = 0; win->x = win->y = 0; xcb_randr_select_input(win->connection, win->screen->root, XCB_RANDR_NOTIFY_MASK_SCREEN_CHANGE | XCB_RANDR_NOTIFY_MASK_OUTPUT_CHANGE | XCB_RANDR_NOTIFY_MASK_OUTPUT_PROPERTY); win->database = xcb_xrm_database_from_default(win->connection); g_assert_nonnull(win->database); window_update_scale(win); g_assert(xcb_errors_context_new(win->connection, &win->errors) == 0); log_debug("Xcb errors loaded"); xcb_depth_iterator_t depth_iter = xcb_screen_allowed_depths_iterator(win->screen); while (depth_iter.rem) { xcb_visualtype_iterator_t visual_iter = xcb_depth_visuals_iterator(depth_iter.data); while (visual_iter.rem) { if (win->screen->root_visual == visual_iter.data->visual_id) { win->visual_type = visual_iter.data; goto found_visual; } xcb_visualtype_next(&visual_iter); } xcb_depth_next(&depth_iter); } found_visual: log_debug("Xcb visual type found [id=%u]", win->visual_type->visual_id); const uint32_t value_mask = XCB_CW_BACK_PIXMAP | XCB_CW_BACK_PIXEL | XCB_CW_BORDER_PIXEL | XCB_CW_OVERRIDE_REDIRECT | XCB_CW_EVENT_MASK | XCB_CW_COLORMAP; const uint32_t event_mask = XCB_EVENT_MASK_EXPOSURE | XCB_EVENT_MASK_STRUCTURE_NOTIFY | XCB_EVENT_MASK_KEY_PRESS | XCB_EVENT_MASK_FOCUS_CHANGE | XCB_EVENT_MASK_BUTTON_RELEASE | XCB_EVENT_MASK_BUTTON_PRESS; xcb_colormap_t colormap = xcb_generate_id(win->connection); xcb_create_colormap(win->connection, XCB_COLORMAP_ALLOC_NONE, colormap, win->screen->root, win->visual_type->visual_id); log_debug("Xcb colormap created [id=%u]", colormap); const uint32_t value_list[] = { XCB_NONE, // back pixmap 0x000000, // back pixel 0x000000, // border pixel true, // override redirect event_mask, // event mask colormap // colormap }; win->width = win->height = 1; log_debug("Window temporary size [width=%d, height=%d]", win->width, win->height); win->x = win->y = 0; log_debug("Window temporary position [x=%d, y=%d]", win->x, win->y); win->window = xcb_generate_id(win->connection); xcb_create_window(win->connection, XCB_COPY_FROM_PARENT, win->window, win->screen->root, win->x, win->y, win->width, win->height, 0, // border XCB_WINDOW_CLASS_INPUT_OUTPUT, win->screen->root_visual, value_mask, value_list); log_debug("Xcb window created [id=%u]", win->window); wm_setup(win); log_debug("Window wm options completed"); xcb_map_window(win->connection, win->window); xcb_flush(win->connection); log_debug("Xcb initialized"); win->surface = cairo_xcb_surface_create(win->connection, win->window, win->visual_type, win->screen_size->width, win->screen_size->height); g_assert_cmpint(cairo_surface_status(win->surface), ==, CAIRO_STATUS_SUCCESS); log_debug("Cairo surface created"); win->cr = cairo_create(win->surface); g_assert_cmpint(cairo_status(win->cr), ==, CAIRO_STATUS_SUCCESS); log_debug("Cairo context created"); attach_source(win); log_debug("Xcb event loop attached"); return win; } cairo_t *window_get_context(Window *win) { return win->cr; } void window_update_scale(Window *win) { const char *dpi_res = "Xft.dpi"; char *dpi_value; if (query_xrm(win, dpi_res, &dpi_value)) { win->dpi = strtod(dpi_value, NULL); g_free(dpi_value); } else { win->dpi = (double)win->screen_size->height * 25.4 / (double)win->screen_size->mheight; log_debug("Fallback dpi value '%.2lf'", win->dpi); } } double window_get_scale(Window *win) { return MAX(1, win->dpi/96.); } void window_get_screen_size(Window *win, int *width, int *height) { *width = win->screen_size->width; *height = win->screen_size->height; } void window_set_opacity(Window *win, double alpha) { // Value between 0 and 1 g_assert_true(alpha >= 0 && alpha <= 1); unsigned long opacity = 0xffffffff * alpha; xcb_atom_t _NET_WM_WINDOW_OPACITY = intern_atom(win, "_NET_WM_WINDOW_OPACITY"); xcb_change_property(win->connection, XCB_PROP_MODE_REPLACE, win->window, _NET_WM_WINDOW_OPACITY, XCB_ATOM_CARDINAL, 32, 1, (char *)&opacity); log_debug("Window updated opacity [%.2lf%%]", alpha * 100.0); } void window_move(Window *win, int x, int y) { if (win->x == x && win->y == y) return; win->x = x; win->y = y; const uint32_t values[] = { x, y }; xcb_configure_window(win->connection, win->window, XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y, values); log_debug("Window updated position [x=%d, y=%d]", win->x, win->y); } void window_resize(Window *win, int width, int height) { if (win->width == width && win->height == height) return; win->width = width; win->height = height; const uint32_t values[] = { width, height }; xcb_configure_window(win->connection, win->window, XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT, values); log_debug("Window updated size [width=%d, height=%d]", win->width, win->height); wm_set_size(win); } static void window_paint_corners(Window *win) { // TODO: Actually make this a parameter int radius = win->height / 2; double degree = M_PI / 180.0; // TODO: Check this value int depth = 1; xcb_pixmap_t bitmap = xcb_generate_id(win->connection); xcb_create_pixmap(win->connection, depth, bitmap, win->window, win->width, win->height); log_debug("Xcb pixmap created [id=%u]", bitmap); cairo_surface_t *surface = cairo_xcb_surface_create_for_bitmap(win->connection, win->screen, bitmap, win->width, win->height); cairo_t *cr = cairo_create(surface); // TODO: Fix antialiasing situation //cairo_set_antialias(cr, CAIRO_ANTIALIAS_GOOD); cairo_set_antialias(cr, CAIRO_ANTIALIAS_NONE); cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); cairo_set_source_rgba(cr, 0, 0, 0, 0); cairo_paint(cr); // TODO: Decouple somewhat drawing and X11 cairo_set_source_rgba(cr, 1, 1, 1, 1); cairo_arc(cr, radius, radius, radius, 90.0 * degree, 270 * degree); cairo_arc(cr, win->width - radius, radius, radius, 270 * degree, 450 * degree); cairo_fill(cr); log_debug("Xcb shape painted"); cairo_show_page(cr); cairo_destroy(cr); cairo_surface_flush(surface); cairo_surface_destroy(surface); xcb_shape_mask(win->connection, XCB_SHAPE_SO_SET, XCB_SHAPE_SK_BOUNDING, win->window, 0, 0, bitmap); xcb_shape_select_input(win->connection, win->window, XCB_SHAPE_NOTIFY); log_debug("Xcb shape mask configured"); xcb_free_pixmap(win->connection, bitmap); } void window_paint_surface(Window *win, cairo_surface_t *surface, int width, int height) { cairo_xcb_surface_set_size(win->surface, width, height); xcb_clear_area(win->connection, false, win->window, 0, 0, 0, 0); cairo_set_source_surface(win->cr, surface, 0, 0); cairo_paint(win->cr); cairo_show_page(win->cr); window_paint_corners(win); xcb_circulate_window(win->connection, XCB_CIRCULATE_RAISE_LOWEST, win->window); xcb_flush(win->connection); } void window_destroy(Window *win) { g_source_destroy(win->source); g_source_unref(win->source); cairo_destroy(win->cr); cairo_surface_destroy(win->surface); xcb_ewmh_connection_wipe(&win->ewmh); xcb_errors_context_free(win->errors); xcb_xrm_database_free(win->database); xcb_disconnect(win->connection); g_free(win); } // vim: ts=4 sw=4 et