Mercurial > wcc
changeset 0:aa723e3948a4 default tip
*: initial commit
awesome
| author | Paper <paper@tflc.us> |
|---|---|
| date | Tue, 09 Sep 2025 00:29:57 -0400 |
| parents | |
| children | |
| files | .hgignore LICENSE Makefile README flashii.c flashii.h wcc.c |
| diffstat | 6 files changed, 996 insertions(+), 0 deletions(-) [+] |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.hgignore Tue Sep 09 00:29:57 2025 -0400 @@ -0,0 +1,4 @@ +syntax: glob + +token.txt +wcc \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Makefile Tue Sep 09 00:29:57 2025 -0400 @@ -0,0 +1,5 @@ +wcc: wcc.c flashii.c + $(CC) -o $@ $^ $(CFLAGS) `pkg-config --cflags --libs libwebsockets` `pkg-config --cflags --libs ncursesw` -fsanitize=address + +clean: + $(RM) -f main \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/README Tue Sep 09 00:29:57 2025 -0400 @@ -0,0 +1,16 @@ +wcc is a shitty little chat program I wrote for flashii's chat protocol +(sockchat). + +It depends on libwebsockets to function correctly. All of the sockchat +specific stuff is located in flashii.c, while the curses frontend is +located in main.c. + +To build it you'll have to provide a Misuzu session token in the source +directory named token.txt. This file is conveniently put in .hgignore +so you don't have to worry about accidentally pushing it or whatever. + +You'll also need a C23 compiler, since I'm using #embed. If anyone +actually needs it to compile with C99 or C11, feel free to give me +a patch. + +contact: paper <paper@tflc.us>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/flashii.c Tue Sep 09 00:29:57 2025 -0400 @@ -0,0 +1,704 @@ +/* + * wcc -- a shitty sockchat client + * + * Copyright (c) 2025 Paper + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "flashii.h" + +#include <libwebsockets.h> +#include <assert.h> + +#define HEARTBEAT_SECONDS (30) + +/* internal linked list of messages to send */ +struct flashii_internal_msgbuf { + struct flashii_internal_msgbuf *next; + + char msg[]; /* meat */ +}; + +struct flashii { + struct lws_context *ctx; + struct lws *lws; + + struct flashii_internal_msgbuf *msgbuf; + + flashii_msg_recv_spec msg_recv; + flashii_user_recv_spec user_recv; + + /* user ID, retrieved after auth success */ + char *id; + /* username */ + char *username; + /* color */ + /* char *color; -- don't care */ + + /* permissions + * odd, they can be separated by \f or ' ', + * so if we want to save them we'll have to write mempbrk */ + + /* maybe save channel name too? */ + + /* maximum message length in unicode characters */ + int32_t max_msg_length; + + time_t last_heartbeat; + int need_heartbeat; +}; + +static char *memduptostr(const char *in, size_t len) +{ + char *q = malloc(len + 1); + if (!q) + return NULL; + + memcpy(q, in, len); + q[len] = 0; + + return q; +} + +/* converts a string-like thing to i64 */ +static int memtoi64(const char *mem, size_t len, int64_t *pr) +{ + size_t i; + int64_t r; + int neg; + + if (len <= 0) + return -1; + + if (!(mem[0] == '-' || (mem[0] >= '0' && mem[0] <= '9'))) + return -1; + + r = 0; + + neg = (mem[0] == '-'); + + for (i = (neg ? 1 : 0); i < len; i++) { + /* FIXME this does absolutely no checking for overflow etc */ + if (!(mem[i] >= '0' && mem[i] <= '9')) + return -1; + + r *= 10; + r += (mem[i] - '0'); + } + + if (mem[0] == '-') + r = -r; + + *pr = r; + + return 0; +} + +/* converts a string-like thing to i32 */ +static int memtoi32(const char *mem, size_t len, int32_t *pr) +{ + int64_t r; + if (memtoi64(mem, len, &r) < 0) + return -1; + + if (r > INT32_MAX || r < INT32_MIN) + return -1; + + *pr = r; + return 0; +} + +/* converts a binary string to u32 + * + * e.x.: + * '10010' -> 0x09 */ +static int memtou32flags(const char *mem, size_t len, uint32_t *pflags) +{ + size_t i; + uint32_t u; + + if (len >= 32) + return -1; /* totally funtastic! */ + + u = 0; + + for (i = 0; i < len; i++) { + switch (mem[i]) { + case '0': + break; + case '1': + u |= (UINT32_C(1) << i); + break; + default: + /* invalid input */ + return -1; + } + } + + *pflags = u; + + return 0; +} + +/* ------------------------------------------------------------------------ */ +/* packet receiving and parsing. + * + * packets are generally received as + * [type] (optional subtype) (data) + * hence I've put together a simple filter thing to be able to handle + * types and subtypes without duplicating a shit ton of code */ + +struct flashii_receive_packet_filter { + const char *prefix; + size_t len; + int (*func)(struct flashii *fls, const char *in, size_t len); +}; + +#define FLASHII_RECEIVE_PACKET_FILTER(prefix, func) \ + { (prefix "\t"), sizeof(prefix), (func) } + +static int flashii_receive_packet_filter(struct flashii *fls, const char *in, + size_t len, struct flashii_receive_packet_filter *filters) +{ + struct flashii_receive_packet_filter *f; + + for (f = filters; f->prefix; f++) { + if (f->len > len || memcmp(in, f->prefix, f->len)) + continue; /* nope */ + + return f->func(fls, in + f->len, len - f->len); + } + + return -1; +} + +/* ------------------------------------------------------------------------ */ +/* enumerate over parts of a packet + * note that whatever returned to the callback is not guaranteed to be + * NUL terminated; you should treat it simply as a memory buffer */ + +/* XXX needs to take in delim */ +static int flashii_receive_packet_enum(struct flashii *fls, const char *in, + size_t len, + int (*cb)(struct flashii *fls, + const char *in, size_t len, size_t index, void *userdata), + void *userdata) +{ + size_t index; + + for (index = 0; /* none */; index++) { + size_t ilen; + + { + const char *x; + x = memchr(in, '\t', len); + ilen = x ? (x - in) : len; + } + + //printf("%d: %.*s\n", (int)index, (int)ilen, in); + + if (cb(fls, in, ilen, index, userdata) < 0) + return -1; + + /* hopefully this is right */ + if (ilen + 1 >= len) + break; + + in = in + ilen + 1; + len -= ilen + 1; + } + + return index; +} + +/* ------------------------------------------------------------------------ */ +/* 0: pong */ + +static int flashii_receive_packet_pong(struct flashii *fls, const char *in, + size_t len) +{ + /* nothing to do */ + return 0; +} + +/* ------------------------------------------------------------------------ */ +/* 1: auth */ + +static int flashii_receive_auth_cb(struct flashii *fls, const char *in, + size_t len, size_t index, void *userdata) +{ + switch (index) { + case 1: + case 2: + case 3: + case 4: + return 0; /* nothing */ + case 0: + fls->id = memduptostr(in, len); + /* user ID */ + return 0; + case 5: + if (memtoi32(in, len, &fls->max_msg_length) < 0) + return -1; + + return 0; + } + + return -1; + + (void)userdata; +} + +static int flashii_receive_packet_auth_success(struct flashii *fls, + const char *in, size_t len) +{ + return (flashii_receive_packet_enum(fls, in, len, flashii_receive_auth_cb, NULL) != 5) ? -1 : 0; +} + +static int flashii_receive_packet_auth(struct flashii *fls, const char *in, + size_t len) +{ + struct flashii_receive_packet_filter f[] = { + FLASHII_RECEIVE_PACKET_FILTER("y", flashii_receive_packet_auth_success), + /* FLASHII_RECEIVE_PACKET_FILTER("n", flashii_receive_packet_auth_failure), */ + /* FLASHII_RECEIVE_PACKET_FILTER("#", flashii_receive_packet_user_add), */ + /* terminate */ + { 0 } + }; + + return flashii_receive_packet_filter(fls, in, len, f); +} + +/* ------------------------------------------------------------------------ */ +/* 2: message add */ + +static int flashii_receive_packet_msg_cb(struct flashii *fls, const char *in, + size_t len, size_t index, void *userdata) +{ + struct flashii_msg *msg = userdata; + + switch (index) { + case 0: + /* UNIX timestamp */ + if (memtoi64(in, len, &msg->timestamp) < 0) + return -1; + return 0; + case 1: + msg->id = memduptostr(in, len); + return 0; + case 2: + msg->body = memduptostr(in, len); + return 0; + case 3: + msg->msg_id = memduptostr(in, len); + return 0; + case 4: + if (memtou32flags(in, len, &msg->flags) < 0) + return -1; + return 0; + } + + return -1; +} + +static int flashii_receive_packet_msg(struct flashii *fls, const char *in, + size_t len) +{ + struct flashii_msg msg; + + if (flashii_receive_packet_enum(fls, in, len, flashii_receive_packet_msg_cb, &msg) != 4) + return -1; + + fls->msg_recv(fls, &msg); + return 0; +} + +/* ------------------------------------------------------------------------ */ +/* 7: context */ + +static int flashii_receive_packet_context_user_list_cb(struct flashii *fls, + const char *in, size_t len, size_t index, void *userdata) +{ + struct flashii_user *user = userdata; + + if (!index) + return 0; /* ignored */ + + switch ((index - 1) % 5) { + case 0: + /* user ID */ + user->id = memduptostr(in, len); + return 0; + case 1: + /* user name */ + user->name = memduptostr(in, len); + return 0; + case 2: + /* color */ + return 0; + case 3: + /* perms */ + return 0; + case 4: + /* visiblility */ + if (memtoi32(in, len, &user->visible) < 0) + return -1; + fls->user_recv(fls, user); + return 0; + } + + return -1; +} + +static int flashii_receive_packet_context_user_list(struct flashii *fls, + const char *in, size_t len) +{ + struct flashii_user user; + + if (flashii_receive_packet_enum(fls, in, len, flashii_receive_packet_context_user_list_cb, &user) < 0) + return -1; + + return 0; +} + +static int flashii_receive_packet_context_msg_add_cb(struct flashii *fls, + const char *in, size_t len, size_t index, void *userdata) +{ + struct flashii_msg *msg = userdata; + + switch (index) { + case 0: + /* UNIX timestamp */ + if (memtoi64(in, len, &msg->timestamp) < 0) + return -1; + return 0; + case 1: + msg->id = memduptostr(in, len); + return 0; + case 2: + /* "user name" wtf */ + return 0; + case 3: + /* color */ + return 0; + case 4: + /* user permissions */ + return 0; + case 5: + msg->body = memduptostr(in, len); + return 0; + case 6: + msg->msg_id = memduptostr(in, len); + return 0; + case 7: + /* notifications */ + return 0; + case 8: +#if 0 + if (memtou32flags(in, len, &msg->flags) < 0) + return -1; +#endif + return 0; + } + + return -1; +} + +static int flashii_receive_packet_context_message_add(struct flashii *fls, + const char *in, size_t len) +{ + struct flashii_msg msg; + + if (flashii_receive_packet_enum(fls, in, len, flashii_receive_packet_context_msg_add_cb, &msg) < 0) + return -1; + + fls->msg_recv(fls, &msg); + return 0; +} + +static int flashii_receive_packet_context(struct flashii *fls, const char *in, + size_t len) +{ + struct flashii_receive_packet_filter f[] = { + FLASHII_RECEIVE_PACKET_FILTER("0", flashii_receive_packet_context_user_list), + FLASHII_RECEIVE_PACKET_FILTER("1", flashii_receive_packet_context_message_add), + /* FLASHII_RECEIVE_PACKET_FILTER("2", ...), */ + /* terminate */ + { 0 } + }; + + return flashii_receive_packet_filter(fls, in, len, f); +} + +/* ------------------------------------------------------------------------ */ + +static int flashii_receive_packet(struct flashii *fls, const char *in, + size_t len) +{ + struct flashii_receive_packet_filter f[] = { + FLASHII_RECEIVE_PACKET_FILTER("0", flashii_receive_packet_pong), + FLASHII_RECEIVE_PACKET_FILTER("1", flashii_receive_packet_auth), + FLASHII_RECEIVE_PACKET_FILTER("2", flashii_receive_packet_msg), + FLASHII_RECEIVE_PACKET_FILTER("7", flashii_receive_packet_context), + /* terminate */ + { 0 } + }; + + return flashii_receive_packet_filter(fls, in, len, f); +} + +/* ------------------------------------------------------------------------ */ + +static int flashii_send_vpacket(struct flashii *fls, const char *fmt, va_list ap) +{ + /* this uses vsnprintf to assemble the packet and sends it off */ + int r; + char *s; + va_list ap2; + + va_copy(ap2, ap); + + r = vsnprintf(NULL, 0, fmt, ap); + + if (r < 0) { + va_end(ap2); + return -1; + } + + s = malloc(LWS_PRE + r + 1); + if (!s) { + va_end(ap2); + return -1; + } + + r = vsnprintf(s + LWS_PRE, r + 1, fmt, ap2); + + va_end(ap2); + + if (r < 0) + return -1; + + lws_write(fls->lws, s + LWS_PRE, r, LWS_WRITE_TEXT); + free(s); + + return 0; +} + +static int flashii_send_packet(struct flashii *fls, const char *fmt, ...) +{ + va_list ap; + int r; + + va_start(ap, fmt); + r = flashii_send_vpacket(fls, fmt, ap); + va_end(ap); + + return r; +} + +static int flashii_send_ping(struct flashii *fls, const char *id) +{ + assert(id); + return flashii_send_packet(fls, "0\t%s", id); +} + +static int flashii_send_auth(struct flashii *fls, const char *scheme, const char *args) +{ + assert(scheme); + assert(args); + return flashii_send_packet(fls, "1\t%s\t%s", scheme, args); +} + +static int flashii_send_msg(struct flashii *fls, const char *id, const char *msg) +{ + assert(id); + assert(msg); + return flashii_send_packet(fls, "2\t%s\t%s", id, msg); +} + +static int flashii_process_msgbuf(struct flashii *fls) +{ + while (fls->msgbuf) { + struct flashii_internal_msgbuf *tmp; + + tmp = fls->msgbuf; + flashii_send_msg(fls, fls->id, tmp->msg); + fls->msgbuf = tmp->next; + free(tmp); + } +} + +/* libwebsockets calls us back here */ +static int flashii_cb(struct lws *wsi, enum lws_callback_reasons reason, + void *userdata, void *in, size_t len) +{ + struct flashii *fls = (struct flashii *)userdata; + static const unsigned char token[] = { +#embed "token.txt" + , 0 + }; + + switch (reason) { + case LWS_CALLBACK_CLIENT_ESTABLISHED: + lws_callback_on_writable(wsi); + break; + case LWS_CALLBACK_CLIENT_RECEIVE: + /* incoming message -- print it out + * we should handle it based on the type */ + flashii_receive_packet(fls, in, len); + break; + case LWS_CALLBACK_CLIENT_WRITEABLE: + if (fls->need_heartbeat) { + flashii_send_ping(fls, fls->id); + fls->need_heartbeat = 0; + } + + /* process the buffer of messages, if any */ + flashii_process_msgbuf(fls); + + flashii_send_auth(fls, "Misuzu", token); + /* send message here using lws_write */ + break; + case LWS_CALLBACK_CLIENT_CLOSED: + case LWS_CALLBACK_CLIENT_CONNECTION_ERROR: + break; +#if 0 + default: + printf("UNKNOWN REASON: %d\n", reason); + break; +#endif + } + + return lws_callback_http_dummy(wsi, reason, userdata, in, len); +} + +static const struct lws_protocols protocols[] = { + { + "sockchat", + flashii_cb, + sizeof(struct flashii), + 0, + }, + { 0 } /* terminator */ +}; + +/* ------------------------------------------------------------------------ */ + +struct flashii *flashii_init(const char *protocol, const char *address, + uint16_t port, flashii_msg_recv_spec msg_recv, + flashii_user_recv_spec user_recv) +{ + struct lws_context_creation_info info; + struct lws_client_connect_info ccinfo; + struct flashii *fls; + + fls = calloc(1, sizeof(*fls)); + if (!fls) + return NULL; + + fls->msg_recv = msg_recv; + fls->user_recv = user_recv; + + lws_context_info_defaults(&info, NULL); + info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT; + info.port = CONTEXT_PORT_NO_LISTEN; /* we do not run any server */ + info.protocols = protocols; + + fls->ctx = lws_create_context(&info); + if (!fls->ctx) { + lwsl_err("lws init failed\n"); + free(fls); + return NULL; + } + + /* konekuto */ + memset(&ccinfo, 0, sizeof(ccinfo)); + + ccinfo.context = fls->ctx; + ccinfo.address = address; + ccinfo.port = port; + ccinfo.path = "/"; + ccinfo.host = address; + ccinfo.origin = address; + ccinfo.protocol = "sockchat"; + ccinfo.ssl_connection = LCCSCF_USE_SSL; + ccinfo.userdata = fls; + + fls->lws = lws_client_connect_via_info(&ccinfo); + if (!fls->lws) { + lwsl_cx_err(fls->ctx, "lws connect failed\n"); + lws_context_destroy(fls->ctx); + free(fls); + return NULL; + } + + return fls; +} + +/* worker + * + * this should be called in an event loop, on the same thread that + * initialized the structure. + * + * `noblock` is a boolean saying... whether we should block :) */ +void flashii_work(struct flashii *fls, int noblock) +{ + time_t thetime; + + time(&thetime); + + if (fls->id) { + if (difftime(thetime, fls->last_heartbeat) >= 30.0) { + fls->need_heartbeat = 1; + fls->last_heartbeat = thetime; + lws_callback_on_writable(fls->lws); + } + } + + lws_service(fls->ctx, (noblock) ? -1 : 0); +} + +int flashii_send_message(struct flashii *fls, const char *msg) +{ + /* append to the internal linked list of messages */ + size_t len = strlen(msg); + + struct flashii_internal_msgbuf *msgbuf = malloc(sizeof(*msgbuf) + len + 1); + if (!msgbuf) + return -1; /* WHAT */ + + msgbuf->next = NULL; + + /* copy it allll in (includes NUL terminator) */ + memcpy(msgbuf->msg, msg, len + 1); + + if (fls->msgbuf) { + struct flashii_internal_msgbuf *tail = fls->msgbuf; + while (tail->next) + tail = tail->next; + tail->next = msgbuf; + } else { + fls->msgbuf = msgbuf; + } + + lws_callback_on_writable(fls->lws); + + return 0; /* yay */ +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/flashii.h Tue Sep 09 00:29:57 2025 -0400 @@ -0,0 +1,66 @@ +/* + * wcc -- a shitty sockchat client + * + * Copyright (c) 2025 Paper + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef WCC_FLASHII_H_ +#define WCC_FLASHII_H_ + +#include <stdint.h> + +/* opaque type */ +struct flashii; + +struct flashii_msg { + int64_t timestamp; /* UNIX timestamp */ + char *id; /* user ID of the author */ + char *body; /* message body; sanitized. this is HTML/BBcode */ + char *msg_id; /* message ID (lol) */ + +#define FLASHII_MSG_FLAG_BOLD (0x01) +#define FLASHII_MSG_FLAG_CURSIVE (0x02) /* AKA italics */ +#define FLASHII_MSG_FLAG_UNDERLINE (0x04) +#define FLASHII_MSG_FLAG_COLON (0x08) +#define FLASHII_MSG_FLAG_PRIVATE (0x10) /* private message */ + uint32_t flags; +}; + +typedef void (*flashii_msg_recv_spec)(struct flashii *fls, struct flashii_msg *msg); + +struct flashii_user { + char *id; + char *name; + /* char *color; -- dont care */ + /* (?) perms; -- dont care */ + int32_t visible; +}; + +typedef void (*flashii_user_recv_spec)(struct flashii *fls, struct flashii_user *user); + +struct flashii *flashii_init(const char *protocol, const char *address, + uint16_t port, flashii_msg_recv_spec msg_recv, + flashii_user_recv_spec user_recv); +void flashii_work(struct flashii *fls, int noblock); +int flashii_send_message(struct flashii *fls, const char *msg); +/* TODO flashii_quit */ + +#endif /* WCC_FLASHII_H_ */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/wcc.c Tue Sep 09 00:29:57 2025 -0400 @@ -0,0 +1,201 @@ +/* + * wcc -- a shitty sockchat client + * + * 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <unistd.h> +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <time.h> +#include <curses.h> +#include <locale.h> + +#include "flashii.h" + +/* ------------------------------------------------------------------------ */ + +/* creates a set (only unique IDs allowed) + * + * the ID must be a string! */ +#define DYNAMIC_SET(type, name, uid) \ + struct name { \ + type *arr; \ + size_t size; \ + size_t alloc; \ + }; \ +\ + static type *name##_lookup(struct name *arr, const char *id) \ + { \ + size_t i; \ + \ + for (i = 0; i < arr->size; i++) \ + if (!strcmp(arr->arr[i].uid, id)) \ + return arr->arr + i; \ + \ + return NULL; \ + } \ +\ + static int name##_insert(struct name *x, type *u) \ + { \ + type *uu; \ +\ + uu = name##_lookup(x, u->uid); \ + if (uu) { \ + /* overwrite it with our own data */ \ + memcpy(uu, u, sizeof(*u)); \ + return 0; \ + } /* else... */ \ +\ + if (x->size + 1 >= x->alloc) { \ + x->alloc = (x->alloc) ? (x->alloc * 2) : 8; \ + x->arr = realloc(x->arr, x->alloc * sizeof(*u)); \ + if (!x->arr) \ + return -1; \ + } \ +\ + memcpy(x->arr + x->size, u, sizeof(*u)); \ + x->size++; \ +\ + return 1; \ + } \ +\ + static int name##_remove(struct name *s, type *u) \ + { \ + /* TODO */ \ + return -1; \ + } + +DYNAMIC_SET(struct flashii_user, flashii_user_set, id) +DYNAMIC_SET(struct flashii_msg, flashii_msg_set, msg_id) + +/* ------------------------------------------------------------------------ */ + +static struct flashii_user_set users = {0}; +static struct flashii_msg_set msgs = {0}; + +static void msg_recv(struct flashii *fls, struct flashii_msg *msg) +{ + flashii_msg_set_insert(&msgs, msg); +} + +static void user_recv(struct flashii *fls, struct flashii_user *user) +{ + flashii_user_set_insert(&users, user); +} + +/* pending message ... */ +static wchar_t pending_msg[65536]; +static size_t pending_msg_len = 0; + +int main(void) +{ + int height, width; + struct flashii *fls; + WINDOW *cw, *iw; + + /* stupid C shit */ + setlocale(LC_ALL, "en_US.UTF-8"); + + initscr(); + cbreak(); + noecho(); + curs_set(1); + + getmaxyx(stdscr, height, width); + +#define INPUT_HEIGHT (3) + cw = newwin(height - INPUT_HEIGHT, width, 0, 0); + iw = newwin(INPUT_HEIGHT, width, height - INPUT_HEIGHT, 0); + + scrollok(cw, TRUE); + box(iw, 0, 0); + nodelay(iw, TRUE); /* no blocking! */ + keypad(iw, TRUE); + + fls = flashii_init("wss", "chatsrv-neru.flashii.net", 443, + msg_recv, user_recv); + if (!fls) + return 1; + + for (;;) { + size_t i; + + /* IM GONNA TALK TO DA POWER */ + flashii_work(fls, 1); + + /* chat window */ + werase(cw); + + /* draw msgs to the screen */ + for (i = 0; i < msgs.size; i++) { + const char *name; + struct flashii_user *user; + + user = flashii_user_set_lookup(&users, msgs.arr[i].id); + name = (user && user->name) ? user->name : msgs.arr[i].id; + + wprintw(cw, "<%s> %s\n", name, msgs.arr[i].body); + } + + wrefresh(cw); + + werase(iw); + + /* handle input */ + box(iw, 0, 0); + for (;;) { + wint_t c; + int r = wget_wch(iw, &c); + if (r == KEY_CODE_YES) { + /* keycode, KEY_DOWN, KEY_UP, etc */ + if (c == KEY_BACKSPACE) { /* backspace */ + pending_msg[--pending_msg_len] = '\0'; + } else { + printf("UNKNOWN KEYCODE: %d\n", c); + } + } else if (r == OK) { + /* unicode */ + if (pending_msg_len >= width - 7) /* or something like that */ + continue; + + if (c == '\r' || c == '\n') { + static char pending_msg_conv[65536]; + wcstombs(pending_msg_conv, pending_msg, pending_msg_len); + flashii_send_message(fls, pending_msg_conv); + memset(pending_msg, 0, pending_msg_len); + pending_msg_len = 0; + } else if (c == '\b' || c == 127) { + pending_msg[--pending_msg_len] = '\0'; + } else { + pending_msg[pending_msg_len++] = c; + } + } else/*if (r == ERR)*/ { + break; + } + } + + mvwprintw(iw, 1, 1, "> %ls", pending_msg); + + wrefresh(iw); + + usleep(10000); + } + + endwin(); + + return 0; +}
