From ab92cac18652f72be12f68b5a96095ba8eb5afdf Mon Sep 17 00:00:00 2001 From: Federico Angelilli Date: Fri, 17 Nov 2023 15:42:49 +0100 Subject: Reorganize project structure --- Makefile | 37 ++++--- comet.c | 72 ------------- connection.c | 297 ----------------------------------------------------- connection.h | 41 -------- draw.c | 127 ----------------------- draw.h | 28 ----- log.c | 26 ----- log.h | 27 ----- src/comet.c | 72 +++++++++++++ src/connect.c | 297 +++++++++++++++++++++++++++++++++++++++++++++++++++++ src/connect.h | 41 ++++++++ src/draw.c | 127 +++++++++++++++++++++++ src/draw.h | 28 +++++ src/log.c | 26 +++++ src/log.h | 27 +++++ src/state.c | 21 ++++ src/state.h | 20 ++++ src/window.c | 321 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/window.h | 39 +++++++ state.c | 21 ---- state.h | 20 ---- window.c | 321 ---------------------------------------------------------- window.h | 39 ------- 23 files changed, 1041 insertions(+), 1034 deletions(-) delete mode 100644 comet.c delete mode 100644 connection.c delete mode 100644 connection.h delete mode 100644 draw.c delete mode 100644 draw.h delete mode 100644 log.c delete mode 100644 log.h create mode 100644 src/comet.c create mode 100644 src/connect.c create mode 100644 src/connect.h create mode 100644 src/draw.c create mode 100644 src/draw.h create mode 100644 src/log.c create mode 100644 src/log.h create mode 100644 src/state.c create mode 100644 src/state.h create mode 100644 src/window.c create mode 100644 src/window.h delete mode 100644 state.c delete mode 100644 state.h delete mode 100644 window.c delete mode 100644 window.h diff --git a/Makefile b/Makefile index 70f37f6..b79cacc 100644 --- a/Makefile +++ b/Makefile @@ -1,21 +1,26 @@ -SRC = $(wildcard *.c) +SRC = $(wildcard src/*.c) OBJ = $(SRC:.c=.o) BIN = comet.bin -DEPS = \ - xcb \ - xcb-aux \ - xcb-icccm \ - xcb-ewmh \ - xcb-xrm \ - xcb-shape \ - xcb-errors \ - "xcb-randr >= 1.5" \ - "glib-2.0 >= 2.44" \ - pangocairo - -CFLAGS := $(shell pkg-config --cflags $(DEPS)) -ggdb -LDFLAGS := $(shell pkg-config --libs $(DEPS)) -lm +PCDEP = xcb \ + xcb-aux \ + xcb-icccm \ + xcb-ewmh \ + xcb-xrm \ + xcb-shape \ + xcb-errors \ + "xcb-randr >= 1.5" \ + "glib-2.0 >= 2.44" \ + pangocairo + +CFLAGS := $(shell pkg-config --cflags $(PCDEP)) +LDFLAGS := $(shell pkg-config --libs $(PCDEP)) -lm + +ifdef RELEASE +CFLAGS += -O2 +else +CFLAGS += -ggdb +endif all: $(BIN) @@ -27,3 +32,5 @@ $(BIN): $(OBJ) clean: rm -rf $(BIN) $(OBJ) + +# vim: ts=4 sw=4 diff --git a/comet.c b/comet.c deleted file mode 100644 index a21b83a..0000000 --- a/comet.c +++ /dev/null @@ -1,72 +0,0 @@ -#include -#include -#include - -#include "window.h" -#include "log.h" -#include "draw.h" -#include "connection.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 = connection_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); - connection_attach_state(con, state); - connection_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/connection.c b/connection.c deleted file mode 100644 index d3eedda..0000000 --- a/connection.c +++ /dev/null @@ -1,297 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include - -#include "log.h" -#include "connection.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 *connection_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 connection_attach_source(Connection *con) -{ - g_assert_null(con->source); - - attach_source(con); - log_debug("Xcb event loop attached"); -} - -void connection_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 connection_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/connection.h b/connection.h deleted file mode 100644 index 21cbf3f..0000000 --- a/connection.h +++ /dev/null @@ -1,41 +0,0 @@ -#ifndef COMET_CONNECTION_H -#define COMET_CONNECTION_H - -#include -#include -#include -#include -#include -#include - -// 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 *connection_create(); - -void connection_attach_source(Connection *con); - -void connection_attach_state(Connection *con, State *state); - -void connection_destroy(Connection *con); - -#endif - -// vim: ts=4 sw=4 et diff --git a/draw.c b/draw.c deleted file mode 100644 index aed4da0..0000000 --- a/draw.c +++ /dev/null @@ -1,127 +0,0 @@ -#include -#include -#include -#include -#include - -#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/draw.h b/draw.h deleted file mode 100644 index 3e83146..0000000 --- a/draw.h +++ /dev/null @@ -1,28 +0,0 @@ -#ifndef COMET_DRAW_H -#define COMET_DRAW_H - -#include -#include - -#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/log.c b/log.c deleted file mode 100644 index f4c529f..0000000 --- a/log.c +++ /dev/null @@ -1,26 +0,0 @@ -#include - -#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/log.h b/log.h deleted file mode 100644 index c96536b..0000000 --- a/log.h +++ /dev/null @@ -1,27 +0,0 @@ -#ifndef COMET_LOG_H -#define COMET_LOG_H - -#include - -#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/comet.c b/src/comet.c new file mode 100644 index 0000000..9733d4b --- /dev/null +++ b/src/comet.c @@ -0,0 +1,72 @@ +#include +#include +#include + +#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 +#include +#include +#include +#include +#include +#include + +#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 +#include +#include +#include +#include +#include + +// 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 +#include +#include +#include +#include + +#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 +#include + +#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 + +#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 + +#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 + +#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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 + +#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 diff --git a/state.c b/state.c deleted file mode 100644 index bb31fa6..0000000 --- a/state.c +++ /dev/null @@ -1,21 +0,0 @@ -#include - -#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/state.h b/state.h deleted file mode 100644 index 696b657..0000000 --- a/state.h +++ /dev/null @@ -1,20 +0,0 @@ -#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/window.c b/window.c deleted file mode 100644 index 734761a..0000000 --- a/window.c +++ /dev/null @@ -1,321 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#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/window.h b/window.h deleted file mode 100644 index 5ee075f..0000000 --- a/window.h +++ /dev/null @@ -1,39 +0,0 @@ -#ifndef COMET_WINDOW_H -#define COMET_WINDOW_H - -#include - -#include "connection.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 -- cgit v1.2.3