15
15
#include <stdio.h>
16
16
#include <stdlib.h>
17
17
#include <string.h>
18
+ #include <sys/ioctl.h>
18
19
#include <termios.h>
19
20
#include <time.h>
20
21
#include <unistd.h>
21
22
22
23
/**
23
24
* @fileoverview program for viewing bbs art files
25
+ * @see https://github.com/blocktronics/artpacks
24
26
* @see http://www.textfiles.com/art/
25
27
*/
26
28
29
+ #define HELP \
30
+ "Usage:\n\
31
+ art [-b %d] [-f %s] [-t %s] FILE...\n\
32
+ \n\
33
+ Flags:\n\
34
+ -b NUMBER specifies simulated modem baud rate, which defaults to\n\
35
+ 2400 since that was the most common modem speed in the\n\
36
+ later half of the 1980s during the BBS golden age; you\n\
37
+ could also say 300 for the slowest experience possible\n\
38
+ or you could say 14.4k to get more of a 90's feel, and\n\
39
+ there's also the infamous 56k to bring you back to y2k\n\
40
+ -f CHARSET specifies charset of input bytes, where the default is\n\
41
+ cp347 which means IBM Code Page 347 a.k.a. DOS\n\
42
+ -t CHARSET specifies output charset used by your terminal, and it\n\
43
+ defaults to utf8 a.k.a. thompson-pike encoding\n\
44
+ \n\
45
+ Supported charsets:\n\
46
+ utf8, ascii, wchar_t, ucs2be, ucs2le, utf16be, utf16le, ucs4be,\n\
47
+ ucs4le, utf16, ucs4, ucs2, eucjp, shiftjis, iso2022jp, gb18030, gbk,\n\
48
+ gb2312, big5, euckr, iso88591, latin1, iso88592, iso88593, iso88594,\n\
49
+ iso88595, iso88596, iso88597, iso88598, iso88599, iso885910,\n\
50
+ iso885911, iso885913, iso885914, iso885915, iso885916, cp1250,\n\
51
+ windows1250, cp1251, windows1251, cp1252, windows1252, cp1253,\n\
52
+ windows1253, cp1254, windows1254, cp1255, windows1255, cp1256,\n\
53
+ windows1256, cp1257, windows1257, cp1258, windows1258, koi8r, koi8u,\n\
54
+ cp437, cp850, cp866, ibm1047, cp1047.\n\
55
+ \n\
56
+ See also:\n\
57
+ http://www.textfiles.com/art/\n\
58
+ https://github.com/blocktronics/artpacks\n\
59
+ \n"
60
+
27
61
#define INBUFSZ 256
28
62
#define OUBUFSZ (INBUFSZ * 6)
29
63
#define SLIT (s ) ((unsigned)s[3] << 24 | s[2] << 16 | s[1] << 8 | s[0])
30
64
31
- volatile sig_atomic_t got_signal ;
65
+ // "When new technology comes out, people don't all buy it right away.
66
+ // If what they have works, some will wait until it doesn't. A few
67
+ // people do get the latest though. In 1984 2400 baud modems became
68
+ // available, so some people had them, but many didn't. A BBS list
69
+ // from 1986 shows operators were mostly 300 and 1200, but some were
70
+ // using 2400. The next 5 years were the hayday of the 2400."
71
+ //
72
+ // https://forum.vcfed.org/index.php?threads/the-2400-baud-modem.44241/
73
+
74
+ int baud_rate = 2400 ; // -b 2400
75
+ const char * from_charset = "CP437" ; // -f CP437
76
+ const char * to_charset = "UTF-8" ; // -t UTF-8
77
+
78
+ volatile sig_atomic_t done ;
32
79
33
80
void on_signal (int sig ) {
34
- got_signal = 1 ;
81
+ done = 1 ;
82
+ (void )sig ;
83
+ }
84
+
85
+ void print (const char * s ) {
86
+ (void )!write (STDOUT_FILENO , s , strlen (s ));
87
+ }
88
+
89
+ int encode_character (char output [8 ], const char * codec , wchar_t character ) {
90
+ size_t inbytesleft = sizeof (wchar_t );
91
+ size_t outbytesleft = 7 ;
92
+ char * inbuf = (char * )& character ;
93
+ char * outbuf = output ;
94
+ iconv_t cd = iconv_open (codec , "wchar_t" );
95
+ if (cd == (iconv_t )- 1 )
96
+ return -1 ;
97
+ size_t result = iconv (cd , & inbuf , & inbytesleft , & outbuf , & outbytesleft );
98
+ iconv_close (cd );
99
+ if (result == (size_t )-1 )
100
+ return -1 ;
101
+ * outbuf = '\0' ;
102
+ return 7 - outbytesleft ;
103
+ }
104
+
105
+ void append_replacement_character (char * * b ) {
106
+ int n = encode_character (* b , to_charset , 0xFFFD );
107
+ if (n == -1 )
108
+ n = encode_character (* b , to_charset , '?' );
109
+ if (n != -1 )
110
+ * b += n ;
111
+ }
112
+
113
+ int compare_time (struct timespec a , struct timespec b ) {
114
+ int cmp ;
115
+ if (!(cmp = (a .tv_sec > b .tv_sec ) - (a .tv_sec < b .tv_sec )))
116
+ cmp = (a .tv_nsec > b .tv_nsec ) - (a .tv_nsec < b .tv_nsec );
117
+ return cmp ;
118
+ }
119
+
120
+ struct timespec add_time (struct timespec x , struct timespec y ) {
121
+ x .tv_sec += y .tv_sec ;
122
+ x .tv_nsec += y .tv_nsec ;
123
+ if (x .tv_nsec >= 1000000000 ) {
124
+ x .tv_nsec -= 1000000000 ;
125
+ x .tv_sec += 1 ;
126
+ }
127
+ return x ;
128
+ }
129
+
130
+ struct timespec subtract_time (struct timespec a , struct timespec b ) {
131
+ a .tv_sec -= b .tv_sec ;
132
+ if (a .tv_nsec < b .tv_nsec ) {
133
+ a .tv_nsec += 1000000000 ;
134
+ a .tv_sec -- ;
135
+ }
136
+ a .tv_nsec -= b .tv_nsec ;
137
+ return a ;
138
+ }
139
+
140
+ struct timespec fromnanos (long long x ) {
141
+ struct timespec ts ;
142
+ ts .tv_sec = x / 1000000000 ;
143
+ ts .tv_nsec = x % 1000000000 ;
144
+ return ts ;
35
145
}
36
146
37
- void process_file (const char * path , int fd , iconv_t cd , int baud_rate ) {
147
+ void process_file (const char * path , int fd , iconv_t cd ) {
148
+ size_t carry = 0 ;
149
+ struct timespec next ;
38
150
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 ();
151
+
152
+ clock_gettime (CLOCK_MONOTONIC , & next );
43
153
44
154
for (;;) {
45
155
46
156
// read from file
47
- ssize_t bytes_read = read (fd , input_buffer , INBUFSZ );
157
+ ssize_t bytes_read = read (fd , input_buffer + carry , INBUFSZ - carry );
158
+ if (!bytes_read )
159
+ return ;
48
160
if (bytes_read == -1 ) {
49
161
perror (path );
50
- exit (1 );
162
+ done = 1 ;
163
+ return ;
51
164
}
52
- if (!bytes_read )
53
- break ;
54
165
55
166
// 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 );
167
+ char * input_ptr = input_buffer ;
168
+ size_t input_left = carry + bytes_read ;
169
+ char output_buffer [OUBUFSZ ];
170
+ char * output_ptr = output_buffer ;
171
+ size_t output_left = OUBUFSZ ;
172
+ size_t ir = iconv (cd , & input_ptr , & input_left , & output_ptr , & output_left );
173
+ carry = 0 ;
174
+ if (ir == (size_t )-1 ) {
175
+ if (errno == EINVAL ) {
176
+ // incomplete multibyte sequence encountered
177
+ memmove (input_buffer , input_ptr , input_left );
178
+ carry = input_left ;
179
+ } else if (errno == EILSEQ && input_left ) {
180
+ // EILSEQ means either
181
+ // 1. illegal input sequence encountered
182
+ // 2. code not encodable in output codec
183
+ //
184
+ // so we skip one byte of input, and insert � or ? in the output
185
+ // this isn't the most desirable behavior, but it is the best we
186
+ // can do, since we don't know specifics about the codecs in use
187
+ //
188
+ // unlike glibc cosmo's iconv implementation may handle case (2)
189
+ // automatically by inserting an asterisk in place of a sequence
190
+ ++ input_ptr ;
191
+ -- input_left ;
192
+ memmove (input_buffer , input_ptr , input_left );
193
+ carry = input_left ;
194
+ if (output_left >= 8 )
195
+ append_replacement_character (& output_ptr );
196
+ } else {
197
+ perror (path );
198
+ done = 1 ;
199
+ return ;
200
+ }
64
201
}
65
202
66
203
// write to terminal
67
- for (char * p = output_buffer ; p < output_ptr ; p ++ ) {
68
- if (got_signal )
204
+ for (char * p = output_buffer ; p < output_ptr ; p ++ ) {
205
+ if (done )
69
206
return ;
70
207
71
- write (STDOUT_FILENO , p , 1 );
208
+ ( void )! write (STDOUT_FILENO , p , 1 );
72
209
73
210
// 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 ;
211
+ int have ;
212
+ if (ioctl (STDIN_FILENO , FIONREAD , & have )) {
213
+ perror ("ioctl" );
214
+ done = 1 ;
215
+ return ;
216
+ }
217
+ if (have > 0 ) {
218
+ char key [4 ] = {0 };
219
+ if (read (STDIN_FILENO , key , sizeof (key )) > 0 ) {
220
+ if (SLIT (key ) == SLIT ("\33[A" ) || // up
221
+ SLIT (key ) == SLIT ("\33[C" )) { // right
222
+ baud_rate *= 1.4 ;
223
+ } else if (SLIT (key ) == SLIT ("\33[B" ) || // down
224
+ SLIT (key ) == SLIT ("\33[D" )) { // left
225
+ baud_rate *= 0.6 ;
226
+ }
227
+ if (baud_rate < 3 )
228
+ baud_rate = 3 ;
229
+ if (baud_rate > 1000000000 )
230
+ baud_rate = 1000000000 ;
82
231
}
83
232
}
84
233
85
234
// insert artificial delay for one byte. we divide by 10 to convert
86
235
// 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 ())));
236
+ struct timespec now ;
237
+ clock_gettime (CLOCK_MONOTONIC , & now );
238
+ next = add_time (next , fromnanos (1e9 / (baud_rate / 10. )));
239
+ if (compare_time (next , now ) > 0 ) {
240
+ struct timespec sleep = subtract_time (next , now );
241
+ nanosleep (& sleep , 0 );
242
+ }
89
243
}
90
244
}
91
245
}
92
246
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
-
247
+ int main (int argc , char * argv []) {
108
248
int opt ;
109
249
while ((opt = getopt (argc , argv , "hb:f:t:" )) != -1 ) {
110
250
switch (opt ) {
111
251
case 'b' : {
112
- char * endptr ;
252
+ char * endptr ;
113
253
double rate = strtod (optarg , & endptr );
114
254
if (* endptr == 'k' ) {
115
255
rate *= 1e3 ;
@@ -132,25 +272,7 @@ int main(int argc, char *argv[]) {
132
272
to_charset = optarg ;
133
273
break ;
134
274
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 ]);
275
+ fprintf (stderr , HELP , baud_rate , from_charset , to_charset );
154
276
exit (0 );
155
277
default :
156
278
fprintf (stderr , "protip: pass the -h flag for help\n" );
@@ -162,6 +284,7 @@ See also:\n\
162
284
exit (1 );
163
285
}
164
286
287
+ // create character transcoder
165
288
iconv_t cd = iconv_open (to_charset , from_charset );
166
289
if (cd == (iconv_t )- 1 ) {
167
290
fprintf (stderr , "error: conversion from %s to %s not supported\n" ,
@@ -180,28 +303,50 @@ See also:\n\
180
303
tcsetattr (STDIN_FILENO , TCSANOW , & t2 );
181
304
}
182
305
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
306
// Process each file specified on the command line
190
- for (int i = optind ; i < argc && !got_signal ; i ++ ) {
307
+ for (int i = optind ; i < argc && !done ; i ++ ) {
308
+
309
+ // open file
191
310
int fd = open (argv [i ], O_RDONLY );
192
311
if (fd == -1 ) {
193
312
perror (argv [i ]);
194
- continue ;
313
+ break ;
195
314
}
196
- process_file (argv [i ], fd , cd , baud_rate );
315
+
316
+ // wait between files
317
+ if (i > optind )
318
+ sleep (1 );
319
+
320
+ print ("\33[?25l" ); // hide cursor
321
+ print ("\33[H" ); // move cursor to top-left
322
+ print ("\33[J" ); // erase display forward
323
+ print ("\33[1;24r" ); // set scrolling region to first 24 lines
324
+ print ("\33[?7h" ); // enable auto-wrap mode
325
+ print ("\33[?3l" ); // 80 column mode (deccolm) vt100
326
+ print ("\33[H" ); // move cursor to top-left, again
327
+
328
+ // get busy
329
+ process_file (argv [i ], fd , cd );
197
330
close (fd );
198
331
}
199
332
200
333
// cleanup
201
334
iconv_close (cd );
202
335
203
- // show cursor
204
- write (STDOUT_FILENO , "\e[?25h" , 6 );
336
+ print ("\33[s" ); // save cursor position
337
+ print ("\33[?25h" ); // show cursor
338
+ print ("\33[0m" ); // reset text attributes (color, bold, etc.)
339
+ print ("\33[?1049l" ); // exit alternate screen mode
340
+ print ("\33(B" ); // exit line drawing and other alt charset modes
341
+ print ("\33[r" ); // reset scrolling region
342
+ print ("\33[?2004l" ); // turn off bracketed paste mode
343
+ print ("\33[4l" ); // exit insert mode
344
+ print ("\33[?1l\33>" ); // exit application keypad mode
345
+ print ("\33[?7h" ); // reset text wrapping mode
346
+ print ("\33[?12l" ); // reset cursor blinking mode
347
+ print ("\33[?6l" ); // reset origin mode
348
+ print ("\33[20l" ); // reset auto newline mode
349
+ print ("\33[u" ); // restore cursor position
205
350
206
351
// restore terminal
207
352
tcsetattr (STDIN_FILENO , TCSANOW , & t );
0 commit comments