Add irc(1).

Co-authored-by: Juhani Krekelä <juhani@krekelä.fi>
This commit is contained in:
Jonas 'Sortie' Termansen 2016-08-18 03:33:37 +02:00
parent f6ec5d2bc0
commit b008298b0a
22 changed files with 3147 additions and 0 deletions

View File

@ -21,6 +21,7 @@ host \
hostname \
ifconfig \
init \
irc \
kblayout \
kblayout-compiler \
login \

21
build-aux/config.mak Normal file
View File

@ -0,0 +1,21 @@
.PHONY: clean-tests
clean-tests:
rm -rf tests
rm -f config.h
.PHONY: clean
clean: clean-tests
config.h: $(addprefix tests/,$(addsuffix .h,$(TESTS)))
cat tests/*.h > config.h
tests/%.h: ../build-aux/tests/%.c
@if [ ! -d tests ]; then mkdir -p tests; fi
@ln -sf ../../build-aux/tests/$*.c tests/$*.c
@if $(CC) $(CFLAGS) $(CPPFLAGS) -Werror=incompatible-pointer-types -c tests/$*.c -o /dev/null 2>tests/$*.log; then \
echo "# tests/$*: Yes" && tail -n 1 $< > $@; \
else \
echo "# tests/$*: No" && true > $@; \
fi
-include ../build-aux/tests/*.d

View File

@ -0,0 +1,8 @@
#include <string.h>
int main(void)
{
void (*ptr)(void*, size_t) = explicit_bzero;
return ptr ? 0 : 1;
}
#define HAVE_EXPLICIT_BZERO 1

View File

@ -0,0 +1,8 @@
#include <stdlib.h>
int main(void)
{
void* (*ptr)(void*, size_t, size_t) = reallocarray;
return ptr ? 0 : 1;
}
#define HAVE_REALLOCARRAY 1

View File

@ -0,0 +1,8 @@
#include <string.h>
int main(void)
{
size_t (*ptr)(char*, const char*, size_t) = strlcat;
return ptr ? 0 : 1;
}
#define HAVE_STRLCAT 1

View File

@ -0,0 +1,8 @@
#include <string.h>
int main(void)
{
size_t (*ptr)(char*, const char*, size_t) = strlcpy;
return ptr ? 0 : 1;
}
#define HAVE_STRLCPY 1

4
irc/.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
irc
*.o
config.h
tests

56
irc/Makefile Normal file
View File

@ -0,0 +1,56 @@
include ../build-aux/platform.mak
include ../build-aux/compiler.mak
include ../build-aux/version.mak
include ../build-aux/dirs.mak
OPTLEVEL?=$(DEFAULT_OPTLEVEL)
CFLAGS?=$(OPTLEVEL)
CFLAGS += -Wall -Wextra
CPPFLAGS += -DVERSIONSTR=\"$(VERSION)\"
ifeq ($(HOST_IS_SORTIX),0)
CPPFLAGS+=-D_GNU_SOURCE
endif
BINARY = irc
#MANPAGES1 = irc.1
OBJS=\
compat.o \
connection.o \
database.o \
irc.o \
scrollback.o \
string.o \
ui.o \
all: $(BINARY)
.PHONY: all install clean
$(OBJS): config.h
%.o: %.c
$(CC) -std=gnu11 $(CFLAGS) $(CPPFLAGS) -c $< -o $@
$(BINARY): $(OBJS)
$(CC) $(CFLAGS) $(OBJS) -o $(BINARY) $(LIBS)
install: all
mkdir -p $(DESTDIR)$(BINDIR)
install $(BINARY) $(DESTDIR)$(BINDIR)
#mkdir -p $(DESTDIR)$(MANDIR)/man1
#install $(MANPAGES1) $(DESTDIR)$(MANDIR)/man1
clean:
rm -f $(BINARY)
rm -f $(OBJS) *.o
TESTS=\
have-explicit_bzero \
have-reallocarray \
have-strlcat \
have-strlcpy \
include ../build-aux/config.mak

65
irc/compat.c Normal file
View File

@ -0,0 +1,65 @@
/*
* Copyright (c) 2016 Jonas 'Sortie' Termansen.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
* compat.c
* Compatibility.
*/
#include <errno.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include "config.h"
#include "compat.h"
#ifndef HAVE_EXPLICIT_BZERO
void explicit_bzero(void* buffer, size_t size)
{
memset(buffer, 0, size);
}
#endif
#ifndef HAVE_REALLOCARRAY
void* reallocarray(void* ptr, size_t nmemb, size_t size)
{
if ( size && nmemb && SIZE_MAX / size < nmemb )
return errno = ENOMEM, (void*) NULL;
return realloc(ptr, nmemb * size);
}
#endif
#ifndef HAVE_STRLCAT
size_t strlcat(char* restrict dest, const char* restrict src, size_t size)
{
size_t dest_len = strnlen(dest, size);
if ( size <= dest_len )
return dest_len + strlen(src);
return dest_len + strlcpy(dest + dest_len, src, size - dest_len);
}
#endif
#ifndef HAVE_STRLCPY
size_t strlcpy(char* restrict dest, const char* restrict src, size_t size)
{
if ( !size )
return strlen(src);
size_t result;
for ( result = 0; result < size-1 && src[result]; result++ )
dest[result] = src[result];
dest[result] = '\0';
return result + strlen(src + result);
}
#endif

40
irc/compat.h Normal file
View File

@ -0,0 +1,40 @@
/*
* Copyright (c) 2016 Jonas 'Sortie' Termansen.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
* combat.h
* Compatibility.
*/
#ifndef COMPAT_H
#define COMPAT_H
#include <stddef.h>
#include "config.h"
#ifndef HAVE_EXPLICIT_BZERO
void explicit_bzero(void*, size_t);
#endif
#ifndef HAVE_REALLOCARRAY
void* reallocarray(void*, size_t, size_t);
#endif
#ifndef HAVE_STRLCAT
size_t strlcat(char* restrict, const char* restrict, size_t);
#endif
#ifndef HAVE_STRLCPY
size_t strlcpy(char* restrict, const char* restrict, size_t);
#endif
#endif

549
irc/connection.c Normal file
View File

