parent
c024147504
commit
a64f1a4ea2
2
Makefile
2
Makefile
|
@ -3,7 +3,7 @@ CFLAGS += -Wall -Wextra -Wpedantic
|
||||||
CFLAGS += -I/usr/local/include -I/usr/local/opt/libressl/include
|
CFLAGS += -I/usr/local/include -I/usr/local/opt/libressl/include
|
||||||
LDFLAGS += -L/usr/local/lib -L/usr/local/opt/libressl/lib
|
LDFLAGS += -L/usr/local/lib -L/usr/local/opt/libressl/lib
|
||||||
LDLIBS = -lcursesw -ltls
|
LDLIBS = -lcursesw -ltls
|
||||||
OBJS = chat.o edit.o handle.o input.o irc.o pls.o tab.o ui.o
|
OBJS = chat.o edit.o handle.o input.o irc.o pls.o tab.o ui.o url.o
|
||||||
|
|
||||||
all: tags chat
|
all: tags chat
|
||||||
|
|
||||||
|
|
1
README
1
README
|
@ -10,4 +10,5 @@ This software requires LibreSSL and targets FreeBSD and Darwin.
|
||||||
input.c Input command handling
|
input.c Input command handling
|
||||||
handle.c Incoming command handling
|
handle.c Incoming command handling
|
||||||
tab.c Tab-complete
|
tab.c Tab-complete
|
||||||
|
url.c URL detection
|
||||||
pls.c Functions which should not have to be written
|
pls.c Functions which should not have to be written
|
||||||
|
|
2
chat.c
2
chat.c
|
@ -31,7 +31,7 @@
|
||||||
static void sigint(int sig) {
|
static void sigint(int sig) {
|
||||||
(void)sig;
|
(void)sig;
|
||||||
input("/quit");
|
input("/quit");
|
||||||
uiHide();
|
uiExit();
|
||||||
exit(EX_OK);
|
exit(EX_OK);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
5
chat.h
5
chat.h
|
@ -50,6 +50,7 @@ void ircFmt(const char *format, ...);
|
||||||
|
|
||||||
void uiInit(void);
|
void uiInit(void);
|
||||||
void uiHide(void);
|
void uiHide(void);
|
||||||
|
void uiExit(void);
|
||||||
void uiDraw(void);
|
void uiDraw(void);
|
||||||
void uiBeep(void);
|
void uiBeep(void);
|
||||||
void uiRead(void);
|
void uiRead(void);
|
||||||
|
@ -77,6 +78,10 @@ void handle(char *line);
|
||||||
void inputTab(void);
|
void inputTab(void);
|
||||||
void input(char *line);
|
void input(char *line);
|
||||||
|
|
||||||
|
void urlScan(const char *str);
|
||||||
|
void urlList(void);
|
||||||
|
void urlOpen(size_t i);
|
||||||
|
|
||||||
void tabTouch(const char *word);
|
void tabTouch(const char *word);
|
||||||
void tabRemove(const char *word);
|
void tabRemove(const char *word);
|
||||||
void tabReplace(const char *prev, const char *next);
|
void tabReplace(const char *prev, const char *next);
|
||||||
|
|
8
handle.c
8
handle.c
|
@ -141,11 +141,12 @@ static void handle332(char *prefix, char *params) {
|
||||||
shift(¶ms);
|
shift(¶ms);
|
||||||
char *chan = shift(¶ms);
|
char *chan = shift(¶ms);
|
||||||
char *topic = shift(¶ms);
|
char *topic = shift(¶ms);
|
||||||
|
urlScan(topic);
|
||||||
|
uiTopicStr(topic);
|
||||||
uiFmt(
|
uiFmt(
|
||||||
"The sign in \3%d%s\3 reads, \"%s\"",
|
"The sign in \3%d%s\3 reads, \"%s\"",
|
||||||
color(chan), chan, topic
|
color(chan), chan, topic
|
||||||
);
|
);
|
||||||
uiTopicStr(topic);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void handleTopic(char *prefix, char *params) {
|
static void handleTopic(char *prefix, char *params) {
|
||||||
|
@ -153,11 +154,12 @@ static void handleTopic(char *prefix, char *params) {
|
||||||
char *user = prift(&prefix);
|
char *user = prift(&prefix);
|
||||||
char *chan = shift(¶ms);
|
char *chan = shift(¶ms);
|
||||||
char *topic = shift(¶ms);
|
char *topic = shift(¶ms);
|
||||||
|
urlScan(topic);
|
||||||
|
uiTopicStr(topic);
|
||||||
uiFmt(
|
uiFmt(
|
||||||
"\3%d%s\3 places a new sign in \3%d%s\3, \"%s\"",
|
"\3%d%s\3 places a new sign in \3%d%s\3, \"%s\"",
|
||||||
color(user), nick, color(chan), chan, topic
|
color(user), nick, color(chan), chan, topic
|
||||||
);
|
);
|
||||||
uiTopicStr(topic);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void handle366(char *prefix, char *params) {
|
static void handle366(char *prefix, char *params) {
|
||||||
|
@ -222,6 +224,7 @@ static void handlePrivmsg(char *prefix, char *params) {
|
||||||
shift(¶ms);
|
shift(¶ms);
|
||||||
char *mesg = shift(¶ms);
|
char *mesg = shift(¶ms);
|
||||||
tabTouch(nick);
|
tabTouch(nick);
|
||||||
|
urlScan(mesg);
|
||||||
bool self = !strcmp(user, chat.user);
|
bool self = !strcmp(user, chat.user);
|
||||||
bool ping = !strncasecmp(mesg, chat.nick, strlen(chat.nick));
|
bool ping = !strncasecmp(mesg, chat.nick, strlen(chat.nick));
|
||||||
if (ping) uiBeep();
|
if (ping) uiBeep();
|
||||||
|
@ -244,6 +247,7 @@ static void handleNotice(char *prefix, char *params) {
|
||||||
char *mesg = shift(¶ms);
|
char *mesg = shift(¶ms);
|
||||||
if (strcmp(chat.chan, chan)) return;
|
if (strcmp(chat.chan, chan)) return;
|
||||||
tabTouch(nick);
|
tabTouch(nick);
|
||||||
|
urlScan(mesg);
|
||||||
uiFmt("-\3%d%s\3- %s", color(user), nick, mesg);
|
uiFmt("-\3%d%s\3- %s", color(user), nick, mesg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
17
input.c
17
input.c
|
@ -73,6 +73,21 @@ static void inputQuit(char *params) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void inputUrl(char *params) {
|
||||||
|
(void)params;
|
||||||
|
urlList();
|
||||||
|
}
|
||||||
|
static void inputOpen(char *params) {
|
||||||
|
if (!params) { urlOpen(1); return; }
|
||||||
|
size_t from = strtoul(strsep(¶ms, "-,"), NULL, 0);
|
||||||
|
if (!params) { urlOpen(from); return; }
|
||||||
|
size_t to = strtoul(strsep(¶ms, "-,"), NULL, 0);
|
||||||
|
if (to < from) to = from;
|
||||||
|
for (size_t i = from; i <= to; ++i) {
|
||||||
|
urlOpen(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static const struct {
|
static const struct {
|
||||||
const char *command;
|
const char *command;
|
||||||
Handler handler;
|
Handler handler;
|
||||||
|
@ -80,8 +95,10 @@ static const struct {
|
||||||
{ "/me", inputMe },
|
{ "/me", inputMe },
|
||||||
{ "/names", inputWho },
|
{ "/names", inputWho },
|
||||||
{ "/nick", inputNick },
|
{ "/nick", inputNick },
|
||||||
|
{ "/open", inputOpen },
|
||||||
{ "/quit", inputQuit },
|
{ "/quit", inputQuit },
|
||||||
{ "/topic", inputTopic },
|
{ "/topic", inputTopic },
|
||||||
|
{ "/url", inputUrl },
|
||||||
{ "/who", inputWho },
|
{ "/who", inputWho },
|
||||||
};
|
};
|
||||||
static const size_t COMMANDS_LEN = sizeof(COMMANDS) / sizeof(COMMANDS[0]);
|
static const size_t COMMANDS_LEN = sizeof(COMMANDS) / sizeof(COMMANDS[0]);
|
||||||
|
|
12
irc.c
12
irc.c
|
@ -15,16 +15,17 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <err.h>
|
#include <err.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <netdb.h>
|
||||||
|
#include <netinet/in.h>
|
||||||
#include <stdarg.h>
|
#include <stdarg.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
#include <sysexits.h>
|
#include <sysexits.h>
|
||||||
#include <tls.h>
|
#include <tls.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <netdb.h>
|
|
||||||
#include <netinet/in.h>
|
|
||||||
#include <sys/socket.h>
|
|
||||||
|
|
||||||
#include "chat.h"
|
#include "chat.h"
|
||||||
|
|
||||||
|
@ -68,6 +69,9 @@ int ircConnect(const char *host, const char *port, const char *webPass) {
|
||||||
int sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
|
int sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
|
||||||
if (sock < 0) err(EX_OSERR, "socket");
|
if (sock < 0) err(EX_OSERR, "socket");
|
||||||
|
|
||||||
|
error = fcntl(sock, F_SETFD, FD_CLOEXEC);
|
||||||
|
if (error) err(EX_IOERR, "fcntl");
|
||||||
|
|
||||||
error = connect(sock, ai->ai_addr, ai->ai_addrlen);
|
error = connect(sock, ai->ai_addr, ai->ai_addrlen);
|
||||||
if (error) err(EX_UNAVAILABLE, "connect");
|
if (error) err(EX_UNAVAILABLE, "connect");
|
||||||
freeaddrinfo(ai);
|
freeaddrinfo(ai);
|
||||||
|
@ -111,7 +115,7 @@ void ircRead(void) {
|
||||||
ssize_t read = tls_read(client, &buf[len], sizeof(buf) - len);
|
ssize_t read = tls_read(client, &buf[len], sizeof(buf) - len);
|
||||||
if (read < 0) errx(EX_IOERR, "tls_read: %s", tls_error(client));
|
if (read < 0) errx(EX_IOERR, "tls_read: %s", tls_error(client));
|
||||||
if (!read) {
|
if (!read) {
|
||||||
uiHide();
|
uiExit();
|
||||||
exit(EX_OK);
|
exit(EX_OK);
|
||||||
}
|
}
|
||||||
len += read;
|
len += read;
|
||||||
|
|
13
ui.c
13
ui.c
|
@ -99,8 +99,9 @@ static struct {
|
||||||
WINDOW *topic;
|
WINDOW *topic;
|
||||||
WINDOW *log;
|
WINDOW *log;
|
||||||
WINDOW *input;
|
WINDOW *input;
|
||||||
int scroll;
|
bool hide;
|
||||||
bool mark;
|
bool mark;
|
||||||
|
int scroll;
|
||||||
} ui;
|
} ui;
|
||||||
|
|
||||||
void uiInit(void) {
|
void uiInit(void) {
|
||||||
|
@ -135,8 +136,13 @@ static void uiResize(void) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void uiHide(void) {
|
void uiHide(void) {
|
||||||
focusDisable();
|
ui.hide = true;
|
||||||
endwin();
|
endwin();
|
||||||
|
}
|
||||||
|
|
||||||
|
void uiExit(void) {
|
||||||
|
uiHide();
|
||||||
|
focusDisable();
|
||||||
printf(
|
printf(
|
||||||
"This program is AGPLv3 free software!\n"
|
"This program is AGPLv3 free software!\n"
|
||||||
"The source is available at <" SOURCE_URL ">.\n"
|
"The source is available at <" SOURCE_URL ">.\n"
|
||||||
|
@ -144,6 +150,7 @@ void uiHide(void) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void uiDraw(void) {
|
void uiDraw(void) {
|
||||||
|
if (ui.hide) return;
|
||||||
pnoutrefresh(
|
pnoutrefresh(
|
||||||
ui.topic,
|
ui.topic,
|
||||||
0, 0,
|
0, 0,
|
||||||
|
@ -359,6 +366,8 @@ static bool keyCode(wint_t ch) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void uiRead(void) {
|
void uiRead(void) {
|
||||||
|
ui.hide = false;
|
||||||
|
|
||||||
bool update = false;
|
bool update = false;
|
||||||
int ret;
|
int ret;
|
||||||
wint_t ch;
|
wint_t ch;
|
||||||
|
|
|
@ -0,0 +1,95 @@
|
||||||
|
/* Copyright (C) 2018 Curtis McEnroe <june@causal.agency>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
#include <err.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <sysexits.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include "chat.h"
|
||||||
|
|
||||||
|
static const char *SCHEMES[] = {
|
||||||
|
"https:",
|
||||||
|
"http:",
|
||||||
|
"ftp:",
|
||||||
|
};
|
||||||
|
static const size_t SCHEMES_LEN = sizeof(SCHEMES) / sizeof(SCHEMES[0]);
|
||||||
|
|
||||||
|
enum { RING_LEN = 16 };
|
||||||
|
static char *ring[RING_LEN];
|
||||||
|
static size_t last;
|
||||||
|
static_assert(!(RING_LEN & (RING_LEN - 1)), "power of two RING_LEN");
|
||||||
|
|
||||||
|
static void push(const char *url, size_t len) {
|
||||||
|
free(ring[last]);
|
||||||
|
ring[last++] = strndup(url, len);
|
||||||
|
last &= RING_LEN - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void urlScan(const char *str) {
|
||||||
|
while (str[0]) {
|
||||||
|
size_t len = 1;
|
||||||
|
for (size_t i = 0; i < SCHEMES_LEN; ++i) {
|
||||||
|
if (strncmp(str, SCHEMES[i], strlen(SCHEMES[i]))) continue;
|
||||||
|
len = strcspn(str, " >\"");
|
||||||
|
push(str, len);
|
||||||
|
}
|
||||||
|
str = &str[len];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void urlList(void) {
|
||||||
|
uiHide();
|
||||||
|
for (size_t i = 0; i < RING_LEN; ++i) {
|
||||||
|
char *url = ring[(i + last) & (RING_LEN - 1)];
|
||||||
|
if (url) printf("%s\n", url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void urlOpen(size_t i) {
|
||||||
|
char *url = ring[(last - i) & (RING_LEN - 1)];
|
||||||
|
if (!url) return;
|
||||||
|
|
||||||
|
int fd[2];
|
||||||
|
int error = pipe(fd);
|
||||||
|
if (error) err(EX_OSERR, "pipe");
|
||||||
|
|
||||||
|
pid_t pid = fork();
|
||||||
|
if (pid < 0) err(EX_OSERR, "fork");
|
||||||
|
|
||||||
|
if (!pid) {
|
||||||
|
close(STDIN_FILENO);
|
||||||
|
dup2(fd[1], STDOUT_FILENO);
|
||||||
|
dup2(fd[1], STDERR_FILENO);
|
||||||
|
execlp("open", "open", url, NULL);
|
||||||
|
perror("open");
|
||||||
|
exit(EX_CONFIG);
|
||||||
|
}
|
||||||
|
close(fd[1]);
|
||||||
|
|
||||||
|
// FIXME: This should technically go on the main event loop.
|
||||||
|
char buf[256];
|
||||||
|
ssize_t len = read(fd[0], buf, sizeof(buf) - 1);
|
||||||
|
if (len < 0) err(EX_IOERR, "read");
|
||||||
|
if (len) {
|
||||||
|
buf[len] = '\0';
|
||||||
|
len = strcspn(buf, "\n");
|
||||||
|
uiFmt("%.*s", (int)len, buf);
|
||||||
|
}
|
||||||
|
close(fd[0]);
|
||||||
|
}
|
Loading…
Reference in New Issue