diff options
author | gretchen <gretchen@gnar.cool> | 2019-11-17 19:40:36 -0800 |
---|---|---|
committer | gretchen <gretchen@gnar.cool> | 2019-11-17 19:40:36 -0800 |
commit | 51bb17e9d13796c83b3a6e96f71f38263f6b7b4f (patch) | |
tree | 15ae9f16b56a093bc1d01723ed730d0a0dd14d56 /norns_shell.c | |
download | norns-etc-51bb17e9d13796c83b3a6e96f71f38263f6b7b4f.tar.gz norns-etc-51bb17e9d13796c83b3a6e96f71f38263f6b7b4f.zip |
Get a lua or sc shell on a norns.
Diffstat (limited to 'norns_shell.c')
-rw-r--r-- | norns_shell.c | 183 |
1 files changed, 183 insertions, 0 deletions
diff --git a/norns_shell.c b/norns_shell.c new file mode 100644 index 0000000..fbd556c --- /dev/null +++ b/norns_shell.c @@ -0,0 +1,183 @@ +#include <time.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <fcntl.h> +#include <signal.h> + +#include <nanomsg/nn.h> +#include <nanomsg/bus.h> +#include <nanomsg/ws.h> + +#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 + <ok>. + */ + /* TODO: time out after three seconds or something. */ + int received = 0; + struct timespec sent_sentinel = {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 <ok> */ + if (!received && !strncmp(buf, "<ok>\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; + } + } + 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; + } + /* treat EAGAIN as "read 0" */ + 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, "Couldn'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; +} + |