aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/comet.c72
-rw-r--r--src/connect.c297
-rw-r--r--src/connect.h41
-rw-r--r--src/draw.c127
-rw-r--r--src/draw.h28
-rw-r--r--src/log.c26
-rw-r--r--src/log.h27
-rw-r--r--src/state.c21
-rw-r--r--src/state.h20
-rw-r--r--src/window.c321
-rw-r--r--src/window.h39
11 files changed, 1019 insertions, 0 deletions
diff --git a/src/comet.c b/src/comet.c
new file mode 100644
index 0000000..9733d4b
--- /dev/null
+++ b/src/comet.c
@@ -0,0 +1,72 @@
+#include <math.h>
+#include <glib.h>
+#include <glib-unix.h>
+
+#include "window.h"
+#include "log.h"
+#include "draw.h"
+#include "connect.h"
+#include "state.h"
+
+#define EVEN(n) ((int)(n) - ((int)(n) % 2 != 0))
+
+static gboolean mainloop_quit(gpointer data)
+{
+ g_main_loop_quit(data);
+ return G_SOURCE_CONTINUE;
+}
+
+static gboolean mainloop_draw(gpointer data)
+{
+ State *state = data;
+ draw_paint(state->draw, state->win);
+ return G_SOURCE_REMOVE;
+}
+
+int main(int argc, char **argv)
+{
+ log_init(G_LOG_LEVEL_DEBUG);
+
+ GMainLoop *mainloop = g_main_loop_new(NULL, FALSE);
+
+ Connection *con = connect_create();
+
+ Window *win = window_create(con);
+
+ int screen_width = con->screen_size->width;
+ int screen_height = con->screen_size->height;
+
+ int height = EVEN(round(screen_height * 0.021));
+ int x_padding = EVEN(round(screen_width * 0.005));
+ int y_padding = EVEN(round(screen_height * 0.004));
+
+ log_debug("Calculated dimensions [height=%d, x_pad=%d, y_pad=%d]", height, x_padding, y_padding);
+
+ Drawable *draw = draw_create("Hack 12", height, x_padding, x_padding, y_padding, 1);
+
+ State *state = state_create(win, draw);
+ connect_attach_state(con, state);
+ connect_attach_source(con);
+
+ guint source_term = g_unix_signal_add(SIGTERM, mainloop_quit, mainloop);
+ guint source_int = g_unix_signal_add(SIGINT, mainloop_quit, mainloop);
+
+ guint id = g_timeout_add(100, mainloop_draw, state);
+
+ log_debug("Starting main loop");
+ g_main_loop_run(mainloop);
+
+ log_debug("Cleaning main loop");
+ g_clear_pointer(&mainloop, g_main_loop_unref);
+
+ g_source_remove(source_term);
+ g_source_remove(source_int);
+
+ state_destroy(state);
+ draw_destroy(draw);
+ window_destroy(win);
+
+ return 0;
+}
+
+// vim: ts=4 sw=4 et
diff --git a/src/connect.c b/src/connect.c
new file mode 100644
index 0000000..0ef12f9
--- /dev/null
+++ b/src/connect.c
@@ -0,0 +1,297 @@
+#include <glib.h>
+#include <xcb/xcb.h>
+#include <xcb/xcb_aux.h>
+#include <xcb/xcb_ewmh.h>
+#include <xcb/xcb_xrm.h>
+#include <xcb/xcb_errors.h>
+#include <xcb/randr.h>
+
+#include "log.h"
+#include "connect.h"
+#include "state.h"
+
+static bool query_xrm(Connection *con, const char *res, char **value)
+{
+ if (xcb_xrm_resource_get_string(con->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 void update_xrm(Connection *con)
+{
+ xcb_flush(con->connection);
+ xcb_xrm_database_t *database = xcb_xrm_database_from_default(con->connection);
+
+ if (database == NULL) {
+ log_warning("Xrm database couldn't be updated");
+ return;
+ }
+
+ xcb_xrm_database_free(con->database);
+ con->database = database;
+ log_debug("Xrm database updated");
+}
+
+static void update_scale(Connection *con)
+{
+ char *dpi_value;
+ if (query_xrm(con, "Xft.dpi", &dpi_value)) {
+ con->screen_dpi = strtod(dpi_value, NULL);
+ g_free(dpi_value);
+ } else {
+ con->screen_dpi = (double)con->screen_size->height * 25.4 / (double)con->screen_size->mheight;
+ log_debug("Fallback dpi value '%.2lf'", con->screen_dpi);
+ }
+}
+
+typedef struct {
+ GSource source;
+ Connection *con;
+ gpointer fd_tag;
+ xcb_generic_event_t *event;
+} EventSource;
+
+static gboolean source_check(GSource *source)
+{
+ EventSource *xsource = (EventSource *)source;
+ xcb_flush(xsource->con->connection);
+ g_assert_null(xsource->event);
+
+ GIOCondition flags = g_source_query_unix_fd(source, xsource->fd_tag);
+ if (flags & G_IO_IN) {
+ g_assert_false(xcb_connection_has_error(xsource->con->connection));
+ xsource->event = xcb_poll_for_event(xsource->con->connection);
+ }
+
+ return xsource->event != NULL;
+}
+
+static void redraw_window(Connection *con, xcb_window_t id)
+{
+ for (int i = 0; i < con->states->len; ++i) {
+ State *state = con->states->pdata[i];
+
+ // Redraw matching window
+ if (state->win->window == id) {
+ log_debug("Redrawing window [id=%d]", id);
+ draw_paint(state->draw, state->win);
+ break;
+ }
+ }
+}
+
+static gboolean source_dispatch(GSource *source, GSourceFunc callback, gpointer data)
+{
+ EventSource *xsource = (EventSource *)source;
+ g_assert_nonnull(xsource->event);
+
+ 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(xsource->con->errors, error->error_code, &extension);
+ const char *major = xcb_errors_get_name_for_major_code(xsource->con->errors, error->major_code);
+ const char *minor = xcb_errors_get_name_for_minor_code(xsource->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_window(xsource->con, expose->window);
+ break;
+ }
+
+ case XCB_CREATE_NOTIFY: {
+ xcb_create_notify_event_t *create = (xcb_create_notify_event_t *)xsource->event;
+ log_debug("Processing event 'CreateNotify' [type=%d]", XCB_CREATE_NOTIFY);
+
+ // TODO: Circulate top the window if override_redirect == 0
+
+ break;
+ }
+
+ case XCB_BUTTON_RELEASE: {
+ log_debug("Mouse event");
+
+ // TODO: Handle click generically by translating the button code
+ 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) {
+ update_xrm(xsource->con);
+ update_scale(xsource->con);
+ redraw_window(xsource->con, property->window);
+ }
+ break;
+ }
+
+ default: {
+ const char *extension;
+ const char *name = xcb_errors_get_name_for_xcb_event(xsource->con->errors, xsource->event, &extension);
+
+ // TODO: Handle XCB_RANDR_SCREEN_CHANGE_NOTIFY
+
+ log_debug("Ignoring event '%s' [type=%d, extension=%s]",
+ name,
+ xsource->event->response_type & 0x7f,
+ extension ? extension : "none");
+ }
+ }
+ } while ((xsource->event = xcb_poll_for_event(xsource->con->connection)) != NULL);
+
+ return G_SOURCE_CONTINUE;
+}
+
+static GSourceFuncs source_fns = {
+ NULL,
+ source_check,
+ source_dispatch,
+ NULL,
+};
+
+static void attach_source(Connection *con)
+{
+ EventSource *source = (EventSource *)g_source_new(&source_fns, sizeof(EventSource));
+ con->source = (GSource *)source;
+ g_source_set_static_name(con->source, "EventSource");
+
+ source->con = con;
+ source->event = NULL;
+ source->fd_tag = g_source_add_unix_fd(con->source, xcb_get_file_descriptor(con->connection), G_IO_IN | G_IO_HUP | G_IO_ERR);
+
+ g_source_attach(con->source, NULL);
+}
+
+Connection *connect_create()
+{
+ Connection *con = g_malloc0(sizeof(Connection));
+ g_assert_nonnull(con);
+
+ int preferred_screen = 0;
+ con->connection = xcb_connect(NULL, &preferred_screen);
+ g_assert_true(con->connection != NULL && !xcb_connection_has_error(con->connection));
+ log_debug("Xcb connection established");
+
+ log_debug("Default screen '%d'", preferred_screen);
+ xcb_screen_iterator_t iter = xcb_setup_roots_iterator(xcb_get_setup(con->connection));
+
+ while (preferred_screen != 0 && iter.rem) {
+ xcb_screen_next(&iter);
+ preferred_screen--;
+ }
+
+ con->screen = iter.data;
+ xcb_generic_error_t *error;
+
+ xcb_randr_query_version_cookie_t version_cookie = xcb_randr_query_version(con->connection,
+ XCB_RANDR_MAJOR_VERSION,
+ XCB_RANDR_MINOR_VERSION);
+
+ xcb_randr_query_version_reply_t *randr_version = xcb_randr_query_version_reply(con->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(con->connection, con->screen->root);
+ xcb_randr_get_screen_info_reply_t *info_reply = xcb_randr_get_screen_info_reply(con->connection, cookie, &error);
+ g_assert_null(error);
+
+ con->screen_size = xcb_randr_get_screen_info_sizes(info_reply);
+ g_assert_nonnull(con->screen_size);
+ log_debug("Screen size [width=%d, height=%d]", con->screen_size->width, con->screen_size->height);
+
+ xcb_randr_select_input(con->connection,
+ con->screen->root,
+ XCB_RANDR_NOTIFY_MASK_SCREEN_CHANGE |
+ XCB_RANDR_NOTIFY_MASK_OUTPUT_CHANGE |
+ XCB_RANDR_NOTIFY_MASK_OUTPUT_PROPERTY);
+
+ log_debug("Xcb searching 32 bit visual");
+ con->visual_type = xcb_aux_find_visual_by_attrs(con->screen, XCB_VISUAL_CLASS_TRUE_COLOR, 32);
+ con->screen_depth = 32;
+
+ if (con->visual_type == NULL) {
+ // NOTE: 24 bit visuals don't have the alpha channel for transparency
+ log_debug("Fallback to 24 bit visual");
+ con->visual_type = xcb_aux_find_visual_by_attrs(con->screen, XCB_VISUAL_CLASS_TRUE_COLOR, 24);
+ con->screen_depth = 24;
+ }
+
+ g_assert_nonnull(con->visual_type);
+ log_debug("Xcb visual type found [id=%u]", con->visual_type->visual_id);
+
+ xcb_intern_atom_cookie_t *ewmh_cookie = xcb_ewmh_init_atoms(con->connection, &con->ewmh);
+ g_assert_true(xcb_ewmh_init_atoms_replies(&con->ewmh, ewmh_cookie, &error));
+ g_assert_null(error);
+ log_debug("Xcb ewmh initialized");
+
+ con->database = xcb_xrm_database_from_default(con->connection);
+ g_assert_nonnull(con->database);
+
+ update_scale(con);
+
+ g_assert(xcb_errors_context_new(con->connection, &con->errors) == 0);
+ log_debug("Xcb errors loaded");
+
+ xcb_flush(con->connection);
+ log_debug("Xcb set up");
+
+ con->states = g_ptr_array_new();
+ g_assert_nonnull(con->states);
+
+ return con;
+}
+
+void connect_attach_source(Connection *con)
+{
+ g_assert_null(con->source);
+
+ attach_source(con);
+ log_debug("Xcb event loop attached");
+}
+
+void connect_attach_state(Connection *con, State *state)
+{
+ g_assert_nonnull(state);
+ g_ptr_array_add(con->states, state);
+ log_debug("Attached state to event loop");
+}
+
+void connect_destroy(Connection *con)
+{
+ g_ptr_array_free(con->states, true);
+
+ g_source_destroy(con->source);
+ g_source_unref(con->source);
+
+ xcb_ewmh_connection_wipe(&con->ewmh);
+ xcb_errors_context_free(con->errors);
+ xcb_xrm_database_free(con->database);
+ xcb_disconnect(con->connection);
+ g_free(con);
+}
+
+// vim: ts=4 sw=4 et
diff --git a/src/connect.h b/src/connect.h
new file mode 100644
index 0000000..e348bcf
--- /dev/null
+++ b/src/connect.h
@@ -0,0 +1,41 @@
+#ifndef COMET_CONNEC_H
+#define COMET_CONNEC_H
+
+#include <glib.h>
+#include <xcb/xcb.h>
+#include <xcb/xcb_ewmh.h>
+#include <xcb/xcb_xrm.h>
+#include <xcb/xcb_errors.h>
+#include <xcb/randr.h>
+
+// Forward declaration
+typedef struct State State;
+
+typedef struct Connection Connection;
+
+// TODO: Make this opaque
+struct Connection {
+ xcb_connection_t *connection;
+ xcb_screen_t *screen;
+ xcb_randr_screen_size_t *screen_size;
+ double screen_dpi;
+ int screen_depth;
+ xcb_visualtype_t *visual_type;
+ xcb_xrm_database_t *database;
+ xcb_errors_context_t *errors;
+ xcb_ewmh_connection_t ewmh;
+ GSource *source;
+ GPtrArray *states;
+};
+
+Connection *connect_create();
+
+void connect_attach_source(Connection *con);
+
+void connect_attach_state(Connection *con, State *state);
+
+void connect_destroy(Connection *con);
+
+#endif
+
+// vim: ts=4 sw=4 et
diff --git a/src/draw.c b/src/draw.c
new file mode 100644
index 0000000..aed4da0
--- /dev/null
+++ b/src/draw.c
@@ -0,0 +1,127 @@
+#include <glib.h>
+#include <math.h>
+#include <pango/pangocairo.h>
+#include <pango/pango-font.h>
+#include <pango/pango-types.h>
+
+#include "draw.h"
+#include "log.h"
+
+// Idea: Either make a to_draw queue where we put things we schedule to redraw
+// (this will also work for animations in the future)
+// or use some flags to trigger drawing
+
+Drawable *draw_create(const char *font, int height, int left_pad, int right_pad, int top_pad, double alpha)
+{
+ Drawable *draw = g_malloc(sizeof(Drawable));
+ g_assert_nonnull(draw);
+
+ log_debug("Pango loading font description '%s'", font);
+ draw->desc = pango_font_description_from_string(font);
+ log_debug("Pango found matching font '%s'", pango_font_description_get_family(draw->desc));
+
+ draw->height = height;
+ draw->left_pad = left_pad;
+ draw->right_pad = right_pad;
+ draw->top_pad = top_pad;
+ draw->alpha = alpha;
+ g_assert(alpha >= 0 && alpha <= 1);
+
+ log_debug("Draw context created [height=%d, left_pad=%d, right_pad=%d, top_pad=%d, alpha=%.2lf]", height, left_pad, right_pad, top_pad, alpha);
+
+ return draw;
+}
+
+void draw_paint(Drawable *draw, Window *win)
+{
+ // FIXME: Does not work for scale != 1
+ //double scale = window_get_scale(win);
+ double scale = 1;
+
+ int screen_width, screen_height;
+ window_get_screen_size(win, &screen_width, &screen_height);
+
+ int width0 = screen_width - draw->right_pad - draw->left_pad;
+ int width = round(width0 * scale);
+ int height = round(draw->height * scale);
+
+ cairo_surface_t *surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height);
+
+ cairo_t *cr = cairo_create(surface);
+ cairo_set_fill_rule(cr, CAIRO_FILL_RULE_EVEN_ODD);
+ cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
+
+ int radius = height / 2;
+ double degree = M_PI / 180.0;
+
+ cairo_set_source_rgba(cr, 0.3, 0.3, 0.3, draw->alpha);
+
+ // TODO: Here we should paint the shape of the bar, however there is a problem with the surface
+ // painted in the pixmap to mask the window shape in window_paint_corners.
+ //
+ // This is caused by some difference between the two shapes (that should technically be the same)
+ // which causes a mismatch between the two layers and leaves some black pixels visible
+
+ //cairo_arc(cr, radius, radius, radius, 90.0 * degree, 270 * degree);
+ //cairo_arc(cr, width - radius, radius, radius, 270 * degree, 450 * degree);
+ //cairo_fill(cr);
+
+ cairo_paint(cr);
+
+ cairo_set_line_width(cr, 1 * scale);
+
+ for (int i = 0; i < 9; i++) {
+
+ PangoLayout *layout = pango_cairo_create_layout (cr);
+ pango_layout_set_font_description(layout, draw->desc);
+
+ int x = (height + cairo_get_line_width(cr) * scale * 2) * i;
+
+ // purple
+ cairo_set_source_rgba(cr, 0.502, 0.168, 0.886, 1);
+ cairo_arc(cr, x + radius, radius, radius, 0 * degree, 360 * degree);
+ cairo_fill(cr);
+
+ cairo_set_source_rgba(cr, 0.8, 0.8, 0.8, 1);
+ cairo_arc(cr, x + radius, radius, radius - 1, 0 * degree, 360 * degree);
+ cairo_stroke(cr);
+
+ cairo_set_source_rgb(cr, 0.8, 0.8, 0.8);
+ char btn[] = { '1' + i, '\0' };
+ pango_layout_set_text(layout, btn, -1);
+
+ int text_w, text_h;
+ pango_layout_get_pixel_size(layout, &text_w, &text_h);
+ text_w = ceil(text_w / scale);
+ text_h = ceil(text_h / scale);
+
+ int text_x = x + radius - (text_w / 2);
+ int text_y = radius - (text_h / 2);
+ cairo_move_to(cr, text_x, text_y);
+
+ pango_cairo_update_layout(cr, layout);
+ pango_cairo_show_layout(cr, layout);
+
+ g_object_unref(layout);
+ }
+
+ cairo_destroy(cr);
+
+ int x = draw->left_pad;
+ int y = draw->top_pad;
+
+ // TODO: Move these somewhere else
+ window_move(win, x, y);
+ window_resize(win, width0, draw->height);
+
+ window_paint_surface(win, surface, width, height);
+ cairo_surface_destroy(surface);
+}
+
+void draw_destroy(Drawable *draw)
+{
+ pango_font_description_free(draw->desc);
+ g_free(draw);
+}
+
+// vim: ts=4 sw=4 et
diff --git a/src/draw.h b/src/draw.h
new file mode 100644
index 0000000..3e83146
--- /dev/null
+++ b/src/draw.h
@@ -0,0 +1,28 @@
+#ifndef COMET_DRAW_H
+#define COMET_DRAW_H
+
+#include <pango/pango-font.h>
+#include <pango/pango-types.h>
+
+#include "window.h"
+
+typedef struct Drawable Drawable;
+
+struct Drawable {
+ PangoFontDescription *desc;
+ int height;
+ int left_pad;
+ int right_pad;
+ int top_pad;
+ double alpha;
+};
+
+Drawable *draw_create(const char *font, int height, int left_pad, int right_pad, int top_pad, double alpha);
+
+void draw_paint(Drawable *draw, Window *win);
+
+void draw_destroy(Drawable *draw);
+
+#endif
+
+// vim: ts=4 sw=4 et
diff --git a/src/log.c b/src/log.c
new file mode 100644
index 0000000..f4c529f
--- /dev/null
+++ b/src/log.c
@@ -0,0 +1,26 @@
+#include <glib.h>
+
+#include "log.h"
+
+static void log_handler(const char *log_domain,
+ GLogLevelFlags level,
+ const char *message,
+ gpointer log_level)
+{
+ GLogLevelFlags message_level = level & G_LOG_LEVEL_MASK;
+
+ if ((GLogLevelFlags)log_level < message_level)
+ return;
+
+ if (message_level <= G_LOG_LEVEL_WARNING)
+ g_printerr("%s\n", message);
+ else
+ g_print("%s\n", message);
+}
+
+void log_init(GLogLevelFlags level)
+{
+ g_log_set_default_handler(log_handler, (void *)level);
+}
+
+// vim: ts=4 sw=4 et
diff --git a/src/log.h b/src/log.h
new file mode 100644
index 0000000..c96536b
--- /dev/null
+++ b/src/log.h
@@ -0,0 +1,27 @@
+#ifndef COMET_LOG_H
+#define COMET_LOG_H
+
+#include <glib.h>
+
+#define DEBUG_FORMAT(format, ...) \
+ "[%s] \x1b[1mdebug\x1b[0m: " format, __func__, ## __VA_ARGS__
+
+#define INFO_FORMAT(format, ...) \
+ "[%s] \x1b[1;96minfo\x1b[0m: " format, __func__, ## __VA_ARGS__
+
+#define WARNING_FORMAT(format, ...) \
+ "[%s] \x1b[1;33mwarning\x1b[0m: " format, __func__, ## __VA_ARGS__
+
+#define ERROR_FORMAT(format, ...) \
+ "[%s] \x1b[1;31merror\x1b[0m: " format, __func__, ## __VA_ARGS__
+
+#define log_debug(...) g_debug(DEBUG_FORMAT(__VA_ARGS__))
+#define log_info(...) g_info(INFO_FORMAT(__VA_ARGS__))
+#define log_warning(...) g_warning(WARNING_FORMAT(__VA_ARGS__))
+#define log_error(...) g_error(ERROR_FORMAT(__VA_ARGS__))
+
+void log_init(GLogLevelFlags level);
+
+#endif
+
+// vim: ts=4 sw=4 et
diff --git a/src/state.c b/src/state.c
new file mode 100644
index 0000000..bb31fa6
--- /dev/null
+++ b/src/state.c
@@ -0,0 +1,21 @@
+#include <glib.h>
+
+#include "state.h"
+
+State *state_create(Window *win, Drawable *draw)
+{
+ State *state = g_malloc(sizeof(State));
+ g_assert_nonnull(state);
+
+ state->win = win;
+ state->draw = draw;
+
+ return state;
+}
+
+void state_destroy(State *state)
+{
+ g_free(state);
+}
+
+// vim: ts=4 sw=4 et
diff --git a/src/state.h b/src/state.h
new file mode 100644
index 0000000..696b657
--- /dev/null
+++ b/src/state.h
@@ -0,0 +1,20 @@
+#ifndef COMET_STATE_H
+#define COMET_STATE_H
+
+#include "window.h"
+#include "draw.h"
+
+typedef struct State State;
+
+struct State {
+ Window *win;
+ Drawable *draw;
+};
+
+State *state_create(Window *win, Drawable *draw);
+
+void state_destroy(State *state);
+
+#endif
+
+// vim: ts=4 sw=4 et
diff --git a/src/window.c b/src/window.c
new file mode 100644
index 0000000..734761a
--- /dev/null
+++ b/src/window.c
@@ -0,0 +1,321 @@
+#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"
+
+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(con->connection);
+ xcb_create_colormap(con->connection, XCB_COLORMAP_ALLOC_NONE, colormap, con->screen->root, con->visual_type->visual_id);
+ log_debug("Xcb colormap created [id=%u]", colormap);
+
+ const uint32_t value_list[] = {
+ XCB_NONE, // back pixmap
+ 0x00000000, // back pixel
+ 0x00000000, // 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(con->connection);
+ xcb_create_window(con->connection,
+ con->screen_depth,
+ win->window,
+ con->screen->root,
+ win->x, win->y,
+ win->width, win->height,
+ 0, // border
+ XCB_WINDOW_CLASS_COPY_FROM_PARENT,
+ con->visual_type->visual_id,
+ 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(con->connection, win->window);
+ xcb_flush(con->connection);
+ log_debug("Xcb initialized");
+
+ win->surface = cairo_xcb_surface_create(con->connection,
+ win->window,
+ con->visual_type,
+ con->screen_size->width,
+ 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");
+
+ 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);
+
+ 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)
+{
+ cairo_destroy(win->cr);
+ cairo_surface_destroy(win->surface);
+
+ g_free(win);
+}
+
+// vim: ts=4 sw=4 et
diff --git a/src/window.h b/src/window.h
new file mode 100644
index 0000000..e6172ac
--- /dev/null
+++ b/src/window.h
@@ -0,0 +1,39 @@
+#ifndef COMET_WINDOW_H
+#define COMET_WINDOW_H
+
+#include <cairo.h>
+
+#include "connect.h"
+
+typedef struct Window Window;
+
+struct Window {
+ Connection *con;
+ xcb_drawable_t window;
+ cairo_surface_t *surface;
+ cairo_t *cr;
+ int x, y;
+ int width, height;
+};
+
+Window *window_create(Connection *con);
+
+cairo_t *window_get_context(Window *win);
+
+double window_get_scale(Window *win);
+
+void window_set_opacity(Window *win, double alpha);
+
+void window_get_screen_size(Window *win, int *width, int *height);
+
+void window_move(Window *win, int x, int y);
+
+void window_resize(Window *win, int width, int height);
+
+void window_paint_surface(Window *win, cairo_surface_t *surface, int width, int height);
+
+void window_destroy(Window *win);
+
+#endif
+
+// vim: ts=4 sw=4 et