@ -0,0 +1,549 @@
/*
* Copyright (c) 2016 Jonas 'Sortie' Termansen.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
* connection.c
* IRC protocol.
*/
#include <sys/socket.h>
#include <assert.h>
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include "compat.h"
#include "connection.h"
void dump_error(const char* message,
size_t message_size,
const struct timespec* when)
{
// TODO: Send the error somewhere appropriate in the UI.
fprintf(stderr, "\e[91m");
struct tm tm;
gmtime_r(&when->tv_sec, &tm);
fprintf(stderr, "[%i-%02i-%02i %02i:%02i:%02i %09li] ",
tm.tm_year + 1900,
tm.tm_mon + 1,
tm.tm_mday,
tm.tm_hour,
tm.tm_min,
tm.tm_sec,
when->tv_nsec);
for ( size_t i = 0; i < message_size; i++ )
{
if ( message[i] == '\r' )
continue;
else if ( message[i] == '\n' )
continue;
else if ( (unsigned char) message[i] < 32 )
{
fprintf(stderr, "\e[31m");
fprintf(stderr, "\\x%02X", (unsigned char) message[i]);
fprintf(stderr, "\e[91m");
}
fputc((unsigned char) message[i], stderr);
}
fprintf(stderr, "\e[m\n");
}
void dump_outgoing(const char* message,
size_t message_size,
const struct timespec* when)
{
return; // TODO: Remove this, or adopt it for a logging mechanism.
fprintf(stderr, "\e[92m");
struct tm tm;
gmtime_r(&when->tv_sec, &tm);
fprintf(stderr, "[%i-%02i-%02i %02i:%02i:%02i %09li] ",
tm.tm_year + 1900,
tm.tm_mon + 1,
tm.tm_mday,
tm.tm_hour,
tm.tm_min,
tm.tm_sec,
when->tv_nsec);
for ( size_t i = 0; i < message_size; i++ )
{
if ( message[i] == '\r' )
continue;
else if ( message[i] == '\n' )
continue;
else if ( (unsigned char) message[i] < 32 )
{
fprintf(stderr, "\e[91m");
fprintf(stderr, "\\x%02X", (unsigned char) message[i]);
fprintf(stderr, "\e[92m");
continue;
}
fputc((unsigned char) message[i], stderr);
}
fprintf(stderr, "\e[m\n");
}
void dump_incoming(const char* message,
size_t message_size,
const struct timespec* when)
{
return; // TODO: Remove this, or adopt it for a logging mechanism.
fprintf(stderr, "\e[93m");
struct tm tm;
gmtime_r(&when->tv_sec, &tm);
fprintf(stderr, "[%i-%02i-%02i %02i:%02i:%02i %09li] ",
tm.tm_year + 1900,
tm.tm_mon + 1,
tm.tm_mday,
tm.tm_hour,
tm.tm_min,
tm.tm_sec,
when->tv_nsec);
for ( size_t i = 0; i < message_size; i++ )
{
if ( message[i] == '\r' )
continue;
else if ( message[i] == '\n' )
continue;
else if ( (unsigned char) message[i] < 32 )
{
fprintf(stderr, "\e[91m");
fprintf(stderr, "\\x%02X", (unsigned char) message[i]);
fprintf(stderr, "\e[93m");
continue;
}
fputc((unsigned char) message[i], stderr);
}
fprintf(stderr, "\e[m\n");
}
void irc_error_vlinef(const char* format, va_list ap_orig)
{
va_list ap;
struct timespec now;
clock_gettime(CLOCK_REALTIME, &now);
va_copy(ap, ap_orig);
char* string;
if ( 0 <= vasprintf(&string, format, ap) )
{
va_end(ap);
dump_error(string, strlen(string), &now);
free(string);
return;
}
va_end(ap);
char buffer[512];
va_copy(ap, ap_orig);
if ( 0 <= vsnprintf(buffer, sizeof(buffer), format, ap) )
{
va_end(ap);
dump_error(buffer, strlen(buffer), &now);
dump_error("(vasprintf failed printing that line)",
strlen("(vasprintf failed printing that line)"), &now);
return;
}
va_end(ap);
dump_error(format, strlen(format), &now);
dump_error("(vsnprintf failed printing format string)",
strlen("(vsnprintf failed printing that format string)"), &now);
}
void irc_error_linef(const char* format, ...)
{
va_list ap;
va_start(ap, format);
irc_error_vlinef(format, ap),
va_end(ap);
}
void irc_transmit(struct irc_connection* irc_connection,
const char* message,
size_t message_size)
{
struct timespec now;
clock_gettime(CLOCK_REALTIME, &now);
if ( irc_connection->connectivity_error )
return;
dump_outgoing(message, message_size, &now);
int fd = irc_connection->fd;
while ( message_size )
{
ssize_t amount = 0;
amount = send(fd, message, message_size, MSG_NOSIGNAL);
if ( amount < 0 || amount == 0 )
{
warn("send");
irc_connection->connectivity_error = true;
return;
}
message += amount;
message_size -= amount;
}
}
void irc_transmit_message(struct irc_connection* irc_connection,
const char* message,
size_t message_size)
{
assert(2 <= message_size);
assert(message[message_size - 2] == '\r');
assert(message[message_size - 1] == '\n');
char buffer[512];
if ( 512 < message_size )
{
memcpy(buffer, message, 510);
buffer[510] = '\r';
buffer[511] = '\n';
message = buffer;
message_size = 512;
}
irc_transmit(irc_connection, message, message_size);
explicit_bzero(buffer, sizeof(buffer));
}
void irc_receive_more_bytes(struct irc_connection* irc_connection)
{
if ( irc_connection->connectivity_error )
return;
int fd = irc_connection->fd;
char* buffer = irc_connection->incoming_buffer;
size_t buffer_size = sizeof(irc_connection->incoming_buffer);
size_t buffer_used = irc_connection->incoming_amount;
size_t buffer_free = buffer_size - buffer_used;
if ( buffer_free == 0 )
return;
// TODO: Use non-blocking IO for transmitting as well so O_NONBLOCK can
// always be used.
// TODO: Use MSG_DONTWAIT when supported in Sortix.
int flags = fcntl(fd, F_GETFL);
fcntl(fd, F_SETFL, flags | O_NONBLOCK);
ssize_t amount = recv(fd, buffer + buffer_used, buffer_free, 0);
fcntl(fd, F_SETFL, flags);
if ( amount < 0 )
{
if ( errno == EAGAIN || errno == EWOULDBLOCK )
return;
warn("recv");
irc_connection->connectivity_error = true;
}
else if ( amount == 0 )
{
// TODO: Gracefully close the connection.
irc_connection->connectivity_error = true;
}
else
{
irc_connection->incoming_amount += amount;
}
}
void irc_receive_pop_bytes(struct irc_connection* irc_connection,
char* buffer,
size_t count)
{
assert(count <= irc_connection->incoming_amount);
memcpy(buffer, irc_connection->incoming_buffer, count);
explicit_bzero(irc_connection->incoming_buffer, count);
memmove(irc_connection->incoming_buffer,
irc_connection->incoming_buffer + count,
irc_connection->incoming_amount - count);
irc_connection->incoming_amount -= count;
explicit_bzero(irc_connection->incoming_buffer + irc_connection->incoming_amount,
count);
}
bool irc_receive_message(struct irc_connection* irc_connection,
char message[512],
struct timespec* when)
{
if ( irc_connection->connectivity_error )
return false;
size_t message_usable = 0;
while ( message_usable < irc_connection->incoming_amount &&
irc_connection->incoming_buffer[message_usable] != '\r' &&
irc_connection->incoming_buffer[message_usable] != '\n' )
message_usable++;
if ( message_usable < irc_connection->incoming_amount &&
irc_connection->incoming_buffer[message_usable] == '\r')
{
message_usable++;
if ( message_usable < irc_connection->incoming_amount &&
irc_connection->incoming_buffer[message_usable] == '\n' )
{
message_usable++;
irc_receive_pop_bytes(irc_connection, message, message_usable);
assert(message[message_usable-2] == '\r');
assert(message[message_usable-1] == '\n');
message[message_usable-2] = '\0';
message[message_usable-1] = '\0';
// TODO: This is not always when the message did arrive.
struct timespec now;
clock_gettime(CLOCK_REALTIME, &now);
dump_incoming(message, message_usable-2, &now);
*when = now;
return true;
}
else if ( message_usable < irc_connection->incoming_amount )
{
// TODO: Handle bad newline sequence.
warnx("recv: bad IRC newline");
irc_connection->connectivity_error = true;
return false;
}
}
else if ( message_usable < irc_connection->incoming_amount &&
irc_connection->incoming_buffer[message_usable] == '\n' )
{
// TODO: Handle bad newline sequence.
warnx("recv: bad IRC newline");
irc_connection->connectivity_error = true;
return false;
}
if ( message_usable == 512 )
{
// TODO: Handle untruncated lines from the server.
warnx("recv: overlong IRC line from server");
irc_connection->connectivity_error = true;
return false;
}
return false;
}
void irc_transmit_string(struct irc_connection* irc_connection,
const char* string)
{
char message[512];
strncpy(message, string, 510);
message[510] = '\0';
message[511] = '\0';
size_t string_truncated_length = strlen(message);
for ( size_t i = 0; i < string_truncated_length; i++ )
{
if ( message[i] == '\r' )
message[i] = ' ';
if ( message[i] == '\n' )
message[i] = ' ';
}
message[string_truncated_length + 0] = '\r';
message[string_truncated_length + 1] = '\n';
size_t message_length = strnlen(message, 512);
irc_transmit_message(irc_connection, message, message_length);
explicit_bzero(message, sizeof(message));
}
__attribute__((format(printf, 2, 0)))
void irc_transmit_vformat(struct irc_connection* irc_connection,
const char* format,
va_list ap)
{
char* string = NULL;
if ( vasprintf(&string, format, ap) < 0 )
{
// TODO: Hmm, what do we do here.
warn("vasprintf");
// TODO: Should the error condition be set?
return;
}
irc_transmit_string(irc_connection, string);
explicit_bzero(string, strlen(string));
free(string);
}
__attribute__((format(printf, 2, 3)))
void irc_transmit_format(struct irc_connection* irc_connection,
const char* format,
...)
{
va_list ap;
va_start(ap, format);
irc_transmit_vformat(irc_connection, format, ap);
va_end(ap);
}
void irc_command_pass(struct irc_connection* irc_connection,
const char* password)
{
irc_transmit_format(irc_connection, "PASS :%s", password);
}
void irc_command_nick(struct irc_connection* irc_connection,
const char* nick)
{
irc_transmit_format(irc_connection, "NICK :%s", nick);
}
void irc_command_user(struct irc_connection* irc_connection,
const char* nick,
const char* local_hostname,
const char* server_hostname,
const char* real_name)
{
// TODO: What if there are spaces in some of these fields?
irc_transmit_format(irc_connection, "USER %s %s %s :%s",
nick, local_hostname, server_hostname, real_name);
}
void irc_command_join(struct irc_connection* irc_connection,
const char* channel)
{
irc_transmit_format(irc_connection, "JOIN :%s", channel);
}
void irc_command_part(struct irc_connection* irc_connection,
const char* channel)
{
irc_transmit_format(irc_connection, "PART :%s", channel);
}
void irc_command_privmsg(struct irc_connection* irc_connection,
const char* where,
const char* what)
{
// TODO: Ensure where is valid.
irc_transmit_format(irc_connection, "PRIVMSG %s :%s", where, what);
}
void irc_command_privmsgf(struct irc_connection* irc_connection,
const char* where,
const char* what_format,
...)
{
va_list ap;
va_start(ap, what_format);
char msg[512];
vsnprintf(msg, sizeof(msg), what_format, ap);
irc_command_privmsg(irc_connection, where, msg);
va_end(ap);
}
void irc_command_notice(struct irc_connection* irc_connection,
const char* where,
const char* what)
{
// TODO: Ensure where is valid.
irc_transmit_format(irc_connection, "NOTICE %s :%s", where, what);
}
void irc_command_noticef(struct irc_connection* irc_connection,
const char* where,
const char* what_format,
...)
{
va_list ap;
va_start(ap, what_format);
char msg[512];
vsnprintf(msg, sizeof(msg), what_format, ap);
irc_command_notice(irc_connection, where, msg);
va_end(ap);
}
void irc_command_kick(struct irc_connection* irc_connection,
const char* where,
const char* who,
const char* why)
{
// TODO: Ensure where and who are valid.
if ( why )
irc_transmit_format(irc_connection, "KICK %s %s :%s", where, who, why);
else
irc_transmit_format(irc_connection, "KICK %s %s", where, who);
}
void irc_command_quit(struct irc_connection* irc_connection,
const char* message)
{
if ( message )
irc_transmit_format(irc_connection, "QUIT :%s", message);
else
irc_transmit_string(irc_connection, "QUIT");
shutdown(irc_connection->fd, SHUT_WR);
}
void irc_command_quit_malfunction(struct irc_connection* irc_connection,
const char* message)
{
if ( message )
irc_transmit_format(irc_connection, "QUIT :%s", message);
else
irc_transmit_string(irc_connection, "QUIT");
shutdown(irc_connection->fd, SHUT_RDWR);
}
void irc_parse_message_parameter(char* message,
char* parameters[16],
size_t* num_parameters_ptr)
{
size_t num_parameters = 0;
while ( message[0] != '\0' )
{
if ( message[0] == ':' || num_parameters == (16-1) -1 )
{
message++;
parameters[num_parameters++] = message;
break;
}
parameters[num_parameters++] = message;
size_t usable = 0;
while ( message[usable] != '\0' && message[usable] != ' ' )
usable++;
char lc = message[usable];
message[usable] = '\0';
if ( lc != '\0' )
message += usable + 1;
else
message += usable;
}
*num_parameters_ptr = num_parameters;
}
void irc_parse_who(char* full, const char** who, const char** whomask)
{
size_t bangpos = strcspn(full, "!");
if ( full[bangpos] == '!' )
{
full[bangpos] = '\0';
*who = full;
*whomask = full + bangpos + 1;
}
else
{
*who = full;
*whomask = "";
}
}

