]> Pileus Git - ~andy/lamechat/commitdiff
Import and basic UI working.
authorAndy Spencer <andy753421@gmail.com>
Thu, 1 Jan 2015 00:08:14 +0000 (00:08 +0000)
committerAndy Spencer <andy753421@gmail.com>
Sun, 27 Aug 2017 21:45:35 +0000 (21:45 +0000)
19 files changed:
.gitignore [new file with mode: 0644]
args.c [new file with mode: 0644]
args.h [new file with mode: 0644]
chat.c [new file with mode: 0644]
chat.h [new file with mode: 0644]
conf.c [new file with mode: 0644]
conf.h [new file with mode: 0644]
config.mk.example [new file with mode: 0644]
irc.c [new file with mode: 0644]
irc.h [new file with mode: 0644]
lamechat.1 [new file with mode: 0644]
main.c [new file with mode: 0644]
makefile [new file with mode: 0644]
util.c [new file with mode: 0644]
util.h [new file with mode: 0644]
view.c [new file with mode: 0644]
view.h [new file with mode: 0644]
xmpp.c [new file with mode: 0644]
xmpp.h [new file with mode: 0644]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..f2dc4f6
--- /dev/null
@@ -0,0 +1,6 @@
+*.o
+*.swp
+*~
+config.mk
+test.mk
+lamechat
diff --git a/args.c b/args.c
new file mode 100644 (file)
index 0000000..4c1adc4
--- /dev/null
+++ b/args.c
@@ -0,0 +1,62 @@
+/*
+ * 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)
+{
+}
diff --git a/args.h b/args.h
new file mode 100644 (file)
index 0000000..bc7fc39
--- /dev/null
+++ b/args.h
@@ -0,0 +1,20 @@
+/*
+ * 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);
diff --git a/chat.c b/chat.c
new file mode 100644 (file)
index 0000000..425562e
--- /dev/null
+++ b/chat.c
@@ -0,0 +1,67 @@
+/*
+ * 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);
+}
diff --git a/chat.h b/chat.h
new file mode 100644 (file)
index 0000000..dbcd31d
--- /dev/null
+++ b/chat.h
@@ -0,0 +1,35 @@
+/*
+ * 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);
diff --git a/conf.c b/conf.c
new file mode 100644 (file)
index 0000000..e82cd9a
--- /dev/null
+++ b/conf.c
@@ -0,0 +1,471 @@
+/*
+ * 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");
+}
diff --git a/conf.h b/conf.h
new file mode 100644 (file)
index 0000000..f765222
--- /dev/null
+++ b/conf.h
@@ -0,0 +1,45 @@
+/*
+ * 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);
diff --git a/config.mk.example b/config.mk.example
new file mode 100644 (file)
index 0000000..ab9150b
--- /dev/null
@@ -0,0 +1,7 @@
+PREFIX  ?= /usr
+CFLAGS  ?= -g -Wall -Werror --std=c99
+
+default: all run
+
+run: lamechat
+       @urxvt -e ./$<
diff --git a/irc.c b/irc.c
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/irc.h b/irc.h
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/lamechat.1 b/lamechat.1
new file mode 100644 (file)
index 0000000..1f3bada
--- /dev/null
@@ -0,0 +1,20 @@
+.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
diff --git a/main.c b/main.c
new file mode 100644 (file)
index 0000000..6ef97b5
--- /dev/null
+++ b/main.c
@@ -0,0 +1,71 @@
+/*
+ * 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;
+}
diff --git a/makefile b/makefile
new file mode 100644 (file)
index 0000000..bd61505
--- /dev/null
+++ b/makefile
@@ -0,0 +1,52 @@
+# 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
diff --git a/util.c b/util.c
new file mode 100644 (file)
index 0000000..7444ac9
--- /dev/null
+++ b/util.c
@@ -0,0 +1,239 @@
+/*
+ * 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);
+}
diff --git a/util.h b/util.h
new file mode 100644 (file)
index 0000000..0c44d35
--- /dev/null
+++ b/util.h
@@ -0,0 +1,64 @@
+/*
+ * 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, ...);
diff --git a/view.c b/view.c
new file mode 100644 (file)
index 0000000..013bbca
--- /dev/null
+++ b/view.c
@@ -0,0 +1,289 @@
+/*
+ * 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();
+}
diff --git a/view.h b/view.h
new file mode 100644 (file)
index 0000000..4a6cb8b
--- /dev/null
+++ b/view.h
@@ -0,0 +1,26 @@
+/*
+ * 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);
diff --git a/xmpp.c b/xmpp.c
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/xmpp.h b/xmpp.h
new file mode 100644 (file)
index 0000000..e69de29