#if 0 ${CC:-gcc} -s -O2 -std=c99 -Wall -o ${SPREED_INSTALL_DIR:-.}/spreed spreed.c exit #endif #define _POSIX_C_SOURCE 200809L #include // setlocale #include // getchar, stdin/out/err #include // getenv, strtoll #include // strstr #include // timespec #include // fwide, mbrtowc, putwchar #include // iswprint, iswspace, iswalnum #define SPRD_VER L"0.1" #define CLR_FG L"\033[31m" #define CLR_RESET L"\033[0m" #define SPRD_CLOCK CLOCK_MONOTONIC #define MS_PER_MIN (60ll * 1000000000ll) #define SPRD_CLOCK_DIFF(t1, t2) ((((long int)t1.tv_sec * 1000000000ll) + (long int)t1.tv_nsec) - (((long int)t2.tv_sec * 1000000000ll) + (long int)t2.tv_nsec)) const unsigned char orps[9] = {0,0,0,1,1,2,2,2,2}; unsigned char get_optimal_recognition_point(size_t len) { if (len >= 10) return 3; return orps[len-1]; } int main(int argc, char *argv[]) { struct timespec ts_last, ts_current, ts_sleep; wchar_t word[37]; wchar_t wc; char *str; long int cooldown, wpm_ns; int c; char chararr[4]; unsigned char u, uu, ichar, iword, b_word_break; setlocale(LC_ALL, ""); fwide(stdout, 1); clock_getres(SPRD_CLOCK, &ts_last); memset(word, 0, 37 * sizeof(wchar_t)); cooldown = 0; wpm_ns = MS_PER_MIN / 250; memset(chararr, 0, sizeof(chararr)); ichar = 0; iword = 0; b_word_break = 0; if (argc > 1) { if (strstr(argv[1], "-v") || strstr(argv[1], "--v") || strstr(argv[1], "-V") || strstr(argv[1], "--V")) { c = fputws(L"spreed version " SPRD_VER L"\n", stdout); if (c == EOF) { perror("fputws()"); } return 0; } } str = getenv("SPREED_WPM"); if (argc > 1) { str = argv[1]; } if (str != NULL) { wpm_ns = strtoll(str, NULL, 10); if (wpm_ns == 0) { fputws(L"Error: parsed wpm as 0, setting to default of 250\n", stdout); wpm_ns = 250; } else if (wpm_ns > (60 * 30)) { wpm_ns = 60 * 30; fputws(L"Limiting to 1800 wpm (30 fps)\n", stdout); } wpm_ns = MS_PER_MIN / wpm_ns; } fputws(L" V\n", stdout); clock_gettime(SPRD_CLOCK, &ts_current); ts_last = ts_current; ts_last.tv_sec -= 60; do { while (cooldown > 0) { clock_gettime(SPRD_CLOCK, &ts_current); cooldown -= SPRD_CLOCK_DIFF(ts_current, ts_last); if (cooldown <= 0) { break; } ts_sleep.tv_sec = 0; ts_sleep.tv_nsec = cooldown > 1000000000ll ? 999999999ll : cooldown; nanosleep(&ts_sleep, NULL); } b_word_break = 0; c = getchar(); if (c == EOF) { b_word_break = iword > 0; goto END_OF_WORD; } chararr[ichar++] = (char)c; switch(mbrtowc(&wc, chararr, 4, NULL)) { case 0: // null char if (iword == 0 && ichar == 1) { ichar = 0; memset(chararr, 0, 4 * sizeof(char)); continue; } else { b_word_break = 1; goto END_OF_WORD; } case (size_t)-2: // incomplete wchar_t, keep reading bytes continue; case (size_t)-1: if (ichar == 4) { // emojis return (size_t)-1 until we have all the bytes fputws(L"\nmbrtowc encoding error\n", stderr); return 1; } continue; } // not a printable character, swallow if (!iswprint(wc) && iword == 0) { ichar = 0; memset(chararr, 0, 4 * sizeof(char)); continue; } if (iswspace(wc)) { if (iword == 0) { ichar = 0; memset(chararr, 0, 4 * sizeof(char)); continue; } b_word_break = 1; } END_OF_WORD: ichar = 0; memset(chararr, 0, 4 * sizeof(char)); if (b_word_break != 0 || iword >= 37) { if (iword == 0) { fputws(L"\n0 length word\n", stderr); return 2; } ichar = get_optimal_recognition_point(iword); uu = 0; putwchar('\r'); for (u = 0; u < 48; ++u) { if (u < 10-ichar) { putwchar(L' '); continue; } if (uu < iword) { if (u == 10) { fputws(CLR_FG, stdout); } putwchar(word[uu++]); if (u == 10) { fputws(CLR_RESET, stdout); } continue; } putwchar(L' '); } fflush(stdout); while (iword > 0) { if (iswalnum(word[iword-1])) { break; } if (word[iword-1] == L'.' || word[iword-1] == L'!' || word[iword-1] == L'?') { cooldown += wpm_ns; break; } iword--; } cooldown += wpm_ns; iword = 0; memset(word, 0, 37 * sizeof(wchar_t)); ts_last = ts_current; } ichar = 0; if (!iswspace(wc)) { word[iword++] = wc; } } while (c != EOF); putwchar('\n'); return 0; }