102
irc/connection.h Normal file
View File

@ -0,0 +1,102 @@
/*
* Copyright (c) 2016 Jonas 'Sortie' Termansen.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
* connection.h
* IRC protocol.
*/
#ifndef CONNECTION_H
#define CONNECTION_H
#include <stdarg.h>
#include <stdbool.h>
#include <stddef.h>
#include <time.h>
struct irc_connection
{
int fd;
bool connectivity_error;
char incoming_buffer[512];
size_t incoming_amount;
};
__attribute__((format(printf, 1, 0)))
void irc_error_vlinef(const char* format, va_list ap);
__attribute__((format(printf, 1, 2)))
void irc_error_linef(const char* format, ...);
void irc_transmit(struct irc_connection* irc_connection,
const char* message,
size_t message_size);
void irc_transmit_message(struct irc_connection* irc_connection,
const char* message,
size_t message_size);
void irc_transmit_string(struct irc_connection* irc_connection,
const char* string);
__attribute__((format(printf, 2, 0)))
void irc_transmit_vformat(struct irc_connection* irc_connection,
const char* format,
va_list ap);
__attribute__((format(printf, 2, 3)))
void irc_transmit_format(struct irc_connection* irc_connection,
const char* format,
...);
void irc_receive_more_bytes(struct irc_connection* irc_connection);
bool irc_receive_message(struct irc_connection* irc_connection,
char message[512],
struct timespec* when);
void irc_command_pass(struct irc_connection* irc_connection,
const char* password);
void irc_command_nick(struct irc_connection* irc_connection,
const char* nick);
void irc_command_user(struct irc_connection* irc_connection,
const char* nick,
const char* local_hostname,
const char* server_hostname,
const char* real_name);
void irc_command_join(struct irc_connection* irc_connection,
const char* channel);
void irc_command_part(struct irc_connection* irc_connection,
const char* channel);
void irc_command_privmsg(struct irc_connection* irc_connection,
const char* where,
const char* what);
__attribute__((format(printf, 3, 4)))
void irc_command_privmsgf(struct irc_connection* irc_connection,
const char* where,
const char* what_format,
...);
void irc_command_notice(struct irc_connection* irc_connection,
const char* where,
const char* what);
__attribute__((format(printf, 3, 4)))
void irc_command_noticef(struct irc_connection* irc_connection,
const char* where,
const char* what_format,
...);
void irc_command_kick(struct irc_connection* irc_connection,
const char* where,
const char* who,
const char* why);
void irc_command_quit(struct irc_connection* irc_connection,
const char* message);
void irc_command_quit_malfunction(struct irc_connection* irc_connection,
const char* message);
void irc_parse_message_parameter(char* message,
char* parameters[16],
size_t* num_parameters_ptr);
void irc_parse_who(char* full, const char** who, const char** whomask);
#endif

