123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394 |
- #include <stdio.h>
- #include <string.h>
- #include <stdlib.h>
- #include <unistd.h>
- #include <sys/types.h>
- #include <sys/socket.h>
- #include <netdb.h>
- #include <arpa/inet.h>
- #include <netinet/in.h>
- #include <errno.h>
- #include <signal.h>
- #include <ncurses.h>
- #include "common.h"
- #include "network.h"
- #define ctrl(key) (0x1f & (key))
- #define ORIGIN_X 1
- #define ORIGIN_Y 2
- #define clamp_yx(y, x) \
- do { \
- if ((y) <= ORIGIN_Y) \
- y = ORIGIN_Y+1; \
- if ((x) <= ORIGIN_X) \
- x = ORIGIN_X+1; \
- if ((x) > ORIGIN_X+WIDTH) \
- x = ORIGIN_X+WIDTH; \
- if ((y) > ORIGIN_Y+HEIGHT) \
- y = ORIGIN_Y+HEIGHT; \
- } while(0)
- char *g_host, *g_port;
- int g_sockfd;
- int toosmall;
- volatile sig_atomic_t running;
- void int_handler(int s) {
- (void)s;
- running = false;
- }
- int connect_socket(char *node, char *service) {
- int sockfd;
- struct addrinfo hints, *result, *rp;
- int s;
- memset(&hints, 0, sizeof(hints));
- hints.ai_family = AF_UNSPEC;
- hints.ai_socktype = SOCK_STREAM;
- if ((s = getaddrinfo(node, service, &hints, &result)) != 0) {
- fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(s));
- exit(EXIT_FAILURE);
- }
- for (rp = result; rp != NULL; rp = rp->ai_next) {
- if ((sockfd = socket(rp->ai_family, rp->ai_socktype,
- rp->ai_protocol)) == -1) {
- perror("socket");
- continue;
- }
- if (connect(sockfd, rp->ai_addr, rp->ai_addrlen) == -1) {
- close(sockfd);
- perror("connect");
- continue;
- }
- break;
- }
- if (rp == NULL) {
- exit(EXIT_FAILURE);
- }
- freeaddrinfo(result);
- g_host = node;
- g_port = service;
- return sockfd;
- }
- void send_hello() {
- uint8_t packet[3];
- *((uint16_t*)packet) = htons(3);
- packet[2] = PKT_TYPE_HELLO;
- if (send_packet(g_sockfd, packet, sizeof packet) < (sizeof packet)) {
- perror("send_packet");
- exit(EXIT_FAILURE);
- }
- }
- char FIELD[(WIDTH+1)*HEIGHT];
- /**
- * Put a character in the local buffer and put it on screen, if it is currently
- * not toosmall. The cursor position is clobbered.
- *
- * x and y are in screen coordinates
- */
- void put_char_raw(int y, int x, char ch) {
- clamp_yx(y, x);
- if (!toosmall)
- mvaddch(y, x, ch);
- y = y - ORIGIN_Y - 1;
- x = x - ORIGIN_X - 1;
- FIELD[y*(WIDTH+1)+x] = ch;
- }
- /**
- * Calls put_char_raw with translated coordinates and prevents the cursor
- * from moving.
- * Called on received network events.
- *
- * x and y are in buffer coordinates
- */
- void put_char_net(int y, int x, char ch) {
- // Translate to screen coordinates
- y = y + ORIGIN_Y + 1;
- x = x + ORIGIN_X + 1;
- int oldy, oldx;
- // Position of the cursor is stored, as put_char_raw moves it.
- getyx(stdscr, oldy, oldx);
- put_char_raw(y, x, ch);
- move(oldy, oldx);
- }
- /**
- * Calls put_char_raw and sends event over network. Prevents cursor from moving.
- * Called on locally generated events (user interactions).
- * Does nothing if toosmall is set, in order to prevent user from doing
- * what they can't see.
- *
- * x and y are in screen coordinates.
- */
- void put_char_usr(int y, int x, char ch) {
- if (toosmall)
- return;
- put_char_raw(y, x, ch);
- // Prevent cursor from moving. While this is contraproductive in some cases,
- // for example drawing vertical lines becomes slightly easier.
- // A better solution would be to enable to user to select one of several
- // movement modes.
- move(y, x);
- struct packet_update_t packet = {
- .size = htons(sizeof(struct packet_update_t)),
- .type = PKT_TYPE_UPDATE,
- .y = (uint8_t)(y-ORIGIN_Y-1),
- .x = (uint8_t)(x-ORIGIN_X-1),
- .ch = ch
- };
- size_t len = sizeof(struct packet_update_t);
- if (send_packet(g_sockfd, (uint8_t*)&packet, len) < len) {
- perror("send_packet");
- exit(EXIT_FAILURE);
- }
- }
- void redraw_field() {
- for (int i = 0; i < HEIGHT; ++i) {
- FIELD[i*(WIDTH+1)+WIDTH] = 0;
- mvprintw(ORIGIN_Y+i+1, ORIGIN_X+1, "%s", &FIELD[i*(WIDTH+1)]);
- }
- }
- void configure_curses() {
- initscr();
- cbreak();
- keypad(stdscr, TRUE);
- noecho();
- nodelay(stdscr, TRUE);
- }
- void draw_rectangle(int y1, int x1, int y2, int x2) {
- mvhline(y1, x1, 0, x2-x1);
- mvhline(y2, x1, 0, x2-x1);
- mvvline(y1, x1, 0, y2-y1);
- mvvline(y1, x2, 0, y2-y1);
- mvaddch(y1, x1, ACS_ULCORNER);
- mvaddch(y1, x2, ACS_URCORNER);
- mvaddch(y2, x1, ACS_LLCORNER);
- mvaddch(y2, x2, ACS_LRCORNER);
- }
- int clamped_move(int y, int x) {
- if (toosmall)
- return 0;
- clamp_yx(y, x);
- return move(y, x);
- }
- void redraw_all() {
- int maxy, maxx;
- getmaxyx(stdscr, maxy, maxx);
- if (maxy < ORIGIN_Y+HEIGHT+2 || maxx < ORIGIN_X+WIDTH+2) {
- clear();
- printw("Terminal too small.");
- toosmall = true;
- return;
- } else {
- toosmall = false;
- }
- int y, x;
- getyx(stdscr, y, x);
- clear();
- draw_rectangle(ORIGIN_Y, ORIGIN_X, ORIGIN_Y+HEIGHT+1, ORIGIN_X+WIDTH+1);
- mvprintw(0, 0, "Connected to %s:%s.", g_host, g_port);
- redraw_field();
- clamped_move(y, x);
- refresh();
- }
- void process_stdin() {
- int ch = getch();
- if (ch == ERR)
- return;
- int y, x;
- getyx(stdscr, y, x);
- if (ch >= 0x20 && ch < 0x7f) {
- put_char_usr(y, x, ch);
- } else switch (ch) {
- case KEY_UP:
- clamped_move(y-1, x);
- break;
- case KEY_DOWN:
- clamped_move(y+1, x);
- break;
- case KEY_LEFT:
- clamped_move(y, x-1);
- break;
- case KEY_RIGHT:
- clamped_move(y, x+1);
- break;
- case KEY_HOME:
- clamped_move(y, 0);
- break;
- case KEY_END:
- clamped_move(y, ORIGIN_X+WIDTH);
- break;
- case KEY_PPAGE:
- clamped_move(0, x);
- break;
- case KEY_NPAGE:
- clamped_move(ORIGIN_Y+HEIGHT, x);
- break;
- case KEY_RESIZE:
- case ctrl('l'):
- redraw_all();
- break;
- case ctrl('r'):
- send_hello();
- break;
- case ctrl('x'):
- running = false;
- return;
- default:
- break;
- }
- refresh();
- }
- void process_network() {
- uint8_t buf[PKT_SIZE_MAX];
- size_t len = sizeof buf;
- int type = recv_packet(g_sockfd, buf, &len);
- if (type < 0) {
- perror("recv_packet");
- exit(EXIT_FAILURE);
- }
- if (type == 0) {
- fprintf(stderr, "Connection closed by server.\n");
- running = false;
- return;
- }
- if (type == PKT_TYPE_UPDATE) {
- struct packet_update_t *pkt = (struct packet_update_t *)buf;
- if (len < sizeof(struct packet_update_t)) {
- fprintf(stderr, "Received truncated packet.\n");
- exit(EXIT_FAILURE);
- }
- put_char_net(pkt->y, pkt->x, pkt->ch);
- refresh();
- } else if (type == PKT_TYPE_INIT) {
- struct packet_init_t *pkt = (struct packet_init_t *)buf;
- if (len < sizeof(struct packet_init_t)) {
- fprintf(stderr, "Received truncated packet.\n");
- exit(EXIT_FAILURE);
- }
- memcpy(FIELD, pkt->data, sizeof FIELD);
- redraw_all();
- }
- }
- void cleanup() {
- if (!isendwin())
- endwin();
- }
- int main(int argc, char *argv[]) {
- if (argc != 3 && argc != 2) {
- fprintf(stderr, "USAGE: %s SERVER [PORT]\n", argv[0]);
- exit(EXIT_FAILURE);
- }
- memset(FIELD, ' ', (WIDTH+1)*HEIGHT);
- for (int i = 0; i < HEIGHT; ++i) {
- FIELD[i*(WIDTH+1)+WIDTH] = '\0';
- }
- char *port;
- if (argc == 2)
- port = PORT;
- if (argc == 3)
- port = argv[2];
- g_sockfd = connect_socket(argv[1], port);
- send_hello();
- configure_curses();
- redraw_all();
- atexit(cleanup);
- fd_set fds_main, rfds;
- FD_ZERO(&fds_main);
- FD_SET(STDIN_FILENO, &fds_main);
- FD_SET(g_sockfd, &fds_main);
- int maxfd = (STDIN_FILENO > g_sockfd) ? STDIN_FILENO : g_sockfd;
- running = true;
- struct sigaction act;
- act.sa_handler = int_handler;
- if (sigaction(SIGINT, &act, NULL) == -1) {
- perror("sigaction");
- }
- process_stdin();
- while (running)
- {
- rfds = fds_main;
- if (select(maxfd+1, &rfds, NULL, NULL, NULL) == -1) {
- if (errno == EINTR) {
- // e.g. SIGWINCH pushes a KEY_RESIZE to the ncurses
- // input queue and needs to be processed.
- // We are using nodelay, so this does not block in case of
- // other signals or ncurses deciding not to generate
- // a KEY_RESIZE for whatever reason.
- process_stdin();
- continue;
- }
- perror("select");
- exit(EXIT_FAILURE);
- } else {
- if (FD_ISSET(g_sockfd, &rfds)) {
- process_network();
- }
- if (FD_ISSET(STDIN_FILENO, &rfds)) {
- process_stdin();
- }
- }
- }
- endwin();
- puts("Shutting down");
- if (shutdown(g_sockfd, SHUT_WR) == -1) {
- perror("shutdown");
- exit(EXIT_FAILURE);
- }
- if (recv_dump_all(g_sockfd) == -1) {
- perror("recv_dump_all");
- exit(EXIT_FAILURE);
- }
- close(g_sockfd);
- return EXIT_SUCCESS;
- }
|