summaryrefslogtreecommitdiff
path: root/userland
diff options
context:
space:
mode:
authorAnton Kling <anton@kling.gg>2024-03-19 01:41:22 +0100
committerAnton Kling <anton@kling.gg>2024-03-19 01:41:22 +0100
commit553b43d2e563dcff74d4c86904fa3737e96d7365 (patch)
tree002b70c9d919b8a18a95bb5754c57db897b5977e /userland
parent0dccff86e50dfe1555b8bc29862dba2b972a3705 (diff)
IRC: Add the IRC client I am working on
Diffstat (limited to 'userland')
-rw-r--r--userland/irc/Makefile21
-rw-r--r--userland/irc/irc.c503
-rw-r--r--userland/irc/sb.c59
-rw-r--r--userland/irc/sb.h22
-rw-r--r--userland/irc/sv.c102
-rw-r--r--userland/irc/sv.h28
-rw-r--r--userland/libc/include/dlfcn.h0
-rw-r--r--userland/libgui/libgui.h2
-rw-r--r--userland/terminal/term.c7
9 files changed, 742 insertions, 2 deletions
diff --git a/userland/irc/Makefile b/userland/irc/Makefile
new file mode 100644
index 0000000..c5f0c3d
--- /dev/null
+++ b/userland/irc/Makefile
@@ -0,0 +1,21 @@
+CC="i686-sb-gcc"
+CFLAGS = -ggdb -O2 -Wall -Wextra -Werror -Wno-pointer-sign -Wno-int-conversion -static
+LIB=-L../libgui -lgui
+INC=-I../libgui
+BINS=irc
+all: $(BINS)
+
+sb.o: sb.c
+ $(CC) $(CFLAGS) $(INC) $(LIB) -o $@ -c $<
+
+sv.o: sv.c
+ $(CC) $(CFLAGS) $(INC) $(LIB) -o $@ -c $<
+
+irc.o: irc.c
+ $(CC) $(CFLAGS) $(INC) $(LIB) -o $@ -c $<
+
+irc: irc.o sb.o sv.o
+ $(CC) $(CFLAGS) -o $@ $^ $(LIB)
+
+clean:
+ rm $(BINS) *.o
diff --git a/userland/irc/irc.c b/userland/irc/irc.c
new file mode 100644
index 0000000..b69e1dd
--- /dev/null
+++ b/userland/irc/irc.c
@@ -0,0 +1,503 @@
+#include "sb.h"
+#include "sv.h"
+#include <assert.h>
+#include <ctype.h>
+#include <fcntl.h>
+#include <libgui.h>
+#include <math.h>
+#include <poll.h>
+#include <pty.h>
+#include <socket.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syscall.h>
+#include <unistd.h>
+
+#define TERMINAL_WIDTH 82
+#define TERMINAL_HEIGHT 40
+
+struct message {
+ char name[9];
+ size_t name_len;
+ char text[510];
+ size_t text_len;
+};
+
+struct irc_channel {
+ struct sv name;
+ struct message *messages;
+ size_t messages_num;
+ size_t messages_cap;
+ int can_send_message;
+ struct irc_channel *prev;
+ struct irc_channel *next;
+};
+
+struct irc_server {
+ u32 socket;
+ struct irc_channel *channels;
+};
+struct irc_channel *selected_channel = NULL;
+void refresh_screen(void);
+
+void irc_add_message(struct irc_server *server, struct sv channel_name,
+ struct sv sender, struct sv msg_text);
+void irc_add_message_to_channel(struct irc_channel *chan, struct sv sender,
+ struct sv msg_text);
+
+u32 tcp_connect_ipv4(u32 ip, u16 port, int *err) {
+ return (u32)syscall(SYS_TCP_CONNECT, ip, port, err, 0, 0);
+}
+
+int tcp_write(u32 socket, const u8 *buffer, u64 len, u64 *out) {
+ return (int)syscall(SYS_TCP_WRITE, socket, buffer, len, out, 0);
+}
+
+int tcp_read(u32 socket, u8 *buffer, u64 buffer_size, u64 *out) {
+ return (int)syscall(SYS_TCP_READ, socket, buffer, buffer_size, out, 0);
+}
+
+u32 gen_ipv4(u8 i1, u8 i2, u8 i3, u8 i4) {
+ return i4 << (32 - 8) | i3 << (32 - 16) | i2 << (32 - 24) | i1 << (32 - 32);
+}
+
+struct event {
+ u8 type; // File descriptor | Socket
+ u32 internal_id;
+};
+
+int queue_create(u32 *id) {
+ return (int)syscall(SYS_QUEUE_CREATE, id, 0, 0, 0, 0);
+}
+
+int queue_add(u32 queue_id, struct event *ev, u32 size) {
+ return (int)syscall(SYS_QUEUE_ADD, queue_id, ev, size, 0, 0);
+}
+
+int queue_wait(u32 queue_id) {
+ return (int)syscall(SYS_QUEUE_WAIT, queue_id, 0, 0, 0, 0);
+}
+
+int tcp_fmt(u32 socket, const char *fmt, ...) {
+ va_list ap;
+ char cmd_str[512];
+ va_start(ap, fmt);
+ int rc = vsnprintf(cmd_str, 512, fmt, ap);
+ va_end(ap);
+ return tcp_write(socket, (u8 *)cmd_str, rc, NULL);
+}
+
+int message_pos_x = 0;
+int message_pos_y = 0;
+
+void enter_raw_mode(void) {
+ printf("\033[X");
+}
+
+void clear_screen(void) {
+ printf("\033[2J");
+}
+
+void mvcursor(int x, int y) {
+ printf("\033[%d;%dH", x, y);
+}
+
+void clear_area(int x, int y, int sx, int sy) {
+ char buffer[sx + 1];
+ memset(buffer, ' ', sx);
+ buffer[sx] = '\0';
+
+ for (int i = 0; i < sy; i++) {
+ mvcursor(x, y + i);
+ printf("%s", buffer);
+ }
+}
+
+int prompt_x = 0;
+int prompt_y = TERMINAL_HEIGHT - 1;
+
+void msg_sv_print(struct sv s);
+void msg_sv_println(struct sv s) {
+ msg_sv_print(s);
+ if (0 != message_pos_x && 0 < s.length) {
+ message_pos_x = 0;
+ message_pos_y++;
+ }
+}
+
+void msg_sv_print(struct sv s) {
+ for (size_t i = 0; i < s.length; i++) {
+ mvcursor(message_pos_x, message_pos_y);
+ if ('\n' == s.s[i]) {
+ message_pos_x = 0;
+ message_pos_y++;
+ continue;
+ }
+ printf("%c", s.s[i]);
+ message_pos_x++;
+ if (message_pos_x > TERMINAL_WIDTH) {
+ message_pos_x = 0;
+ message_pos_y++;
+ }
+ }
+ mvcursor(prompt_x, prompt_y);
+}
+
+#define RPL_WELCOME C_TO_SV("001")
+#define RPL_YOURHOST C_TO_SV("002")
+#define RPL_CREATED C_TO_SV("003")
+#define RPL_MYINFO C_TO_SV("004")
+#define RPL_BOUNCE C_TO_SV("005")
+#define RPL_LUSERCLIENT C_TO_SV("251")
+#define RPL_NOTOPIC C_TO_SV("331")
+#define RPL_TOPIC C_TO_SV("332")
+#define ERR_NOMOTD C_TO_SV("422")
+
+#define RPL_MOTDSTART C_TO_SV("375")
+#define RPL_MOTD C_TO_SV("372")
+#define RPL_ENDOFMOTD C_TO_SV("376")
+
+struct sv server_nick = C_TO_SV("testuser");
+
+void handle_msg(struct irc_server *server, struct sv msg) {
+ struct sv message_origin = C_TO_SV("");
+ int has_prefix = (':' == sv_peek(msg));
+ if (has_prefix) {
+ message_origin = sv_split_delim(msg, &msg, ' ');
+ }
+
+ struct sv command = sv_split_delim(msg, &msg, ' ');
+ struct sv command_parameters = msg;
+
+#define START_HANDLE_CMD \
+ if (0) { \
+ }
+#define HANDLE_CMD(_cmd) else if (sv_eq(_cmd, command))
+#define PASSTHROUGH_CMD(_cmd) \
+ HANDLE_CMD(_cmd) { \
+ struct sv intended_recipient = \
+ sv_split_delim(command_parameters, &command_parameters, ' '); \
+ if (sv_eq(intended_recipient, server_nick)) { \
+ irc_add_message_to_channel(&server->channels[0], C_TO_SV("*"), \
+ command_parameters); \
+ } \
+ }
+#define PASSTHROUGH_REPLY_CMD(_cmd) \
+ HANDLE_CMD(_cmd) { \
+ struct sv intended_recipient = \
+ sv_split_delim(command_parameters, &command_parameters, ' '); \
+ /* Remove the ':' */ \
+ command_parameters = sv_trim_left(command_parameters, 1); \
+ if (sv_eq(intended_recipient, server_nick)) { \
+ irc_add_message_to_channel(&server->channels[0], C_TO_SV("*"), \
+ command_parameters); \
+ } \
+ }
+#define PASSTHROUGH_CHANNEL_CMD(_cmd) \
+ HANDLE_CMD(_cmd) { \
+ struct sv intended_recipient = \
+ sv_split_delim(command_parameters, &command_parameters, ' '); \
+ struct sv channel = \
+ sv_split_delim(command_parameters, &command_parameters, ' '); \
+ /* Remove the ':' */ \
+ command_parameters = sv_trim_left(command_parameters, 1); \
+ if (sv_eq(intended_recipient, server_nick)) { \
+ irc_add_message(server, channel, C_TO_SV("*"), command_parameters); \
+ } \
+ }
+#define END_HANDLE_CMD \
+ else { \
+ msg_sv_print(C_TO_SV("Unknown command recieved from server: ")); \
+ msg_sv_print(command); \
+ msg_sv_print(C_TO_SV("\n")); \
+ }
+
+ START_HANDLE_CMD
+ HANDLE_CMD(C_TO_SV("JOIN")) {
+ struct sv channel = sv_split_space(command_parameters, NULL);
+
+ struct sb join_message;
+ sb_init(&join_message);
+ sb_append_sv(&join_message, message_origin);
+ sb_append(&join_message, " has joined the channel.");
+ irc_add_message(server, channel, C_TO_SV("*"), SB_TO_SV(join_message));
+ sb_free(&join_message);
+ }
+ HANDLE_CMD(C_TO_SV("PRIVMSG")) {
+ struct sv channel = sv_split_delim(command_parameters, &msg, ' ');
+ struct sv nick = sv_trim_left(message_origin, 1);
+ nick = sv_split_delim(nick, NULL, '!');
+ /* Remove the ':' */
+ msg = sv_trim_left(msg, 1);
+ irc_add_message(server, channel, nick, msg);
+ }
+ PASSTHROUGH_CHANNEL_CMD(RPL_NOTOPIC)
+ PASSTHROUGH_CHANNEL_CMD(RPL_TOPIC)
+
+ PASSTHROUGH_REPLY_CMD(RPL_MOTDSTART)
+ PASSTHROUGH_REPLY_CMD(RPL_MOTD)
+ PASSTHROUGH_REPLY_CMD(RPL_ENDOFMOTD)
+
+ PASSTHROUGH_REPLY_CMD(RPL_WELCOME)
+ PASSTHROUGH_REPLY_CMD(RPL_YOURHOST)
+ PASSTHROUGH_REPLY_CMD(RPL_CREATED)
+ PASSTHROUGH_REPLY_CMD(RPL_MYINFO)
+ PASSTHROUGH_REPLY_CMD(RPL_BOUNCE)
+ PASSTHROUGH_REPLY_CMD(RPL_LUSERCLIENT)
+ PASSTHROUGH_REPLY_CMD(ERR_NOMOTD)
+ END_HANDLE_CMD
+}
+
+void send_message(struct irc_server *server, struct irc_channel *chan,
+ struct sv msg) {
+ if (!chan->can_send_message) {
+ return;
+ }
+ struct sb s;
+ sb_init(&s);
+ sb_append(&s, "PRIVMSG ");
+ sb_append_sv(&s, chan->name);
+ sb_append(&s, " :");
+ sb_append_sv(&s, msg);
+ sb_append(&s, "\n");
+
+ if (msg.length > 512) {
+ // TODO I am too lazy to add functionality to split msg right now
+ assert(0);
+ }
+ tcp_write(server->socket, s.string, s.length, NULL);
+ irc_add_message(server, chan->name, server_nick, msg);
+ sb_free(&s);
+}
+
+void irc_add_channel(struct irc_server *server, struct sv name) {
+ struct irc_channel *prev = server->channels;
+ struct irc_channel *chan;
+ if (!server->channels) {
+ server->channels = malloc(sizeof(struct irc_channel));
+ chan = server->channels;
+ } else {
+ struct irc_channel *t = server->channels;
+ for (; t->next;) {
+ t = t->next;
+ }
+ prev = t;
+ t->next = malloc(sizeof(struct irc_channel));
+ chan = t->next;
+ }
+
+ chan->name = sv_clone(name);
+ chan->messages = malloc(256 * sizeof(struct message));
+ chan->messages_cap = 256;
+ chan->messages_num = 0;
+ chan->prev = prev;
+ chan->next = NULL;
+ chan->can_send_message = 1;
+}
+
+void irc_join_channel(struct irc_server *server, const char *name) {
+ assert(tcp_fmt(server->socket, "JOIN %s\n", name));
+ irc_add_channel(server, C_TO_SV(name));
+}
+
+void irc_show_channel(struct irc_channel *channel) {
+ clear_area(0, 0, TERMINAL_WIDTH, prompt_y - 1);
+ message_pos_x = 0;
+ message_pos_y = 0;
+ for (size_t i = 0; i < channel->messages_num; i++) {
+ struct message *m = &channel->messages[i];
+ struct sv nick = {
+ .s = m->name,
+ .length = m->name_len,
+ };
+ struct sv msg = {
+ .s = m->text,
+ .length = m->text_len,
+ };
+ msg_sv_print(nick);
+ msg_sv_print(C_TO_SV(": "));
+ msg_sv_print(msg);
+ msg_sv_print(C_TO_SV("\n"));
+ }
+}
+
+void irc_add_message_to_channel(struct irc_channel *chan, struct sv sender,
+ struct sv msg_text) {
+ if (chan->messages_num >= chan->messages_cap - 1) {
+ chan->messages_cap += 256;
+ chan->messages =
+ realloc(chan->messages, chan->messages_cap * sizeof(struct message));
+ }
+ struct message *msg = &chan->messages[chan->messages_num];
+ chan->messages_num++;
+
+ assert(sender.length <= 9);
+ assert(msg_text.length <= 510);
+
+ msg->name_len = sender.length;
+ msg->text_len = msg_text.length;
+ memcpy(msg->name, sender.s, sender.length);
+ memcpy(msg->text, msg_text.s, msg_text.length);
+ if (selected_channel == chan) {
+ refresh_screen();
+ }
+}
+
+struct irc_channel *irc_get_channel(struct irc_server *server,
+ struct sv channel_name) {
+ struct irc_channel *chan = server->channels;
+ for (; chan; chan = chan->next) {
+ if (sv_eq(chan->name, channel_name)) {
+ break;
+ }
+ }
+ if (!chan) {
+ // If a message was recieved but we have not joined the channel it
+ // is probably a PRIVMSG that this application turns into a channel
+ // message where the "channel_name" is the sender. Or the server is
+ // confused and thinks we already have joined a channel. Either way
+ // it should be added to the list of joined channels.
+ irc_add_channel(server, channel_name);
+ // Recursion as the function should now no longer fail
+ return irc_get_channel(server, channel_name);
+ }
+ return chan;
+}
+
+void irc_add_message(struct irc_server *server, struct sv channel_name,
+ struct sv sender, struct sv msg_text) {
+ struct irc_channel *chan = irc_get_channel(server, channel_name);
+ irc_add_message_to_channel(chan, sender, msg_text);
+}
+
+int irc_connect_server(struct irc_server *server, u32 ip, u16 port) {
+ int err;
+ u32 socket = tcp_connect_ipv4(ip, port, &err);
+ if (err) {
+ assert(0);
+ return 0;
+ }
+ server->socket = socket;
+ irc_add_channel(server, C_TO_SV("*"));
+ server->channels[0].can_send_message = 0;
+ return 1;
+}
+
+void refresh_screen(void) {
+ irc_show_channel(selected_channel);
+}
+
+int main(void) {
+ clear_screen();
+
+ u32 id;
+ queue_create(&id);
+
+ struct event fd_ev;
+ fd_ev.type = 0;
+ fd_ev.internal_id = 0;
+ queue_add(id, &fd_ev, 1);
+
+ u32 ip = gen_ipv4(10, 0, 2, 2);
+ struct irc_server server_ctx;
+ irc_connect_server(&server_ctx, ip, 6667);
+
+ selected_channel = &server_ctx.channels[0];
+
+ const char *nick = SV_TO_C(server_nick);
+ const char *realname = "John Doe";
+ assert(tcp_fmt(server_ctx.socket, "NICK %s\n", nick));
+ assert(tcp_fmt(server_ctx.socket, "USER %s * * :%s\n", nick, realname));
+ irc_join_channel(&server_ctx, "#secretclub");
+
+ struct event socket_ev;
+ socket_ev.type = 1;
+ socket_ev.internal_id = server_ctx.socket;
+ queue_add(id, &socket_ev, 1);
+
+ struct sb your_msg;
+ sb_init(&your_msg);
+
+ char current_msg[512];
+ u32 msg_usage = 0;
+
+ for (;;) {
+ queue_wait(id);
+ {
+ char buffer[4096];
+ int rc = mread(0, buffer, 4096, 0);
+ for (int i = 0; i < rc; i++) {
+ char c = buffer[i];
+
+ if (('n' & 0x1F) == c) {
+ struct irc_channel *next_channel = selected_channel->next;
+ if (next_channel) {
+ selected_channel = next_channel;
+ refresh_screen();
+ }
+ continue;
+ }
+ if (('p' & 0x1F) == c) {
+ struct irc_channel *prev_channel = selected_channel->prev;
+ if (prev_channel) {
+ selected_channel = prev_channel;
+ refresh_screen();
+ }
+ continue;
+ }
+
+ if ('\b' == c) {
+ int n = sb_delete_right(&your_msg, 1);
+ if (0 == n) {
+ continue;
+ }
+ prompt_x -= n;
+ mvcursor(prompt_x, prompt_y);
+ printf(" ");
+ mvcursor(prompt_x, prompt_y);
+ continue;
+ }
+
+ if ('\n' == c) {
+ send_message(&server_ctx, selected_channel, SB_TO_SV(your_msg));
+ prompt_x = 0;
+ clear_area(0, prompt_y, 50, 1);
+ mvcursor(prompt_x, prompt_y);
+ sb_reset(&your_msg);
+ continue;
+ }
+
+ mvcursor(prompt_x, prompt_y);
+ printf("%c", c);
+ prompt_x++;
+
+ sb_append_char(&your_msg, c);
+ }
+ }
+ {
+ u64 out;
+ char buffer[4096];
+ if (!tcp_read(server_ctx.socket, buffer, 4096, &out)) {
+ continue;
+ }
+
+ for (u64 i = 0; i < out; i++) {
+ assert(msg_usage < 512);
+ if ('\n' == buffer[i] && i > 1 && '\r' == buffer[i - 1]) {
+ handle_msg(&server_ctx, (struct sv){
+ .s = current_msg,
+ .length = msg_usage,
+ });
+ msg_usage = 0;
+ continue;
+ }
+ current_msg[msg_usage] = buffer[i];
+ msg_usage++;
+ }
+ }
+ }
+ return 0;
+}
diff --git a/userland/irc/sb.c b/userland/irc/sb.c
new file mode 100644
index 0000000..b8b8915
--- /dev/null
+++ b/userland/irc/sb.c
@@ -0,0 +1,59 @@
+#include "sb.h"
+#include <math.h>
+#include <stdlib.h>
+#include <string.h>
+
+void sb_init(struct sb *ctx) {
+ ctx->string = malloc(512);
+ ctx->length = 0;
+ ctx->capacity = 512;
+}
+
+void sb_free(struct sb *ctx) {
+ ctx->length = 0;
+ ctx->capacity = 0;
+ free(ctx->string);
+ ctx->string = NULL;
+}
+
+void sb_reset(struct sb *ctx) {
+ ctx->length = 0;
+}
+
+int sb_isempty(const struct sb *ctx) {
+ return (0 == ctx->length);
+}
+
+void sb_append_char(struct sb *ctx, char c) {
+ if (1 > ctx->capacity - ctx->length) {
+ ctx->capacity += 32;
+ ctx->string = realloc(ctx->string, ctx->capacity);
+ }
+ memcpy(ctx->string + ctx->length, &c, 1);
+ ctx->length++;
+}
+
+int sb_delete_right(struct sb *ctx, int n) {
+ n = min(n, ctx->length);
+ ctx->length -= n;
+ return n;
+}
+
+void sb_append(struct sb *ctx, const char *s) {
+ size_t l = strlen(s);
+ if (l > ctx->capacity - ctx->length) {
+ ctx->capacity += l;
+ ctx->string = realloc(ctx->string, ctx->capacity);
+ }
+ memcpy(ctx->string + ctx->length, s, l);
+ ctx->length += l;
+}
+
+void sb_append_sv(struct sb *ctx, struct sv sv) {
+ if (sv.length > ctx->capacity - ctx->length) {
+ ctx->capacity += sv.length;
+ ctx->string = realloc(ctx->string, ctx->capacity);
+ }
+ memcpy(ctx->string + ctx->length, sv.s, sv.length);
+ ctx->length += sv.length;
+}
diff --git a/userland/irc/sb.h b/userland/irc/sb.h
new file mode 100644
index 0000000..aad5806
--- /dev/null
+++ b/userland/irc/sb.h
@@ -0,0 +1,22 @@
+#ifndef SB_H
+#define SB_H
+#include "sv.h"
+#include <stddef.h>
+
+struct sb {
+ char *string;
+ size_t length;
+ size_t capacity;
+};
+
+struct sv;
+
+void sb_init(struct sb *ctx);
+void sb_free(struct sb *ctx);
+void sb_reset(struct sb *ctx);
+int sb_isempty(const struct sb *ctx);
+void sb_append_char(struct sb *ctx, char c);
+int sb_delete_right(struct sb *ctx, int n);
+void sb_append(struct sb *ctx, const char *s);
+void sb_append_sv(struct sb *ctx, struct sv sv);
+#endif
diff --git a/userland/irc/sv.c b/userland/irc/sv.c
new file mode 100644
index 0000000..c29d082
--- /dev/null
+++ b/userland/irc/sv.c
@@ -0,0 +1,102 @@
+#include "sv.h"
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+
+char *SV_TO_C(struct sv s) {
+ char *c_string = malloc(s.length + 1);
+ memcpy(c_string, s.s, s.length);
+ c_string[s.length] = '\0';
+ return c_string;
+}
+
+struct sv sv_split_space(const struct sv input, struct sv *rest) {
+ struct sv r = {
+ .s = input.s,
+ };
+ for (size_t i = 0; i < input.length; i++) {
+ if (isspace(input.s[i])) {
+ r.length = i;
+ if (rest) {
+ rest->s += i + 1;
+ rest->length -= (i + 1);
+ }
+ return r;
+ }
+ }
+
+ if (rest) {
+ rest->s = NULL;
+ rest->length = 0;
+ }
+ return input;
+}
+
+struct sv sv_split_delim(const struct sv input, struct sv *rest, char delim) {
+ struct sv r = {
+ .s = input.s,
+ };
+ for (size_t i = 0; i < input.length; i++) {
+ if (delim == input.s[i]) {
+ r.length = i;
+ if (rest) {
+ rest->s += i + 1;
+ rest->length -= (i + 1);
+ }
+ return r;
+ }
+ }
+
+ if (rest) {
+ rest->s = NULL;
+ rest->length = 0;
+ }
+ return input;
+}
+
+int sv_isempty(struct sv s) {
+ return (0 == s.length);
+}
+
+char sv_peek(struct sv s) {
+ if (0 == s.length) {
+ return '\0';
+ }
+ return s.s[0];
+}
+
+int sv_eq(struct sv a, struct sv b) {
+ if (a.length != b.length) {
+ return 0;
+ }
+ for (size_t i = 0; i < a.length; i++) {
+ if (a.s[i] != b.s[i]) {
+ return 0;
+ }
+ }
+ return 1;
+}
+
+struct sv sv_trim_left(struct sv s, size_t n) {
+ if (s.length < n) {
+ s.s += s.length;
+ s.length = 0;
+ return s;
+ }
+ s.s += n;
+ s.length -= n;
+ return s;
+}
+
+struct sv sv_clone(struct sv s) {
+ struct sv new_sv;
+ new_sv.length = s.length;
+ char *new_string = malloc(s.length);
+ memcpy(new_string, s.s, s.length);
+ new_sv.s = new_string;
+ return new_sv;
+}
+
+struct sv sv_clone_from_c(const char *s) {
+ return sv_clone(C_TO_SV(s));
+}
diff --git a/userland/irc/sv.h b/userland/irc/sv.h
new file mode 100644
index 0000000..615af2c
--- /dev/null
+++ b/userland/irc/sv.h
@@ -0,0 +1,28 @@
+#ifndef SV_H
+#define SV_H
+#include "sb.h"
+#include <stddef.h>
+
+#define SB_TO_SV(_sb) \
+ (struct sv) { \
+ .s = (_sb).string, .length = (_sb).length \
+ }
+
+#define C_TO_SV(_c_string) \
+ ((struct sv){.length = strlen(_c_string), .s = (_c_string)})
+
+struct sv {
+ const char *s;
+ size_t length;
+};
+
+char *SV_TO_C(struct sv s);
+struct sv sv_split_delim(const struct sv input, struct sv *rest, char delim);
+struct sv sv_split_space(const struct sv input, struct sv *rest);
+int sv_isempty(struct sv s);
+char sv_peek(struct sv s);
+int sv_eq(struct sv a, struct sv b);
+struct sv sv_trim_left(struct sv s, size_t n);
+struct sv sv_clone(struct sv s);
+struct sv sv_clone_from_c(const char *s);
+#endif
diff --git a/userland/libc/include/dlfcn.h b/userland/libc/include/dlfcn.h
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/userland/libc/include/dlfcn.h
diff --git a/userland/libgui/libgui.h b/userland/libgui/libgui.h
index 07fd3db..cb8d9a7 100644
--- a/userland/libgui/libgui.h
+++ b/userland/libgui/libgui.h
@@ -19,7 +19,7 @@ typedef struct {
// Taken from drivers/keyboard.c
struct KEY_EVENT {
char c;
- uint8_t mode; // (shift (0 bit)) (alt (1 bit))
+ uint8_t mode; // (shift (0 bit)) (alt (1 bit)) (ctrl (2 bit))
uint8_t release; // 0 pressed, 1 released
};
diff --git a/userland/terminal/term.c b/userland/terminal/term.c
index 62aaa55..b734bb5 100644
--- a/userland/terminal/term.c
+++ b/userland/terminal/term.c
@@ -248,7 +248,12 @@ void run() {
if (0 == e.ev.c) {
continue;
}
- write(cmdfd, &e.ev.c, 1);
+ char c = e.ev.c;
+ int ctrl_down = (1 == ((e.ev.mode >> 2) & 1));
+ if (ctrl_down) {
+ c &= 0x1f;
+ }
+ write(cmdfd, &c, 1);
}
}
}