237
irc/database.c Normal file
View File

@ -0,0 +1,237 @@
/*
* Copyright (c) 2016 Jonas 'Sortie' Termansen.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
* database.c
* Data structure for keeping track of channels and people.
*/
#include <sys/types.h>
#include <assert.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include "database.h"
#include "network.h"
#include "string.h"
struct channel* find_channel(const struct network* state, const char* channel_name)
{
assert(channel_name);
for ( struct channel* channel = state->channels; channel; channel = channel->next_channel )
if ( strchannelcmp(channel->name, channel_name) == 0 )
return channel;
return NULL;
}
struct channel* add_channel(struct network* state, const char* channel_name)
{
assert(channel_name);
assert(!find_channel(state, channel_name));
struct channel* channel = (struct channel*) calloc(sizeof(struct channel), 1);
if ( !channel )
return NULL;
channel->name = strdup(channel_name);
if ( !channel->name )
return free(channel), (struct channel*) NULL;
channel->people = NULL;
channel->prev_channel = NULL;
channel->next_channel = state->channels;
if ( state->channels )
state->channels->prev_channel = channel;
state->channels = channel;
return channel;
}
struct channel* get_channel(struct network* state, const char* channel_name)
{
assert(channel_name);
for ( struct channel* result = find_channel(state, channel_name); result; result = NULL )
return result;
return add_channel(state, channel_name);
}
void remove_channel(struct network* state, struct channel* channel)
{
while ( channel->people )
remove_person_from_channel(state, channel->people);
if ( channel->prev_channel )
channel->prev_channel->next_channel = channel->next_channel;
else
state->channels = channel->next_channel;
if ( channel->next_channel )
channel->next_channel->prev_channel = channel->prev_channel;
free(channel->name);
free(channel);
}
struct person* find_person(const struct network* state, const char* nick)
{
assert(nick);
for ( struct person* person = state->people; person; person = person->next_person )
if ( strnickcmp(person->nick, nick) == 0 )
return person;
return NULL;
}
struct person* add_person(struct network* state, const char* nick)
{
assert(nick);
assert(!find_person(state, nick));
struct person* person = (struct person*) calloc(sizeof(struct person), 1);
if ( !person )
return NULL;
person->nick = strdup(nick);
if ( !person->nick )
return free(person), (struct person*) NULL;
person->prev_person = NULL;
person->next_person = state->people;
if ( state->people )
state->people->prev_person = person;
state->people = person;
return person;
}
struct person* get_person(struct network* state, const char* nick)
{
assert(nick);
for ( struct person* result = find_person(state, nick); result; result = NULL )
return result;
return add_person(state, nick);
}
void remove_person(struct network* state, struct person* person)
{
while ( person->channels )
remove_person_from_channel(state, person->channels);
if ( person->prev_person )
person->prev_person->next_person = person->next_person;
else
state->people = person->next_person;
if ( person->next_person )
person->next_person->prev_person = person->prev_person;
free(person->nick);
free(person);
}
struct channel_person* find_person_in_channel(const struct network* state, const char* nick, const char* channel_name)
{
assert(nick);
assert(channel_name);
struct channel* channel = find_channel(state, channel_name);
if ( !channel )
return NULL;
for ( struct channel_person* channel_person = channel->people; channel_person; channel_person = channel_person->next_person_in_channel )
{
assert(channel_person->person);
assert(channel_person->person->nick);
if ( strnickcmp(channel_person->person->nick, nick) == 0 )
return channel_person;
}
return NULL;
}
struct channel_person* add_person_to_channel(struct network* state, struct person* person, struct channel* channel)
{
assert(person);
assert(channel);
assert(person->nick);
assert(channel->name);
assert(!find_person_in_channel(state, person->nick, channel->name));
struct channel_person* channel_person = (struct channel_person*)
calloc(sizeof(struct channel_person), 1);
if ( !channel_person )
return NULL;
channel_person->channel = channel;
channel_person->person = person;
channel_person->prev_channel_person = NULL;
channel_person->next_channel_person = state->channel_people;
if ( state->channel_people )
state->channel_people->prev_channel_person = channel_person;
state->channel_people = channel_person;
channel_person->prev_person_in_channel = NULL;
channel_person->next_person_in_channel = channel->people;
if ( channel->people )
channel->people->prev_person_in_channel = channel_person;
channel->people = channel_person;
channel_person->prev_channel_for_person = NULL;
channel_person->next_channel_for_person = person->channels;
if ( person->channels )
person->channels->prev_channel_for_person = channel_person;
person->channels = channel_person;
return channel_person;
}
struct channel_person* get_person_in_channel(struct network* state, struct person* person, struct channel* channel)
{
for ( struct channel_person* result =
find_person_in_channel(state, person->nick, channel->name); result; result = NULL )
return result;
return add_person_to_channel(state, person, channel);
}
void remove_person_from_channel(struct network* state, struct channel_person* channel_person)
{
if ( state->channel_people != channel_person )
channel_person->prev_channel_person->next_channel_person = channel_person->next_channel_person;
else
state->channel_people = channel_person->next_channel_person;
if ( channel_person->next_channel_person )
channel_person->next_channel_person->prev_channel_person = channel_person->prev_channel_person;
if ( channel_person->channel->people != channel_person )
channel_person->prev_person_in_channel->next_person_in_channel = channel_person->next_person_in_channel;
else
channel_person->channel->people = channel_person->next_person_in_channel;
if ( channel_person->next_person_in_channel )
channel_person->next_person_in_channel->prev_person_in_channel = channel_person->prev_person_in_channel;
if ( channel_person->person->channels != channel_person )
channel_person->prev_channel_for_person->next_channel_for_person = channel_person->next_channel_for_person;
else
channel_person->person->channels = channel_person->next_channel_for_person;
if ( channel_person->next_channel_for_person )
channel_person->next_channel_for_person->prev_channel_for_person = channel_person->prev_channel_for_person;
free(channel_person);
}

