aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/any_log.h722
-rw-r--r--src/comet.c29
-rw-r--r--src/display.c168
-rw-r--r--src/display.h28
-rw-r--r--src/window.c296
-rw-r--r--src/window.h31
6 files changed, 1274 insertions, 0 deletions
diff --git a/src/any_log.h b/src/any_log.h
new file mode 100644
index 0000000..c4f143c
--- /dev/null
+++ b/src/any_log.h
@@ -0,0 +1,722 @@
+// any_log
+//
+// A single-file library that provides a simple and somewhat opinionated
+// interface for logging and structured logging.
+//
+// To use this library you should choose a suitable file to put the
+// implementation and define ANY_LOG_IMPLEMENT. For example
+//
+// #define ANY_LOG_IMPLEMENT
+// #include "any_log.h"
+//
+// Additionally, you can customize the library behavior by defining certain
+// macros in the file where you put the implementation. You can see which are
+// supported by reading the code guarded by ANY_LOG_IMPLEMENT.
+//
+// This library is licensed under the terms of the MIT license.
+// A copy of the license is included at the end of this file.
+//
+
+#ifndef ANY_LOG_INCLUDE
+#define ANY_LOG_INCLUDE
+
+#include <stdio.h>
+
+// These values represent the decreasing urgency of a log invocation.
+//
+// panic: indicates a fatal error and using it will result in
+// the program termination (see any_log_panic)
+//
+// error: indicates a (non-fatal) error
+//
+// warn: indicates a warning
+//
+// info: indicates an information (potentially useful to the user)
+//
+// debug: indicates debugging information
+//
+// trace: indicates verbose debugging information and can be completely
+// disabled by defining ANY_LOG_NO_TRACE before including
+//
+// NOTE: The value ANY_LOG_ALL is not an actual level and it is used as
+// a sentinel to indicate the last value of any_log_level_t
+//
+typedef enum {
+ ANY_LOG_PANIC,
+ ANY_LOG_ERROR,
+ ANY_LOG_WARN,
+ ANY_LOG_INFO,
+ ANY_LOG_DEBUG,
+ ANY_LOG_TRACE,
+ ANY_LOG_ALL,
+} any_log_level_t;
+
+// The value of ANY_LOG_MODULE is used to indicate the current module.
+// By default it is defined as __FILE__, so that it will coincide with the
+// source file path (relative to the compiler cwd).
+//
+// You can customize ANY_LOG_MODULE before including the header by simply
+// defining it. For example
+//
+// #define ANY_LOG_MODULE "my-library"
+// #include "any_log.h"
+//
+#ifndef ANY_LOG_MODULE
+#define ANY_LOG_MODULE __FILE__
+#endif
+
+// C99 and later define the __func__ variable
+#ifndef ANY_LOG_FUNC
+#define ANY_LOG_FUNC __func__
+#endif
+
+// log_panic is implemented with the function any_log_panic, which takes
+// some extra parameters compared with the other log levels. This way we can
+// include as many information as possible for identifying fatal errors.
+//
+// You can change the format string and exit function in the implementation
+// (see ANY_LOG_EXIT, ANY_LOG_PANIC_BEFORE and ANY_LOG_PANIC_AFTER).
+//
+// NOTE: log_panic will always terminate the program and should be used only
+// for non recoverable situations! For normal errors just use log_error
+//
+#define log_panic(...) any_log_panic(__FILE__, __LINE__, ANY_LOG_MODULE, ANY_LOG_FUNC, __VA_ARGS__)
+
+// log_[level] provide normal printf style logging.
+//
+// The logs will be filtered according to the global log level. See any_log_level.
+//
+// You should invoke log_[level] with a format string and any number of
+// matched arguments. For example
+//
+// log_error("This is an error");
+// log_debug("The X is %d (padding %d)", X, 10);
+//
+// log_trace and log_debug can be disabled completely (to avoid their overhead
+// in release/optimized builds) by defining ANY_LOG_NO_TRACE and ANY_LOG_NO_DEBUG
+// respectively. As this will work only if they are defined before every header
+// include, it is recommended to define this from the compiler.
+//
+#define log_error(...) any_log_format(ANY_LOG_ERROR, ANY_LOG_MODULE, ANY_LOG_FUNC, __VA_ARGS__)
+#define log_warn(...) any_log_format(ANY_LOG_WARN, ANY_LOG_MODULE, ANY_LOG_FUNC, __VA_ARGS__)
+#define log_info(...) any_log_format(ANY_LOG_INFO, ANY_LOG_MODULE, ANY_LOG_FUNC, __VA_ARGS__)
+
+#ifdef ANY_LOG_NO_DEBUG
+#define log_debug(...)
+#else
+#define log_debug(...) any_log_format(ANY_LOG_DEBUG, ANY_LOG_MODULE, ANY_LOG_FUNC, __VA_ARGS__)
+#endif
+
+#ifdef ANY_LOG_NO_TRACE
+#define log_trace(...)
+#else
+#define log_trace(...) any_log_format(ANY_LOG_TRACE, ANY_LOG_MODULE, ANY_LOG_FUNC, __VA_ARGS__)
+#endif
+
+// log_value_[level] provide structured logging.
+//
+// The logs will be filtered according to the global log level. See any_log_level.
+//
+// You should always pass a message string (printf style specifiers are ignored)
+// and some key-value pairs.
+//
+// The key are simply strings. It is advised to pass only literals for security.
+//
+// The value can be of type int, unsigned int, pointer (void *), double and
+// string (char *).
+//
+// The value type is specified by a type specifier at the start of the key
+// string and should be like so
+//
+// key = (type_specifier ANY_LOG_VALUE_TYPE_SEP)? ...
+//
+// By default ANY_LOG_VALUE_TYPE_SEP is the character ':'.
+//
+// type_specifier | type | default format
+// | |
+// b | bool (promoted to int) | "%s", b ? "true" : "false"
+// d, i | int | "%d"
+// x, u | unsigned int | "%#x"
+// l | long int | "%ld"
+// p | void * | "%p"
+// f | double | "%lf"
+// s | char * (0-terminated) | "%s"
+//
+// g | any_log_formatter_t (function) + ANY_LOG_VALUE_GENERIC_TYPE
+//
+// If no type specifier is given the function will assume the type given
+// by ANY_LOG_VALUE_DEFAULT_TYPE (by default string).
+//
+// The 'g' specifier is handled differently than the others. It needs two parameters,
+// the first must be a custom formatter function (of type any_log_formatter_t) to
+// format the second value of type ANY_LOG_VALUE_GENERIC_TYPE (by default void *).
+// By defining ANY_LOG_NO_GENERIC you can disable this custom type specifier.
+//
+// Example usage of value logging
+//
+// log_value_info("Created graphical context",
+// "d:width", width,
+// "d:height", height,
+// "p:window", window_handle,
+// "f:scale", scale_factor_dpi,
+// "b:hidden", visibility == HIDDEN,
+// "g:widgets", ANY_LOG_FORMATTER(widget_format), widgets,
+// "appname", "nice app");
+//
+// In the implementation you can customize the format of every key-value pair
+// and of the message. This is useful if you want to adhere to a structured
+// logging format like JSON. For example
+//
+// #define ANY_LOG_IMPLEMENT
+// #define ANY_LOG_VALUE_BEFORE(level, module, func, message)
+// "{\"module\": \"%s\", \"function\": \"%s\", \"level\": \"%s\", \"message\": \"%s\", ",
+// module, func, any_log_level_strings[level], message
+//
+// #define ANY_LOG_VALUE_BOOL(key, value) "\"%s\": %s", key, (value ? "true" : "false")
+// #define ANY_LOG_VALUE_INT(key, value) "\"%s\": %d", key, value
+// #define ANY_LOG_VALUE_HEX(key, value) "\"%s\": %u", key, value
+// #define ANY_LOG_VALUE_LONG(key, value) "\"%s\": %ld", key, value
+// #define ANY_LOG_VALUE_PTR(key, value) "\"%s\": \"%p\"", key, value
+// #define ANY_LOG_VALUE_DOUBLE(key, value) "\"%s\": %lf", key, value
+// #define ANY_LOG_VALUE_STRING(key, value) "\"%s \": \"%s\"", key, value
+// #define ANY_LOG_VALUE_AFTER(level, module, func, message) "}\n"
+// #define ANY_LOG_NO_GENERIC
+// #include "any_log.h"
+//
+// As with log_trace and log_debug, log_value_trace and log_value_debug can be
+// disabled by defining ANY_LOG_NO_TRACE and ANY_LOG_NO_DEBUG respectively.
+//
+#define log_value_error(...) any_log_value(ANY_LOG_ERROR, ANY_LOG_MODULE, ANY_LOG_FUNC, __VA_ARGS__, (char *)NULL)
+#define log_value_warn(...) any_log_value(ANY_LOG_WARN, ANY_LOG_MODULE, ANY_LOG_FUNC, __VA_ARGS__, (char *)NULL)
+#define log_value_info(...) any_log_value(ANY_LOG_INFO, ANY_LOG_MODULE, ANY_LOG_FUNC, __VA_ARGS__, (char *)NULL)
+#define log_value_debug(...) any_log_value(ANY_LOG_DEBUG, ANY_LOG_MODULE, ANY_LOG_FUNC, __VA_ARGS__, (char *)NULL)
+
+#ifdef ANY_LOG_NO_DEBUG
+#define log_value_debug(...)
+#else
+#define log_value_debug(...) any_log_value(ANY_LOG_DEBUG, ANY_LOG_MODULE, ANY_LOG_FUNC, __VA_ARGS__, (char *)NULL)
+#endif
+
+#ifdef ANY_LOG_NO_TRACE
+#define log_value_trace(...)
+#else
+#define log_value_trace(...) any_log_value(ANY_LOG_TRACE, ANY_LOG_MODULE, ANY_LOG_FUNC, __VA_ARGS__, (char *)NULL)
+#endif
+
+#ifndef ANY_LOG_NO_GENERIC
+
+#ifndef ANY_LOG_VALUE_GENERIC_TYPE
+#define ANY_LOG_VALUE_GENERIC_TYPE void *
+#endif
+
+// The type of the format functions for custom types
+//
+typedef void (*any_log_formatter_t)(FILE *stream, ANY_LOG_VALUE_GENERIC_TYPE value);
+
+#define ANY_LOG_FORMATTER(f) ((any_log_formatter_t)(f))
+
+#endif
+
+#ifdef __GNUC__
+#define ANY_LOG_ATTRIBUTE(...) __attribute__((__VA_ARGS__))
+#else
+#define ANY_LOG_ATTRIBUTE(...)
+#endif
+
+// All log functions will output to the file stream specified by any_log_stream.
+//
+// You should always set this global to a valid stream (eg in main) before
+// invoking any_log macros or functions!
+//
+extern FILE *any_log_stream;
+
+// All log functions will ignore the message if the level is below the
+// threshold specified in any_log_level.
+//
+// To modify the log level you can assign a any_log_level_t to this global.
+//
+// By default it has value ANY_LOG_LEVEL_DEFAULT (see implementation).
+//
+extern any_log_level_t any_log_level;
+
+// This is a simple utility function that sets both any_log_level and
+// any_log_stream with a single call.
+//
+// Call this function before any use of log_* (for example in main) to
+// correctly initialize the library!
+//
+void any_log_init(FILE *stream, any_log_level_t level);
+
+// An array containing the strings corresponding to the log levels.
+//
+// Can be modified in the implementation by defining the macros ANY_LOG_[level]_STRING.
+//
+// The functions any_log_level_to_string and any_log_level_from_string are
+// provided for easy conversion.
+//
+extern const char *any_log_level_strings[ANY_LOG_ALL];
+
+ANY_LOG_ATTRIBUTE(pure)
+const char *any_log_level_to_string(any_log_level_t level);
+
+ANY_LOG_ATTRIBUTE(pure)
+any_log_level_t any_log_level_from_string(const char *string);
+
+// The default format macros for all logging function uses the global
+// any_log_color to get the color sequence to use when printing the logs.
+//
+// By default this global points to any_log_colors_default, but you can
+// set it to any_log_colors_disabled or to a custom array of your choice.
+//
+// The array you give should have length ANY_LOG_ALL + 3 and this organization
+//
+// from ANY_LOG_PANIC to ANY_LOG_TRACE: the colors indexed by log levels
+// ANY_LOG_ALL: the color reset sequence
+// ANY_LOG_ALL + 1: the color for the module
+// ANY_LOG_ALL + 2: the color for the function
+//
+// NOTE: If you changed the default format in the implementation
+// (by redefining ANY_LOG_FORMAT_*, ANY_LOG_VALUE_* and ANY_LOG_PANIC_*),
+// you can safely ignore these variables and use whatever method you
+// prefer to get the colors.
+//
+extern const char **any_log_colors;
+
+// This array contains the default colors.
+//
+// See ANY_LOG_[level]_COLOR, ANY_LOG_RESET_COLOR, ANY_LOG_MODULE_COLOR and
+// ANY_LOG_FUNC_COLOR in the implementation.
+//
+extern const char *any_log_colors_default[ANY_LOG_ALL + 3];
+
+// This array contains empty strings.
+extern const char *any_log_colors_disabled[ANY_LOG_ALL + 3];
+
+// NOTE: You should never call the functions below directly!
+// See the above explanations on how to use logging.
+
+ANY_LOG_ATTRIBUTE(format(printf, 4, 5))
+ANY_LOG_ATTRIBUTE(nonnull(4))
+void any_log_format(any_log_level_t level, const char *module,
+ const char *func, const char *format, ...);
+
+ANY_LOG_ATTRIBUTE(nonnull(4))
+void any_log_value(any_log_level_t level, const char *module,
+ const char *func, const char *message, ...);
+
+ANY_LOG_ATTRIBUTE(noreturn)
+ANY_LOG_ATTRIBUTE(format(printf, 5, 6))
+ANY_LOG_ATTRIBUTE(nonnull(1, 4))
+void any_log_panic(const char *file, int line, const char *module,
+ const char *func, const char *format, ...);
+
+#endif
+
+#ifdef ANY_LOG_IMPLEMENT
+
+#include <stdarg.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+
+// For the C standard we can't assign stdout or any other streams here,
+// since they are not constant.
+//
+// Thus it is imperative to set this variable to a valid FILE * before using it!
+//
+FILE *any_log_stream = NULL;
+
+// The default value for any_log_level
+#ifndef ANY_LOG_LEVEL_DEFAULT
+#define ANY_LOG_LEVEL_DEFAULT ANY_LOG_INFO
+#endif
+
+any_log_level_t any_log_level = ANY_LOG_LEVEL_DEFAULT;
+
+// Utility function to initialize the library
+void any_log_init(FILE *stream, any_log_level_t level)
+{
+ any_log_stream = stream;
+ any_log_level = level;
+}
+
+// Log level strings
+#ifndef ANY_LOG_PANIC_STRING
+#define ANY_LOG_PANIC_STRING "panic"
+#endif
+#ifndef ANY_LOG_ERROR_STRING
+#define ANY_LOG_ERROR_STRING "error"
+#endif
+#ifndef ANY_LOG_WARN_STRING
+#define ANY_LOG_WARN_STRING "warn"
+#endif
+#ifndef ANY_LOG_INFO_STRING
+#define ANY_LOG_INFO_STRING "info"
+#endif
+#ifndef ANY_LOG_DEBUG_STRING
+#define ANY_LOG_DEBUG_STRING "debug"
+#endif
+#ifndef ANY_LOG_TRACE_STRING
+#define ANY_LOG_TRACE_STRING "trace"
+#endif
+
+const char *any_log_level_strings[ANY_LOG_ALL] = {
+ ANY_LOG_PANIC_STRING,
+ ANY_LOG_ERROR_STRING,
+ ANY_LOG_WARN_STRING,
+ ANY_LOG_INFO_STRING ,
+ ANY_LOG_DEBUG_STRING,
+ ANY_LOG_TRACE_STRING,
+};
+
+const char *any_log_level_to_string(any_log_level_t level)
+{
+ return level >= ANY_LOG_PANIC && level <= ANY_LOG_TRACE
+ ? any_log_level_strings[level] : "";
+}
+
+any_log_level_t any_log_level_from_string(const char *string)
+{
+ for (int level = ANY_LOG_PANIC; level < ANY_LOG_ALL; level++) {
+ if (strcmp(any_log_level_strings[level], string) == 0)
+ return (any_log_level_t)level;
+ }
+
+ return ANY_LOG_ALL;
+}
+
+// These colors related variables are provided just to provide a uniform
+// interface for setting the colors. If you decide to change the default
+// log format macros, feel free to ignore all this variables.
+//
+const char **any_log_colors = any_log_colors_default;
+
+// Log colors indexed by log level, with the addition of special colors
+// for func, module and reset sequence.
+//
+#ifndef ANY_LOG_PANIC_COLOR
+#define ANY_LOG_PANIC_COLOR "\x1b[1;91m"
+#endif
+#ifndef ANY_LOG_ERROR_COLOR
+#define ANY_LOG_ERROR_COLOR "\x1b[31m"
+#endif
+#ifndef ANY_LOG_WARN_COLOR
+#define ANY_LOG_WARN_COLOR "\x1b[1;33m"
+#endif
+#ifndef ANY_LOG_INFO_COLOR
+#define ANY_LOG_INFO_COLOR "\x1b[1;96m"
+#endif
+#ifndef ANY_LOG_DEBUG_COLOR
+#define ANY_LOG_DEBUG_COLOR "\x1b[1;37m"
+#endif
+#ifndef ANY_LOG_TRACE_COLOR
+#define ANY_LOG_TRACE_COLOR "\x1b[1;90m"
+#endif
+#ifndef ANY_LOG_RESET_COLOR
+#define ANY_LOG_RESET_COLOR "\x1b[0m"
+#endif
+#ifndef ANY_LOG_MODULE_COLOR
+#define ANY_LOG_MODULE_COLOR ""
+#endif
+#ifndef ANY_LOG_FUNC_COLOR
+#define ANY_LOG_FUNC_COLOR "\x1b[1m"
+#endif
+
+const char *any_log_colors_default[ANY_LOG_ALL + 3] = {
+ ANY_LOG_PANIC_COLOR,
+ ANY_LOG_ERROR_COLOR,
+ ANY_LOG_WARN_COLOR,
+ ANY_LOG_INFO_COLOR ,
+ ANY_LOG_DEBUG_COLOR,
+ ANY_LOG_TRACE_COLOR,
+ ANY_LOG_RESET_COLOR,
+ ANY_LOG_MODULE_COLOR,
+ ANY_LOG_FUNC_COLOR
+};
+
+const char *any_log_colors_disabled[ANY_LOG_ALL + 3] = {
+ "", "", "", "", "", "", "",
+};
+
+// Format for any_log_format (used at the start)
+#ifndef ANY_LOG_FORMAT_BEFORE
+#define ANY_LOG_FORMAT_BEFORE(level, module, func) \
+ "[%s%s%s %s%s%s] %s%s%s: ", any_log_colors[ANY_LOG_ALL + 1], module, any_log_colors[ANY_LOG_ALL], any_log_colors[ANY_LOG_ALL + 2], \
+ func, any_log_colors[ANY_LOG_ALL], any_log_colors[level], any_log_level_strings[level], any_log_colors[ANY_LOG_ALL]
+#endif
+
+// Format for any_log_format (used at the end)
+#ifndef ANY_LOG_FORMAT_AFTER
+#define ANY_LOG_FORMAT_AFTER(level, module, func) "\n"
+#endif
+
+void any_log_format(any_log_level_t level, const char *module,
+ const char *func, const char *format, ...)
+{
+ if (level > any_log_level)
+ return;
+
+ fprintf(any_log_stream, ANY_LOG_FORMAT_BEFORE(level, module, func));
+
+ va_list args;
+ va_start(args, format);
+ vfprintf(any_log_stream, format, args);
+ va_end(args);
+
+ fprintf(any_log_stream, ANY_LOG_FORMAT_AFTER(level, module, func));
+
+ // NOTE: Suppress compiler warning if the user customizes the format string
+ // and doesn't use these values in it
+ (void)module;
+ (void)func;
+}
+
+// This is used in the parsing of the type specifier from the key
+//
+// NOTE: It must be a character
+#ifndef ANY_LOG_VALUE_TYPE_SEP
+#define ANY_LOG_VALUE_TYPE_SEP ':'
+#endif
+
+// Format for any_log_value (used at the start)
+#ifndef ANY_LOG_VALUE_BEFORE
+#define ANY_LOG_VALUE_BEFORE(level, module, func, message) \
+ "[%s%s%s %s%s%s] %s%s%s: %s [", any_log_colors[ANY_LOG_ALL + 1], module, any_log_colors[ANY_LOG_ALL], any_log_colors[ANY_LOG_ALL + 2], \
+ func, any_log_colors[ANY_LOG_ALL], any_log_colors[level], any_log_level_strings[level], any_log_colors[ANY_LOG_ALL], message
+#endif
+
+// Format for any_log_value (used at the end)
+#ifndef ANY_LOG_VALUE_AFTER
+#define ANY_LOG_VALUE_AFTER(level, module, func, message) "]\n"
+#endif
+
+// Format for pairs with a bool value
+//
+// NOTE: C automatically promotes boolean types to int
+#ifndef ANY_LOG_VALUE_BOOL
+#define ANY_LOG_VALUE_BOOL(key, value) "%s=%s", key, (value ? "true" : "false")
+#endif
+
+// Format for pairs with an int value
+#ifndef ANY_LOG_VALUE_INT
+#define ANY_LOG_VALUE_INT(key, value) "%s=%d", key, value
+#endif
+
+// Format for pairs with an unsinged int value (hex by default)
+#ifndef ANY_LOG_VALUE_HEX
+#define ANY_LOG_VALUE_HEX(key, value) "%s=%#x", key, value
+#endif
+
+// Format for pairs with a long int value
+#ifndef ANY_LOG_VALUE_LONG
+#define ANY_LOG_VALUE_LONG(key, value) "%s=%ld", key, value
+#endif
+
+// Format for pairs with a pointer value
+#ifndef ANY_LOG_VALUE_PTR
+#define ANY_LOG_VALUE_PTR(key, value) "%s=%p", key, value
+#endif
+
+// Format for pairs with a double value
+#ifndef ANY_LOG_VALUE_DOUBLE
+#define ANY_LOG_VALUE_DOUBLE(key, value) "%s=%lf", key, value
+#endif
+
+// Format for pairs with a string value
+#ifndef ANY_LOG_VALUE_STRING
+#define ANY_LOG_VALUE_STRING(key, value) "%s=\"%s\"", key, value
+#endif
+
+#ifndef ANY_LOG_NO_GENERIC
+
+// Format custom types with the given formatter function
+#ifndef ANY_LOG_VALUE_GENERIC
+#define ANY_LOG_VALUE_GENERIC(key, stream, formatter, value) \
+ do { \
+ fprintf(stream, "%s=", key); \
+ formatter(stream, value); \
+ } while (false)
+#endif
+
+#endif
+
+// The default is to use string
+#ifndef ANY_LOG_VALUE_DEFAULT
+#define ANY_LOG_VALUE_DEFAULT(key, value) ANY_LOG_VALUE_STRING(key, value)
+#define ANY_LOG_VALUE_DEFAULT_TYPE char *
+#endif
+
+// This is used as a separator between different pairs
+#ifndef ANY_LOG_VALUE_PAIR_SEP
+#define ANY_LOG_VALUE_PAIR_SEP ", "
+#endif
+
+void any_log_value(any_log_level_t level, const char *module,
+ const char *func, const char *message, ...)
+{
+ if (level > any_log_level)
+ return;
+
+ fprintf(any_log_stream, ANY_LOG_VALUE_BEFORE(level, module, func, message));
+
+ va_list args;
+ va_start(args, message);
+
+ // NOTE: This function should be called with at least one pair
+ char *key = va_arg(args, char *);
+
+ while (key != NULL) {
+ if (key[0] != '\0' && key[1] == ANY_LOG_VALUE_TYPE_SEP) {
+ key += 2;
+ switch (tolower(key[-2])) {
+ case 'b': {
+ int value = va_arg(args, int);
+ fprintf(any_log_stream, ANY_LOG_VALUE_BOOL(key, value));
+ break;
+ }
+
+ case 'd':
+ case 'i': {
+ int value = va_arg(args, int);
+ fprintf(any_log_stream, ANY_LOG_VALUE_INT(key, value));
+ break;
+ }
+
+ case 'x':
+ case 'u': {
+ unsigned int value = va_arg(args, unsigned int);
+ fprintf(any_log_stream, ANY_LOG_VALUE_HEX(key, value));
+ break;
+ }
+
+ case 'l': {
+ long int value = va_arg(args, long int);
+ fprintf(any_log_stream, ANY_LOG_VALUE_LONG(key, value));
+ break;
+ }
+
+ case 'p': {
+ void *value = va_arg(args, void *);
+ fprintf(any_log_stream, ANY_LOG_VALUE_PTR(key, value));
+ break;
+ }
+
+ case 'f': {
+ double value = va_arg(args, double);
+ fprintf(any_log_stream, ANY_LOG_VALUE_DOUBLE(key, value));
+ break;
+ }
+
+ case 's': {
+ char *value = va_arg(args, char *);
+ fprintf(any_log_stream, ANY_LOG_VALUE_STRING(key, value));
+ break;
+ }
+
+#ifndef ANY_LOG_NO_GENERIC
+ case 'g': {
+ any_log_formatter_t formatter = va_arg(args, any_log_formatter_t);
+ ANY_LOG_VALUE_GENERIC_TYPE value = va_arg(args, ANY_LOG_VALUE_GENERIC_TYPE);
+ ANY_LOG_VALUE_GENERIC(key, any_log_stream, formatter, value);
+ break;
+ }
+#endif
+ default:
+ goto tdefault;
+ }
+ } else {
+tdefault:
+ ANY_LOG_VALUE_DEFAULT_TYPE value = va_arg(args, ANY_LOG_VALUE_DEFAULT_TYPE);
+ fprintf(any_log_stream, ANY_LOG_VALUE_DEFAULT(key, value));
+ }
+
+ key = va_arg(args, char *);
+ if (key == NULL)
+ break;
+
+ fprintf(any_log_stream, ANY_LOG_VALUE_PAIR_SEP);
+ }
+
+ va_end(args);
+ fprintf(any_log_stream, ANY_LOG_VALUE_AFTER(level, module, func, message));
+
+ (void)module;
+ (void)func;
+ (void)message;
+}
+
+// Using log_panic results in a call to any_log_panic, which should terminate
+// the program. The value of ANY_LOG_EXIT is used to specify an action to
+// take at the end of the aforementioned function.
+// By default it is abort
+//
+// NOTE: This function should never return!
+//
+#ifndef ANY_LOG_EXIT
+#define ANY_LOG_EXIT(file, line, module, func) abort()
+#endif
+
+// Format for any_log_panic (used at the start)
+#ifndef ANY_LOG_PANIC_BEFORE
+#define ANY_LOG_PANIC_BEFORE(file, line, module, func) \
+ "[%s%s%s %s%s%s] %s%s%s: ", any_log_colors[ANY_LOG_ALL + 1], module, any_log_colors[ANY_LOG_ALL], any_log_colors[ANY_LOG_ALL + 2], \
+ func, any_log_colors[ANY_LOG_ALL], any_log_colors[ANY_LOG_PANIC], any_log_level_strings[ANY_LOG_PANIC], any_log_colors[ANY_LOG_ALL]
+#endif
+
+// Format for any_log_panic (used at the start)
+#ifndef ANY_LOG_PANIC_AFTER
+#define ANY_LOG_PANIC_AFTER(file, line, module, func) \
+ "\n%spanic was invoked from%s %s:%d (%s%s%s)\n", any_log_colors[ANY_LOG_PANIC], any_log_colors[ANY_LOG_ALL], \
+ file, line, any_log_colors[ANY_LOG_ALL + 1], module, any_log_colors[ANY_LOG_ALL]
+#endif
+
+// NOTE: This function *exceptionally* gets more location information
+// because we want to be specific at least for fatal errors
+//
+void any_log_panic(const char *file, int line, const char *module,
+ const char *func, const char *format, ...)
+{
+ fprintf(any_log_stream, ANY_LOG_PANIC_BEFORE(file, line, module, func));
+
+ va_list args;
+ va_start(args, format);
+ vfprintf(any_log_stream, format, args);
+ va_end(args);
+
+ fprintf(any_log_stream, ANY_LOG_PANIC_AFTER(file, line, module, func));
+
+ (void)module;
+ (void)func;
+ (void)file;
+ (void)line;
+
+ ANY_LOG_EXIT(file, line, module, func);
+
+ // In a way or another, this function shall not return
+ abort();
+}
+
+#endif
+
+// MIT License
+//
+// Copyright (c) 2024 Federico Angelilli
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
diff --git a/src/comet.c b/src/comet.c
new file mode 100644
index 0000000..72bc3b5
--- /dev/null
+++ b/src/comet.c
@@ -0,0 +1,29 @@
+#include <stdlib.h>
+#include <locale.h>
+
+#include "window.h"
+
+#define ANY_LOG_IMPLEMENT
+#include "any_log.h"
+
+int main(int argc, char **argv)
+{
+ setlocale(LC_CTYPE, "");
+ any_log_init(stdout, ANY_LOG_DEBUG);
+
+ display_t display;
+ display_init(&display);
+
+ window_t window;
+ window_init(&window, &display);
+
+ window_move(&window, 100, 100);
+ window_resize(&window, 1000, 1000);
+
+ while (true);
+
+ window_close(&window);
+ display_close(&display);
+
+ return 0;
+}
diff --git a/src/display.c b/src/display.c
new file mode 100644
index 0000000..09549ce
--- /dev/null
+++ b/src/display.c
@@ -0,0 +1,168 @@
+#include <stdlib.h>
+#include <stdbool.h>
+#include <assert.h>
+#include <string.h>
+
+#include "any_log.h"
+#include "display.h"
+
+static bool query_xrm(display_t *display, const char *resource, char **value)
+{
+ bool found = xcb_xrm_resource_get_string(display->database, resource, NULL, value) >= 0;
+ log_value_debug("Xrm query",
+ "b:found", found,
+ "s:resource", resource,
+ "s:value", *value ? *value : "");
+ return found;
+}
+
+static void update_xrm(display_t *display)
+{
+ xcb_flush(display->connection);
+ xcb_xrm_database_t *database = xcb_xrm_database_from_default(display->connection);
+
+ if (database == NULL) {
+ log_warn("Xrm database couldn't be updated");
+ return;
+ }
+
+ xcb_xrm_database_free(display->database);
+ display->database = database;
+ log_debug("Xrm database updated");
+}
+
+static void update_scale(display_t *display)
+{
+ char *dpi_value;
+ if (query_xrm(display, "Xft.dpi", &dpi_value)) {
+ display->screen_dpi = strtod(dpi_value, NULL);
+ free(dpi_value);
+
+ // Ignore invalid values
+ if (display->screen_dpi != 0)
+ return;
+ }
+
+ display->screen_dpi = (double)display->screen_size->height * 25.4 / (double)display->screen_size->mheight;
+ log_debug("Fallback dpi value '%.2lf'", display->screen_dpi);
+}
+
+// Check if point (px, py) is inside a rectangle in (x, y), (x+w, y), (x, y+h) and (w+h, y+h)
+static inline bool in_rect(int px, int py, int x, int y, int w, int h)
+{
+ return px >= x && px <= x + w && py >= y && py <= y + h;
+}
+
+// Check if point (px, py) is inside a circle of radius r and center (x, y)
+static inline bool in_circle(int px, int py, int x, int y, int r)
+{
+ int dx = x - px;
+ int dy = y - py;
+ return (dx * dx + dy * dy) <= r * r;
+}
+
+// Check if point (px, py) is inside a capsule in (x, y), (x+w, y), (x, y+h) and (w+h, y+h)
+static inline bool in_capsule(int px, int py, int x, int y, int w, int h)
+{
+ assert(w >= h);
+ int radius = h / 2;
+
+ // Trivial case
+ if (w == h)
+ return in_circle(px, py, x + radius, y + radius, radius);
+
+ // General case
+ return in_circle(px, py, x + radius, y + radius, radius)
+ || in_circle(px, py, x + w - radius, y + radius, radius)
+ || in_rect(px, py, x + radius, y, w - 2 * radius, h);
+}
+
+void display_init(display_t *display)
+{
+ memset(display, 0, sizeof(display_t));
+
+ int preferred_screen = 0;
+ display->connection = xcb_connect(NULL, &preferred_screen);
+ assert(display->connection != NULL && !xcb_connection_has_error(display->connection));
+
+ log_value_debug("Xcb connection established",
+ "i:preferred_screen", preferred_screen);
+
+ xcb_screen_iterator_t iter = xcb_setup_roots_iterator(xcb_get_setup(display->connection));
+ while (preferred_screen != 0 && iter.rem) {
+ xcb_screen_next(&iter);
+ preferred_screen--;
+ }
+
+ display->screen = iter.data;
+ xcb_generic_error_t *error;
+
+ xcb_randr_query_version_cookie_t version_cookie = xcb_randr_query_version(display->connection,
+ XCB_RANDR_MAJOR_VERSION,
+ XCB_RANDR_MINOR_VERSION);
+
+ xcb_randr_query_version_reply_t *randr_version = xcb_randr_query_version_reply(display->connection,
+ version_cookie,
+ &error);
+
+ assert(error == NULL);
+ log_value_debug("RandR loaded",
+ "i:major", randr_version->major_version,
+ "i:minor", randr_version->minor_version);
+
+ assert(randr_version->major_version >= 1);
+ free(randr_version);
+
+ xcb_randr_get_screen_info_cookie_t cookie = xcb_randr_get_screen_info(display->connection, display->screen->root);
+ display->info_reply = xcb_randr_get_screen_info_reply(display->connection, cookie, &error);
+ assert(error == NULL);
+
+ display->screen_size = xcb_randr_get_screen_info_sizes(display->info_reply);
+ assert(display->screen_size != NULL);
+ log_debug("Display size [width=%d, height=%d]", display->screen_size->width, display->screen_size->height);
+
+ xcb_randr_select_input(display->connection,
+ display->screen->root,
+ XCB_RANDR_NOTIFY_MASK_SCREEN_CHANGE |
+ XCB_RANDR_NOTIFY_MASK_OUTPUT_CHANGE |
+ XCB_RANDR_NOTIFY_MASK_OUTPUT_PROPERTY);
+
+ log_debug("Searching 32 bit visual");
+ display->visual_type = xcb_aux_find_visual_by_attrs(display->screen, XCB_VISUAL_CLASS_TRUE_COLOR, 32);
+ display->screen_depth = 32;
+
+ if (display->visual_type == NULL) {
+ // NOTE: 24 bit visuals don't have the alpha channel for transparency
+ log_debug("Falling back to 24 bit visual");
+ display->visual_type = xcb_aux_find_visual_by_attrs(display->screen, XCB_VISUAL_CLASS_TRUE_COLOR, 24);
+ display->screen_depth = 24;
+ }
+
+ assert(display->visual_type != 0);
+
+ xcb_intern_atom_cookie_t *ewmh_cookie = xcb_ewmh_init_atoms(display->connection, &display->ewmh);
+ assert(xcb_ewmh_init_atoms_replies(&display->ewmh, ewmh_cookie, &error));
+
+ assert(error == NULL);
+ log_debug("Xcb EWMH initialized");
+
+ display->database = xcb_xrm_database_from_default(display->connection);
+ assert(display->database != NULL);
+
+ // TODO: Dpi aware scaling
+ update_scale(display);
+
+ assert(xcb_errors_context_new(display->connection, &display->errors) == 0);
+ log_debug("Xcb errors loaded");
+
+ xcb_flush(display->connection);
+}
+
+void display_close(display_t *display)
+{
+ xcb_ewmh_connection_wipe(&display->ewmh);
+ xcb_errors_context_free(display->errors);
+ xcb_xrm_database_free(display->database);
+ xcb_disconnect(display->connection);
+ free(display->info_reply);
+}
diff --git a/src/display.h b/src/display.h
new file mode 100644
index 0000000..8d99cad
--- /dev/null
+++ b/src/display.h
@@ -0,0 +1,28 @@
+#ifndef COMET_DISPLAY_H
+#define COMET_DISPLAY_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>
+
+typedef struct {
+ xcb_connection_t *connection;
+ xcb_screen_t *screen;
+ xcb_randr_get_screen_info_reply_t *info_reply;
+ 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;
+} display_t;
+
+void display_init(display_t *display);
+
+void display_close(display_t *display);
+
+#endif
diff --git a/src/window.c b/src/window.c
new file mode 100644
index 0000000..e45eb27
--- /dev/null
+++ b/src/window.c
@@ -0,0 +1,296 @@
+#include <string.h>
+#include <math.h>
+#include <assert.h>
+#include <unistd.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 "any_log.h"
+#include "window.h"
+
+static xcb_atom_t intern_atom(window_t *window, const char *atom)
+{
+ xcb_generic_error_t *error;
+ xcb_intern_atom_cookie_t cookie = xcb_intern_atom(window->display->connection, false, strlen(atom), atom);
+ xcb_intern_atom_reply_t *reply = xcb_intern_atom_reply(window->display->connection, cookie, &error);
+
+ assert(error == NULL);
+ xcb_atom_t id = reply->atom;
+ free(reply);
+ return id;
+}
+
+static void wm_set_size(window_t *window)
+{
+ xcb_size_hints_t hints = { 0 };
+ xcb_icccm_size_hints_set_size(&hints, false, window->width, window->height);
+ xcb_icccm_size_hints_set_min_size(&hints, window->width, window->height);
+ xcb_icccm_size_hints_set_max_size(&hints, window->width, window->height);
+ xcb_icccm_size_hints_set_base_size(&hints, window->width, window->height);
+ xcb_icccm_size_hints_set_position(&hints, false, window->x, window->y);
+
+ xcb_icccm_set_wm_size_hints(window->display->connection, window->window, XCB_ATOM_WM_NORMAL_HINTS, &hints);
+ log_debug("Xcb icccm size hints updated");
+}
+
+static void wm_set_struts(window_t *window)
+{
+ const long end = window->x + window->width - 1;
+ uint32_t values[12] = { 0 };
+
+ values[2] = window->height; // top
+ values[8] = window->x; // top y0
+ values[9] = end > 0 ? end : 0; // top y1
+
+ xcb_change_property(window->display->connection,
+ XCB_PROP_MODE_REPLACE,
+ window->window,
+ window->display->ewmh._NET_WM_STRUT,
+ XCB_ATOM_CARDINAL,
+ 32,
+ 4,
+ values);
+
+ xcb_change_property(window->display->connection,
+ XCB_PROP_MODE_REPLACE,
+ window->window,
+ window->display->ewmh._NET_WM_STRUT_PARTIAL,
+ XCB_ATOM_CARDINAL,
+ 32,
+ 12,
+ values);
+
+ log_debug("Xcb EWMH struts updated");
+}
+
+static void wm_setup(window_t *window)
+{
+ const char *title = "comet";
+ xcb_icccm_set_wm_name(window->display->connection, window->window, XCB_ATOM_STRING, 8, strlen(title), title);
+
+ const char class[] = "comet\0Comet";
+ xcb_icccm_set_wm_class(window->display->connection, window->window, strlen(class), class);
+
+ log_value_debug("Updated window information",
+ "s:title", title,
+ "s:class", "comet\\0Comet");
+
+ xcb_generic_error_t *error;
+ xcb_intern_atom_cookie_t *ewmh_cookie = xcb_ewmh_init_atoms(window->display->connection, &window->display->ewmh);
+
+ assert(xcb_ewmh_init_atoms_replies(&window->display->ewmh, ewmh_cookie, &error));
+ assert(error == NULL);
+ log_debug("Xcb EWMH connected");
+
+ xcb_ewmh_set_wm_window_type(&window->display->ewmh, window->window, 1, &window->display->ewmh._NET_WM_WINDOW_TYPE_DOCK);
+ xcb_ewmh_set_wm_desktop(&window->display->ewmh, window->window, 0xFFFFFFFF);
+ xcb_ewmh_set_wm_pid(&window->display->ewmh, window->window, getpid());
+
+ xcb_atom_t state[] = {
+ window->display->ewmh._NET_WM_STATE_STICKY,
+ window->display->ewmh._NET_WM_STATE_ABOVE
+ };
+ xcb_ewmh_set_wm_state(&window->display->ewmh, window->window, 2, state);
+
+ // TODO: These should be updated depdending on the situation!
+ wm_set_size(window);
+ wm_set_struts(window);
+}
+
+void window_init(window_t *window, display_t *display)
+{
+ memset(window, 0, sizeof(window_t));
+ window->display = display;
+
+ // Set mask and params for CreateWindow
+ window->cw_mask = XCB_CW_BACK_PIXMAP | XCB_CW_BACK_PIXEL | XCB_CW_BORDER_PIXEL
+ | XCB_CW_OVERRIDE_REDIRECT | XCB_CW_EVENT_MASK | XCB_CW_COLORMAP;
+
+ 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_EVENT_MASK_POINTER_MOTION | XCB_EVENT_MASK_POINTER_MOTION_HINT;
+
+ xcb_colormap_t colormap = xcb_generate_id(display->connection);
+ xcb_create_colormap(display->connection, XCB_COLORMAP_ALLOC_NONE, colormap,
+ display->screen->root, display->visual_type->visual_id);
+
+ XCB_AUX_ADD_PARAM(&window->cw_mask, &window->cw_params, back_pixmap, XCB_NONE);
+ XCB_AUX_ADD_PARAM(&window->cw_mask, &window->cw_params, back_pixel, 0x00000000);
+ XCB_AUX_ADD_PARAM(&window->cw_mask, &window->cw_params, border_pixel, 0x00000000);
+ XCB_AUX_ADD_PARAM(&window->cw_mask, &window->cw_params, override_redirect, true);
+ XCB_AUX_ADD_PARAM(&window->cw_mask, &window->cw_params, event_mask, event_mask);
+ XCB_AUX_ADD_PARAM(&window->cw_mask, &window->cw_params, colormap, colormap);
+
+ // Temporary position and size
+ window->width = window->height = 1;
+ window->x = window->y = 0;
+
+ window->window = xcb_generate_id(display->connection);
+ xcb_aux_create_window(display->connection,
+ display->screen_depth,
+ window->window,
+ display->screen->root,
+ window->x, window->y,
+ window->width, window->height,
+ 0, // border
+ XCB_WINDOW_CLASS_COPY_FROM_PARENT,
+ display->visual_type->visual_id,
+ window->cw_mask,
+ &window->cw_params);
+
+ log_value_debug("Xcb window created",
+ "u:id", window->window);
+
+ wm_setup(window);
+ log_debug("Updated window WM options");
+
+ xcb_map_window(display->connection, window->window);
+ xcb_flush(display->connection);
+
+ window->surface = cairo_xcb_surface_create(display->connection,
+ window->window,
+ display->visual_type,
+ display->screen_size->width,
+ display->screen_size->height);
+
+ assert(cairo_surface_status(window->surface) == CAIRO_STATUS_SUCCESS);
+ log_trace("Cairo surface created");
+
+ window->cr = cairo_create(window->surface);
+ assert(cairo_status(window->cr) == CAIRO_STATUS_SUCCESS);
+ log_trace("Cairo context created");
+}
+
+//double window_get_scale(window_t *window)
+//{
+// const int n = 4;
+// return MAX(1, floor((window->display->screen_dpi / 96.0) * n) * (1.0 / n));
+//}
+
+void window_move(window_t *window, int x, int y)
+{
+ if (window->x == x && window->y == y)
+ return;
+
+ const uint32_t values[] = { x, y };
+ xcb_configure_window(window->display->connection,
+ window->window,
+ XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y,
+ values);
+
+ log_value_debug("Updated window position",
+ "i:x", x,
+ "i:y", y);
+
+ window->x = x;
+ window->y = y;
+}
+
+static void window_reshape(window_t *window)
+{
+ // TODO: Actually make this a parameter
+ int radius = window->height / 2;
+ double degree = M_PI / 180.0;
+
+ // TODO: Check this value
+ int depth = 1;
+
+ xcb_pixmap_t bitmap = xcb_generate_id(window->display->connection);
+ xcb_create_pixmap(window->display->connection, depth, bitmap, window->window, window->width, window->height);
+ log_debug("Xcb pixmap created [id=%u]", bitmap);
+
+ cairo_surface_t *surface = cairo_xcb_surface_create_for_bitmap(window->display->connection,
+ window->display->screen,
+ bitmap,
+ window->width,
+ window->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, window->width - radius, radius, radius, 270 * degree, 450 * degree);
+ cairo_fill(cr);
+
+ log_trace("Xcb shape painted");
+
+ cairo_show_page(cr);
+ cairo_destroy(cr);
+ cairo_surface_flush(surface);
+ cairo_surface_destroy(surface);
+
+ xcb_shape_mask(window->display->connection, XCB_SHAPE_SO_SET, XCB_SHAPE_SK_BOUNDING, window->window, 0, 0, bitmap);
+ xcb_shape_select_input(window->display->connection, window->window, XCB_SHAPE_NOTIFY);
+ log_debug("Xcb shape mask updated");
+
+ xcb_free_pixmap(window->display->connection, bitmap);
+}
+
+void window_resize(window_t *window, int width, int height)
+{
+ if (window->width == width && window->height == height)
+ return;
+
+ const uint32_t values[] = { width, height };
+ xcb_configure_window(window->display->connection,
+ window->window,
+ XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT,
+ values);
+
+ log_value_debug("Updated window size",
+ "i:width", width,
+ "i:height", height);
+
+ window->width = width;
+ window->height = height;
+
+ // Update wm hints
+ wm_set_size(window);
+
+ // Update shape mask
+ window_reshape(window);
+}
+
+void window_present(window_t *window, cairo_surface_t *surface, int width, int height)
+{
+ cairo_xcb_surface_set_size(window->surface, width, height);
+ xcb_clear_area(window->display->connection, false, window->window, 0, 0, 0, 0);
+
+ cairo_save(window->cr);
+ cairo_set_source_surface(window->cr, surface, 0, 0);
+
+ cairo_paint(window->cr);
+ cairo_show_page(window->cr);
+
+ cairo_surface_flush(window->surface);
+ cairo_restore(window->cr);
+
+ xcb_circulate_window(window->display->connection, XCB_CIRCULATE_RAISE_LOWEST, window->window);
+ xcb_flush(window->display->connection);
+}
+
+void window_close(window_t *window)
+{
+ cairo_destroy(window->cr);
+ cairo_surface_destroy(window->surface);
+}
diff --git a/src/window.h b/src/window.h
new file mode 100644
index 0000000..74cc231
--- /dev/null
+++ b/src/window.h
@@ -0,0 +1,31 @@
+#ifndef COMET_WINDOW_H
+#define COMET_WINDOW_H
+
+#include <cairo.h>
+#include <xcb/xcb.h>
+#include <xcb/xcb_aux.h>
+
+#include "display.h"
+
+typedef struct {
+ display_t *display;
+ xcb_drawable_t window;
+ uint32_t cw_mask;
+ xcb_params_cw_t cw_params;
+ cairo_surface_t *surface;
+ cairo_t *cr;
+ int x, y;
+ int width, height;
+} window_t;
+
+void window_init(window_t *window, display_t *display);
+
+void window_move(window_t *window, int x, int y);
+
+void window_resize(window_t *window, int width, int height);
+
+void window_present(window_t *window, cairo_surface_t *surface, int width, int height);
+
+void window_close(window_t *window);
+
+#endif