aboutsummaryrefslogtreecommitdiff
path: root/window.c
diff options
context:
space:
mode:
authorFederico Angelilli <code@fedang.net>2023-11-15 22:35:41 +0100
committerFederico Angelilli <code@fedang.net>2023-11-15 22:35:41 +0100
commit22086e50382b99de092899a203e520c30f50b618 (patch)
tree90ca79a7e9fedd52b21d458cf91f1fbd8a56bd06 /window.c
parent7d795eede96e08bf7673a3264648b33693adea21 (diff)
Add `Connection` to separate xcb setup from window logic
Diffstat (limited to 'window.c')
-rw-r--r--window.c455
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