diff options
| author | Joel Klinghed <the_jk@spawned.biz> | 2021-01-27 22:06:49 +0100 |
|---|---|---|
| committer | Joel Klinghed <the_jk@spawned.biz> | 2021-01-27 22:06:49 +0100 |
| commit | 06950aab233de6a2f47293d59575bb42f6131660 (patch) | |
| tree | 62f6eed4a6d35414f656d22b9ac7420849018a11 /src/main.c | |
| parent | 1ef9c463f1efc1adfb62e42ab3dd17e8c6394373 (diff) | |
Complete rewrite using C++ and with shared state support
Diffstat (limited to 'src/main.c')
| -rw-r--r-- | src/main.c | 896 |
1 files changed, 0 insertions, 896 deletions
diff --git a/src/main.c b/src/main.c deleted file mode 100644 index 9bb5b6c..0000000 --- a/src/main.c +++ /dev/null @@ -1,896 +0,0 @@ -#include "common.h" - -#include <errno.h> -#include <math.h> -#include <stdio.h> -#include <string.h> - -#include <xcb/xcb.h> -#include <xcb/xcb_event.h> -#include <xcb/xcb_keysyms.h> -#if HAVE_XCB_ICCCM -# include <xcb/xcb_icccm.h> -#endif - -#define HAVE_STRUCT_TIMESPEC 1 - -#include "paths.h" -#include "safe_fifo.h" -#include "thread.h" - -#if HAVE_XCB_ICCCM -typedef struct atoms_t -{ - xcb_atom_t WM_DELETE_WINDOW; - xcb_atom_t WM_PROTOCOLS; -} atoms_t; -#endif - -typedef enum message_type_t -{ - MSG_QUIT, - MSG_DRAW, - MSG_SWAP, - MSG_CLEAR -} message_type_t; - -typedef struct message_t -{ - message_type_t type; -} message_t; - -static message_t quit_message = { MSG_QUIT }; -static message_t draw_message = { MSG_DRAW }; -static message_t swap_message = { MSG_SWAP }; -static message_t clear_message = { MSG_CLEAR }; - -typedef struct time_target_t time_target_t; - -typedef bool (* timer_callback_t)(void *data); - -struct time_target_t -{ - time_target_t *next; - struct timespec target; - unsigned long interval_ms; - void *data; - timer_callback_t callback; -}; - -typedef struct main_t -{ - xcb_connection_t *conn; - xcb_window_t wnd; - xcb_gcontext_t gc; - xcb_font_t font; -#if HAVE_XCB_ICCCM - atoms_t atoms; -#endif - safe_fifo_t *queue; - const char *state; - paths_t *paths; - time_target_t *first_timer; - bool save_queued; - bool redraw_queued; - unsigned int width, height; - uint32_t background[2], foreground[2]; - - /* State */ - bool working; - time_t last_time; - unsigned long total_min; -} main_t; - -static const char DEFAULT_STATE[] = "timer.state"; - -static const char FONT_NAME[] = "7x13"; - -static const unsigned long REDRAW_INTERVAL_MS = 36 * 1000; - -static bool handle_arguments(main_t *m, int argc, char **argv, int *exitcode); -static void draw(main_t *m); -static void *run_xcb(void *arg); -static bool load_state(main_t *m); -static bool save_state(main_t *m); -static void default_state(main_t *m); -static void swap(main_t *m); -static void clear(main_t *m); -static void queue_save(main_t *m); -static void add_timer(main_t *m, unsigned long interval_ms, - timer_callback_t callback, void *data); -static void insert_timer(main_t *m, time_target_t *target); -static void run_timer(main_t *m); -static bool redraw(void *arg); -static int timeval_cmp(const struct timespec* x, const struct timespec* y); - -int main(int argc, char **argv) -{ - main_t m; - xcb_screen_iterator_t iter; - xcb_screen_t *screen; - int screen_index; - uint32_t mask, values[2]; - xcb_void_cookie_t cookie[10]; - size_t cookies = 0; - int exitcode; - bool quit = false; - memset(&m, 0, sizeof(m)); - - if (!handle_arguments(&m, argc, argv, &exitcode)) - { - return exitcode; - } - - m.paths = paths_new(); - if (!m.paths) - { - fputs("Unable to allocate memory.\n", stderr); - return EXIT_FAILURE; - } - - if (!load_state(&m)) - { - fputs("Error loading old state.\n", stderr); - paths_unref(m.paths); - return EXIT_FAILURE; - } - - m.conn = xcb_connect(NULL, &screen_index); - if (!m.conn) - { - fputs("Unable to connect to X11 display.\n", stderr); - paths_unref(m.paths); - return EXIT_FAILURE; - } - -#if HAVE_XCB_ICCCM - { - xcb_intern_atom_cookie_t cookie[2]; - xcb_intern_atom_reply_t *reply; - xcb_generic_error_t *err; - - cookie[0] = xcb_intern_atom(m.conn, 0, strlen("WM_DELETE_WINDOW"), - "WM_DELETE_WINDOW");; - cookie[1] = xcb_intern_atom(m.conn, 0, strlen("WM_PROTOCOLS"), - "WM_PROTOCOLS");; - reply = xcb_intern_atom_reply(m.conn, cookie[0], &err); - if (!reply) - { - fprintf(stderr, "ICCCM init atoms failed\n"); - xcb_disconnect(m.conn); - paths_unref(m.paths); - return EXIT_FAILURE; - } - m.atoms.WM_DELETE_WINDOW = reply->atom; - free(reply); - reply = xcb_intern_atom_reply(m.conn, cookie[1], &err); - if (!reply) - { - fprintf(stderr, "ICCCM init atoms failed\n"); - xcb_disconnect(m.conn); - paths_unref(m.paths); - return EXIT_FAILURE; - } - m.atoms.WM_PROTOCOLS = reply->atom; - free(reply); - } -#endif - - iter = xcb_setup_roots_iterator(xcb_get_setup(m.conn)); - while (screen_index-- > 0) - { - xcb_screen_next(&iter); - } - - screen = iter.data; - - { - xcb_alloc_color_cookie_t req[4]; - xcb_alloc_color_reply_t* rep; - /* background, not working */ - req[0] = xcb_alloc_color(m.conn, screen->default_colormap, - 0x8000, 0x8000, 0x8000); - /* background, working */ - req[1] = xcb_alloc_color(m.conn, screen->default_colormap, - 0x8000, 0xffff, 0x8000); - /* foreground, not working */ - req[2] = xcb_alloc_color(m.conn, screen->default_colormap, - 0, 0, 0); - /* foreground, working */ - req[3] = xcb_alloc_color(m.conn, screen->default_colormap, - 0, 0, 0); - rep = xcb_alloc_color_reply(m.conn, req[0], 0); - m.background[0] = rep ? rep->pixel : screen->white_pixel; - free(rep); - rep = xcb_alloc_color_reply(m.conn, req[1], 0); - m.background[1] = rep ? rep->pixel : screen->white_pixel; - free(rep); - rep = xcb_alloc_color_reply(m.conn, req[2], 0); - m.foreground[0] = rep ? rep->pixel : screen->black_pixel; - free(rep); - rep = xcb_alloc_color_reply(m.conn, req[3], 0); - m.foreground[1] = rep ? rep->pixel : screen->black_pixel; - free(rep); - } - - m.font = xcb_generate_id(m.conn); - cookie[cookies++] = xcb_open_font_checked(m.conn, m.font, - sizeof(FONT_NAME) / - sizeof(FONT_NAME[0]), - FONT_NAME); - - mask = XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK; - values[0] = screen->black_pixel; - values[1] = XCB_EVENT_MASK_EXPOSURE | XCB_EVENT_MASK_BUTTON_PRESS | - XCB_EVENT_MASK_KEY_RELEASE; - - m.width = 100; - m.height = 50; - - m.wnd = xcb_generate_id(m.conn); - cookie[cookies++] = xcb_create_window_checked(m.conn, XCB_COPY_FROM_PARENT, - m.wnd, screen->root, - 0, 0, m.width, m.height, 0, - XCB_WINDOW_CLASS_INPUT_OUTPUT, - screen->root_visual, - mask, values); - - m.gc = xcb_generate_id(m.conn); - mask = XCB_GC_FONT; - values[0] = m.font; - cookie[cookies++] = xcb_create_gc_checked(m.conn, m.gc, m.wnd, - mask, values); - - cookie[cookies++] = xcb_map_window_checked(m.conn, m.wnd); - -#if HAVE_XCB_ICCCM - { - xcb_atom_t protocols[1]; - protocols[0] = m.atoms.WM_DELETE_WINDOW; - - xcb_icccm_set_wm_protocols(m.conn, m.wnd, m.atoms.WM_PROTOCOLS, - 1, protocols); - xcb_icccm_set_wm_name(m.conn, m.wnd, XCB_ATOM_STRING, 8, 5, "timer"); - xcb_icccm_set_wm_class(m.conn, m.wnd, 8, "jk.timer"); - } -#endif - - while (cookies--) - { - xcb_generic_error_t *error = xcb_request_check(m.conn, cookie[cookies]); - if (error) - { - fprintf(stderr, "Error: %d\n", error->error_code); - xcb_disconnect(m.conn); - paths_unref(m.paths); - return EXIT_FAILURE; - } - } - - xcb_flush(m.conn); - - m.queue = safe_fifo_new(); - - thread_new(run_xcb, &m); - - if (m.working) - { - add_timer(&m, REDRAW_INTERVAL_MS, redraw, &m); - m.redraw_queued = true; - } - - while (!quit) - { - message_t *msg; - if (m.first_timer) - { - struct timespec now; - thread_abstime(&now, 0); - if (timeval_cmp(&now, &m.first_timer->target) >= 0) - { - run_timer(&m); - msg = safe_fifo_trypop(m.queue); - } - else - { - msg = safe_fifo_timedpop(m.queue, &m.first_timer->target); - } - } - else - { - msg = safe_fifo_pop(m.queue); - } - if (msg) - { - switch (msg->type) - { - case MSG_QUIT: - quit = true; - break; - case MSG_DRAW: - draw(&m); - break; - case MSG_SWAP: - swap(&m); - break; - case MSG_CLEAR: - clear(&m); - break; - } - } - } - - if (m.save_queued) - { - save_state(&m); - } - - xcb_close_font(m.conn, m.font); - xcb_free_gc(m.conn, m.gc); - xcb_destroy_window(m.conn, m.wnd); - - xcb_disconnect(m.conn); - while (m.first_timer) - { - time_target_t *next = m.first_timer->next; - free(m.first_timer); - m.first_timer = next; - } - paths_unref(m.paths); - safe_fifo_unref(m.queue); - - return EXIT_SUCCESS; -} - -static void print_usage(void) -{ - fputs("Usage: timer [OPTION]...\n", stdout); - fputs("Timer is a timekeeping tool.\n", stdout); - fputs("\n", stdout); - fputs("Options:\n", stdout); - fputs(" -state FILE load state from FILE instead of default\n", stdout); - fputs(" -help display this information and exit\n", stdout); - fputs(" -version display version and exit\n", stdout); -} - -static void print_version(void) -{ - fputs("timer version " VERSION " written by Joel Klinghed\n", stdout); -} - -bool handle_arguments(main_t *m, int argc, char **argv, int *exitcode) -{ - bool usage = false, version = false, error = false; - int a; - for (a = 1; a < argc; a++) - { - if (argv[a][0] == '-') - { - if (strcmp(argv[a] + 1, "help") == 0) - { - usage = true; - break; - } - else if (strcmp(argv[a] + 1, "version") == 0) - { - version = true; - break; - } - else if (strcmp(argv[a] + 1, "state") == 0) - { - if (++a == argc) - { - error = true; - fputs("Option `state` expects an argument.\n", stderr); - break; - } - if (m->state) - { - error = true; - fputs("Option `state` given twice.\n", stderr); - break; - } - m->state = argv[a]; - } - else - { - error = true; - fprintf(stderr, "Unknown option: %s\n", argv[a] + 1); - break; - } - } - else - { - break; - } - } - if (!error && a < argc) - { - fputs("Too many arguments given.\n", stderr); - error = true; - } - if (usage) - { - print_usage(); - *exitcode = error ? EXIT_FAILURE : EXIT_SUCCESS; - return false; - } - if (error) - { - fputs("Try `timer -help` for usage.\n", stderr); - *exitcode = EXIT_FAILURE; - return false; - } - if (version) - { - print_version(); - *exitcode = EXIT_SUCCESS; - return false; - } - return true; -} - -void draw(main_t *m) -{ - xcb_rectangle_t r; - xcb_char2b_t *tmp; - uint32_t values[2]; - char text[50]; - int x, y, i, len; - xcb_query_text_extents_cookie_t cookie; - xcb_query_text_extents_reply_t *reply; - - if (m->working) - { - double diff = difftime(time(NULL), m->last_time); - diff /= 60.0 * 60.0; - - len = snprintf(text, sizeof(text), "%.2f (%.2f)", diff, - (double)m->total_min / 60.0 + diff); - } - else - { - len = snprintf(text, sizeof(text), "(%.2f)", - (double)m->total_min / 60.0); - } - if (len < 0 || len == sizeof(text)) - { - return; - } - tmp = malloc(len * sizeof(xcb_char2b_t)); - for (i = 0; i < len; i++) - { - tmp[i].byte1 = 0; - tmp[i].byte2 = text[i]; - } - - cookie = xcb_query_text_extents(m->conn, m->font, len, tmp); - - r.x = 0; - r.y = 0; - r.width = m->width; - r.height = m->height; - - values[0] = m->background[m->working ? 1 : 0]; - xcb_change_gc(m->conn, m->gc, XCB_GC_FOREGROUND, values); - xcb_poly_fill_rectangle(m->conn, m->wnd, m->gc, 1, &r); - - values[0] = m->foreground[m->working ? 1 : 0]; - values[1] = m->background[m->working ? 1 : 0]; - xcb_change_gc(m->conn, m->gc, XCB_GC_FOREGROUND | XCB_GC_BACKGROUND, values); - - x = 0; - y = m->height / 2; - - reply = xcb_query_text_extents_reply(m->conn, cookie, NULL); - if (reply) - { - if (reply->overall_width < m->width) - { - x = (m->width - reply->overall_width) / 2; - } - else - { - x = 0; - } - y += reply->font_ascent / 2; - free(reply); - } - - xcb_image_text_16(m->conn, len, m->wnd, m->gc, x, y, tmp); - - xcb_flush(m->conn); - - free(tmp); -} - -static void send_message(main_t *m, message_t* msg) -{ - safe_fifo_push(m->queue, msg); -} - -void *run_xcb(void *arg) -{ - main_t *m = arg; - xcb_key_symbols_t *syms; - - syms = xcb_key_symbols_alloc(m->conn); - - for (;;) - { - bool done = false; - xcb_generic_event_t *event = xcb_wait_for_event(m->conn); - if (!event || xcb_connection_has_error(m->conn)) - { - free(event); - break; - } - switch (XCB_EVENT_RESPONSE_TYPE(event)) - { - case XCB_EXPOSE: - { - xcb_expose_event_t *e = (xcb_expose_event_t*)event; - if (e->count == 0) - { - send_message(m, &draw_message); - } - break; - } -#if HAVE_XCB_ICCCM - case XCB_CLIENT_MESSAGE: - { - xcb_client_message_event_t *e = (xcb_client_message_event_t*)event; - if (e->type == m->atoms.WM_PROTOCOLS && e->format == 32 && - e->data.data32[0] == m->atoms.WM_DELETE_WINDOW) - { - done = true; - } - break; - } -#endif - case XCB_BUTTON_PRESS: - { - xcb_button_press_event_t *e = (xcb_button_press_event_t*)event; - if (e->detail == 1) - { - send_message(m, &swap_message); - } - break; - } - case XCB_KEY_RELEASE: - { - xcb_key_press_event_t *e = (xcb_key_press_event_t*)event; - xcb_keysym_t sym = xcb_key_press_lookup_keysym(syms, e, 0); - if (sym == ' ') - { - send_message(m, &swap_message); - } - else if (sym == 'r' && (e->state & XCB_MOD_MASK_CONTROL)) - { - send_message(m, &clear_message); - } - break; - } - case XCB_MAPPING_NOTIFY: - { - xcb_mapping_notify_event_t *e = (xcb_mapping_notify_event_t*)event; - xcb_refresh_keyboard_mapping(syms, e); - break; - } - } - free(event); - if (done) - { - break; - } - } - xcb_key_symbols_free(syms); - send_message(m, &quit_message); - return NULL; -} - -bool load_state(main_t *m) -{ - FILE *fh; - char *tmp, *line; - const char *fname; - char buf[1024]; - char *end; - long l; - unsigned long ul; - struct tm tm; - if (m->state) - { - fname = m->state; - tmp = NULL; - } - else - { - const char *dir = paths_user_dir(m->paths, PATHS_DATA); - size_t dlen = strlen(dir); - tmp = malloc(dlen + 1 + sizeof(DEFAULT_STATE)); - if (!tmp) - { - fputs("Out of memory\n", stderr); - return false; - } - memcpy(tmp, dir, dlen); - tmp[dlen] = '/'; - memcpy(tmp + dlen + 1, DEFAULT_STATE, sizeof(DEFAULT_STATE)); - fname = tmp; - } - fh = fopen(fname, "rb"); - if (!fh) - { - if (errno == ENOENT) - { - default_state(m); - free(tmp); - return true; - } - fprintf(stderr, "Error loading state from %s: %s\n", fname, - strerror(errno)); - free(tmp); - return false; - } - - line = fgets(buf, sizeof(buf), fh); - if (!line) - { - fprintf(stderr, "Error reading state from %s: %s\n", fname, - strerror(errno)); - free(tmp); - return false; - } - - fclose(fh); - - end = NULL; - l = strtol(line, &end, 10); - if ((l != -1 && l != 1) || !end || *end != '|') - { - fprintf(stderr, "Invalid data in state %s: %s\n", fname, - strerror(errno)); - free(tmp); - return false; - } - - line = end + 1; - end = NULL; - ul = strtoul(line, &end, 10); - if (!end || *end != '|') - { - fprintf(stderr, "Invalid data in state %s: %s\n", fname, - strerror(errno)); - free(tmp); - return false; - } - - end = strptime(end + 1, "%Y-%m-%d %H:%M:%S", &tm); - if (!end) - { - fprintf(stderr, "Invalid data in state %s: %s\n", fname, - strerror(errno)); - free(tmp); - return false; - } - - m->working = l == 1; - m->last_time = timegm(&tm); - m->total_min = ul; - - free(tmp); - return true; -} - -bool save_state(main_t *m) -{ - paths_file_t *file = paths_write(m->paths, PATHS_DATA, - m->state ? m->state : DEFAULT_STATE, - PATHS_CREATE | PATHS_TRUNCATE, - 0600); - char buf[1024]; - size_t len2; - int pos, len; - m->save_queued = false; - - if (!file) - { - fprintf(stderr, "Failed to save state: %s\n", strerror(errno)); - return false; - } - - len = snprintf(buf, sizeof(buf), "%d|%lu|", - m->working ? 1 : -1, m->total_min); - if (len < 0 || len == sizeof(buf)) - { - fprintf(stderr, "Failed to save state: %s\n", strerror(errno)); - paths_file_abort(file); - return false; - } - len2 = strftime(buf + len, sizeof(buf) - len, "%Y-%m-%d %H:%M:%S", - gmtime(&m->last_time)); - if (len2 == 0 || len2 == sizeof(buf) - len) - { - fprintf(stderr, "Failed to save state: %s\n", strerror(errno)); - paths_file_abort(file); - return false; - } - len += len2; - pos = 0; - while (pos < len) - { - ssize_t got = paths_file_write(file, buf + pos, len - pos); - if (got <= 0) - { - fprintf(stderr, "Failed to save state: %s\n", strerror(errno)); - paths_file_abort(file); - return false; - } - pos += got; - } - - if (paths_file_close(file)) - { - fprintf(stderr, "Failed to finish saving state: %s\n", strerror(errno)); - paths_file_abort(file); - return false; - } - return true; -} - -void default_state(main_t *m) -{ - m->working = false; - m->last_time = time(NULL); - m->total_min = 0; -} - -void swap(main_t *m) -{ - if (m->working) - { - double diff = difftime(time(NULL), m->last_time); - m->total_min += round(diff / 60.0); - m->working = false; - } - else - { - m->working = true; - if (!m->redraw_queued) - { - add_timer(m, REDRAW_INTERVAL_MS, redraw, m); - m->redraw_queued = true; - } - } - - m->last_time = time(NULL); - - draw(m); - queue_save(m); -} - -void clear(main_t *m) -{ - if (m->working) - { - return; - } - - default_state(m); - draw(m); - queue_save(m); -} - -static bool do_save(void *arg) -{ - main_t *m = arg; - assert(m->save_queued); - save_state(m); - return false; -} - -void queue_save(main_t *m) -{ - if (m->save_queued) - { - return; - } - add_timer(m, 5000, do_save, m); - m->save_queued = true; -} - -void add_timer(main_t *m, unsigned long interval_ms, - timer_callback_t callback, void *data) -{ - time_target_t* tgt = calloc(1, sizeof(time_target_t)); - assert(interval_ms > 0); - tgt->interval_ms = interval_ms; - tgt->callback = callback; - tgt->data = data; - thread_abstime(&tgt->target, interval_ms); - insert_timer(m, tgt); -} - -void insert_timer(main_t* m, time_target_t *target) -{ - time_target_t *cur, *last; - assert(target->next == NULL); - if (!m->first_timer) - { - m->first_timer = target; - return; - } - last = NULL; - cur = m->first_timer; - while (cur) - { - if (timeval_cmp(&cur->target, &target->target) > 0) - { - target->next = cur; - if (last == NULL) - { - m->first_timer = target; - return; - } - else - { - break; - } - } - last = cur; - cur = last->next; - } - last->next = target; -} - -void run_timer(main_t *m) -{ - time_target_t *target = m->first_timer; - m->first_timer = target->next; - target->next = NULL; - if (target->callback(target->data)) - { - thread_abstime(&target->target, target->interval_ms); - insert_timer(m, target); - } - else - { - free(target); - } -} - -bool redraw(void *arg) -{ - main_t* m = arg; - assert(m->redraw_queued); - if (m->working) - { - draw(m); - return true; - } - m->redraw_queued = false; - return false; -} - -int timeval_cmp(const struct timespec* x, const struct timespec* y) -{ - assert(x && y); - if (x->tv_sec < y->tv_sec) - { - return -1; - } - else if (x->tv_sec == y->tv_sec) - { - if (x->tv_nsec < y->tv_nsec) - { - return -1; - } - else if (x->tv_nsec == y->tv_nsec) - { - return 0; - } - } - - return 1; -} |
