diff --git a/usr.bin/Makefile b/usr.bin/Makefile --- a/usr.bin/Makefile +++ b/usr.bin/Makefile @@ -172,6 +172,7 @@ unvis \ vis \ vmstat \ + vtspeakd \ w \ wall \ wc \ diff --git a/usr.bin/vtspeakd/Makefile b/usr.bin/vtspeakd/Makefile new file mode 100644 --- /dev/null +++ b/usr.bin/vtspeakd/Makefile @@ -0,0 +1,6 @@ +# $FreeBSD$ + +PROG= vtspeakd +MAN= # + +.include diff --git a/usr.bin/vtspeakd/vtspeakd.c b/usr.bin/vtspeakd/vtspeakd.c new file mode 100644 --- /dev/null +++ b/usr.bin/vtspeakd/vtspeakd.c @@ -0,0 +1,375 @@ +/*- + * Copyright (c) 2022 Hans Petter Selasky + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#define WRAP(str) (char []){str} + +static const char *speak_prog = "/usr/local/bin/espeak"; +static const char *beep_prog = "/usr/bin/beep"; +static void (*fn)(void); +static bool background; +static bool chatty; + +static void +chatty_char(char ch, char *buffer, size_t *ppos) +{ + char temp[32]; + size_t len; + + switch (ch) { + case 'a' ... 'z': + case 'A' ... 'Z': + case '0' ... '9': + temp[0] = (char)ch; + temp[1] = 0; + break; + case '~': + strlcpy(temp, " tilde ", sizeof(temp)); + break; + case '#': + strlcpy(temp, " hash ", sizeof(temp)); + break; + case '+': + strlcpy(temp, " plus ", sizeof(temp)); + break; + case '-': + strlcpy(temp, " minus ", sizeof(temp)); + break; + case '_': + strlcpy(temp, " underscore ", sizeof(temp)); + break; + case '*': + strlcpy(temp, " multiply ", sizeof(temp)); + break; + case '/': + strlcpy(temp, " divide ", sizeof(temp)); + break; + case '\\': + strlcpy(temp, " backslash ", sizeof(temp)); + break; + case '=': + strlcpy(temp, " equal ", sizeof(temp)); + break; + case '@': + strlcpy(temp, " at ", sizeof(temp)); + break; + case '^': + strlcpy(temp, " xor ", sizeof(temp)); + break; + case '|': + strlcpy(temp, " pipe ", sizeof(temp)); + break; + case '&': + strlcpy(temp, " and ", sizeof(temp)); + break; + case '(': + strlcpy(temp, " opening parenthesis ", sizeof(temp)); + break; + case ')': + strlcpy(temp, " closing parenthesis ", sizeof(temp)); + break; + case '[': + strlcpy(temp, " opening bracket ", sizeof(temp)); + break; + case ']': + strlcpy(temp, " closing bracket ", sizeof(temp)); + break; + case '{': + strlcpy(temp, " opening curlybracket ", sizeof(temp)); + break; + case '}': + strlcpy(temp, " closing curlybracket ", sizeof(temp)); + break; + case '.': + strlcpy(temp, " period ", sizeof(temp)); + break; + case ',': + strlcpy(temp, " comma ", sizeof(temp)); + break; + case ':': + strlcpy(temp, " colon ", sizeof(temp)); + break; + case ';': + strlcpy(temp, " semicolon ", sizeof(temp)); + break; + case '!': + strlcpy(temp, " not ", sizeof(temp)); + break; + case '$': + strlcpy(temp, " dollar ", sizeof(temp)); + break; + case '?': + strlcpy(temp, " questionmark ", sizeof(temp)); + break; + case '>': + strlcpy(temp, " greaterthan ", sizeof(temp)); + break; + case '<': + strlcpy(temp, " lessthan ", sizeof(temp)); + break; + case '"': + strlcpy(temp, " quote ", sizeof(temp)); + break; + case '\'': + strlcpy(temp, " apostrophe ", sizeof(temp)); + break; + case '%': + strlcpy(temp, " percent ", sizeof(temp)); + break; + case ' ': + temp[0] = ' '; + temp[1] = 0; + break; + default: + snprintf(temp, sizeof(temp), " byte %u ", (uint8_t)ch); + break; + } + + len = strlen(temp); + + if (buffer != NULL) + memcpy(buffer + *ppos, temp, len); + *ppos += len; +} + +static char * +chatty_buffer(char *data, size_t off) +{ + size_t pos; + char *ndata; + + /* Compute size of chatty buffer: */ + for (size_t x = pos = 0; data[x + off] != 0; x++) + chatty_char(data[x + off], NULL, &pos); + + if (pos != 0) { + ndata = malloc(off + pos + 1); + for (size_t x = pos = 0; data[x + off] != 0; x++) + chatty_char(data[x + off], ndata, &pos); + memcpy(ndata, data, off); + ndata[pos] = 0; /* zero terminate string buffer */ + free(data); + return (ndata); + } else { + return (data); + } +} + +static void +sync_execv(const char *path, char *const argv[]) +{ + int pid; + + pid = fork(); + if (pid == 0) { + execv(path, argv); + exit(0); + } else if (pid != -1) { + waitpid(pid, NULL, 0); + } +} + +static void +feed(void) +{ + unsigned tmp = 1; + sysctlbyname("kern.vt.accessibility.feed", NULL, NULL, &tmp, sizeof(tmp)); +} + +static void +speak(void) +{ + char buffer[256]; + char *data; + char *argv[4]; + + size_t len; + uint16_t row = 0; + uint16_t col = 0; + + len = sizeof(row); + sysctlbyname("kern.vt.accessibility.row", &row, &len, NULL, 0); + + len = sizeof(col); + sysctlbyname("kern.vt.accessibility.col", &col, &len, NULL, 0); + + len = 0; + sysctlbyname("kern.vt.accessibility.text_utf8", NULL, &len, NULL, 0); + if (len == 0) + return; + + data = malloc(len + 1); + memset(data, 0, len + 1); + sysctlbyname("kern.vt.accessibility.text_utf8", data, &len, NULL, 0); + + if (chatty) + data = chatty_buffer(data, 0); + + snprintf(buffer, sizeof(buffer), "row %u column %u ", row, col); + + argv[0] = WRAP("espeak"); + argv[1] = WRAP("--"); + argv[2] = buffer; + argv[3] = NULL; + + sync_execv(speak_prog, argv); + + argv[2] = data; + + sync_execv(speak_prog, argv); + + free(data); +} + +static void +beep(void) +{ + char rbuf[16]; + char cbuf[16]; + + char *data; + char *argv[5]; + + size_t len; + uint16_t row = 0; + uint16_t col = 0; + + len = sizeof(row); + sysctlbyname("kern.vt.accessibility.row", &row, &len, NULL, 0); + + len = sizeof(col); + sysctlbyname("kern.vt.accessibility.col", &col, &len, NULL, 0); + + len = 0; + sysctlbyname("kern.vt.accessibility.text_utf8", NULL, &len, NULL, 0); + if (len == 0) + return; + + data = malloc(3 + len + 1); + memset(data, 0, 3 + len + 1); + sysctlbyname("kern.vt.accessibility.text_utf8", data + 3, &len, NULL, 0); + + data[0] = '-'; + data[1] = 't'; + data[2] = ' '; + + if (chatty) + data = chatty_buffer(data, 3); + + snprintf(rbuf, sizeof(rbuf), "-s %u", row); + snprintf(cbuf, sizeof(cbuf), "-s %u", col); + + argv[0] = WRAP("beep"); + argv[1] = rbuf; + argv[2] = WRAP("-F 110"); + argv[3] = WRAP("-D 50"); + argv[4] = NULL; + + sync_execv(beep_prog, argv); + + argv[0] = WRAP("beep"); + argv[1] = cbuf; + argv[2] = WRAP("-F 220"); + argv[3] = WRAP("-D 50"); + argv[4] = NULL; + + sync_execv(beep_prog, argv); + + argv[0] = WRAP("beep"); + argv[1] = data; + argv[2] = WRAP("-F 330"); + argv[3] = WRAP("-D 50"); + argv[4] = NULL; + + sync_execv(beep_prog, argv); + + free(data); +} + +static void +usage(void) +{ + fprintf(stderr, "Usage: %s [parameters]\n" + "\t" "-b Beep the console contents (default)\n" + "\t" "-s Speak the console contents\n" + "\t" "-c Be chatty\n" + "\t" "-S # Default: %s\n" + "\t" "-B Run in background\n" + "\t" "-h Show usage\n", + getprogname(), speak_prog); + exit(1); +} + +int +main(int argc, char **argv) +{ + int c; + + while ((c = getopt(argc, argv, "BbcshS:")) != -1) { + switch (c) { + case 'B': + background = true; + break; + case 'b': + fn = &beep; + break; + case 'c': + chatty = true; + break; + case 's': + fn = &speak; + break; + case 'S': + speak_prog = optarg; + break; + default: + usage(); + break; + } + } + + if (fn == NULL) + fn = &beep; + + if (background && daemon(0, 0) != 0) + errx(1, "daemon(0,0) failed"); + + for (;;) { + fn(); + feed(); + sleep(1); + } + return (0); +}