diff options
| author | Federico Angelilli <code@fedang.net> | 2023-11-17 15:42:49 +0100 |
|---|---|---|
| committer | Federico Angelilli <code@fedang.net> | 2023-11-17 15:42:49 +0100 |
| commit | ab92cac18652f72be12f68b5a96095ba8eb5afdf (patch) | |
| tree | e52a5f295c7cda59e67a341fa87d25983bd56745 /src | |
| parent | f8363e89257e8b0a4ff71accbd7b6be22935274f (diff) | |
Reorganize project structure
Diffstat (limited to 'src')
| -rw-r--r-- | src/comet.c | 72 | ||||
| -rw-r--r-- | src/connect.c | 297 | ||||
| -rw-r--r-- | src/connect.h | 41 | ||||
| -rw-r--r-- | src/draw.c | 127 | ||||
| -rw-r--r-- | src/draw.h | 28 | ||||
| -rw-r--r-- | src/log.c | 26 | ||||
| -rw-r--r-- | src/log.h | 27 | ||||
| -rw-r--r-- | src/state.c | 21 | ||||
| -rw-r--r-- | src/state.h | 20 | ||||
| -rw-r--r-- | src/window.c | 321 | ||||
| -rw-r--r-- | src/window.h | 39 |
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 |
