#include #include #include #include #include #include #include #include #include #define MAX_LINE 1024 int killed = 0; void handle_signal(int sig_num) { killed = 1; } int main (int argc, char **argv) { char *endpoint = "ws://norns.local:5555"; int exit = 0; int s = 0; signal(SIGINT, handle_signal); signal(SIGTERM, handle_signal); /* Configure the websocket endpoint. */ if (argc > 2 || (argc == 2 && (!strcmp(argv[1], "--help") || !strcmp(argv[1], "-h")))) { fprintf(stderr, "Get a lua or supercollider shell on a norns.\n" "Usage: %s [ws://norns.local:5555]\n", argv[0]); goto error; } if (argc == 2) endpoint = argv[1]; /* connect to the norns; use text, like it expects. */ if ((s = nn_socket(AF_SP, NN_BUS)) < 0) { fprintf(stderr, "Can't create socket: %m\n"); goto error; } if (nn_connect(s, endpoint) < 0) { fprintf(stderr, "Can't connect to %s: %m\n", endpoint); goto error; } if (nn_setsockopt(s, NN_WS, NN_WS_MSG_TYPE, &(int) {NN_WS_MSG_TYPE_TEXT}, sizeof(int)) < 0) { fprintf(stderr, "Couldn't switch to text mode: %m\n"); goto error; } /* since nanomsg has its own poll framework, we'll read to this buffer in a nonblocking way until we get a newline. */ size_t line_n = 0; char line[MAX_LINE + 1] = {0}; int fl = 0; if ((fl = fcntl(STDIN_FILENO, F_GETFL)) < 0) { fprintf(stderr, "Couldn't read stdin fd flags: %m\n"); goto error; } if (fcntl(STDIN_FILENO, F_SETFL, fl | O_NONBLOCK)) { fprintf(stderr, "Couldn't set stdin non-blocking: %m\n"); goto error; } /* NN_BUS doesn't guarantee delivery, unfortunately. in practice on a LAN this means that the first couple of messages are dropped while the connection is being set up. this seems strange to me (it seems like you'd have to go out of your way to drop messages in a websocket handler?) and I'm not sure this is the right fit for norns, but that's how it is. we partially solve this issue by sending an empty comment -- as a sentinel -- repeatedly until we get the corresponding . */ /* TODO: time out after three seconds or something. */ int received = 0; struct timespec sent_sentinel = {0}; /* we'll also wait for one message after EOF... */ int sent_last = 0; struct nn_pollfd poll_s = { .fd = s, .events = NN_POLLIN | NN_POLLOUT }; while (!killed) { switch (nn_poll(&poll_s, 1, 1000)) { case -1: fprintf(stderr, "error polling: %m\n"); goto error; case 0: fprintf(stderr, "# timeout...\n"); break; case 1: /* If we can read, echo out the message (unless it's the response to the sentinel, which we handle differently). */ if (poll_s.revents & NN_POLLIN) { char *buf = NULL; size_t n = 0; if ((n = nn_recv(s, &buf, NN_MSG, 0)) < 0) { fprintf(stderr, "Couldn't receive: %m\n"); goto error; } /* swallow the first */ if (!received && !strncmp(buf, "\n\n", n)) { fprintf(stderr, "# it lives!\n"); } else { /* strip final newline, if there are two... */ if (n >= 2 && buf[n - 1] == '\n' && buf[n - 2] == '\n') n -= 1; if (write(STDOUT_FILENO, buf, n) < n) { fprintf(stderr, "Couldn't write: %m\n"); goto error; } if (sent_last) goto finish; } nn_freemsg(buf); received = 1; } /* If we can write, see if there's a full line. */ if (poll_s.revents & NN_POLLOUT) { if (!received) { /* only do this five times a second... */ struct timespec now = {0}; if (clock_gettime(CLOCK_MONOTONIC, &now)) { fprintf(stderr, "Can't use the clock? %m\n"); goto error; } /* first bit syncs with the clock... */ if ((now.tv_sec - sent_sentinel.tv_sec <= 1) && (now.tv_nsec - sent_sentinel.tv_nsec <= (1000000000 / 5))) break; memcpy(&sent_sentinel, &now, sizeof(struct timespec)); /* send the sentinel comment */ if (nn_send(s, "--\n", 3, 0) != 3) { fprintf(stderr, "Couldn't send: %m\n"); goto error; } } else { int n = 0; if ((n = read(STDIN_FILENO, &line[line_n], MAX_LINE - line_n)) < 0) { if (errno != EAGAIN) { fprintf(stderr, "Can't read: %m\n"); goto error; } } if (n == 0) sent_last = 1; /* treat EAGAIN as "read 0" */ if (n < 1) n = 0; line_n += n; if (line_n >= 1 && line[line_n - 1] == '\n') { if ((nn_send(s, line, line_n, 0)) != line_n) { fprintf(stderr, "Can't send: %m\n"); goto error; } line_n = 0; } if (line_n == MAX_LINE) { fprintf(stderr, "Line too long :(.\n"); goto error; } } } break; } } goto finish; error: exit = 1; finish: if (s > 0) nn_close(s); return exit; }