summaryrefslogtreecommitdiff
path: root/src/main.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/main.c')
-rw-r--r--src/main.c896
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;
-}