Browse Source

Tabify source

And other style changes.
v1
Causal Agent 1 year ago
parent
commit
eb420c49bb
Signed by: Curtis McEnroe <june@causal.agency> GPG Key ID: CEA2F97ADCFCD77C
10 changed files with 916 additions and 919 deletions
  1. +0
    -2
      Makefile
  2. +10
    -10
      README
  3. +331
    -332
      client.c
  4. +122
    -122
      help.c
  5. +2
    -2
      index.html
  6. +73
    -73
      merge.c
  7. +18
    -18
      meta.c
  8. +282
    -282
      server.c
  9. +9
    -9
      sshd_config
  10. +69
    -69
      torus.h

+ 0
- 2
Makefile View File

@@ -45,5 +45,3 @@ tags: *.h *.c

clean:
rm -f tags $(BINS) termcap termcap.db chroot.tar

.PHONY: all clean

+ 10
- 10
README View File

@@ -2,14 +2,14 @@ Welcome to ascii.town!

This software targets FreeBSD and Darwin.

torus.h Shared structures and message protocol.
server.c Maps torus.dat and listens on torus.sock.
client.c Curses client.
help.c Automated client which redraws a helpful banner.
meta.c Exports metadata from data file to CSV.
merge.c Interactively merges two data files.
snapshot.sh Snapshots data file, appropriate for cron.
torus.h Shared structures and message protocol.
server.c Maps torus.dat and listens on torus.sock.
client.c Curses client.
help.c Automated client which redraws a helpful banner.
meta.c Exports metadata from data file to CSV.
merge.c Interactively merges two data files.
snapshot.sh Snapshots data file, appropriate for cron.

sshd_config Anonymous login to chrooted client.
termcap.diff Patch for bright colors in xterm and rxvt.
index.html Friendly directions.
sshd_config Anonymous login to chrooted client.
termcap.diff Patch for bright colors in xterm and rxvt.
index.html Friendly directions.

+ 331
- 332
client.c View File