73
irc/database.h Normal file
View File

@ -0,0 +1,73 @@
/*
* Copyright (c) 2016 Jonas 'Sortie' Termansen.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
* database.h
* Data structure for keeping track of channels and people.
*/
#ifndef DATABASE_H
#define DATABASE_H
struct channel;
struct channel_person;
struct network;
struct person;
struct person
{
struct person* prev_person;
struct person* next_person;
char* nick;
struct channel_person* channels;
bool always_observable; // myself or having private messaged me
};
struct channel_person
{
struct channel_person* prev_channel_person;
struct channel_person* next_channel_person;
struct channel_person* prev_person_in_channel;
struct channel_person* next_person_in_channel;
struct channel_person* prev_channel_for_person;
struct channel_person* next_channel_for_person;
struct channel* channel;
struct person* person;
bool is_operator;
bool is_voiced;
};
struct channel
{
struct channel* prev_channel;
struct channel* next_channel;
char* name;
char* topic;
struct channel_person* people;
};
struct channel* find_channel(const struct network* state, const char* channel_name);
struct channel* add_channel(struct network* state, const char* channel_name);
struct channel* get_channel(struct network* state, const char* channel_name);
void remove_channel(struct network* state, struct channel* channel);
struct person* find_person(const struct network* state, const char* nick);
struct person* add_person(struct network* state, const char* nick);
struct person* get_person(struct network* state, const char* nick);
void remove_person(struct network* state, struct person* person);
struct channel_person* find_person_in_channel(const struct network* state, const char* nick, const char* channel_name);
struct channel_person* add_person_to_channel(struct network* state, struct person* person, struct channel* channel);
struct channel_person* get_person_in_channel(struct network* state, struct person* person, struct channel* channel);
void remove_person_from_channel(struct network* state, struct channel_person* channel_person);
#endif

1007
irc/irc.c Normal file

File diff suppressed because it is too large Load Diff

24
irc/network.h Normal file
View File

@ -0,0 +1,24 @@
#ifndef NETWORK_H
#define NETWORK_H
struct account;
struct channel;
struct channel_person;
struct person;
struct network
{
struct irc_connection* irc_connection;
struct account* accounts;
struct channel* channels;
struct person* people;
struct channel_person* channel_people;
struct scrollback* scrollbacks;
char* nick;
char* real_name;
char* password;
char* server_hostname;
const char* autojoin;
};
#endif

205
irc/scrollback.c Normal file
View File

@ -0,0 +1,205 @@
/*
* Copyright (c) 2016 Jonas 'Sortie' Termansen.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
* scrollback.c
* Ordered messages for display.
*/
#include <assert.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include "compat.h"
#include "network.h"
#include "scrollback.h"
#include "string.h"
void message_free(struct message* msg)
{
free(msg->who);
free(msg->what);
}
void scrollback_free(struct scrollback* sb)
{
if ( sb->network )
{
if ( sb->scrollback_prev )
sb->scrollback_prev->scrollback_next = sb->scrollback_next;
else
sb->network->scrollbacks = sb->scrollback_next;
if ( sb->scrollback_next )
sb->scrollback_next->scrollback_prev = sb->scrollback_prev;
sb->scrollback_prev = NULL;
sb->scrollback_next = NULL;
sb->network = NULL;
}
for ( size_t i = 0; i < sb->messages_count; i++ )
message_free(&sb->messages[i]);
free(sb->messages);
free(sb->name);
free(sb);
}
struct scrollback* find_scrollback_network(const struct network* network)
{
// TODO: The server hostname can be a valid nick, for instance if the
// hostname doesn't contain any dot characters.
for ( struct scrollback* sb = network->scrollbacks;
sb;
sb = sb->scrollback_next )
{
if ( sb->name[0] == '#' )
continue;
if ( !strnickcmp(network->server_hostname, sb->name) )
return sb;
}
return NULL;
}
struct scrollback* find_scrollback(const struct network* network,
const char* name)
{
assert(name);
for ( struct scrollback* sb = network->scrollbacks;
sb;
sb = sb->scrollback_next )
{
if ( name[0] == '#' && sb->name[0] == '#' )
{
if ( strchannelcmp(name + 1, sb->name + 1) == 0 )
return sb;
}
else if ( name[0] != '#' && sb->name[0] != '#' )
{
if ( strnickcmp(name + 1, sb->name + 1) == 0 )
return sb;
}
}
return NULL;
}
struct scrollback* add_scrollback(struct network* network, const char* name)
{
struct scrollback* sb =
(struct scrollback*) calloc(1, sizeof(struct scrollback));
if ( !sb )
return NULL;
if ( !(sb->name = strdup(name)) )
return scrollback_free(sb), (struct scrollback*) NULL;
sb->network = network;
sb->scrollback_next = sb->network->scrollbacks;
if ( sb->scrollback_next )
sb->scrollback_next->scrollback_prev = sb;
sb->network->scrollbacks = sb;
return sb;
}
struct scrollback* get_scrollback(struct network* network, const char* name)
{
struct scrollback* result = find_scrollback(network, name);
if ( result )
return result;
return add_scrollback(network, name);
}
bool scrollback_add_message(struct scrollback* sb,
enum activity activity,
const struct message* msg)
{
if ( sb->messages_count == sb->messages_allocated )
{
size_t new_allocated = 2 * sb->messages_allocated;
if ( new_allocated == 0 )
new_allocated = 64;
struct message* new_messages = (struct message*)
reallocarray(sb->messages, new_allocated, sizeof(struct message));
if ( !new_messages )
return false;
sb->messages = new_messages;
sb->messages_allocated = new_allocated;
}
sb->messages[sb->messages_count++] = *msg;
size_t who_width = strlen(msg->who); // TODO: Unicode?
if ( sb->who_width < who_width )
sb->who_width = who_width;
if ( sb->activity < activity )
sb->activity = activity;
return true;
}
static void message_timestamp(struct message* msg)
{
struct tm tm;
time_t now = time(NULL);
localtime_r(&now, &tm);
msg->sec = tm.tm_sec;
msg->min = tm.tm_min;
msg->hour = tm.tm_hour;
}
bool scrollback_print(struct scrollback* sb,
enum activity activity,
const char* who,
const char* what)
{
struct message msg;
memset(&msg, 0, sizeof(msg));
message_timestamp(&msg);
if ( (msg.who = strdup(who)) &&
(msg.what = strdup(what)) &&
scrollback_add_message(sb, activity, &msg) )
return true;
message_free(&msg);
return false;
}
bool scrollback_printf(struct scrollback* sb,
enum activity activity,
const char* who,
const char* whatf,
...)
{
struct message msg;
memset(&msg, 0, sizeof(msg));
message_timestamp(&msg);
va_list ap;
va_start(ap, whatf);
int len = vasprintf(&msg.what, whatf, ap);
va_end(ap);
if ( (msg.who = strdup(who)) &&
0 <= len &&
scrollback_add_message(sb, activity, &msg) )
return true;
message_free(&msg);
return false;
}
void scrollback_clear(struct scrollback* sb)
{
for ( size_t i = 0; i < sb->messages_count; i++ )
{
free(sb->messages[i].who);
free(sb->messages[i].what);
}
sb->messages_count = 0;
sb->messages_allocated = 0;
free(sb->messages);
sb->messages = NULL;
}

