diff --git a/users/sterni/blipqn/.gitignore b/users/sterni/blipqn/.gitignore new file mode 100644 index 000000000..cc7836862 --- /dev/null +++ b/users/sterni/blipqn/.gitignore @@ -0,0 +1,2 @@ +*.so +*.o diff --git a/users/sterni/blipqn/GNUmakefile b/users/sterni/blipqn/GNUmakefile new file mode 100644 index 000000000..273a69e8f --- /dev/null +++ b/users/sterni/blipqn/GNUmakefile @@ -0,0 +1,24 @@ +DESTDIR ?= +PREFIX ?= /usr +LIBDIR ?= $(DESTDIR)$(PREFIX)/lib +BINDIR ?= $(DESTDIR)$(PREFIX)/bin + +CFLAGS ?= -Os +CFLAGS += -Wall -Wextra +LIBNAME = libflipdot.so + +$(LIBNAME): flipdot.o + $(CC) -shared -o $@ $^ + +.PHONY: clean fmt install +clean: + rm -f *.o + rm -f $(LIBNAME) + +fmt: + clang-format -style=google -i *.c + +install: $(LIBNAME) + install -Dm755 $(LIBNAME) -t $(LIBDIR) + install -Dm644 blipqn.bqn -t $(LIBDIR) + install -Dm755 examples.bqn $(BINDIR)/blipqn-examples diff --git a/users/sterni/blipqn/blipqn.bqn b/users/sterni/blipqn/blipqn.bqn new file mode 100644 index 000000000..a9d904ca0 --- /dev/null +++ b/users/sterni/blipqn/blipqn.bqn @@ -0,0 +1,20 @@ +MakeFlipdot⇐ + +# - host (char*) argument: +# - Assume char is i8 (safe even if it's actually u8 if +# ASCII). We can't let •FFI check this for us since CBQN doesn't support +# specifying c7. We do this in MakeFlipdot +# - Needs to be zero-terminated, we also do this in MakeFlipdot. +# - bitmap_len (size_t) argument: assume size_t is at least u32. +ffi_send_to_flipdot ← "libflipdot.so" •FFI "i8"‿"send_to_flipdot"‿"*i8:c8"‿"u16"‿"*u8"‿"u32" + +Compact ← +´∘(⌽ × 2⊸⋆∘↕∘≠)˘∘(∘‿8⊸⥊) + +MakeFlipdot ← { + host‿port 𝕊 w‿h: + "Total pixel count must be divisible by 8" ! 0=8|w×h + "Hostname must be ASCII" ! ∧´(@+127)≥host + shape ⇐ h‿w + empty ⇐ shape⥊0 + Send ⇐ {"sendto(2) error"!FFI_send_to_flipdot (host∾@)‿port∾(⊢⋈≠) Compact 𝕩} +} diff --git a/users/sterni/blipqn/default.nix b/users/sterni/blipqn/default.nix new file mode 100644 index 000000000..028c45a0f --- /dev/null +++ b/users/sterni/blipqn/default.nix @@ -0,0 +1,26 @@ +{ pkgs, lib, ... }: + +let + inherit (pkgs) llvmPackages; + drv = llvmPackages.stdenv.mkDerivation { + name = "blipqn"; + + src = lib.cleanSource ./.; + + makeFlags = [ "PREFIX=$(out)" ]; + + nativeBuildInputs = [ + llvmPackages.clang-tools + ]; + + buildInputs = [ + pkgs.cbqn + ]; + + passthru.debug = drv.overrideAttrs (old: { + CFLAGS = "-g -Werror"; + }); + }; +in + +drv diff --git a/users/sterni/blipqn/examples.bqn b/users/sterni/blipqn/examples.bqn new file mode 100755 index 000000000..85c5b479e --- /dev/null +++ b/users/sterni/blipqn/examples.bqn @@ -0,0 +1,29 @@ +#!/usr/bin/env BQN + +⟨MakeFlipdot⟩ ⇐ •Import "../lib/"⊸∾⍟("bin" (⊣≡(-∘≠⊸↑)) •path) "blipqn.bqn" + +examples ← ⍉>⟨ +"random"‿{𝕩.Send 𝕩.shape •rand.Range 2}, +"235"‿{𝕩.Send 1˙⌾(4‿22⊸⊑) 𝕩.empty}, +⟩ + +usage ← "Usage: "∾•name∾" HOST PORT WIDTH HEIGHT EXAMPLE"∾" + + where EXAMPLE is one of + +"∾∾{(7↑"")∾"- "∾𝕩∾@+10}¨⊏examples + +# TODO(sterni): this is an atrocious interface +opts ← { + (•Exit 1˙∘•Out)⍟(5⊸≠≠•args) usage + host‿port‿width‿height‿example ← •args + + example ⇐ + addr ⇐ host⋈•ParseFloat port + dim ⇐ •ParseFloat¨ width‿height +} + +action ← examples (⊏⊸(⊑∘⊐) ⊑ ⊏˜⟜1)⟜< opts.example + +flip ← opts.addr MakeFlipdot opts.dim +Action flip diff --git a/users/sterni/blipqn/flipdot.c b/users/sterni/blipqn/flipdot.c new file mode 100644 index 000000000..1bdd06c8f --- /dev/null +++ b/users/sterni/blipqn/flipdot.c @@ -0,0 +1,47 @@ +#define _DEFAULT_SOURCE // see getnameinfo(3), for NI_MAX* +#define _POSIX_C_SOURCE 200112L // see getaddrinfo(3) +#include +#include +#include +#include +#include +#include +#include + +int resolve_addr(char *host, char *port, struct addrinfo **addrs) { + struct addrinfo hints; + memset(&hints, 0, sizeof(hints)); + + hints.ai_socktype = SOCK_DGRAM; + hints.ai_family = AF_UNSPEC; + hints.ai_flags |= AI_NUMERICSERV; + + return getaddrinfo(host, port, &hints, addrs); +} + +// Send given bytes[len] to the host:port via UDP, returns 1 if all bytes +// where sent and no locally detectable errors occurred. +int8_t send_to_flipdot(char *host, in_port_t port_number, uint8_t *bitmap, + size_t bitmap_len) { + char port[NI_MAXSERV]; + int sockfd = -1; + ssize_t sent = 0; + struct addrinfo *addrs = NULL; + + if (snprintf(port, sizeof port, "%d", port_number) < 0) goto error; + + if (resolve_addr(host, port, &addrs) != 0) goto error; + + sockfd = socket(addrs->ai_family, SOCK_DGRAM, IPPROTO_UDP); + if (sockfd < 0) goto error; + + sent = + sendto(sockfd, bitmap, bitmap_len, 0, addrs->ai_addr, addrs->ai_addrlen); + + if (sent != (ssize_t)bitmap_len) goto error; + +error: + if (addrs != NULL) freeaddrinfo(addrs); + if (sockfd >= 0) close(sockfd); + return (sent == (ssize_t)bitmap_len); +}