@@ -1,4 +1,4 @@
/* Copyright (c) 2017, Curtis McEnroe <curtis@cmcenroe.me>
/* Copyright (C) 2017 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
@@ -30,398 +30,397 @@
#include "torus.h"

enum {
ESC = 0x1B,
DEL = 0x7F,
ESC = 0x1B,
DEL = 0x7F,
};

static int client;

static void clientMessage(struct ClientMessage msg) {
ssize_t size = send(client, &msg, sizeof(msg), 0);
if (size < 0) err(EX_IOERR, "send");
ssize_t size = send(client, &msg, sizeof(msg), 0);
if (size < 0) err(EX_IOERR, "send");
}

static void clientMove(int8_t dx, int8_t dy) {
struct ClientMessage msg = {
.type = CLIENT_MOVE,
.move = { .dx = dx, .dy = dy },
};
clientMessage(msg);
struct ClientMessage msg = {
.type = CLIENT_MOVE,
.move = { .dx = dx, .dy = dy },
};
clientMessage(msg);
}

static void clientPut(uint8_t color, char cell) {
struct ClientMessage msg = {
.type = CLIENT_PUT,
.put = { .color = color, .cell = cell },
};
clientMessage(msg);
struct ClientMessage msg = {
.type = CLIENT_PUT,
.put = { .color = color, .cell = cell },
};
clientMessage(msg);
}

static void clientSpawn(uint8_t spawn) {
struct ClientMessage msg = {
.type = CLIENT_SPAWN,
.spawn = spawn,
};
clientMessage(msg);
struct ClientMessage msg = {
.type = CLIENT_SPAWN,
.spawn = spawn,
};
clientMessage(msg);
}

static chtype colorAttrs(uint8_t color) {
uint8_t bright = color & COLOR_BRIGHT;
uint8_t fg = color & 0x07;
uint8_t bg = color & 0x70;
return COLOR_PAIR(bg >> 1 | fg) | (bright ? A_BOLD : 0);
uint8_t bright = color & COLOR_BRIGHT;
uint8_t fg = color & 0x07;
uint8_t bg = color & 0x70;
return COLOR_PAIR(bg >> 1 | fg) | (bright ? A_BOLD : 0);
}

static uint8_t attrsColor(chtype attrs) {
chtype bright = attrs & A_BOLD;
short fg = PAIR_NUMBER(attrs) & 007;
short bg = PAIR_NUMBER(attrs) & 070;
return bg << 1 | fg | (bright ? COLOR_BRIGHT : 0);
chtype bright = attrs & A_BOLD;
short fg = PAIR_NUMBER(attrs) & 007;
short bg = PAIR_NUMBER(attrs) & 070;
return bg << 1 | fg | (bright ? COLOR_BRIGHT : 0);
}

static struct {
int8_t speed;
uint8_t color;
enum {
MODE_NORMAL,
MODE_INSERT,
MODE_REPLACE,
MODE_PUT,
MODE_DRAW,
} mode;
int8_t dx;
int8_t dy;
uint8_t len;
char draw;
int8_t speed;
uint8_t color;
enum {
MODE_NORMAL,
MODE_INSERT,
MODE_REPLACE,
MODE_PUT,
MODE_DRAW,
} mode;
int8_t dx;
int8_t dy;
uint8_t len;
char draw;
} input = {
.speed = 1,
.color = COLOR_WHITE,
.dx = 1,
.speed = 1,
.color = COLOR_WHITE,
.dx = 1,
};

static void colorFg(uint8_t fg) {
input.color = (input.color & 0xF8) | fg;
input.color = (input.color & 0xF8) | fg;
}

static void colorBg(uint8_t bg) {
input.color = (input.color & 0x0F) | (bg << 4);
input.color = (input.color & 0x0F) | (bg << 4);
}

static void colorInvert(void) {
input.color =
(input.color & 0x08) |
((input.color & 0x07) << 4) |
((input.color & 0x70) >> 4);
input.color =
(input.color & 0x08) |
((input.color & 0x07) << 4) |
((input.color & 0x70) >> 4);
}

static void insertMode(int8_t dx, int8_t dy) {
input.mode = MODE_INSERT;
input.dx = dx;
input.dy = dy;
input.len = 0;
input.mode = MODE_INSERT;
input.dx = dx;
input.dy = dy;
input.len = 0;
}

static void swapCell(int8_t dx, int8_t dy) {
uint8_t aColor = attrsColor(inch());
char aCell = inch() & A_CHARTEXT;
int sy, sx;
getyx(stdscr, sy, sx);
move(sy + dy, sx + dx);
uint8_t bColor = attrsColor(inch());
char bCell = inch() & A_CHARTEXT;
move(sy, sx);
clientPut(bColor, bCell);
clientMove(dx, dy);
clientPut(aColor, aCell);
uint8_t aColor = attrsColor(inch());
char aCell = inch() & A_CHARTEXT;
int sy, sx;
getyx(stdscr, sy, sx);
move(sy + dy, sx + dx);
uint8_t bColor = attrsColor(inch());
char bCell = inch() & A_CHARTEXT;
move(sy, sx);
clientPut(bColor, bCell);
clientMove(dx, dy);
clientPut(aColor, aCell);
}

static void readInput(void) {
int c = getch();

if (input.mode == MODE_INSERT) {
if (c == ESC) {
input.mode = MODE_NORMAL;
clientMove(-input.dx, -input.dy);
} else if (!input.dx && !input.dy) {
switch (c) {
case 'h': insertMode(-1, 0); break;
case 'j': insertMode( 0, 1); break;
case 'k': insertMode( 0, -1); break;
case 'l': insertMode( 1, 0); break;
case 'y': insertMode(-1, -1); break;
case 'u': insertMode( 1, -1); break;
case 'b': insertMode(-1, 1); break;
case 'n': insertMode( 1, 1); break;
}
} else if (c == '\b' || c == DEL) {
clientMove(-input.dx, -input.dy);
clientPut(input.color, ' ');
input.len--;
} else if (c == '\n') {
clientMove(input.dy, input.dx);
clientMove(-input.dx * input.len, -input.dy * input.len);
input.len = 0;
} else if (isprint(c)) {
clientPut(input.color, c);
clientMove(input.dx, input.dy);
input.len++;
}
return;
}

if (input.mode == MODE_REPLACE) {
if (isprint(c)) clientPut(attrsColor(inch()), c);
input.mode = MODE_NORMAL;
return;
}

if (input.mode == MODE_PUT) {
if (isprint(c)) clientPut(input.color, c);
input.mode = MODE_NORMAL;
return;
}

if (input.mode == MODE_DRAW && !input.draw) {
if (c == ESC) input.mode = MODE_NORMAL;
if (isprint(c)) {
input.draw = c;
clientPut(input.color, c);
}
return;
}

switch (c) {
case ESC: input.mode = MODE_NORMAL; break;

case 'q': endwin(); exit(EX_OK);
case 'Q': {
if ((input.color & 0x07) < SPAWNS_LEN) {
clientSpawn(input.color & 0x07);
} else {
clientSpawn(0);
}
} break;

case 'a': clientMove(1, 0); // fallthrough
case 'i': insertMode(1, 0); break;
case 'I': insertMode(0, 0); break;
case 'r': input.mode = MODE_REPLACE; break;
case 'p': input.mode = MODE_PUT; break;
case 'R': input.mode = MODE_DRAW; input.draw = 0; break;
case 'x': clientPut(attrsColor(inch()), ' '); break;

case '~': {
clientPut(input.color, inch() & A_CHARTEXT);
clientMove(input.dx, input.dy);
} break;

case '[': if (input.speed > 1) input.speed--; break;
case ']': if (input.speed < 4) input.speed++; break;

case 'h': clientMove(-input.speed, 0); break;
case 'j': clientMove( 0, input.speed); break;
case 'k': clientMove( 0, -input.speed); break;
case 'l': clientMove( input.speed, 0); break;
case 'y': clientMove(-input.speed, -input.speed); break;
case 'u': clientMove( input.speed, -input.speed); break;
case 'b': clientMove(-input.speed, input.speed); break;
case 'n': clientMove( input.speed, input.speed); break;

case 'H': swapCell(-1, 0); break;
case 'J': swapCell( 0, 1); break;
case 'K': swapCell( 0, -1); break;
case 'L': swapCell( 1, 0); break;
case 'Y': swapCell(-1, -1); break;
case 'U': swapCell( 1, -1); break;
case 'B': swapCell(-1, 1); break;
case 'N': swapCell( 1, 1); break;

case '`': input.color = attrsColor(inch()); break;

case '0': colorFg(COLOR_BLACK); break;
case '1': colorFg(COLOR_RED); break;
case '2': colorFg(COLOR_GREEN); break;
case '3': colorFg(COLOR_YELLOW); break;
case '4': colorFg(COLOR_BLUE); break;
case '5': colorFg(COLOR_MAGENTA); break;
case '6': colorFg(COLOR_CYAN); break;
case '7': colorFg(COLOR_WHITE); break;

case ')': colorBg(COLOR_BLACK); break;
case '!': colorBg(COLOR_RED); break;
case '@': colorBg(COLOR_GREEN); break;
case '#': colorBg(COLOR_YELLOW); break;
case '$': colorBg(COLOR_BLUE); break;
case '%': colorBg(COLOR_MAGENTA); break;
case '^': colorBg(COLOR_CYAN); break;
case '&': colorBg(COLOR_WHITE); break;

case '*':
case '8': input.color ^= COLOR_BRIGHT; break;

case '(':
case '9': colorInvert(); break;

case KEY_LEFT: clientMove(-1, 0); break;
case KEY_DOWN: clientMove( 0, 1); break;
case KEY_UP: clientMove( 0, -1); break;
case KEY_RIGHT: clientMove( 1, 0); break;
}

if (input.mode == MODE_DRAW && input.draw) clientPut(input.color, input.draw);
int c = getch();

if (input.mode == MODE_INSERT) {
if (c == ESC) {
input.mode = MODE_NORMAL;
clientMove(-input.dx, -input.dy);
} else if (!input.dx && !input.dy) {
switch (c) {
break; case 'h': insertMode(-1, 0);
break; case 'j': insertMode( 0, 1);
break; case 'k': insertMode( 0, -1);
break; case 'l': insertMode( 1, 0);
break; case 'y': insertMode(-1, -1);
break; case 'u': insertMode( 1, -1);
break; case 'b': insertMode(-1, 1);
break; case 'n': insertMode( 1, 1);
}
} else if (c == '\b' || c == DEL) {
clientMove(-input.dx, -input.dy);
clientPut(input.color, ' ');
input.len--;
} else if (c == '\n') {
clientMove(input.dy, input.dx);
clientMove(-input.dx * input.len, -input.dy * input.len);
input.len = 0;
} else if (isprint(c)) {
clientPut(input.color, c);
clientMove(input.dx, input.dy);
input.len++;
}
return;
}

if (input.mode == MODE_REPLACE) {
if (isprint(c)) clientPut(attrsColor(inch()), c);
input.mode = MODE_NORMAL;
return;
}

if (input.mode == MODE_PUT) {
if (isprint(c)) clientPut(input.color, c);
input.mode = MODE_NORMAL;
return;
}

if (input.mode == MODE_DRAW && !input.draw) {
if (c == ESC) input.mode = MODE_NORMAL;
if (isprint(c)) {
input.draw = c;
clientPut(input.color, c);
}
return;
}

switch (c) {
break; case ESC: input.mode = MODE_NORMAL;

break; case 'q': endwin(); exit(EX_OK);
break; case 'Q': {
if ((input.color & 0x07) < SPAWNS_LEN) {
clientSpawn(input.color & 0x07);
} else {
clientSpawn(0);
}
}

break; case 'i': insertMode(1, 0);
break; case 'a': clientMove(1, 0); insertMode(1, 0);
break; case 'I': insertMode(0, 0);
break; case 'r': input.mode = MODE_REPLACE;
break; case 'p': input.mode = MODE_PUT;
break; case 'R': input.mode = MODE_DRAW; input.draw = 0;
break; case 'x': clientPut(attrsColor(inch()), ' ');

break; case '~': {
clientPut(input.color, inch() & A_CHARTEXT);
clientMove(input.dx, input.dy);
}

break; case '[': if (input.speed > 1) input.speed--;
break; case ']': if (input.speed < 4) input.speed++;

break; case 'h': clientMove(-input.speed, 0);
break; case 'j': clientMove( 0, input.speed);
break; case 'k': clientMove( 0, -input.speed);
break; case 'l': clientMove( input.speed, 0);
break; case 'y': clientMove(-input.speed, -input.speed);
break; case 'u': clientMove( input.speed, -input.speed);
break; case 'b': clientMove(-input.speed, input.speed);
break; case 'n': clientMove( input.speed, input.speed);

break; case 'H': swapCell(-1, 0);
break; case 'J': swapCell( 0, 1);
break; case 'K': swapCell( 0, -1);
break; case 'L': swapCell( 1, 0);
break; case 'Y': swapCell(-1, -1);
break; case 'U': swapCell( 1, -1);
break; case 'B': swapCell(-1, 1);
break; case 'N': swapCell( 1, 1);

break; case '`': input.color = attrsColor(inch());

break; case '0': colorFg(COLOR_BLACK);
break; case '1': colorFg(COLOR_RED);
break; case '2': colorFg(COLOR_GREEN);
break; case '3': colorFg(COLOR_YELLOW);
break; case '4': colorFg(COLOR_BLUE);
break; case '5': colorFg(COLOR_MAGENTA);
break; case '6': colorFg(COLOR_CYAN);
break; case '7': colorFg(COLOR_WHITE);

break; case ')': colorBg(COLOR_BLACK);
break; case '!': colorBg(COLOR_RED);
break; case '@': colorBg(COLOR_GREEN);
break; case '#': colorBg(COLOR_YELLOW);
break; case '$': colorBg(COLOR_BLUE);
break; case '%': colorBg(COLOR_MAGENTA);
break; case '^': colorBg(COLOR_CYAN);
break; case '&': colorBg(COLOR_WHITE);

break; case '*': case '8': input.color ^= COLOR_BRIGHT;

break; case '(': case '9': colorInvert();

break; case KEY_LEFT: clientMove(-1, 0);
break; case KEY_DOWN: clientMove( 0, 1);
break; case KEY_UP: clientMove( 0, -1);
break; case KEY_RIGHT: clientMove( 1, 0);
}

if (input.mode == MODE_DRAW && input.draw) clientPut(input.color, input.draw);
}

static void serverPut(uint8_t x, uint8_t y, uint8_t color, char cell) {
mvaddch(y, x, colorAttrs(color) | cell);
mvaddch(y, x, colorAttrs(color) | cell);
}

static void serverTile(void) {
struct Tile tile;
ssize_t size = recv(client, &tile, sizeof(tile), 0);
if (size < 0) err(EX_IOERR, "recv");
if ((size_t)size < sizeof(tile)) {
errx(EX_PROTOCOL, "This tile isn't big enough...");
}
for (int y = 0; y < CELL_ROWS; ++y) {
for (int x = 0; x < CELL_COLS; ++x) {
serverPut(x, y, tile.colors[y][x], tile.cells[y][x]);
}
}
struct Tile tile;
ssize_t size = recv(client, &tile, sizeof(tile), 0);
if (size < 0) err(EX_IOERR, "recv");
if ((size_t)size < sizeof(tile)) {
errx(EX_PROTOCOL, "This tile isn't big enough...");
}
for (int y = 0; y < CELL_ROWS; ++y) {
for (int x = 0; x < CELL_COLS; ++x) {
serverPut(x, y, tile.colors[y][x], tile.cells[y][x]);
}
}
}

static void serverCursor(uint8_t oldX, uint8_t oldY, uint8_t newX, uint8_t newY) {
if (oldX != CURSOR_NONE) {
move(oldY, oldX);
addch(inch() & ~A_REVERSE);
}
if (newX != CURSOR_NONE) {
move(newY, newX);
addch(inch() | A_REVERSE);
}
if (oldX != CURSOR_NONE) {
move(oldY, oldX);
addch(inch() & ~A_REVERSE);
}
if (newX != CURSOR_NONE) {
move(newY, newX);
addch(inch() | A_REVERSE);
}
}

static void readMessage(void) {
struct ServerMessage msg;
ssize_t size = recv(client, &msg, sizeof(msg), 0);
if (size < 0) err(EX_IOERR, "recv");
if ((size_t)size < sizeof(msg)) errx(EX_PROTOCOL, "A message was cut short.");

int sy, sx;
getyx(stdscr, sy, sx);

switch (msg.type) {
case SERVER_TILE: {
serverTile();
} break;

case SERVER_MOVE: {
move(msg.move.cellY, msg.move.cellX);
refresh();
} return;

case SERVER_PUT: {
serverPut(
msg.put.cellX,
msg.put.cellY,
msg.put.color,
msg.put.cell
);
} break;

case SERVER_CURSOR: {
serverCursor(
msg.cursor.oldCellX,
msg.cursor.oldCellY,
msg.cursor.newCellX,
msg.cursor.newCellY
);
} break;

default: errx(EX_PROTOCOL, "I don't know what %d means!", msg.type);
}

move(sy, sx);
refresh();
struct ServerMessage msg;
ssize_t size = recv(client, &msg, sizeof(msg), 0);
if (size < 0) err(EX_IOERR, "recv");
if ((size_t)size < sizeof(msg)) errx(EX_PROTOCOL, "A message was cut short.");

int sy, sx;
getyx(stdscr, sy, sx);

switch (msg.type) {
break; case SERVER_TILE: {
serverTile();
}

break; case SERVER_MOVE: {
move(msg.move.cellY, msg.move.cellX);
refresh();
return;
}

break; case SERVER_PUT: {
serverPut(
msg.put.cellX,
msg.put.cellY,
msg.put.color,
msg.put.cell
);
}

break; case SERVER_CURSOR: {
serverCursor(
msg.cursor.oldCellX,
msg.cursor.oldCellY,
msg.cursor.newCellX,
msg.cursor.newCellY
);
}

break; default: errx(EX_PROTOCOL, "I don't know what %d means!", msg.type);
}

move(sy, sx);
refresh();
}

static void drawBorder(void) {
if (LINES < CELL_ROWS || COLS < CELL_COLS) {
endwin();
fprintf(stderr, "Sorry, your terminal is too small!\n");
fprintf(stderr, "It needs to be at least 80x25 characters.\n");
exit(EX_CONFIG);
}
if (LINES > CELL_ROWS) {
mvhline(CELL_ROWS, 0, 0, CELL_COLS);
}
if (COLS > CELL_COLS) {
mvvline(0, CELL_COLS, 0, CELL_ROWS);
}
if (LINES > CELL_ROWS && COLS > CELL_COLS) {
mvaddch(CELL_ROWS, CELL_COLS, ACS_LRCORNER);
}
if (LINES < CELL_ROWS || COLS < CELL_COLS) {
endwin();
fprintf(stderr, "Sorry, your terminal is too small!\n");
fprintf(stderr, "It needs to be at least 80x25 characters.\n");
exit(EX_CONFIG);
}
if (LINES > CELL_ROWS) {
mvhline(CELL_ROWS, 0, 0, CELL_COLS);
}
if (COLS > CELL_COLS) {
mvvline(0, CELL_COLS, 0, CELL_ROWS);
}
if (LINES > CELL_ROWS && COLS > CELL_COLS) {
mvaddch(CELL_ROWS, CELL_COLS, ACS_LRCORNER);
}
}

static void initColors(void) {
if (!has_colors()) {
endwin();
fprintf(
stderr,
"Sorry, your terminal doesn't support colors!\n"
"If you think it does, check TERM.\n"
);
exit(EX_CONFIG);
}
start_color();
if (COLOR_PAIRS < 64) {
endwin();
fprintf(
stderr,
"Sorry, your terminal doesn't support enough color pairs!\n"
"If you think it does, check TERM.\n"
);
exit(EX_CONFIG);
}
for (int bg = COLOR_BLACK; bg < COLOR_BRIGHT; ++bg) {
for (int fg = COLOR_BLACK; fg < COLOR_BRIGHT; ++fg) {
init_pair(PAIR_NUMBER(colorAttrs(bg << 4 | fg)), fg, bg);
}
}
if (!has_colors()) {
endwin();
fprintf(
stderr,
"Sorry, your terminal doesn't support colors!\n"
"If you think it does, check TERM.\n"
);
exit(EX_CONFIG);
}
start_color();
if (COLOR_PAIRS < 64) {
endwin();
fprintf(
stderr,
"Sorry, your terminal doesn't support enough color pairs!\n"
"If you think it does, check TERM.\n"
);
exit(EX_CONFIG);
}
for (int bg = COLOR_BLACK; bg < COLOR_BRIGHT; ++bg) {
for (int fg = COLOR_BLACK; fg < COLOR_BRIGHT; ++fg) {
init_pair(PAIR_NUMBER(colorAttrs(bg << 4 | fg)), fg, bg);
}
}
}

int main() {
client = socket(PF_LOCAL, SOCK_STREAM, 0);
if (client < 0) err(EX_OSERR, "socket");
struct sockaddr_un addr = {
.sun_family = AF_LOCAL,
.sun_path = "torus.sock",
};
int error = connect(client, (struct sockaddr *)&addr, sizeof(addr));
if (error) err(EX_NOINPUT, "torus.sock");
initscr();
cbreak();
noecho();
keypad(stdscr, true);
set_escdelay(100);
initColors();
drawBorder();
struct pollfd fds[2] = {
{ .fd = STDIN_FILENO, .events = POLLIN },
{ .fd = client, .events = POLLIN },
};
for (;;) {
int nfds = poll(fds, 2, -1);
if (nfds < 0 && errno == EINTR) continue;
if (nfds < 0) err(EX_IOERR, "poll");
if (fds[0].revents) readInput();
if (fds[1].revents) readMessage();
}
client = socket(PF_LOCAL, SOCK_STREAM, 0);
if (client < 0) err(EX_OSERR, "socket");
struct sockaddr_un addr = {
.sun_family = AF_LOCAL,
.sun_path = "torus.sock",
};
int error = connect(client, (struct sockaddr *)&addr, sizeof(addr));
if (error) err(EX_NOINPUT, "torus.sock");
initscr();
cbreak();
noecho();
keypad(stdscr, true);
set_escdelay(100);
initColors();
drawBorder();
struct pollfd fds[2] = {
{ .fd = STDIN_FILENO, .events = POLLIN },
{ .fd = client, .events = POLLIN },
};
for (;;) {
int nfds = poll(fds, 2, -1);
if (nfds < 0 && errno == EINTR) continue;
if (nfds < 0) err(EX_IOERR, "poll");
if (fds[0].revents) readInput();
if (fds[1].revents) readMessage();
}
}

+ 122
- 122
help.c View File

@@ -1,4 +1,4 @@
/* Copyright (c) 2017, Curtis McEnroe <curtis@cmcenroe.me>
/* Copyright (C) 2017 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
@@ -26,37 +26,37 @@
static int client;

static void clientMessage(struct ClientMessage msg) {
ssize_t size = send(client, &msg, sizeof(msg), 0);
if (size < 0) err(EX_IOERR, "send");
ssize_t size = send(client, &msg, sizeof(msg), 0);
if (size < 0) err(EX_IOERR, "send");
}

static void clientMove(int8_t dx, int8_t dy) {
struct ClientMessage msg = {
.type = CLIENT_MOVE,
.move = { .dx = dx, .dy = dy },
};
clientMessage(msg);
struct ClientMessage msg = {
.type = CLIENT_MOVE,
.move = { .dx = dx, .dy = dy },
};
clientMessage(msg);
}

static void clientPut(uint8_t color, char cell) {
struct ClientMessage msg = {
.type = CLIENT_PUT,
.put = { .color = color, .cell = cell },
};
clientMessage(msg);
struct ClientMessage msg = {
.type = CLIENT_PUT,
.put = { .color = color, .cell = cell },
};
clientMessage(msg);
}

static const useconds_t DELAY = 50000;

enum {
R = COLOR_RED,
G = COLOR_GREEN,
Y = COLOR_YELLOW,
B = COLOR_BLUE,
M = COLOR_MAGENTA,
C = COLOR_CYAN,
W = COLOR_WHITE,
I = COLOR_BRIGHT | COLOR_WHITE,
R = COLOR_RED,
G = COLOR_GREEN,
Y = COLOR_YELLOW,
B = COLOR_BLUE,
M = COLOR_MAGENTA,
C = COLOR_CYAN,
W = COLOR_WHITE,
I = COLOR_BRIGHT | COLOR_WHITE,
};

static void h(void) { clientMove(-1, 0); usleep(DELAY); }
@@ -69,116 +69,116 @@ static void b(void) { clientMove(-1, 1); usleep(DELAY); }
static void n(void) { clientMove( 1, 1); usleep(DELAY); }

static void p(uint8_t color, char cell) {
clientPut(color, cell);
usleep(DELAY);
clientPut(color, cell);
usleep(DELAY);
}

static uint8_t len;

static void s(uint8_t color, const char *str) {
for (; *str; ++len, ++str) {
clientPut(color, *str);
clientMove(1, 0);
usleep(DELAY);
}
for (; *str; ++len, ++str) {
clientPut(color, *str);
clientMove(1, 0);
usleep(DELAY);
}
}

static void r(void) {
clientMove(-len, 1);
usleep(DELAY);
len = 0;
clientMove(-len, 1);
usleep(DELAY);
len = 0;
}

int main() {
client = socket(PF_LOCAL, SOCK_STREAM, 0);
if (client < 0) err(EX_OSERR, "socket");
struct sockaddr_un addr = {
.sun_family = AF_LOCAL,
.sun_path = "torus.sock",
};
int error = connect(client, (struct sockaddr *)&addr, sizeof(addr));
if (error) err(EX_NOINPUT, "torus.sock");
pid_t pid = fork();
if (pid < 0) err(EX_OSERR, "fork");
if (!pid) {
for (;;) {
char buf[4096];
ssize_t size = recv(client, buf, sizeof(buf), 0);
if (size < 0) err(EX_IOERR, "recv");
if (!size) return EX_OK;
}
}
clientMove(-CELL_INIT_X, -CELL_INIT_Y);
clientMove(28, 0);
for (;;) {
for (int i = 0; i < 10; ++i) {
clientMove(0, 1);
usleep(DELAY / 5);
}
for (int i = 0; i < 11; ++i) {
for (int j = 0; j < 28; ++j) {
clientPut(W, ' ');
if (i % 2) clientMove(1, 0);
else clientMove(-1, 0);
usleep(DELAY / 5);
}
clientPut(W, ' ');
if (i != 10) clientMove(0, -1);
usleep(DELAY / 5);
}
j(); l(); l();
s(W, "Welcome to "); s(I, "ascii.town"); s(W, "!"); r();
r(); r();
n(); n(); s(W, "o-"); s(I, "l");
h(); b(); p(W, '\\'); n(); p(I, 'n');
y(); h(); p(W, '|'); j(); p(I, 'j');
y(); p(W, '/'); b(); p(I, 'b');
k(); u(); p(W, '-'); h(); p(I, 'h');
u(); p(W, '\\'); y(); p(I, 'y');
n(); l(); p(W, '|'); k(); p(I, 'k');
n(); p(W, '/'); u(); p(I, 'u');
u(); s(W, " "); len = 0;
s(I, "q "); s(W, "quit"); r();
s(I, "i "); s(W, "insert"); r();
s(I, "r "); s(W, "replace"); r();
s(I, "R "); s(W, "draw"); r();
s(I, "~ "); s(W, "color"); r();
s(I, "` "); s(W, "pipette"); r();
s(I, "* "); s(W, "bright");
s(W, " "); len = 0;
clientPut(W, '7'); k();
clientPut(C, '6'); k();
clientPut(M, '5'); k();
clientPut(B, '4'); k();
clientPut(Y, '3'); k();
clientPut(G, '2'); k();
clientPut(R, '1'); k();
l(); n();
clientPut(R << 4, '!'); j();
clientPut(G << 4, '@'); j();
clientPut(Y << 4, '#'); j();
clientPut(B << 4, '$'); j();
clientPut(M << 4, '%'); j();
clientPut(C << 4, '^'); j();
clientPut(W << 4, '&'); j();
h(); k(); k(); k(); k(); k(); k(); k(); k(); k(); h();
sleep(30);
u(); l(); l(); l();
}
client = socket(PF_LOCAL, SOCK_STREAM, 0);
if (client < 0) err(EX_OSERR, "socket");
struct sockaddr_un addr = {
.sun_family = AF_LOCAL,
.sun_path = "torus.sock",
};
int error = connect(client, (struct sockaddr *)&addr, sizeof(addr));
if (error) err(EX_NOINPUT, "torus.sock");
pid_t pid = fork();
if (pid < 0) err(EX_OSERR, "fork");
if (!pid) {
for (;;) {
char buf[4096];
ssize_t size = recv(client, buf, sizeof(buf), 0);
if (size < 0) err(EX_IOERR, "recv");
if (!size) return EX_OK;
}
}
clientMove(-CELL_INIT_X, -CELL_INIT_Y);
clientMove(28, 0);
for (;;) {
for (int i = 0; i < 10; ++i) {
clientMove(0, 1);
usleep(DELAY / 5);
}
for (int i = 0; i < 11; ++i) {
for (int j = 0; j < 28; ++j) {
clientPut(W, ' ');
if (i % 2) clientMove(1, 0);
else clientMove(-1, 0);
usleep(DELAY / 5);
}
clientPut(W, ' ');
if (i != 10) clientMove(0, -1);
usleep(DELAY / 5);
}
j(); l(); l();
s(W, "Welcome to "); s(I, "ascii.town"); s(W, "!"); r();
r(); r();
n(); n(); s(W, "o-"); s(I, "l");
h(); b(); p(W, '\\'); n(); p(I, 'n');
y(); h(); p(W, '|'); j(); p(I, 'j');
y(); p(W, '/'); b(); p(I, 'b');
k(); u(); p(W, '-'); h(); p(I, 'h');
u(); p(W, '\\'); y(); p(I, 'y');
n(); l(); p(W, '|'); k(); p(I, 'k');
n(); p(W, '/'); u(); p(I, 'u');
u(); s(W, " "); len = 0;
s(I, "q "); s(W, "quit"); r();
s(I, "i "); s(W, "insert"); r();
s(I, "r "); s(W, "replace"); r();
s(I, "R "); s(W, "draw"); r();
s(I, "~ "); s(W, "color"); r();
s(I, "` "); s(W, "pipette"); r();
s(I, "* "); s(W, "bright");
s(W, " "); len = 0;
clientPut(W, '7'); k();
clientPut(C, '6'); k();
clientPut(M, '5'); k();
clientPut(B, '4'); k();
clientPut(Y, '3'); k();
clientPut(G, '2'); k();
clientPut(R, '1'); k();
l(); n();
clientPut(R << 4, '!'); j();
clientPut(G << 4, '@'); j();
clientPut(Y << 4, '#'); j();
clientPut(B << 4, '$'); j();
clientPut(M << 4, '%'); j();
clientPut(C << 4, '^'); j();
clientPut(W << 4, '&'); j();
h(); k(); k(); k(); k(); k(); k(); k(); k(); k(); h();
sleep(30);
u(); l(); l(); l();
}
}

+ 2
- 2
index.html View File

@@ -4,11 +4,11 @@
Hey there, friend. This should get you there.
<p>
<code>
ssh torus@ascii.town
ssh torus@ascii.town
</code>
<p>
You can also take a detour and play NetHack.
<p>
<code>
ssh nethack@ascii.town
ssh nethack@ascii.town
</code>

+ 73
- 73
merge.c View File

@@ -1,4 +1,4 @@
/* Copyright (c) 2017, Curtis McEnroe <curtis@cmcenroe.me>
/* Copyright (C) 2017 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
@@ -23,79 +23,79 @@
#include "torus.h"

static void drawTile(int offsetY, const struct Tile *tile) {
for (uint8_t y = 0; y < CELL_ROWS; ++y) {
for (uint8_t x = 0; x < CELL_COLS; ++x) {
uint8_t color = tile->colors[y][x];
char cell = tile->cells[y][x];
int attrs = COLOR_PAIR(color & ~COLOR_BRIGHT);
if (color & COLOR_BRIGHT) attrs |= A_BOLD;
mvaddch(offsetY + y, x, attrs | cell);
}
}
for (uint8_t y = 0; y < CELL_ROWS; ++y) {
for (uint8_t x = 0; x < CELL_COLS; ++x) {
uint8_t color = tile->colors[y][x];
char cell = tile->cells[y][x];
int attrs = COLOR_PAIR(color & ~COLOR_BRIGHT);
if (color & COLOR_BRIGHT) attrs |= A_BOLD;
mvaddch(offsetY + y, x, attrs | cell);
}
}
}

int main(int argc, char *argv[]) {
if (argc != 4) return EX_USAGE;
FILE *fileA = fopen(argv[1], "r");
if (!fileA) err(EX_NOINPUT, "%s", argv[1]);
FILE *fileB = fopen(argv[2], "r");
if (!fileB) err(EX_NOINPUT, "%s", argv[2]);
FILE *fileC = fopen(argv[3], "w");
if (!fileC) err(EX_CANTCREAT, "%s", argv[3]);
initscr();
cbreak();
noecho();
keypad(stdscr, true);
set_escdelay(100);
start_color();
for (int bg = COLOR_BLACK; bg < COLOR_BRIGHT; ++bg) {
for (int fg = COLOR_BLACK; fg < COLOR_BRIGHT; ++fg) {
init_pair(bg << 4 | fg, fg, bg);
}
}
mvhline(CELL_ROWS, 0, 0, CELL_COLS);
mvhline(CELL_ROWS * 2 + 1, 0, 0, CELL_COLS);
mvvline(0, CELL_COLS, 0, CELL_ROWS * 2 + 1);
mvaddch(CELL_ROWS, CELL_COLS, ACS_RTEE);
mvaddch(CELL_ROWS * 2 + 1, CELL_COLS, ACS_LRCORNER);
struct Tile tileA, tileB;
for (;;) {
size_t countA = fread(&tileA, sizeof(tileA), 1, fileA);
if (ferror(fileA)) err(EX_IOERR, "%s", argv[1]);
size_t countB = fread(&tileB, sizeof(tileB), 1, fileB);
if (ferror(fileB)) err(EX_IOERR, "%s", argv[2]);
if (!countA && !countB) break;
if (!countA || !countB) errx(EX_DATAERR, "different size inputs");
const struct Tile *tileC = (tileA.accessTime > tileB.accessTime)
? &tileA
: &tileB;
if (tileA.modifyTime != tileB.modifyTime) {
drawTile(0, &tileA);
drawTile(CELL_ROWS + 1, &tileB);
move(CELL_ROWS * 2 + 2, 0);
refresh();
int c;
do { c = getch(); } while (c != 'a' && c != 'b');
tileC = (c == 'a') ? &tileA : &tileB;
}
fwrite(tileC, sizeof(*tileC), 1, fileC);
if (ferror(fileC)) err(EX_IOERR, "%s", argv[3]);
}
endwin();
return EX_OK;
if (argc != 4) return EX_USAGE;
FILE *fileA = fopen(argv[1], "r");
if (!fileA) err(EX_NOINPUT, "%s", argv[1]);
FILE *fileB = fopen(argv[2], "r");
if (!fileB) err(EX_NOINPUT, "%s", argv[2]);
FILE *fileC = fopen(argv[3], "w");
if (!fileC) err(EX_CANTCREAT, "%s", argv[3]);
initscr();
cbreak();
noecho();
keypad(stdscr, true);
set_escdelay(100);
start_color();
for (int bg = COLOR_BLACK; bg < COLOR_BRIGHT; ++bg) {
for (int fg = COLOR_BLACK; fg < COLOR_BRIGHT; ++fg) {
init_pair(bg << 4 | fg, fg, bg);
}
}
mvhline(CELL_ROWS, 0, 0, CELL_COLS);
mvhline(CELL_ROWS * 2 + 1, 0, 0, CELL_COLS);
mvvline(0, CELL_COLS, 0, CELL_ROWS * 2 + 1);
mvaddch(CELL_ROWS, CELL_COLS, ACS_RTEE);
mvaddch(CELL_ROWS * 2 + 1, CELL_COLS, ACS_LRCORNER);
struct Tile tileA, tileB;
for (;;) {
size_t countA = fread(&tileA, sizeof(tileA), 1, fileA);
if (ferror(fileA)) err(EX_IOERR, "%s", argv[1]);
size_t countB = fread(&tileB, sizeof(tileB), 1, fileB);
if (ferror(fileB)) err(EX_IOERR, "%s", argv[2]);
if (!countA && !countB) break;
if (!countA || !countB) errx(EX_DATAERR, "different size inputs");
const struct Tile *tileC = (tileA.accessTime > tileB.accessTime)
? &tileA
: &tileB;
if (tileA.modifyTime != tileB.modifyTime) {
drawTile(0, &tileA);
drawTile(CELL_ROWS + 1, &tileB);
move(CELL_ROWS * 2 + 2, 0);
refresh();
int c;
do { c = getch(); } while (c != 'a' && c != 'b');
tileC = (c == 'a') ? &tileA : &tileB;
}
fwrite(tileC, sizeof(*tileC), 1, fileC);
if (ferror(fileC)) err(EX_IOERR, "%s", argv[3]);
}
endwin();
return EX_OK;
}

+ 18
- 18
meta.c View File

@@ -1,4 +1,4 @@
/* Copyright (c) 2017, Curtis McEnroe <curtis@cmcenroe.me>
/* Copyright (C) 2017 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
@@ -21,22 +21,22 @@
#include "torus.h"

int main() {
printf("tileX,tileY,createTime,modifyCount,modifyTime,accessCount,accessTime\n");
for (int i = 0;; ++i) {
struct Tile tile;
size_t count = fread(&tile, sizeof(tile), 1, stdin);
if (ferror(stdin)) err(EX_IOERR, "(stdin)");
if (!count) return EX_OK;
printf("tileX,tileY,createTime,modifyCount,modifyTime,accessCount,accessTime\n");
for (int i = 0;; ++i) {
struct Tile tile;
size_t count = fread(&tile, sizeof(tile), 1, stdin);
if (ferror(stdin)) err(EX_IOERR, "(stdin)");
if (!count) return EX_OK;

printf(
"%d,%d,%ld,%u,%ld,%u,%ld\n",
i % TILE_COLS,
i / TILE_COLS,
tile.createTime,
tile.modifyCount,
tile.modifyTime,
tile.accessCount,
tile.accessTime
);
}
printf(
"%d,%d,%ld,%u,%ld,%u,%ld\n",
i % TILE_COLS,
i / TILE_COLS,
tile.createTime,
tile.modifyCount,
tile.modifyTime,
tile.accessCount,
tile.accessTime
);
}
}

+ 282
- 282
server.c View File

@@ -1,4 +1,4 @@
/* Copyright (c) 2017, Curtis McEnroe <curtis@cmcenroe.me>
/* Copyright (C) 2017 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
@@ -38,337 +38,337 @@
static struct Tile *tiles;

static void tilesMap(void) {
int fd = open("torus.dat", O_CREAT | O_RDWR, 0644);
if (fd < 0) err(EX_CANTCREAT, "torus.dat");
int fd = open("torus.dat", O_CREAT | O_RDWR, 0644);
if (fd < 0) err(EX_CANTCREAT, "torus.dat");

int error = ftruncate(fd, TILES_SIZE);
if (error) err(EX_IOERR, "ftruncate");
int error = ftruncate(fd, TILES_SIZE);
if (error) err(EX_IOERR, "ftruncate");

tiles = mmap(NULL, TILES_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (tiles == MAP_FAILED) err(EX_OSERR, "mmap");
tiles = mmap(NULL, TILES_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (tiles == MAP_FAILED) err(EX_OSERR, "mmap");

error = madvise(tiles, TILES_SIZE, MADV_RANDOM);
if (error) err(EX_OSERR, "madvise");
error = madvise(tiles, TILES_SIZE, MADV_RANDOM);
if (error) err(EX_OSERR, "madvise");

#ifdef MADV_NOCORE
error = madvise(tiles, TILES_SIZE, MADV_NOCORE);
if (error) err(EX_OSERR, "madvise");
error = madvise(tiles, TILES_SIZE, MADV_NOCORE);
if (error) err(EX_OSERR, "madvise");
#endif
}

static struct Tile *tileGet(uint32_t tileX, uint32_t tileY) {
struct Tile *tile = &tiles[tileY * TILE_ROWS + tileX];
if (!tile->createTime) {
memset(tile->cells, ' ', CELLS_SIZE);
memset(tile->colors, COLOR_WHITE, CELLS_SIZE);
tile->createTime = time(NULL);
}
return tile;
struct Tile *tile = &tiles[tileY * TILE_ROWS + tileX];
if (!tile->createTime) {
memset(tile->cells, ' ', CELLS_SIZE);
memset(tile->colors, COLOR_WHITE, CELLS_SIZE);
tile->createTime = time(NULL);
}
return tile;
}

static struct Tile *tileAccess(uint32_t tileX, uint32_t tileY) {
struct Tile *tile = tileGet(tileX, tileY);
tile->accessTime = time(NULL);
tile->accessCount++;
return tile;
struct Tile *tile = tileGet(tileX, tileY);
tile->accessTime = time(NULL);
tile->accessCount++;
return tile;
}

static struct Tile *tileModify(uint32_t tileX, uint32_t tileY) {
struct Tile *tile = tileGet(tileX, tileY);
tile->modifyTime = time(NULL);
tile->modifyCount++;
return tile;
struct Tile *tile = tileGet(tileX, tileY);
tile->modifyTime = time(NULL);
tile->modifyCount++;
return tile;
}

static struct Client {
int fd;
int fd;

uint32_t tileX;
uint32_t tileY;
uint8_t cellX;
uint8_t cellY;
uint32_t tileX;
uint32_t tileY;
uint8_t cellX;
uint8_t cellY;

struct Client *prev;
struct Client *next;
struct Client *prev;
struct Client *next;
} *clientHead;

static struct Client *clientAdd(int fd) {
struct Client *client = malloc(sizeof(*client));
if (!client) err(EX_OSERR, "malloc");
client->fd = fd;
client->tileX = TILE_VOID_X;
client->tileY = TILE_VOID_Y;
client->cellX = CELL_INIT_X;
client->cellY = CELL_INIT_Y;
client->prev = NULL;
if (clientHead) {
clientHead->prev = client;
client->next = clientHead;
} else {
client->next = NULL;
}
clientHead = client;
return client;
struct Client *client = malloc(sizeof(*client));
if (!client) err(EX_OSERR, "malloc");
client->fd = fd;
client->tileX = TILE_VOID_X;
client->tileY = TILE_VOID_Y;
client->cellX = CELL_INIT_X;
client->cellY = CELL_INIT_Y;
client->prev = NULL;
if (clientHead) {
clientHead->prev = client;
client->next = clientHead;
} else {
client->next = NULL;
}
clientHead = client;
return client;
}

static bool clientSend(const struct Client *client, struct ServerMessage msg) {
ssize_t size = send(client->fd, &msg, sizeof(msg), 0);
if (size < 0) return false;
ssize_t size = send(client->fd, &msg, sizeof(msg), 0);
if (size < 0) return false;

if (msg.type == SERVER_TILE) {
struct Tile *tile = tileAccess(client->tileX, client->tileY);
size = send(client->fd, tile, sizeof(*tile), 0);
if (size < 0) return false;
}
if (msg.type == SERVER_TILE) {
struct Tile *tile = tileAccess(client->tileX, client->tileY);
size = send(client->fd, tile, sizeof(*tile), 0);
if (size < 0) return false;
}

return true;
return true;
}

static void clientCast(const struct Client *origin, struct ServerMessage msg) {
for (struct Client *client = clientHead; client; client = client->next) {
if (client == origin) continue;
if (client->tileX != origin->tileX) continue;
if (client->tileY != origin->tileY) continue;
clientSend(client, msg);
}
for (struct Client *client = clientHead; client; client = client->next) {
if (client == origin) continue;
if (client->tileX != origin->tileX) continue;
if (client->tileY != origin->tileY) continue;
clientSend(client, msg);
}
}

static void clientRemove(struct Client *client) {
if (client->prev) client->prev->next = client->next;
if (client->next) client->next->prev = client->prev;
if (clientHead == client) clientHead = client->next;
struct ServerMessage msg = {
.type = SERVER_CURSOR,
.cursor = {
.oldCellX = client->cellX, .oldCellY = client->cellY,
.newCellX = CURSOR_NONE, .newCellY = CURSOR_NONE,
},
};
clientCast(client, msg);
close(client->fd);
free(client);
if (client->prev) client->prev->next = client->next;
if (client->next) client->next->prev = client->prev;
if (clientHead == client) clientHead = client->next;
struct ServerMessage msg = {
.type = SERVER_CURSOR,
.cursor = {
.oldCellX = client->cellX, .oldCellY = client->cellY,
.newCellX = CURSOR_NONE, .newCellY = CURSOR_NONE,
},
};
clientCast(client, msg);
close(client->fd);
free(client);
}

static bool clientCursors(const struct Client *client) {
struct ServerMessage msg = {
.type = SERVER_CURSOR,
.cursor = { .oldCellX = CURSOR_NONE, .oldCellY = CURSOR_NONE },
};
for (struct Client *friend = clientHead; friend; friend = friend->next) {
if (friend == client) continue;
if (friend->tileX != client->tileX) continue;
if (friend->tileY != client->tileY) continue;
msg.cursor.newCellX = friend->cellX;
msg.cursor.newCellY = friend->cellY;
if (!clientSend(client, msg)) return false;
}
return true;
struct ServerMessage msg = {
.type = SERVER_CURSOR,
.cursor = { .oldCellX = CURSOR_NONE, .oldCellY = CURSOR_NONE },
};
for (struct Client *friend = clientHead; friend; friend = friend->next) {
if (friend == client) continue;
if (friend->tileX != client->tileX) continue;
if (friend->tileY != client->tileY) continue;
msg.cursor.newCellX = friend->cellX;
msg.cursor.newCellY = friend->cellY;
if (!clientSend(client, msg)) return false;
}
return true;
}

static bool clientUpdate(const struct Client *client, const struct Client *old) {
struct ServerMessage msg = {
.type = SERVER_MOVE,
.move = { .cellX = client->cellX, .cellY = client->cellY },
};
if (!clientSend(client, msg)) return false;
if (client->tileX != old->tileX || client->tileY != old->tileY) {
msg.type = SERVER_TILE;
if (!clientSend(client, msg)) return false;
if (!clientCursors(client)) return false;
msg = (struct ServerMessage) {
.type = SERVER_CURSOR,
.cursor = {
.oldCellX = old->cellX, .oldCellY = old->cellY,
.newCellX = CURSOR_NONE, .newCellY = CURSOR_NONE,
},
};
clientCast(old, msg);
msg = (struct ServerMessage) {
.type = SERVER_CURSOR,
.cursor = {
.oldCellX = CURSOR_NONE, .oldCellY = CURSOR_NONE,
.newCellX = client->cellX, .newCellY = client->cellY,
},
};
clientCast(client, msg);
} else {
msg = (struct ServerMessage) {
.type = SERVER_CURSOR,
.cursor = {
.oldCellX = old->cellX, .oldCellY = old->cellY,
.newCellX = client->cellX, .newCellY = client->cellY,
},
};
clientCast(client, msg);
}
return true;
struct ServerMessage msg = {
.type = SERVER_MOVE,
.move = { .cellX = client->cellX, .cellY = client->cellY },
};
if (!clientSend(client, msg)) return false;
if (client->tileX != old->tileX || client->tileY != old->tileY) {
msg.type = SERVER_TILE;
if (!clientSend(client, msg)) return false;
if (!clientCursors(client)) return false;
msg = (struct ServerMessage) {
.type = SERVER_CURSOR,
.cursor = {
.oldCellX = old->cellX, .oldCellY = old->cellY,
.newCellX = CURSOR_NONE, .newCellY = CURSOR_NONE,
},
};
clientCast(old, msg);
msg = (struct ServerMessage) {
.type = SERVER_CURSOR,
.cursor = {
.oldCellX = CURSOR_NONE, .oldCellY = CURSOR_NONE,
.newCellX = client->cellX, .newCellY = client->cellY,
},
};
clientCast(client, msg);
} else {
msg = (struct ServerMessage) {
.type = SERVER_CURSOR,
.cursor = {
.oldCellX = old->cellX, .oldCellY = old->cellY,
.newCellX = client->cellX, .newCellY = client->cellY,
},
};
clientCast(client, msg);
}
return true;
}

static bool clientSpawn(struct Client *client, uint8_t spawn) {
if (spawn >= SPAWNS_LEN) return false;
struct Client old = *client;
client->tileX = SPAWNS[spawn].tileX;
client->tileY = SPAWNS[spawn].tileY;
client->cellX = CELL_INIT_X;
client->cellY = CELL_INIT_Y;
return clientUpdate(client, &old);
if (spawn >= SPAWNS_LEN) return false;
struct Client old = *client;
client->tileX = SPAWNS[spawn].tileX;
client->tileY = SPAWNS[spawn].tileY;
client->cellX = CELL_INIT_X;
client->cellY = CELL_INIT_Y;
return clientUpdate(client, &old);
}

static bool clientMove(struct Client *client, int8_t dx, int8_t dy) {
struct Client old = *client;
if (dx > CELL_COLS - client->cellX) dx = CELL_COLS - client->cellX;
if (dx < -client->cellX - 1) dx = -client->cellX - 1;
if (dy > CELL_ROWS - client->cellY) dy = CELL_ROWS - client->cellY;
if (dy < -client->cellY - 1) dy = -client->cellY - 1;
client->cellX += dx;
client->cellY += dy;
if (client->cellX == CELL_COLS) {
client->tileX++;
client->cellX = 0;
}
if (client->cellX == UINT8_MAX) {
client->tileX--;
client->cellX = CELL_COLS - 1;
}
if (client->cellY == CELL_ROWS) {
client->tileY++;
client->cellY = 0;
}
if (client->cellY == UINT8_MAX) {
client->tileY--;
client->cellY = CELL_ROWS - 1;
}
if (client->tileX == TILE_COLS) client->tileX = 0;
if (client->tileX == UINT32_MAX) client->tileX = TILE_COLS - 1;
if (client->tileY == TILE_ROWS) client->tileY = 0;
if (client->tileY == UINT32_MAX) client->tileY = TILE_ROWS - 1;
assert(client->cellX < CELL_COLS);
assert(client->cellY < CELL_ROWS);
assert(client->tileX < TILE_COLS);
assert(client->tileY < TILE_ROWS);
return clientUpdate(client, &old);
struct Client old = *client;
if (dx > CELL_COLS - client->cellX) dx = CELL_COLS - client->cellX;
if (dx < -client->cellX - 1) dx = -client->cellX - 1;
if (dy > CELL_ROWS - client->cellY) dy = CELL_ROWS - client->cellY;
if (dy < -client->cellY - 1) dy = -client->cellY - 1;
client->cellX += dx;
client->cellY += dy;
if (client->cellX == CELL_COLS) {
client->tileX++;
client->cellX = 0;
}
if (client->cellX == UINT8_MAX) {
client->tileX--;
client->cellX = CELL_COLS - 1;
}
if (client->cellY == CELL_ROWS) {
client->tileY++;
client->cellY = 0;
}
if (client->cellY == UINT8_MAX) {
client->tileY--;
client->cellY = CELL_ROWS - 1;
}
if (client->tileX == TILE_COLS) client->tileX = 0;
if (client->tileX == UINT32_MAX) client->tileX = TILE_COLS - 1;
if (client->tileY == TILE_ROWS) client->tileY = 0;
if (client->tileY == UINT32_MAX) client->tileY = TILE_ROWS - 1;
assert(client->cellX < CELL_COLS);
assert(client->cellY < CELL_ROWS);
assert(client->tileX < TILE_COLS);
assert(client->tileY < TILE_ROWS);
return clientUpdate(client, &old);
}

static bool clientPut(const struct Client *client, uint8_t color, char cell) {
struct Tile *tile = tileModify(client->tileX, client->tileY);
tile->colors[client->cellY][client->cellX] = color;
tile->cells[client->cellY][client->cellX] = cell;
struct ServerMessage msg = {
.type = SERVER_PUT,
.put = {
.cellX = client->cellX,
.cellY = client->cellY,
.color = color,
.cell = cell,
},
};
bool success = clientSend(client, msg);
clientCast(client, msg);
return success;
struct Tile *tile = tileModify(client->tileX, client->tileY);
tile->colors[client->cellY][client->cellX] = color;
tile->cells[client->cellY][client->cellX] = cell;
struct ServerMessage msg = {
.type = SERVER_PUT,
.put = {
.cellX = client->cellX,
.cellY = client->cellY,
.color = color,
.cell = cell,
},
};
bool success = clientSend(client, msg);
clientCast(client, msg);
return success;
}

int main() {
int error;
tilesMap();
int server = socket(PF_LOCAL, SOCK_STREAM, 0);
if (server < 0) err(EX_OSERR, "socket");
error = unlink("torus.sock");
if (error && errno != ENOENT) err(EX_IOERR, "torus.sock");
struct sockaddr_un addr = {
.sun_family = AF_LOCAL,
.sun_path = "torus.sock",
};
error = bind(server, (struct sockaddr *)&addr, sizeof(addr));
if (error) err(EX_CANTCREAT, "torus.sock");
error = listen(server, 0);
if (error) err(EX_OSERR, "listen");
int kq = kqueue();
if (kq < 0) err(EX_OSERR, "kqueue");
struct kevent event = {
.ident = server,
.filter = EVFILT_READ,
.flags = EV_ADD,
};
int nevents = kevent(kq, &event, 1, NULL, 0, NULL);
if (nevents < 0) err(EX_OSERR, "kevent");
for (;;) {
nevents = kevent(kq, NULL, 0, &event, 1, NULL);
if (nevents < 0) err(EX_IOERR, "kevent");
if (!event.udata) {
int fd = accept(server, NULL, NULL);
if (fd < 0) err(EX_IOERR, "accept");
fcntl(fd, F_SETFL, O_NONBLOCK);
int on = 1;
error = setsockopt(fd, SOL_SOCKET, SO_NOSIGPIPE, &on, sizeof(on));
if (error) err(EX_IOERR, "setsockopt");
struct Client *client = clientAdd(fd);
struct kevent event = {
.ident = fd,
.filter = EVFILT_READ,
.flags = EV_ADD,
.udata = client,
};
nevents = kevent(kq, &event, 1, NULL, 0, NULL);
if (nevents < 0) err(EX_IOERR, "kevent");
if (!clientSpawn(client, 0)) clientRemove(client);
continue;
}
struct Client *client = event.udata;
if (event.flags & EV_EOF) {
clientRemove(client);
continue;
}
struct ClientMessage msg;
ssize_t size = recv(client->fd, &msg, sizeof(msg), 0);
if (size != sizeof(msg)) {
clientRemove(client);
continue;
}
bool success = false;
if (msg.type == CLIENT_MOVE) {
success = clientMove(client, msg.move.dx, msg.move.dy);
} else if (msg.type == CLIENT_PUT) {
success = clientPut(client, msg.put.color, msg.put.cell);
} else if (msg.type == CLIENT_SPAWN) {
success = clientSpawn(client, msg.spawn);
}
if (!success) clientRemove(client);
}
int error;
tilesMap();
int server = socket(PF_LOCAL, SOCK_STREAM, 0);
if (server < 0) err(EX_OSERR, "socket");
error = unlink("torus.sock");
if (error && errno != ENOENT) err(EX_IOERR, "torus.sock");
struct sockaddr_un addr = {
.sun_family = AF_LOCAL,
.sun_path = "torus.sock",
};
error = bind(server, (struct sockaddr *)&addr, sizeof(addr));
if (error) err(EX_CANTCREAT, "torus.sock");
error = listen(server, 0);
if (error) err(EX_OSERR, "listen");
int kq = kqueue();
if (kq < 0) err(EX_OSERR, "kqueue");
struct kevent event = {
.ident = server,
.filter = EVFILT_READ,
.flags = EV_ADD,
};
int nevents = kevent(kq, &event, 1, NULL, 0, NULL);
if (nevents < 0) err(EX_OSERR, "kevent");
for (;;) {
nevents = kevent(kq, NULL, 0, &event, 1, NULL);
if (nevents < 0) err(EX_IOERR, "kevent");
if (!event.udata) {
int fd = accept(server, NULL, NULL);
if (fd < 0) err(EX_IOERR, "accept");
fcntl(fd, F_SETFL, O_NONBLOCK);
int on = 1;
error = setsockopt(fd, SOL_SOCKET, SO_NOSIGPIPE, &on, sizeof(on));
if (error) err(EX_IOERR, "setsockopt");
struct Client *client = clientAdd(fd);
struct kevent event = {
.ident = fd,
.filter = EVFILT_READ,
.flags = EV_ADD,
.udata = client,
};
nevents = kevent(kq, &event, 1, NULL, 0, NULL);
if (nevents < 0) err(EX_IOERR, "kevent");
if (!clientSpawn(client, 0)) clientRemove(client);
continue;
}
struct Client *client = event.udata;
if (event.flags & EV_EOF) {
clientRemove(client);
continue;
}
struct ClientMessage msg;
ssize_t size = recv(client->fd, &msg, sizeof(msg), 0);
if (size != sizeof(msg)) {
clientRemove(client);
continue;
}
bool success = false;
if (msg.type == CLIENT_MOVE) {
success = clientMove(client, msg.move.dx, msg.move.dy);
} else if (msg.type == CLIENT_PUT) {
success = clientPut(client, msg.put.color, msg.put.cell);
} else if (msg.type == CLIENT_SPAWN) {
success = clientSpawn(client, msg.spawn);
}
if (!success) clientRemove(client);
}
}

+ 9
- 9
sshd_config View File

@@ -1,13 +1,13 @@
UsePAM no

Match User torus
PasswordAuthentication yes
PermitEmptyPasswords yes
ChrootDirectory /home/torus
ForceCommand client
PasswordAuthentication yes
PermitEmptyPasswords yes
ChrootDirectory /home/torus
ForceCommand client

AllowAgentForwarding no
AllowTcpForwarding no
AllowStreamLocalForwarding no
MaxSessions 1
X11Forwarding no
AllowAgentForwarding no
AllowTcpForwarding no
AllowStreamLocalForwarding no
MaxSessions 1
X11Forwarding no

+ 69
- 69
torus.h View File

@@ -1,4 +1,4 @@
/* Copyright (c) 2017, Curtis McEnroe <curtis@cmcenroe.me>
/* Copyright (C) 2017 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
@@ -34,20 +34,20 @@
#undef COLOR_WHITE

enum {
COLOR_BLACK,
COLOR_RED,
COLOR_GREEN,
COLOR_YELLOW,
COLOR_BLUE,
COLOR_MAGENTA,
COLOR_CYAN,
COLOR_WHITE,
COLOR_BRIGHT,
COLOR_BLACK,
COLOR_RED,
COLOR_GREEN,
COLOR_YELLOW,
COLOR_BLUE,
COLOR_MAGENTA,
COLOR_CYAN,
COLOR_WHITE,
COLOR_BRIGHT,
};

enum {
CELL_ROWS = 25,
CELL_COLS = 80,
CELL_ROWS = 25,
CELL_COLS = 80,
};
static const size_t CELLS_SIZE = sizeof(char[CELL_ROWS][CELL_COLS]);

@@ -55,21 +55,21 @@ static const uint8_t CELL_INIT_X = CELL_COLS / 2;
static const uint8_t CELL_INIT_Y = CELL_ROWS / 2;

struct ALIGNED(4096) Tile {
time_t createTime;
time_t modifyTime;
char ALIGNED(16) cells[CELL_ROWS][CELL_COLS];
uint8_t ALIGNED(16) colors[CELL_ROWS][CELL_COLS];
uint32_t modifyCount;
uint32_t accessCount;
time_t accessTime;
time_t createTime;
time_t modifyTime;
char ALIGNED(16) cells[CELL_ROWS][CELL_COLS];
uint8_t ALIGNED(16) colors[CELL_ROWS][CELL_COLS];
uint32_t modifyCount;
uint32_t accessCount;
time_t accessTime;
};
static_assert(4096 == sizeof(struct Tile), "struct File is page-sized");
static_assert(4096 == sizeof(struct Tile), "struct Tile is page-sized");
static_assert(16 == offsetof(struct Tile, cells), "stable cells offset");
static_assert(2016 == offsetof(struct Tile, colors), "stable colors offset");

enum {
TILE_ROWS = 512,
TILE_COLS = 512,
TILE_ROWS = 512,
TILE_COLS = 512,
};
static const size_t TILES_SIZE = sizeof(struct Tile[TILE_ROWS][TILE_COLS]);

@@ -77,61 +77,61 @@ static const uint32_t TILE_VOID_X = UINT32_MAX;
static const uint32_t TILE_VOID_Y = UINT32_MAX;

static const struct {
uint32_t tileX;
uint32_t tileY;
uint32_t tileX;
uint32_t tileY;
} SPAWNS[] = {
{ 0, 0 },
{ TILE_COLS * 3 / 4, TILE_ROWS * 3 / 4 }, // NW
{ TILE_COLS * 1 / 4, TILE_ROWS * 3 / 4 }, // NE
{ TILE_COLS * 1 / 4, TILE_ROWS * 1 / 4 }, // SE
{ TILE_COLS * 3 / 4, TILE_ROWS * 1 / 4 }, // SW
{ 0, 0 },
{ TILE_COLS * 3 / 4, TILE_ROWS * 3 / 4 }, // NW
{ TILE_COLS * 1 / 4, TILE_ROWS * 3 / 4 }, // NE
{ TILE_COLS * 1 / 4, TILE_ROWS * 1 / 4 }, // SE
{ TILE_COLS * 3 / 4, TILE_ROWS * 1 / 4 }, // SW
};
static const size_t SPAWNS_LEN = sizeof(SPAWNS) / sizeof(SPAWNS[0]);

struct ServerMessage {
enum PACKED {
SERVER_TILE,
SERVER_MOVE,
SERVER_PUT,
SERVER_CURSOR,
} type;
union {
struct {
uint8_t cellX;
uint8_t cellY;
} move;
struct {
uint8_t cellX;
uint8_t cellY;
uint8_t color;
char cell;
} put;
struct {
uint8_t oldCellX;
uint8_t oldCellY;
uint8_t newCellX;
uint8_t newCellY;
} cursor;
};
enum PACKED {
SERVER_TILE,
SERVER_MOVE,
SERVER_PUT,
SERVER_CURSOR,
} type;
union {
struct {
uint8_t cellX;
uint8_t cellY;
} move;
struct {
uint8_t cellX;
uint8_t cellY;
uint8_t color;
char cell;
} put;
struct {
uint8_t oldCellX;
uint8_t oldCellY;
uint8_t newCellX;
uint8_t newCellY;
} cursor;
};
};

static const uint8_t CURSOR_NONE = UINT8_MAX;

struct ClientMessage {
enum PACKED {
CLIENT_MOVE,