--- /dev/null
+*.o
+*.swp
+*~
+config.mk
+test.mk
+lamechat
--- /dev/null
+/*
+ * Copyright (C) 2016 Andy Spencer <andy753421@gmail.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <getopt.h>
+
+#include "util.h"
+#include "args.h"
+
+/* Options */
+static char *short_options = "h";
+
+struct option long_options[] = {
+ {"help", 0, NULL, 'h'},
+};
+
+/* Usage */
+static void usage(char *name)
+{
+ printf("Usage:\n");
+ printf(" %s [OPTION...] [CALENDAR]\n", name);
+ printf("\n");
+ printf("Options:\n");
+ printf(" -h, --help Print usage information\n");
+}
+
+/* Initialize */
+void args_setup(int argc, char **argv)
+{
+ /* Parse arguments */
+ while (1) {
+ int c = getopt_long(argc, argv,
+ short_options, long_options, NULL);
+ if (c == -1)
+ break;
+ switch (c) {
+ case 'h':
+ usage(argv[0]);
+ exit(0);
+ break;
+ }
+ }
+}
+
+void args_start(void)
+{
+}
--- /dev/null
+/*
+ * Copyright (C) 2016 Andy Spencer <andy753421@gmail.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/* Functions */
+void args_setup(int argc, char **argv);
+void args_start(void);
--- /dev/null
+/*
+ * Copyright (C) 2017 Andy Spencer <andy753421@gmail.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#include "util.h"
+#include "conf.h"
+#include "chat.h"
+
+/* Global data */
+log_t *chat_log;
+int chat_len;
+
+/* Local data */
+static buf_t log_buf;
+
+/* Local functions */
+
+/* View init */
+void chat_init(void)
+{
+ chat_log = log_buf.data;
+ chat_len = 0;
+
+ // Append some lines */
+ chat_send("Copyright (C) 2017 Andy Spencer <andy753421@gmail.com>");
+ chat_send("This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.");
+ chat_send("This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.");
+ chat_send("You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>.");
+}
+
+void chat_send(const char *msg)
+{
+ append(&log_buf, NULL, sizeof(log_t));
+ chat_log = log_buf.data;
+
+ log_t *log = &chat_log[chat_len];
+ log->when = time(NULL);
+ log->from = "andy";
+ log->room = "room";
+ log->msg = strcopy(msg);
+
+ chat_len++;
+}
+
+void chat_exit(void)
+{
+ for (int i = 0; i < chat_len; i++)
+ free((void*)chat_log[i].msg);
+ release(&log_buf);
+}
--- /dev/null
+/*
+ * Copyright (C) 2017 Andy Spencer <andy753421@gmail.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/* Message Types */
+typedef unsigned long long stamp_t;
+
+typedef struct {
+ stamp_t when;
+ char *from;
+ char *room;
+ char *msg;
+} log_t;
+
+/* Global Data */
+extern log_t *chat_log;
+extern int chat_len;
+
+/* Chat functions */
+void chat_init(void);
+void chat_send(const char *msg);
+void chat_exit(void);
--- /dev/null
+/*
+ * Copyright (C) 2013 Andy Spencer <andy753421@gmail.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+#include <errno.h>
+
+#include "util.h"
+#include "conf.h"
+
+/* Saved formattnig struct */
+typedef struct line_t {
+ char *group;
+ char *name;
+ char *key;
+ char *value;
+ char *text;
+ int dirty;
+ struct line_t *next;
+} line_t;
+
+/* Constat data */
+static const char *booleans[] = {
+ "false",
+ "true"
+};
+
+/* Setup info */
+static char *filename;
+
+/* Static data */
+static line_t *settings;
+
+/* Parsing and saving */
+static int wasfound;
+static int needsave;
+
+static char *lastgroup;
+static char *lastname;
+static char *lastkey;
+
+/* Helper functions */
+static void conf_error(const char *value)
+{
+ if (lastname)
+ error("invalid value '%s' for %s.%s.%s",
+ value, lastgroup, lastname, lastkey);
+ else
+ error("invalid value '%s' for %s.%s",
+ value, lastgroup, lastkey);
+}
+
+static void set_value(const char *group, const char *name,
+ const char *key, const char *value)
+{
+ int ingroup = 0;
+ line_t *groupend = NULL;
+ line_t *fileend = NULL;
+
+ /* Queue a save next time around */
+ needsave = 1;
+
+ /* Look though for existing items */
+ for (line_t *line = settings; line; line = line->next) {
+ /* Search for the correct group */
+ if (line->group)
+ ingroup = match(line->group, group) &&
+ match(line->name, name);
+ /* Set value */
+ if (ingroup && match(line->key, key)) {
+ free(line->value);
+ line->value = strcopy(value);
+ line->dirty = 1;
+ return;
+ }
+ /* Record positions for new keys */
+ if (ingroup && line->key && line->value)
+ groupend = line;
+ else
+ fileend = line;
+ }
+
+ /* Create new items */
+ if (groupend) {
+ /* Append to group */
+ line_t *line = new0(line_t);
+ line->key = strcopy(key);
+ line->value = strcopy(value);
+ line->next = groupend->next;
+ groupend->next = line;
+ } else if (fileend) {
+ /* Create new group */
+ line_t *blank = new0(line_t);
+ line_t *header = new0(line_t);
+ line_t *line = new0(line_t);
+ fileend->next = blank;
+ blank->next = header;
+ header->group = strcopy(group);
+ header->name = strcopy(name);
+ header->next = line;
+ line->key = strcopy(key);
+ line->value = strcopy(value);
+ } else {
+ /* Create new file */
+ line_t *header = new0(line_t);
+ line_t *line = new0(line_t);
+ settings = header;
+ header->group = strcopy(group);
+ header->name = strcopy(name);
+ header->next = line;
+ line->key = strcopy(key);
+ line->value = strcopy(value);
+ }
+}
+
+/* Parsing functions */
+static char *scan_white(char *src)
+{
+ while (*src == ' ' || *src == '\t')
+ src++;
+ return src;
+}
+
+static char *scan_word(char *dst, char *src)
+{
+ while (islower(*src))
+ *dst++ = *src++;
+ *dst = '\0';
+ return src;
+}
+
+static char *scan_value(char *dst, char *src)
+{
+ char *start = dst;
+ while (*src != '#' && *src != '\0')
+ *dst++ = *src++;
+ do
+ *dst-- = '\0';
+ while (dst > start && (*dst == ' ' || *dst == '\t'));
+ return src;
+}
+
+static char *scan_quote(char *dst, char *src)
+{
+ while (*src != '"' && *src != '\0') {
+ if (*src == '\\') {
+ switch (*++src) {
+ case '\0': *dst++ = '\0'; break;
+ case '\\': *dst++ = '\\'; src++; break;
+ case 't': *dst++ = '\t'; src++; break;
+ case 'r': *dst++ = '\r'; src++; break;
+ case 'n': *dst++ = '\n'; src++; break;
+ default: *dst++ = *src; src++; break;
+ }
+ } else {
+ *dst++ = *src++;
+ }
+ }
+ if (*src == '"')
+ src++;
+ *dst = '\0';
+ return src;
+}
+
+static void parse_line(line_t *dst, char *text, const char *file, int lnum)
+{
+ char *chr = scan_white(text);
+ if (*chr == '[') {
+ dst->group = malloc(strlen(chr)+1);
+ chr = scan_white(chr+1);
+ chr = scan_word(dst->group, chr);
+ chr = scan_white(chr);
+ if (*chr == '"') {
+ dst->name = malloc(strlen(chr)+1);
+ chr = scan_quote(dst->name, chr+1);
+ chr = scan_white(chr);
+ }
+ if (*chr != ']')
+ error("parsing group at %s:%d,%d -- '%.8s..'",
+ file, lnum, 1+chr-text, chr);
+ }
+ else if (islower(*chr)) {
+ dst->key = malloc(strlen(chr)+1);
+ chr = scan_white(chr);
+ chr = scan_word(dst->key, chr);
+ chr = scan_white(chr);
+ if (*chr != '=')
+ error("parsing key at %s:%d,%d -- '%.8s..'",
+ file, lnum, 1+chr-text, chr);
+ else
+ chr++;
+ chr = scan_white(chr);
+ dst->value = malloc(strlen(chr)+1);
+ if (*chr == '"')
+ chr = scan_quote(dst->value, chr+1);
+ else
+ chr = scan_value(dst->value, chr);
+ } else if (*chr != '#' && *chr != '\n' && *chr != '\0') {
+ error("parsing file at %s:%d,%d -- '%.8s..'",
+ file, lnum, 1+chr-text, chr);
+ }
+}
+
+/* File I/O functions */
+static void conf_load(const char *path, parser_t parser)
+{
+ line_t *prev = NULL;
+ char *group = NULL, *name = NULL;
+
+ /* read the whole file */
+ int len;
+ char *start = read_file(path, &len);
+ if (!start)
+ return;
+
+ /* run parser */
+ int lnum;
+ char *sol, *eol;
+ for (lnum = 1, sol = start; sol < (start+len); lnum++, sol = eol+1) {
+ eol = strchr(sol, '\n') ?: &start[len];
+ eol[0] = '\0';
+
+ /* update current group info */
+ line_t *line = new0(line_t);
+ line->text = strcopy(sol);
+ parse_line(line, sol, path, lnum);
+ group = line->group ? line->group : group;
+ name = line->group ? line->name : name;
+
+ /* Parse dynamic groups */
+ if (line->group && line->name) {
+ wasfound = 0;
+ lastgroup = line->group;
+ lastname = line->name;
+ lastkey = NULL;
+ parser(line->group, line->name, "", "");
+ if (!wasfound)
+ error("unknown group: line %d - [%s \"%s\"]",
+ lnum, group, name ?: "");
+ }
+
+ /* Parse static key/value pairs */
+ if (group && line->key && line->value) {
+ wasfound = 0;
+ lastgroup = group;
+ lastname = name;
+ lastkey = line->key;
+ parser(group, name?:"", line->key, line->value);
+ if (!wasfound)
+ error("unknown setting: line %d - %s.%s.%s = '%s'\n",
+ lnum, group, name?:"", line->key, line->value);
+ }
+
+ /* debug printout */
+ debug("parse: %s.%s.%s = '%s'", group, name, line->key, line->value);
+
+ /* save line formatting for the next write */
+ if (prev == NULL)
+ settings = line;
+ else
+ prev->next = line;
+ prev = line;
+
+ }
+ free(start);
+}
+
+void conf_save(const char *path)
+{
+ FILE *fd = fopen(path, "wt+");
+ if (!fd)
+ return;
+
+ for (line_t *cur = settings; cur; cur = cur->next) {
+ /* Output existing items */
+ if (cur->text && !cur->dirty)
+ fprintf(fd, "%s\n", cur->text);
+
+ /* Output group and name headers */
+ else if (cur->group && cur->name)
+ fprintf(fd, "[%s \"%s\"]\n", cur->group, cur->name);
+
+ /* Output group only headers */
+ else if (cur->group)
+ fprintf(fd, "[%s]\n", cur->group);
+
+ /* Output key/value pairs - todo: add quotes */
+ else if (cur->key && cur->value)
+ fprintf(fd, "\t%s = %s\n", cur->key, cur->value);
+
+ /* Output blank lines */
+ else
+ fprintf(fd, "\n");
+ }
+
+ fclose(fd);
+}
+
+/* Initialize */
+void conf_setup(const char *name, parser_t parser)
+{
+ const char *home = getenv("HOME");
+ filename = alloc0(strlen(home) + 1 + strlen(name) + 1);
+ sprintf(filename, "%s/%s", home, name);
+ conf_load(filename, parser);
+}
+
+void conf_start(void)
+{
+}
+
+/* Update */
+void conf_sync(void)
+{
+ if (needsave)
+ conf_save(filename);
+ needsave = 0;
+}
+
+/* Getters */
+int get_enum(const char *value, const char **map, int n)
+{
+ wasfound = 1;
+ for (int i = 0; i < n; i++)
+ if (match(map[i], value))
+ return i;
+ conf_error(value);
+ return 0;
+}
+
+int get_bool(const char *value)
+{
+ wasfound = 1;
+ return get_enum(value, booleans, N_ELEMENTS(booleans));
+}
+
+int get_number(const char *value)
+{
+ wasfound = 1;
+ errno = 0;
+ int rval = atoi(value);
+ if (errno)
+ conf_error(value);
+ return rval;
+}
+
+char *get_string(const char *value)
+{
+ wasfound = 1;
+ return (char*)value;
+}
+
+char *get_name(const char *name)
+{
+ wasfound = 1;
+ return (char*)name;
+}
+
+/* Setters */
+void set_enum(const char *group, const char *name,
+ const char *key, int value,
+ const char **map, int n)
+{
+ if (value >= 0 && value < n)
+ set_value(group, name, key, map[value]);
+}
+
+void set_bool(const char *group, const char *name,
+ const char *key, int value)
+{
+ set_enum(group, name, key, value,
+ booleans, N_ELEMENTS(booleans));
+}
+
+void set_number(const char *group, const char *name,
+ const char *key, int value)
+{
+ char buf[32];
+ snprintf(buf, sizeof(buf), "%d", value);
+ set_value(group, name, key, buf);
+}
+
+void set_string(const char *group, const char *name,
+ const char *key, const char *value)
+{
+ set_value(group, name, key, value);
+}
+
+void set_name(const char *group, const char *name, const char *value)
+{
+ for (line_t *line = settings; line; line = line->next) {
+ if (match(line->group, group) &&
+ match(line->name, name)) {
+ free(line->name);
+ line->name = strcopy(value);
+ line->dirty = 1;
+ break;
+ }
+ }
+}
+
+/* Config parser */
+static const char *colors[] = {"red", "green", "blue"};
+
+static int test_bin = 1;
+static int test_num = 42;
+static char *test_str = "str";
+static int test_clr = 0;
+
+static void test_parser(const char *group, const char *name,
+ const char *key, const char *value)
+{
+ if (match(group, "test")) {
+ if (match(key, "bin"))
+ test_bin = get_bool(value);
+ else if (match(key, "clr"))
+ test_clr = get_enum(value, colors, N_ELEMENTS(colors));
+ else if (match(key, "num"))
+ test_num = get_number(value);
+ else if (match(key, "str"))
+ test_str = get_string(value);
+ }
+}
+
+void conf_test(void)
+{
+ printf("conf_test:\n");
+
+ /* Read values from a file */
+ conf_load("data/test.rc", test_parser);
+
+ printf("\nload:\n");
+ printf(" bin: %-8d\n", test_bin);
+ printf(" clr: %-8s\n", colors[test_clr]);
+ printf(" num: %-8d\n", test_num);
+ printf(" str: %-8s\n", test_str);
+
+ /* Update values */
+ set_bool ("test", 0, "bin", 1);
+ set_enum ("test", 0, "clr", 2, colors, N_ELEMENTS(colors));
+ set_number("test", 0, "num", -9999);
+ set_string("test", 0, "str", "hello");
+
+ set_string("test", 0, "new", "new0");
+ set_string("test", "new", "new", "new1");
+ set_string("new", 0, "newa", "new2");
+ set_string("new", 0, "newb", "new3");
+ set_string("new", "new", "newa", "new4");
+ set_string("new", "new", "newb", "new5");
+
+ set_name ("func", "name", "test");
+
+ /* Write back to file */
+ conf_save("data/test_out.rc");
+}
--- /dev/null
+/*
+ * Copyright (C) 2013 Andy Spencer <andy753421@gmail.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/* Handlers */
+typedef void (*parser_t)(const char *group, const char *name,
+ const char *key, const char *value);
+
+/* Getters */
+int get_enum(const char *value, const char **map, int n);
+int get_bool(const char *value);
+int get_number(const char *value);
+char *get_string(const char *value);
+char *get_name(const char *name);
+
+/* Setters */
+void set_enum(const char *group, const char *name,
+ const char *key, int value,
+ const char **map, int n);
+void set_bool(const char *group, const char *name,
+ const char *key, int value);
+void set_number(const char *group, const char *name,
+ const char *key, int value);
+void set_string(const char *group, const char *name,
+ const char *key, const char *value);
+void set_name(const char *group, const char *name,
+ const char *value);
+
+/* Functions */
+void conf_setup(const char *name, parser_t parser);
+void conf_start(void);
+void conf_sync(void);
--- /dev/null
+PREFIX ?= /usr
+CFLAGS ?= -g -Wall -Werror --std=c99
+
+default: all run
+
+run: lamechat
+ @urxvt -e ./$<
--- /dev/null
+.TH LAMECHAT 1 "August 2017" lamechat
+.SH NAME
+lamechat \- curses chat program
+.SH SYNOPSIS
+.B lamechat\fR [\fIOPTION\fR..]
+.SH DESCRIPTION
+lamechat is a curses chat program.
+.SH OPTIONS
+.TP
+todo
+.SH CONFIGURATION
+.TP
+todo
+.SH FILES
+.TP
+todo
+.SH SEE ALSO
+.BR irssi (1),
+.SH BUGS
+Many
--- /dev/null
+/*
+ * Copyright (C) 2012-2013 Andy Spencer <andy753421@gmail.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <signal.h>
+
+#include "util.h"
+#include "args.h"
+#include "conf.h"
+#include "chat.h"
+#include "view.h"
+
+/* Config parser */
+static void on_config(const char *group, const char *name, const char *key, const char *value)
+{
+ view_config(group, name, key, value);
+}
+
+/* Control-C handler, so we don't hose the therminal */
+static void on_sigint(int signum)
+{
+ view_exit();
+ exit(0);
+}
+
+/* Main */
+int main(int argc, char **argv)
+{
+ /* Misc setup */
+ signal(SIGINT, on_sigint);
+
+ /* Configuration */
+ args_setup(argc, argv);
+ conf_setup(".lamechatrc", on_config);
+
+ /* Initialize */
+ util_init();
+ chat_init();
+ view_init();
+
+ /* Common main */
+ args_start();
+ conf_start();
+
+ /* Mode main */
+ while (run(100)) {
+ view_sync();
+ conf_sync();
+ }
+
+ /* Exit */
+ chat_exit();
+ view_exit();
+
+ return 0;
+}
--- /dev/null
+# lamechat - curses chat program
+# See COPYING file for license details.
+
+-include config.mk
+
+# Settings
+VERSION ?= 0.1-rc1
+PREFIX ?= /usr/local
+MANPREFIX ?= $(PREFIX)/share/man
+
+# Compiler
+GCC ?= gcc
+CFLAGS ?= -Wall --std=c99
+LDFLAGS ?= -lncursesw -lcurl -lexpat
+
+# Sources
+PROG ?= lamechat
+SOURCES ?= main util args conf view chat irc xmpp
+
+# Targets
+all: $(PROG)
+
+clean:
+ rm -f *.o $(PROG)
+
+dist:
+ tar -czf $(PROG)-$(VERSION).tar.gz --transform s::$(PROG)-$(VERSION)/: \
+ README COPYING config.mk.example makefile */*.txt */*.1 */*.c */*.h
+
+install: all
+ install -m 755 -D $(PROG) $(DESTDIR)$(PREFIX)/bin/$(PROG)
+ install -m 644 -D doc/$(PROG).1 $(DESTDIR)$(MANPREFIX)/man1/$(PROG).1
+
+uninstall:
+ rm -f $(DESTDIR)$(PREFIX)/bin/$(PROG)
+ rm -f $(DESTDIR)$(MANPREFIX)/man1/$(PROG).1
+
+memcheck: $(PROG)
+ valgrind --log-file=valgrind.out \
+ --track-origins=yes \
+ --leak-check=full \
+ --leak-resolution=high \
+ ./$(PROG)
+
+# Rules
+$(PROG): $(SOURCES:%=%.o)
+ $(GCC) $(CFLAGS) -o $@ $+ $(LDFLAGS)
+
+%.o: %.c $(wildcard *.h makefile config.mk)
+ $(GCC) $(CFLAGS) -c -o $@ $<
+
+.PHONY: all clean dist install uninstall
--- /dev/null
+/*
+ * Copyright (C) 2012-2013 Andy Spencer <andy753421@gmail.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#define _XOPEN_SOURCE
+#define _XOPEN_SOURCE_EXTENDED
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <errno.h>
+
+#include <sys/epoll.h>
+
+#include "view.h"
+#include "util.h"
+
+#pragma weak view_exit
+#pragma weak view_debug
+
+/* Static data */
+static int epoll = 0;
+static FILE *debug_fd = NULL;
+static int running = 0;
+
+/* View debugging */
+extern void view_debug(const char *fmt, va_list ap);
+
+/* Helper functions */
+static void message(FILE *output_fd, const char *prefix, const char *fmt, va_list ap)
+{
+ va_list tmp;
+
+ /* Log to standard out */
+ if (output_fd) {
+ va_copy(tmp, ap);
+ fprintf(output_fd, "%s: ", prefix);
+ vfprintf(output_fd, fmt, tmp);
+ fprintf(output_fd, "\n");
+ fflush(output_fd);
+ }
+
+ /* Log to debug file */
+ if (debug_fd) {
+ va_copy(tmp, ap);
+ fprintf(debug_fd, "%s: ", prefix);
+ vfprintf(debug_fd, fmt, tmp);
+ fprintf(debug_fd, "\n");
+ fflush(debug_fd);
+ }
+
+ /* Log to status bar */
+ if (&view_debug) {
+ va_copy(tmp, ap);
+ view_debug(fmt, tmp);
+ }
+}
+
+/* Initialize */
+void util_init(void)
+{
+ epoll = epoll_create(1);
+ debug_fd = fopen("/tmp/lamechat.log", "w+");
+ running = 1;
+}
+
+/* String functions */
+void strsub(char *str, char find, char repl)
+{
+ for (char *cur = str; *cur; cur++)
+ if (*cur == find)
+ *cur = repl;
+}
+
+char *strcopy(const char *str)
+{
+ if (str == NULL)
+ return NULL;
+ return strdup(str);
+}
+
+int match(const char *a, const char *b)
+{
+ if (a == b)
+ return 1;
+ if (!a || !b)
+ return 0;
+ return !strcmp(a, b);
+}
+
+/* Memory functions */
+void *alloc0(int size)
+{
+ void *data = calloc(1, size);
+ if (!data)
+ error("memory allocation failed");
+ return data;
+}
+
+void append(buf_t *buf, const void *data, int len)
+{
+ if (buf->len + len + 1 > buf->max) {
+ buf->max += (((buf->len+len)/4096)+1)*4096;
+ buf->data = realloc(buf->data, buf->max);
+ if (!buf->data)
+ error("buffer reallocation allocation failed");
+ }
+ if (data)
+ memcpy(buf->data + buf->len, data, len);
+ buf->len += len;
+ ((char*)buf->data)[buf->len] = '\0';
+}
+
+void release(buf_t *buf)
+{
+ free(buf->data);
+ buf->data = 0;
+ buf->len = 0;
+ buf->max = 0;
+}
+
+/* File functions */
+char *read_file(const char *path, int *len)
+{
+ /* we could use stat, but we'll try to be portable */
+ FILE *fd = fopen(path, "rt+");
+ if (!fd)
+ return NULL;
+
+ int block = 512; // read size
+ int size = 512; // buffer size
+ int slen = 0; // string length
+ char *buf = malloc(size);
+ if (!buf)
+ goto err;
+
+ while (!feof(fd)) {
+ if (slen + block + 1 > size) {
+ size *= 2;
+ buf = realloc(buf, size);
+ if (!buf)
+ goto err;
+ }
+ slen += fread(&buf[slen], 1, block, fd);
+ buf[slen] = '\0';
+ }
+
+err:
+ if (len)
+ *len = slen;
+ fclose(fd);
+ return buf;
+}
+
+/* Polling functions */
+int add_fd(int fd, cb_t *cb)
+{
+ struct epoll_event ctl = {
+ .events = EPOLLIN | EPOLLOUT | EPOLLERR,
+ .data.ptr = cb,
+ };
+ return epoll_ctl(epoll, EPOLL_CTL_ADD, fd, &ctl);
+}
+
+int del_fd(int fd)
+{
+ return epoll_ctl(epoll, EPOLL_CTL_DEL, fd, NULL);
+}
+
+int run(int timeout)
+{
+ cb_t *cb;
+ int count;
+ struct epoll_event event;
+
+ while (running) {
+ errno = 0;
+ debug("Polling");
+ count = epoll_wait(epoll, &event, 1, timeout);
+ debug("Got data");
+ if (errno == EINTR) {
+ debug("Interrupt");
+ continue;
+ }
+ if (count < 0) {
+ debug("Error");
+ continue;
+ }
+ if (!(cb = event.data.ptr))
+ continue;
+ if (!(cb->func))
+ continue;
+ cb->func(cb->data);
+ debug("Valid data");
+ return running;
+ }
+ return running;
+}
+
+void quit(void)
+{
+ running = 0;
+}
+
+/* Debugging functions */
+void debug(char *fmt, ...)
+{
+ va_list ap;
+ va_start(ap, fmt);
+ message(NULL, "debug", fmt, ap);
+ va_end(ap);
+}
+
+void error(char *fmt, ...)
+{
+ va_list ap;
+ va_start(ap, fmt);
+ fflush(stdout);
+ fflush(stderr);
+ message(stderr, "error", fmt, ap);
+ va_end(ap);
+ if (view_exit)
+ view_exit();
+ exit(-1);
+}
--- /dev/null
+/*
+ * Copyright (C) 2012-2013 Andy Spencer <andy753421@gmail.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/* Macros */
+#define ABS(a) ((a) > 0 ? (a) : -(a))
+#define MIN(a,b) ((a) < (b) ? (a) : (b))
+#define MAX(a,b) ((a) > (b) ? (a) : (b))
+#define CLAMP(x,l,h) MIN(MAX(x,l),h)
+#define ROUND(x) ((int)((x)+0.5))
+#define N_ELEMENTS(x) (sizeof(x)/sizeof((x)[0]))
+
+#define new0(type) alloc0(sizeof(type))
+
+/* Types */
+typedef struct {
+ void *data;
+ int len;
+ int max;
+} buf_t;
+
+typedef struct {
+ void (*func)(void *data);
+ void *data;
+} cb_t;
+
+/* Debug functions */
+void util_init(void);
+
+/* Stirng functions */
+void strsub(char *str, char find, char repl);
+char *strcopy(const char *str);
+int match(const char *a, const char *b);
+
+/* Memory functions */
+void *alloc0(int size);
+void append(buf_t *buf, const void *data, int len);
+void release(buf_t *buf);
+
+/* File functions */
+char *read_file(const char *path, int *len);
+
+/* Polling functions */
+int add_fd(int fd, cb_t *cb);
+int del_fd(int fd);
+int run(int timeout);
+void quit(void);
+
+/* Debug functions */
+void debug(char *fmt, ...);
+void error(char *fmt, ...);
--- /dev/null
+/*
+ * Copyright (C) 2012-2013 Andy Spencer <andy753421@gmail.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#define _XOPEN_SOURCE
+#define _XOPEN_SOURCE_EXTENDED
+
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <locale.h>
+#include <ncurses.h>
+
+#include "util.h"
+#include "conf.h"
+#include "chat.h"
+#include "view.h"
+
+/* Extra keys */
+#define KEY_CTRL_G '\7'
+#define KEY_CTRL_L '\14'
+#define KEY_RETURN '\12'
+#define KEY_ESCAPE '\33'
+
+/* Local data */
+static cb_t poll = {};
+static int running = 0;
+
+static char cmd_buf[512];
+static int cmd_pos;
+static int cmd_len;
+
+/* Local functions */
+static void print_word(char **msg, int *row, int *col, int indent, int print)
+{
+ char *start = *msg;
+ while (*start && isspace(*start))
+ start++;
+
+ char *end = start;
+ while (*end && !isspace(*end))
+ end++;
+ *msg = end;
+
+ int len = end-start;
+ if ((*col != indent) && (*col + 1 + len > COLS)) {
+ *col = indent;
+ *row = (*row)+1;
+ }
+
+ if (*row < 1 || *row > LINES-2)
+ print = 0;
+
+ if (*col != indent) {
+ if (print)
+ mvaddch(*row, *col, ' ');
+ *col += 1;
+ }
+ if (print)
+ mvaddnstr(*row, *col, start, len);
+ *col += len;
+}
+
+static void print_msg(log_t *log, int *row, int print)
+{
+ static char buf[512];
+ char *hdr = buf;
+ char *msg = log->msg;
+ int col = 0, indent = 0;
+
+ snprintf(buf, sizeof(buf), "%02d:%02d [%s] %s: ",
+ 0, 0, log->room, log->from );
+
+ while (*hdr)
+ print_word(&hdr, row, &col, indent, print);
+ indent = col;
+ while (*msg)
+ print_word(&msg, row, &col, indent, print);
+
+ (*row)++;
+}
+
+static void draw_header(void)
+{
+ attron(COLOR_PAIR(COLOR_TITLE) | A_REVERSE);
+ move(0, 0);
+ clrtoeol();
+ printw("%-*s", COLS, "Header Bar");
+ attroff(COLOR_PAIR(COLOR_TITLE) | A_REVERSE);
+}
+
+static void draw_chat(void)
+{
+ int space = LINES-3;
+ int row = 0;
+ int log = chat_len-1;
+
+ /* Clear window */
+ for (int i = 1; i < LINES-2; i++) {
+ move(i, 0);
+ clrtoeol();
+ }
+
+ /* Compute lines */
+ while (row < space && log >= 0)
+ print_msg(&chat_log[log--], &row, 0);
+
+ /* Compute skip lines */
+ row = 1 + (space - row);
+ log = log + 1;
+
+ /* Print lines */
+ while (row <= space && log < chat_len)
+ print_msg(&chat_log[log++], &row, 1);
+}
+
+static void draw_status(void)
+{
+ attron(COLOR_PAIR(COLOR_TITLE) | A_REVERSE);
+ move(LINES-2, 0);
+ clrtoeol();
+ printw("%-*s", COLS, "Status Bar");
+ attroff(COLOR_PAIR(COLOR_TITLE) | A_REVERSE);
+}
+
+static void draw_cmdline(void) {
+ move(LINES-1, 0);
+ clrtoeol();
+ printw("[] %.*s", cmd_len, cmd_buf);
+ move(LINES-1, 2 + 1 + cmd_pos);
+}
+
+static void draw_view(void)
+{
+ draw_header();
+ draw_chat();
+ draw_status();
+ draw_cmdline();
+}
+
+/* View init */
+void view_init(void)
+{
+ /* Set default escape timeout */
+ if (!getenv("ESCDELAY"))
+ putenv("ESCDELAY=25");
+
+ /* Setup Curses */
+ setlocale(LC_ALL, "");
+ initscr();
+ cbreak();
+ noecho();
+ keypad(stdscr, TRUE);
+ start_color();
+ timeout(100);
+ use_default_colors();
+ mousemask(ALL_MOUSE_EVENTS, NULL);
+
+ init_pair(COLOR_TITLE, COLOR_BLUE, -1);
+ init_pair(COLOR_ERROR, COLOR_RED, -1);
+
+ /* Draw initial view */
+ draw_view();
+
+ /* Register callback */
+ poll.data = NULL;
+ poll.func = (void*)view_sync;
+ add_fd(0, &poll);
+
+ /* Set running */
+ running = 1;
+}
+
+/* Config parser */
+void view_config(const char *group, const char *name, const char *key, const char *value)
+{
+}
+
+/* View event */
+void view_sync(void)
+{
+ MEVENT btn;
+ int chr = getch();
+
+ /* Misc ncurses */
+ if (chr == ERR) {
+ return;
+ }
+ if (chr == KEY_MOUSE) {
+ if (getmouse(&btn) != OK)
+ return;
+ }
+
+ /* Window management */
+ if (chr == KEY_RESIZE) {
+ clear();
+ draw_view();
+ }
+ else if (chr == KEY_CTRL_L) {
+ clear();
+ draw_view();
+ }
+ else if (chr == KEY_CTRL_G) {
+ draw_view();
+ }
+
+ /* Cmdline Input */
+ else if (chr == KEY_RETURN) {
+ cmd_buf[cmd_len] = '\0';
+ cmd_pos = 0;
+ cmd_len = 0;
+ chat_send(cmd_buf);
+ draw_chat();
+ draw_cmdline();
+ }
+ else if (chr == KEY_ESCAPE) {
+ cmd_pos = 0;
+ cmd_len = 0;
+ draw_cmdline();
+ }
+ else if (chr == KEY_LEFT) {
+ if (cmd_pos > 0)
+ cmd_pos--;
+ draw_cmdline();
+ }
+ else if (chr == KEY_RIGHT) {
+ if (cmd_pos < cmd_len)
+ cmd_pos++;
+ draw_cmdline();
+ }
+ else if (chr == KEY_BACKSPACE) {
+ if (cmd_pos > 0) {
+ memmove(&cmd_buf[cmd_pos-1],
+ &cmd_buf[cmd_pos],
+ (cmd_len-cmd_pos)+1);
+ cmd_pos--;
+ cmd_len--;
+ }
+ draw_cmdline();
+ }
+ else if (chr == KEY_DC) {
+ if (cmd_pos < cmd_len) {
+ memmove(&cmd_buf[cmd_pos],
+ &cmd_buf[cmd_pos+1],
+ (cmd_len-cmd_pos)+1);
+ cmd_len--;
+ draw_cmdline();
+ }
+ }
+ else if (isprint(chr)) {
+ if (cmd_len+2 < sizeof(cmd_buf)) {
+ memmove(&cmd_buf[cmd_pos+1],
+ &cmd_buf[cmd_pos],
+ (cmd_len-cmd_pos)+1);
+ cmd_buf[cmd_pos] = chr;
+ cmd_pos++;
+ cmd_len++;
+ draw_cmdline();
+ } else {
+ debug("form: out of space");
+ }
+ }
+
+ /* Unknown control character */
+ else {
+ debug("main: Unhandled key - Dec %3d, Hex %02x, Oct %03o, Chr <%c>",
+ chr, chr, chr, chr);
+ }
+}
+
+void view_exit(void)
+{
+ if (!running)
+ return;
+ endwin();
+}
--- /dev/null
+/*
+ * Copyright (C) 2012-2013 Andy Spencer <andy753421@gmail.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/* Configuration */
+#define COLOR_TITLE 1
+#define COLOR_ERROR 2
+
+/* View functions */
+void view_init(void);
+void view_sync(void);
+void view_exit(void);
+void view_config(const char *group, const char *name, const char *key, const char *value);