82
irc/scrollback.h Normal file
View File

@ -0,0 +1,82 @@
/*
* Copyright (c) 2016 Jonas 'Sortie' Termansen.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
* scrollback.c
* Ordered messages for display.
*/
#ifndef SCROLLBACK_H
#define SCROLLBACK_H
#include <stdbool.h>
#include <stddef.h>
struct network;
enum activity
{
ACTIVITY_NONE,
ACTIVITY_NONTALK,
ACTIVITY_TALK,
ACTIVITY_HIGHLIGHT,
};
struct message
{
int hour;
int min;
int sec;
char* who;
char* what;
};
struct scrollback
{
struct network* network;
struct scrollback* scrollback_prev;
struct scrollback* scrollback_next;
char* name;
struct message* messages;
size_t messages_count;
size_t messages_allocated;
size_t who_width;
enum activity activity;
};
void message_free(struct message* msg);
void scrollback_free(struct scrollback* sb);
struct scrollback* find_scrollback_network(const struct network* network);
struct scrollback* find_scrollback(const struct network* network,
const char* name);
struct scrollback* add_scrollback(struct network* network,
const char* name);
struct scrollback* get_scrollback(struct network* network,
const char* name);
bool scrollback_add_message(struct scrollback* sb,
enum activity activity,
const struct message* msg);
bool scrollback_print(struct scrollback* sb,
enum activity activity,
const char* who,
const char* what);
__attribute__((format(printf, 4, 5)))
bool scrollback_printf(struct scrollback* sb,
enum activity activity,
const char* who,
const char* whatf,
...);
void scrollback_clear(struct scrollback* sb);
#endif

34
irc/string.c Normal file
View File

@ -0,0 +1,34 @@
/*
* Copyright (c) 2016 Jonas 'Sortie' Termansen.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
* string.c
* String utility functions and compatibility.
*/
#include <string.h>
#include "string.h"
// TODO: Implement this properly in accordance with IRC RFC rules.
int strchannelcmp(const char* a, const char* b)
{
return strcasecmp(a, b);
}
// TODO: Implement this properly in accordance with IRC RFC rules.
int strnickcmp(const char* a, const char* b)
{
return strcasecmp(a, b);
}

28
irc/string.h Normal file
View File

@ -0,0 +1,28 @@
/*
* Copyright (c) 2016 Jonas 'Sortie' Termansen.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
* string.h
* String utility functions and compatibility.
*/
#ifndef STRING_H
#define STRING_H
#include <stddef.h>
int strchannelcmp(const char* a, const char* b);
int strnickcmp(const char* a, const char* b);
#endif

545
irc/ui.c Normal file
View File

