aboutsummaryrefslogtreecommitdiff
path: root/norns_shell.c
diff options
context:
space:
mode:
authorgretchen <gretchen@gnar.cool>2019-11-17 19:40:36 -0800
committergretchen <gretchen@gnar.cool>2019-11-17 19:40:36 -0800
commit51bb17e9d13796c83b3a6e96f71f38263f6b7b4f (patch)
tree15ae9f16b56a093bc1d01723ed730d0a0dd14d56 /norns_shell.c
downloadnorns-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.c183
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;
+}
+