diff options
Diffstat (limited to 'window.c')
| -rw-r--r-- | window.c | 455 |
1 files changed, 455 insertions, 0 deletions
diff --git a/window.c b/window.c new file mode 100644 index 0000000..6bb9f50 --- /dev/null +++ b/window.c @@ -0,0 +1,455 @@ +#include <glib.h> +#include <unistd.h> +#include <math.h> +#include <cairo.h> +#include <cairo-xcb.h> +#include <xcb/xcb.h> +#include <xcb/xproto.h> +#include <xcb/xcb_aux.h> +#include <xcb/xcb_icccm.h> +#include <xcb/xcb_ewmh.h> +#include <xcb/xcb_xrm.h> +#include <xcb/xcb_errors.h> +#include <xcb/randr.h> +#include <xcb/shape.h> + +#include "window.h" +#include "log.h" + +// TODO: Event logic should be decoupled from Window logic +#include "draw.h" + +struct Window { + Connection *con; + xcb_drawable_t window; + cairo_surface_t *surface; + cairo_t *cr; + 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->con->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->con->errors, error->error_code, &extension); + const char *major = xcb_errors_get_name_for_major_code(win->con->errors, error->major_code); + const char *minor = xcb_errors_get_name_for_minor_code(win->con->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->con->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->con->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->con->connection), G_IO_IN | G_IO_HUP | G_IO_ERR); + g_source_attach(win->source, NULL); +} + +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->con->connection, false, strlen(atom), atom); + xcb_intern_atom_reply_t *reply = xcb_intern_atom_reply(win->con->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->con->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->con->connection, + XCB_PROP_MODE_REPLACE, + win->window, + win->con->ewmh._NET_WM_STRUT, + XCB_ATOM_CARDINAL, + 32, + 4, + values); + + xcb_change_property(win->con->connection, + XCB_PROP_MODE_REPLACE, + win->window, + win->con->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->con->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->con->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->con->connection, &win->con->ewmh); + + g_assert_true(xcb_ewmh_init_atoms_replies(&win->con->ewmh, ewmh_cookie, &error)); + g_assert_null(error); + log_debug("Xcb ewmh connected"); + + xcb_ewmh_set_wm_window_type(&win->con->ewmh, win->window, 1, &win->con->ewmh._NET_WM_WINDOW_TYPE_DOCK); + + xcb_atom_t state[] = { + win->con->ewmh._NET_WM_STATE_STICKY, + win->con->ewmh._NET_WM_STATE_ABOVE + }; + xcb_ewmh_set_wm_state(&win->con->ewmh, win->window, G_N_ELEMENTS(state), state); + + xcb_ewmh_set_wm_desktop(&win->con->ewmh, win->window, 0xFFFFFFFF); + + xcb_ewmh_set_wm_pid(&win->con->ewmh, win->window, getpid()); + + wm_set_size(win); + + wm_set_struts(win); +} + +Window *window_create(Connection *con) +{ + Window *win = g_malloc0(sizeof(Window)); + g_assert_nonnull(win); + win->con = con; + + 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->con->connection); + xcb_create_colormap(win->con->connection, XCB_COLORMAP_ALLOC_NONE, colormap, win->con->screen->root, win->con->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->con->connection); + xcb_create_window(win->con->connection, + XCB_COPY_FROM_PARENT, + win->window, win->con->screen->root, + win->x, win->y, + win->width, win->height, + 0, // border + XCB_WINDOW_CLASS_INPUT_OUTPUT, + win->con->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->con->connection, win->window); + xcb_flush(win->con->connection); + log_debug("Xcb initialized"); + + win->surface = cairo_xcb_surface_create(win->con->connection, + win->window, + win->con->visual_type, + win->con->screen_size->width, + win->con->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; +} + +double window_get_scale(Window *win) +{ + return MAX(1, win->con->screen_dpi/96.); +} + +void window_get_screen_size(Window *win, int *width, int *height) +{ + *width = win->con->screen_size->width; + *height = win->con->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->con->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->con->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->con->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->con->connection); + xcb_create_pixmap(win->con->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->con->connection, + win->con->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->con->connection, XCB_SHAPE_SO_SET, XCB_SHAPE_SK_BOUNDING, win->window, 0, 0, bitmap); + xcb_shape_select_input(win->con->connection, win->window, XCB_SHAPE_NOTIFY); + log_debug("Xcb shape mask configured"); + + xcb_free_pixmap(win->con->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->con->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->con->connection, XCB_CIRCULATE_RAISE_LOWEST, win->window); + xcb_flush(win->con->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); + + g_free(win); +} + +// vim: ts=4 sw=4 et |