@ -0,0 +1,545 @@
/*
* Copyright (c) 2016 Jonas 'Sortie' Termansen.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
* ui.c
* User Interface.
*/
#include <sys/ioctl.h>
#include <err.h>
#include <signal.h>
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <termios.h>
#include <wchar.h>
#include "connection.h"
#include "network.h"
#include "scrollback.h"
#include "ui.h"
struct cell
{
wchar_t c;
int fgcolor;
int bgcolor;
};
static struct termios saved_termios;
void tty_show(struct cell* cells, size_t cols, size_t rows)
{
printf("\e[H");
int fgcolor = -1;
int bgcolor = -1;
mbstate_t ps;
memset(&ps, 0, sizeof(ps));
for ( size_t r = 0; r < rows; r++ )
{
for ( size_t c = 0; c < cols; c++ )
{
struct cell* cell = &cells[r * cols + c];
if ( fgcolor != cell->fgcolor )
{
printf("\e[%im", cell->fgcolor);
fgcolor = cell->fgcolor;
}
if ( bgcolor != cell->bgcolor )
{
printf("\e[%im", cell->bgcolor);
bgcolor = cell->bgcolor;
}
char mb[MB_CUR_MAX];
size_t amount = wcrtomb(mb, cell->c, &ps);
if ( amount == (size_t) -1 )
continue;
fwrite(mb, 1, amount, stdout);
}
if ( r + 1 != rows )
printf("\n");
}
fflush(stdout);
}
void on_sigquit(int sig)
{
// TODO: This is not async signal safe.
ui_destroy(NULL);
// TODO: Use sigaction so the handler only runs once.
//raise(sig);
(void) sig;
raise(SIGKILL);
}
void ui_initialize(struct ui* ui, struct network* network)
{
memset(ui, 0, sizeof(*ui));
ui->network = network;
ui->current = find_scrollback_network(network);
struct winsize ws;
if ( ioctl(1, TIOCGWINSZ, &ws) < 0 )
err(1, "stdout: ioctl: TIOCGWINSZ");
if ( tcgetattr(0, &saved_termios) < 0 )
err(1, "stdin: tcgetattr");
struct termios tcattr;
memcpy(&tcattr, &saved_termios, sizeof(struct termios));
tcattr.c_lflag &= ~(ECHO | ICANON | IEXTEN);
tcattr.c_iflag |= ICRNL | ISIG;
tcattr.c_cc[VMIN] = 1;
tcattr.c_cc[VTIME] = 0;
signal(SIGINT, SIG_IGN);
signal(SIGQUIT, on_sigquit);
tcsetattr(0, TCSADRAIN, &tcattr);
if ( getenv("TERM") && strcmp(getenv("TERM"), "sortix") != 0 )
{
printf("\e[?1049h");
fflush(stdout);
}
}
void ui_destroy(struct ui* ui)
{
// TODO.
(void) ui;
// TODO: This should be done in an atexit handler as well.
if ( getenv("TERM") && strcmp(getenv("TERM"), "sortix") != 0 )
{
printf("\e[?1049l");
fflush(stdout);
}
tcsetattr(0, TCSADRAIN, &saved_termios);
}
void increment_offset(size_t* o_ptr, size_t* line_ptr, size_t cols)
{
if ( (*o_ptr)++ == cols )
{
*o_ptr = 0;
(*line_ptr)++;
}
}
void ui_render(struct ui* ui)
{
mbstate_t ps;
struct winsize ws;
if ( ioctl(1, TIOCGWINSZ, &ws) < 0 )
err(1, "stdout: ioctl: TIOCGWINSZ");
size_t cols = ws.ws_col;
size_t rows = ws.ws_row;
struct cell* cells = calloc(sizeof(struct cell) * cols, rows);
if ( !cells )
err(1, "calloc");
for ( size_t r = 0; r < rows; r++ )
{
for ( size_t c = 0; c < cols; c++ )
{
struct cell* cell = &cells[r * cols + c];
cell->c = L' ';
cell->fgcolor = 0;
cell->bgcolor = 0;
}
}
// TODO: What if the terminal isn't large enough?
struct scrollback* sb = ui->current;
sb->activity = ACTIVITY_NONE;
size_t title_from = 0;
size_t when_offset = 0;
size_t when_width = 2 + 1 + 2 + 1 + 2;
size_t who_offset = when_offset + when_width + 1;
size_t who_width = sb->who_width;
size_t div_offset = who_offset + who_width + 1;
size_t what_offset = div_offset + 2;
size_t what_width = cols - what_offset;
size_t input_width = cols;
size_t input_num_lines = 1;
for ( size_t i = 0, o = 0; i < ui->input_used; i++ )
{
wchar_t wc = ui->input[i];
int w = wcwidth(wc);
if ( w < 0 || w == 0 )
continue;
if ( input_width <= o )
{
input_num_lines++;
o = 0;
}
o += w;
}
char* title;
if ( asprintf(&title, "%s @ %s / %s", ui->network->nick,
ui->network->server_hostname, ui->current->name) < 0 )
err(1, "asprintf");
size_t title_len = strlen(title);
size_t title_how_many = cols < title_len ? cols : title_len;
size_t title_offset = (cols - title_how_many) / 2;
for ( size_t i = 0; i < title_how_many; i++ )
{
char c = title[i];
size_t cell_r = title_from;
size_t cell_c = title_offset + i;
struct cell* cell = &cells[cell_r * cols + cell_c];
cell->c = btowc((unsigned char) c);
}
free(title);
size_t scrollbacks_from = title_from + 1;
size_t scrollbacks_lines = 1;
size_t scrollbacks_o = 0;
for ( struct scrollback* iter = ui->network->scrollbacks;
iter;
iter = iter->scrollback_next )
{
if ( iter->scrollback_prev )
{
increment_offset(&scrollbacks_o, &scrollbacks_lines, cols);
increment_offset(&scrollbacks_o, &scrollbacks_lines, cols);
}
for ( size_t i = 0; iter->name[i]; i++ )
{
char c = iter->name[i];
size_t cell_r = scrollbacks_from + (scrollbacks_lines - 1);
size_t cell_c = scrollbacks_o;
struct cell* cell = &cells[cell_r * cols + cell_c];
int fgcolor = 0;
if ( iter == sb )
fgcolor = 1; // TODO: Boldness should be its own property.
else if ( iter->activity == ACTIVITY_NONTALK )
fgcolor = 31;
else if ( iter->activity == ACTIVITY_TALK )
fgcolor = 91;
else if ( iter->activity == ACTIVITY_HIGHLIGHT )
fgcolor = 94;
cell->c = btowc((unsigned char) c);
cell->fgcolor = fgcolor;
increment_offset(&scrollbacks_o, &scrollbacks_lines, cols);
}
}
size_t horhigh_from = scrollbacks_from + scrollbacks_lines;
for ( size_t c = 0; c < cols; c++ )
{
size_t cell_r = horhigh_from;
size_t cell_c = c;
struct cell* cell = &cells[cell_r * cols + cell_c];
cell->c = c == div_offset ? L'' : L'';
}
size_t sb_from = horhigh_from + 1;
// TODO: What if the input is too big?
size_t input_bottom = rows - input_num_lines;
size_t input_offset = 0;
for ( size_t i = 0, o = 0, line = 0; i < ui->input_used; i++ )
{
wchar_t wc = ui->input[i];
int w = wcwidth(wc);
if ( w < 0 || w == 0 )
continue;
if ( input_width <= o )
{
line++;
o = 0;
}
// TODO: If 1 < w.
size_t cell_r = input_bottom + line;
size_t cell_c = input_offset + o;
struct cell* cell = &cells[cell_r * cols + cell_c];
cell->c = wc;
o += w;
}
size_t horlow_from = input_bottom - 1;
for ( size_t c = 0; c < cols; c++ )
{
size_t cell_r = horlow_from;
size_t cell_c = c;
struct cell* cell = &cells[cell_r * cols + cell_c];
cell->c = c == div_offset ? L'' : L'';
}
size_t sb_to = horlow_from;
for ( size_t r = sb_to - 1; r != sb_from - 1; r-- )
{
size_t cell_r = r;
size_t cell_c = div_offset;
struct cell* cell = &cells[cell_r * cols + cell_c];
cell->c = L'';
}
for ( size_t r = sb_to - 1, m = sb->messages_count - 1;
r != (sb_from - 1) && m != SIZE_MAX;
r--, m-- )
{
struct message* msg = &sb->messages[m];
size_t num_lines = 1;
size_t max_lines = sb_from - r + 1;
memset(&ps, 0, sizeof(ps));
for ( size_t i = 0, o = 0; msg->what[i]; )
{
wchar_t wc;
size_t amount = mbrtowc(&wc, msg->what + i, SIZE_MAX, &ps);
if ( amount == (size_t) -1 || amount == (size_t) -2 )
{
// TODO.
memset(&ps, 0, sizeof(ps));
continue;
}
i += amount;
int w = wcwidth(wc);
if ( w < 0 || w == 0 )
continue;
if ( what_width <= o )
{
num_lines++;
o = 0;
}
o += w;
}
size_t how_many_lines = max_lines < num_lines ? max_lines : num_lines;
size_t first_line = num_lines - how_many_lines;
if ( 1 < how_many_lines )
r -= how_many_lines - 1;
if ( first_line == 0 )
{
char when[2 + 1 + 2 + 1 + 2 + 1 + 1];
snprintf(when, sizeof(when), "%02i:%02i:%02i ",
msg->hour, msg->min, msg->sec);
for ( size_t i = 0; when[i]; i++ )
{
size_t cell_r = r;
size_t cell_c = when_offset + i;
struct cell* cell = &cells[cell_r * cols + cell_c];
cell->c = btowc((unsigned char) when[i]);
}
memset(&ps, 0, sizeof(ps));
size_t msg_who_width = strlen(msg->who);
size_t msg_who_how_many = who_width < msg_who_width ? who_width : msg_who_width;
size_t msg_who_first = msg_who_width - msg_who_how_many;
size_t msg_who_offset = who_width - msg_who_how_many;
for ( size_t i = 0; i < msg_who_how_many; i++ )
{
char c = msg->who[msg_who_first + i];
size_t cell_r = r;
size_t cell_c = who_offset + msg_who_offset + i;
struct cell* cell = &cells[cell_r * cols + cell_c];
cell->c = btowc((unsigned char) c);
}
}
for ( size_t i = 0, o = 0, line = 0; msg->what[i]; )
{
wchar_t wc;
size_t amount = mbrtowc(&wc, msg->what + i, SIZE_MAX, &ps);
if ( amount == (size_t) -1 || amount == (size_t) -2 )
{
// TODO.
memset(&ps, 0, sizeof(ps));
continue;
}
i += amount;
int w = wcwidth(wc);
if ( w < 0 || w == 0 )
continue;
if ( what_width <= o )
{
line++;
o = 0;
}
// TODO: If 1 < w.
if ( first_line <= line )
{
size_t cell_r = r + line - first_line;
size_t cell_c = what_offset + o;
struct cell* cell = &cells[cell_r * cols + cell_c];
cell->c = wc;
}
o += w;
}
}
(void) ui;
tty_show(cells, cols, rows);
free(cells);
}
static bool is_command(const char* input,
const char* cmd,
const char** param)
{
size_t cmdlen = strlen(cmd);
if ( strncmp(input, cmd, cmdlen) != 0 )
return false;
if ( !input[cmdlen] )
{
if ( param )
*param = NULL;
return true;
}
if ( input[cmdlen] != ' ' )
return false;
if ( !param )
return false;
*param = input + cmdlen + 1;
return true;
}
static bool is_command_param(const char* input,
const char* cmd,
const char** param)
{
if ( !is_command(input, cmd, param) )
return false;
if ( !*param )
return false; // TODO: Help message in scrollback.
return true;
}
void ui_input_char(struct ui* ui, char c)
{
wchar_t wc;
size_t amount = mbrtowc(&wc, &c, 1, &ui->input_ps);
if ( amount == (size_t) -2 )
return;
if ( amount == (size_t) -1 )
{
// TODO.
memset(&ui->input_ps, 0, sizeof(ui->input_ps));
return;
}
if ( wc == L'\b' || wc == 127 )
{
if ( 0 < ui->input_used )
ui->input_used--;
}
else if ( wc == L'\f' /* ^L */ )
{
scrollback_clear(ui->current);
// TODO: Schedule full redraw?
}
else if ( wc == L'\n' )
{
char input[4 * sizeof(ui->input) / sizeof(ui->input[0])];
mbstate_t ps;
memset(&ps, 0, sizeof(ps));
const wchar_t* wcs = ui->input;
size_t amount = wcsnrtombs(input, &wcs, ui->input_used, sizeof(input), &ps);
ui->input_used = 0;
if ( amount == (size_t) -1 )
return;
input[amount < sizeof(input) ? amount : amount - 1] = '\0';
struct irc_connection* conn = ui->network->irc_connection;
const char* who = ui->network->nick;
const char* where = ui->current->name;
const char* param;
if ( input[0] == '/' && input[1] != '/' )
{
if ( !input[1] )
return;
if ( is_command_param(input, "/w", &param) ||
is_command_param(input, "/window", &param) )
{
struct scrollback* sb = find_scrollback(ui->network, param);
if ( sb )
ui->current = sb;
}
else if ( is_command_param(input, "/query", &param) )
{
if ( param[0] == '#' )
return; // TODO: Help in scrollback.
struct scrollback* sb = get_scrollback(ui->network, param);
if ( sb )
ui->current = sb;
}
else if ( is_command_param(input, "/join", &param) )
{
irc_command_join(conn, param);
struct scrollback* sb = get_scrollback(ui->network, param);
if ( sb )
ui->current = sb;
}
// TODO: Make it default to the current channel if any.
else if ( is_command_param(input, "/part", &param) )
{
irc_command_part(conn, param);
}
else if ( is_command(input, "/quit", &param) )
{
irc_command_quit(conn, param ? param : "Quiting");
}
else if ( is_command_param(input, "/nick", &param) )
{
irc_command_nick(conn, param);
}
else if ( is_command_param(input, "/raw", &param) )
{
irc_transmit_string(conn, param);
}
else if ( is_command_param(input, "/me", &param) )
{
scrollback_printf(ui->current, ACTIVITY_NONE, "*", "%s %s",
who, param);
irc_command_privmsgf(conn, where, "\x01""ACTION %s""\x01",
param);
}
else if ( is_command(input, "/clear", &param) )
{
scrollback_clear(ui->current);
}
// TODO: /ban
// TODO: /ctcp
// TODO: /deop
// TODO: /devoice
// TODO: /kick
// TODO: /mode
// TODO: /op
// TODO: /quiet
// TODO: /topic
// TODO: /voice
else
{
scrollback_printf(ui->current, ACTIVITY_NONE, "*",
"%s :Unknown command", input + 1);
}
}
else
{
const char* what = input;
if ( what[0] == '/' )
what++;
scrollback_print(ui->current, ACTIVITY_NONE, who, what);
irc_command_privmsg(conn, where, what);
}
}
else
{
if ( ui->input_used < sizeof(ui->input) / sizeof(ui->input[0]) )
ui->input[ui->input_used++] = wc;
}
}

42
irc/ui.h Normal file
View File

@ -0,0 +1,42 @@
/*
* Copyright (c) 2016 Jonas 'Sortie' Termansen.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
* ui.h
* User Interface.
*/
#ifndef UI_H
#define UI_H
#include <wchar.h>
struct network;
struct scrollback;
struct ui
{
struct network* network;
struct scrollback* current;
wchar_t input[512];
size_t input_used;
mbstate_t input_ps;
};
void ui_initialize(struct ui* ui, struct network* network);
void ui_render(struct ui* ui);
void ui_input_char(struct ui* ui, char c);
void ui_destroy(struct ui* ui);
#endif