Skip to content

Commit 4b2a00f

Browse files
committed
Introduce example program for viewing BBS art
1 parent 2f4e6e8 commit 4b2a00f

File tree

2 files changed

+217
-5
lines changed

2 files changed

+217
-5
lines changed

examples/art.c

Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
#if 0
2+
/*─────────────────────────────────────────────────────────────────╗
3+
│ To the extent possible under law, Justine Tunney has waived │
4+
│ all copyright and related or neighboring rights to this file, │
5+
│ as it is written in the following disclaimers: │
6+
│ • http://unlicense.org/ │
7+
│ • http://creativecommons.org/publicdomain/zero/1.0/ │
8+
╚─────────────────────────────────────────────────────────────────*/
9+
#endif
10+
#include <errno.h>
11+
#include <fcntl.h>
12+
#include <getopt.h>
13+
#include <iconv.h>
14+
#include <signal.h>
15+
#include <stdio.h>
16+
#include <stdlib.h>
17+
#include <string.h>
18+
#include <termios.h>
19+
#include <time.h>
20+
#include <unistd.h>
21+
22+
/**
23+
* @fileoverview program for viewing bbs art files
24+
* @see http://www.textfiles.com/art/
25+
*/
26+
27+
#define INBUFSZ 256
28+
#define OUBUFSZ (INBUFSZ * 6)
29+
#define SLIT(s) ((unsigned)s[3] << 24 | s[2] << 16 | s[1] << 8 | s[0])
30+
31+
volatile sig_atomic_t got_signal;
32+
33+
void on_signal(int sig) {
34+
got_signal = 1;
35+
}
36+
37+
void process_file(const char *path, int fd, iconv_t cd, int baud_rate) {
38+
char input_buffer[INBUFSZ];
39+
char output_buffer[OUBUFSZ];
40+
size_t input_left, output_left;
41+
char *input_ptr, *output_ptr;
42+
struct timespec next = timespec_mono();
43+
44+
for (;;) {
45+
46+
// read from file
47+
ssize_t bytes_read = read(fd, input_buffer, INBUFSZ);
48+
if (bytes_read == -1) {
49+
perror(path);
50+
exit(1);
51+
}
52+
if (!bytes_read)
53+
break;
54+
55+
// modernize character set
56+
input_ptr = input_buffer;
57+
input_left = bytes_read;
58+
output_ptr = output_buffer;
59+
output_left = OUBUFSZ;
60+
if (iconv(cd, &input_ptr, &input_left, &output_ptr, &output_left) ==
61+
(size_t)-1) {
62+
perror(path);
63+
exit(1);
64+
}
65+
66+
// write to terminal
67+
for (char *p = output_buffer; p < output_ptr; p++) {
68+
if (got_signal)
69+
return;
70+
71+
write(STDOUT_FILENO, p, 1);
72+
73+
// allow arrow keys to change baud rate
74+
char key[4] = {0};
75+
if (read(STDIN_FILENO, key, sizeof(key)) > 0) {
76+
if (SLIT(key) == SLIT("\e[A") || // up
77+
SLIT(key) == SLIT("\e[C")) { // right
78+
baud_rate *= 1.4;
79+
} else if (SLIT(key) == SLIT("\e[B") || // down
80+
SLIT(key) == SLIT("\e[D")) { // left
81+
baud_rate *= 0.6;
82+
}
83+
}
84+
85+
// insert artificial delay for one byte. we divide by 10 to convert
86+
// bits to bytes, because that is how many bits 8-N-1 encoding used
87+
next = timespec_add(next, timespec_fromnanos(1e9 / (baud_rate / 10.)));
88+
usleep(timespec_tomicros(timespec_subz(next, timespec_mono())));
89+
}
90+
}
91+
}
92+
93+
int main(int argc, char *argv[]) {
94+
95+
// "When new technology comes out, people don't all buy it right away.
96+
// If what they have works, some will wait until it doesn't. A few
97+
// people do get the latest though. In 1984 2400 baud modems became
98+
// available, so some people had them, but many didn't. A BBS list
99+
// from 1986 shows operators were mostly 300 and 1200, but some were
100+
// using 2400. The next 5 years were the hayday of the 2400."
101+
//
102+
// https://forum.vcfed.org/index.php?threads/the-2400-baud-modem.44241/
103+
104+
int baud_rate = 2400; // -b 2400
105+
const char *from_charset = "CP437"; // -f CP437
106+
const char *to_charset = "UTF-8"; // -t UTF-8
107+
108+
int opt;
109+
while ((opt = getopt(argc, argv, "hb:f:t:")) != -1) {
110+
switch (opt) {
111+
case 'b': {
112+
char *endptr;
113+
double rate = strtod(optarg, &endptr);
114+
if (*endptr == 'k') {
115+
rate *= 1e3;
116+
++endptr;
117+
} else if (*endptr == 'm') {
118+
rate *= 1e6;
119+
++endptr;
120+
}
121+
if (*endptr || baud_rate <= 0) {
122+
fprintf(stderr, "%s: invalid baud rate: %s\n", argv[0], optarg);
123+
exit(1);
124+
}
125+
baud_rate = rate;
126+
break;
127+
}
128+
case 'f':
129+
from_charset = optarg;
130+
break;
131+
case 't':
132+
to_charset = optarg;
133+
break;
134+
case 'h':
135+
fprintf(stderr, "\
136+
Usage:\n\
137+
%s [-b BAUD] [-f CP437] [-t UTF-8] FILE...\n\
138+
\n\
139+
Supported charsets:\n\
140+
utf8, wchart, ucs2be, ucs2le, utf16be, utf16le, ucs4be, ucs4le,\n\
141+
ascii, utf16, ucs4, ucs2, eucjp, shiftjis, iso2022jp, gb18030, gbk,\n\
142+
gb2312, big5, euckr, iso88591, latin1, iso88592, iso88593, iso88594,\n\
143+
iso88595, iso88596, iso88597, iso88598, iso88599, iso885910,\n\
144+
iso885911, iso885913, iso885914, iso885915, iso885916, cp1250,\n\
145+
windows1250, cp1251, windows1251, cp1252, windows1252, cp1253,\n\
146+
windows1253, cp1254, windows1254, cp1255, windows1255, cp1256,\n\
147+
windows1256, cp1257, windows1257, cp1258, windows1258, koi8r, koi8u,\n\
148+
cp437, cp850, cp866, ibm1047, cp1047.\n\
149+
\n\
150+
See also:\n\
151+
http://www.textfiles.com/art/\n\
152+
\n",
153+
argv[0]);
154+
exit(0);
155+
default:
156+
fprintf(stderr, "protip: pass the -h flag for help\n");
157+
exit(1);
158+
}
159+
}
160+
if (optind == argc) {
161+
fprintf(stderr, "%s: missing operand\n", argv[0]);
162+
exit(1);
163+
}
164+
165+
iconv_t cd = iconv_open(to_charset, from_charset);
166+
if (cd == (iconv_t)-1) {
167+
fprintf(stderr, "error: conversion from %s to %s not supported\n",
168+
from_charset, to_charset);
169+
exit(1);
170+
}
171+
172+
// catch ctrl-c
173+
signal(SIGINT, on_signal);
174+
175+
// don't wait until newline to read() keystrokes
176+
struct termios t;
177+
if (!tcgetattr(STDIN_FILENO, &t)) {
178+
struct termios t2 = t;
179+
t2.c_lflag &= ~(ICANON | ECHO);
180+
tcsetattr(STDIN_FILENO, TCSANOW, &t2);
181+
}
182+
183+
// make stdin nonblocking
184+
fcntl(STDIN_FILENO, F_SETFL, fcntl(STDIN_FILENO, F_GETFL) | O_NONBLOCK);
185+
186+
// hide cursor
187+
write(STDOUT_FILENO, "\e[?25l", 6);
188+
189+
// Process each file specified on the command line
190+
for (int i = optind; i < argc && !got_signal; i++) {
191+
int fd = open(argv[i], O_RDONLY);
192+
if (fd == -1) {
193+
perror(argv[i]);
194+
continue;
195+
}
196+
process_file(argv[i], fd, cd, baud_rate);
197+
close(fd);
198+
}
199+
200+
// cleanup
201+
iconv_close(cd);
202+
203+
// show cursor
204+
write(STDOUT_FILENO, "\e[?25h", 6);
205+
206+
// restore terminal
207+
tcsetattr(STDIN_FILENO, TCSANOW, &t);
208+
}

libc/calls/usleep.c

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,14 @@
3434
* @norestart
3535
*/
3636
int usleep(uint64_t micros) {
37-
errno_t err;
38-
struct timespec ts = timespec_frommicros(micros);
39-
err = clock_nanosleep(CLOCK_REALTIME, 0, &ts, 0);
40-
if (err)
41-
return errno = err, -1;
37+
// All OSes except OpenBSD return instantly on usleep(0). So we might
38+
// as well avoid system call overhead and helping OpenBSD work better
39+
if (micros) {
40+
errno_t err;
41+
struct timespec ts = timespec_frommicros(micros);
42+
err = clock_nanosleep(CLOCK_MONOTONIC, 0, &ts, 0);
43+
if (err)
44+
return errno = err, -1;
45+
}
4246
return 0;
4347
}

0 commit comments

Comments
 (0)