The repository formerly known as dotfiles
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

psfed.c 15KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563
  1. /* Copyright (C) 2018 Curtis McEnroe <june@causal.agency>
  2. *
  3. * This program is free software: you can redistribute it and/or modify
  4. * it under the terms of the GNU Affero General Public License as published by
  5. * the Free Software Foundation, either version 3 of the License, or
  6. * (at your option) any later version.
  7. *
  8. * This program is distributed in the hope that it will be useful,
  9. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. * GNU Affero General Public License for more details.
  12. *
  13. * You should have received a copy of the GNU Affero General Public License
  14. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  15. */
  16. #include <err.h>
  17. #include <errno.h>
  18. #include <fcntl.h>
  19. #include <linux/fb.h>
  20. #include <locale.h>
  21. #include <stdbool.h>
  22. #include <stdint.h>
  23. #include <stdio.h>
  24. #include <stdlib.h>
  25. #include <string.h>
  26. #include <sys/ioctl.h>
  27. #include <sys/mman.h>
  28. #include <sysexits.h>
  29. #include <termios.h>
  30. #include <unistd.h>
  31. #include <wchar.h>
  32. static const wchar_t CP437[256] =
  33. L"\0☺☻♥♦♣♠•◘○◙♂♀♪♫☼"
  34. L"►◄↕‼¶§▬↨↑↓→←∟↔▲▼"
  35. L" !\"#$%&'()*+,-./"
  36. L"0123456789:;<=>?"
  37. L"@ABCDEFGHIJKLMNO"
  38. L"PQRSTUVWXYZ[\\]^_"
  39. L"`abcdefghijklmno"
  40. L"pqrstuvwxyz{|}~⌂"
  41. L"ÇüéâäàåçêëèïîìÄÅ"
  42. L"ÉæÆôöòûùÿÖÜ¢£¥₧ƒ"
  43. L"áíóúñѪº¿⌐¬½¼¡«»"
  44. L"░▒▓│┤╡╢╖╕╣║╗╝╜╛┐"
  45. L"└┴┬├─┼╞╟╚╔╩╦╠═╬╧"
  46. L"╨╤╥╙╘╒╓╫╪┘┌█▄▌▐▀"
  47. L"αßΓπΣσµτΦΘΩδ∞φε∩"
  48. L"≡±≥≤⌠⌡÷≈°∙·√ⁿ²■\0";
  49. static struct {
  50. uint32_t width;
  51. uint32_t height;
  52. uint32_t *buffer;
  53. uint32_t background;
  54. } frame;
  55. static void frameClear(void) {
  56. for (uint32_t i = 0; i < frame.width * frame.height; ++i) {
  57. frame.buffer[i] = frame.background;
  58. }
  59. }
  60. static void frameOpen(void) {
  61. const char *dev = getenv("FRAMEBUFFER");
  62. if (!dev) dev = "/dev/fb0";
  63. int fd = open(dev, O_RDWR);
  64. if (fd < 0) err(EX_OSFILE, "%s", dev);
  65. struct fb_var_screeninfo info;
  66. int error = ioctl(fd, FBIOGET_VSCREENINFO, &info);
  67. if (error) err(EX_IOERR, "%s", dev);
  68. frame.width = info.xres;
  69. frame.height = 3 * info.yres / 4;
  70. frame.buffer = mmap(
  71. NULL, sizeof(*frame.buffer) * frame.width * frame.height,
  72. PROT_READ | PROT_WRITE, MAP_SHARED,
  73. fd, 0
  74. );
  75. if (frame.buffer == MAP_FAILED) err(EX_IOERR, "%s", dev);
  76. close(fd);
  77. frame.background = frame.buffer[0];
  78. atexit(frameClear);
  79. }
  80. static const uint32_t Magic = 0x864AB572;
  81. static const uint32_t Version = 0;
  82. static const uint32_t FlagUnicode = 1 << 0;
  83. static uint32_t bytes(uint32_t bits) {
  84. return (bits + 7) / 8;
  85. }
  86. static char *path;
  87. static struct {
  88. uint32_t magic;
  89. uint32_t version;
  90. uint32_t size;
  91. uint32_t flags;
  92. struct {
  93. uint32_t len;
  94. uint32_t size;
  95. uint32_t height;
  96. uint32_t width;
  97. } glyph;
  98. } header;
  99. static uint8_t *glyphs;
  100. static void fileRead(uint32_t newLen, uint32_t newWidth, uint32_t newHeight) {
  101. FILE *file = fopen(path, "r");
  102. if (file) {
  103. size_t len = fread(&header, sizeof(header), 1, file);
  104. if (ferror(file)) err(EX_IOERR, "%s", path);
  105. if (len < 1) errx(EX_DATAERR, "%s: truncated header", path);
  106. } else {
  107. if (errno != ENOENT) err(EX_NOINPUT, "%s", path);
  108. header.magic = Magic;
  109. header.version = Version;
  110. header.size = sizeof(header);
  111. header.flags = 0;
  112. header.glyph.len = newLen;
  113. header.glyph.size = bytes(newWidth) * newHeight;
  114. header.glyph.height = newHeight;
  115. header.glyph.width = newWidth;
  116. }
  117. if (header.magic != Magic) {
  118. errx(EX_DATAERR, "%s: invalid magic %08X", path, header.magic);
  119. }
  120. if (header.version != Version) {
  121. errx(EX_DATAERR, "%s: unsupported version %u", path, header.version);
  122. }
  123. if (header.flags & FlagUnicode) {
  124. errx(EX_DATAERR, "%s: unsupported unicode table", path);
  125. }
  126. if (header.flags) {
  127. errx(EX_DATAERR, "%s: unsupported flags %08X", path, header.flags);
  128. }
  129. if (file && header.size > sizeof(header)) {
  130. int error = fseek(file, header.size, SEEK_SET);
  131. if (error) err(EX_IOERR, "%s", path);
  132. warnx("%s: truncating long header", path);
  133. header.size = sizeof(header);
  134. }
  135. glyphs = calloc(header.glyph.len, header.glyph.size);
  136. if (!glyphs) err(EX_OSERR, "calloc");
  137. if (file) {
  138. size_t len = fread(glyphs, header.glyph.size, header.glyph.len, file);
  139. if (ferror(file)) err(EX_IOERR, "%s", path);
  140. if (len < header.glyph.len) {
  141. errx(EX_DATAERR, "%s: truncated glyphs", path);
  142. }
  143. fclose(file);
  144. }
  145. }
  146. static void fileWrite(void) {
  147. FILE *file = fopen(path, "w");
  148. if (!file) err(EX_CANTCREAT, "%s", path);
  149. fwrite(&header, sizeof(header), 1, file);
  150. if (ferror(file)) err(EX_IOERR, "%s", path);
  151. fwrite(glyphs, header.glyph.size, header.glyph.len, file);
  152. if (ferror(file)) err(EX_IOERR, "%s", path);
  153. int error = fclose(file);
  154. if (error) err(EX_IOERR, "%s", path);
  155. }
  156. static uint8_t *glyph(uint32_t index) {
  157. return &glyphs[header.glyph.size * index];
  158. }
  159. static uint8_t *bitByte(uint32_t index, uint32_t x, uint32_t y) {
  160. return &glyph(index)[bytes(header.glyph.width) * y + x / 8];
  161. }
  162. static uint8_t bitGet(uint32_t index, uint32_t x, uint32_t y) {
  163. return *bitByte(index, x, y) >> (7 - x % 8) & 1;
  164. }
  165. static void bitFlip(uint32_t index, uint32_t x, uint32_t y) {
  166. *bitByte(index, x, y) ^= 1 << (7 - x % 8);
  167. }
  168. static void bitSet(uint32_t index, uint32_t x, uint32_t y, uint8_t bit) {
  169. *bitByte(index, x, y) &= ~(1 << (7 - x % 8));
  170. *bitByte(index, x, y) |= bit << (7 - x % 8);
  171. }
  172. static void drawGlyph(
  173. uint32_t destX, uint32_t destY, uint32_t scale,
  174. uint32_t index, bool select, uint32_t selectX, uint32_t selectY
  175. ) {
  176. destX <<= scale;
  177. destY <<= scale;
  178. for (uint32_t y = 0; y < (header.glyph.height << scale); ++y) {
  179. if (destY + y >= frame.height) break;
  180. for (uint32_t x = 0; x < (header.glyph.width << scale); ++x) {
  181. if (destX + x >= frame.width) break;
  182. uint32_t glyphX = x >> scale;
  183. uint32_t glyphY = y >> scale;
  184. uint32_t fill = -bitGet(index, glyphX, glyphY);
  185. if (select) fill ^= 0x77;
  186. if (selectX == glyphX && selectY == glyphY) fill ^= 0x77;
  187. frame.buffer[frame.width * (destY + y) + destX + x] = fill;
  188. }
  189. }
  190. }
  191. static void drawBorder(uint32_t destX, uint32_t destY, uint32_t scale) {
  192. destX <<= scale;
  193. destY <<= scale;
  194. for (uint32_t y = 0; y < destY; ++y) {
  195. if (y >= frame.height) break;
  196. uint32_t fill = -(y >> scale & 1) ^ 0x555555;
  197. for (uint32_t x = 0; x < (uint32_t)(1 << scale); ++x) {
  198. if (destX + x >= frame.width) break;
  199. frame.buffer[frame.width * y + destX + x] = fill;
  200. }
  201. }
  202. for (uint32_t x = 0; x < destX; ++x) {
  203. if (x >= frame.width) break;
  204. uint32_t fill = -(x >> scale & 1) ^ 0x555555;
  205. for (uint32_t y = 0; y < (uint32_t)(1 << scale); ++y) {
  206. if (destY + y >= frame.height) break;
  207. frame.buffer[frame.width * (destY + y) + x] = fill;
  208. }
  209. }
  210. }
  211. enum { LF = '\n', Esc = '\33', Del = '\177' };
  212. static enum {
  213. Normal,
  214. Edit,
  215. Preview,
  216. Discard,
  217. } mode;
  218. static struct {
  219. uint32_t scale;
  220. uint32_t index;
  221. bool modified;
  222. bool to;
  223. uint32_t from;
  224. } normal;
  225. static struct {
  226. uint32_t scale;
  227. uint32_t index;
  228. uint32_t x;
  229. uint32_t y;
  230. uint8_t *undo;
  231. uint8_t *copy;
  232. } edit = {
  233. .scale = 4,
  234. };
  235. static const uint32_t NormalCols = 32;
  236. static void drawNormal(void) {
  237. for (uint32_t i = 0; i < header.glyph.len; ++i) {
  238. drawGlyph(
  239. header.glyph.width * (i % NormalCols),
  240. header.glyph.height * (i / NormalCols),
  241. normal.scale,
  242. i, (i == normal.index), -1, -1
  243. );
  244. }
  245. }
  246. static void normalDec(uint32_t n) {
  247. if (normal.index >= n) normal.index -= n;
  248. }
  249. static void normalInc(uint32_t n) {
  250. if (normal.index + n < header.glyph.len) normal.index += n;
  251. }
  252. static void normalPrint(const char *prefix) {
  253. if (normal.index <= 256) {
  254. printf("%s: %02X '%lc'\n", prefix, normal.index, CP437[normal.index]);
  255. } else {
  256. printf("%s: %02X\n", prefix, normal.index);
  257. }
  258. }
  259. static void inputNormal(char ch) {
  260. if (normal.to) {
  261. if (ch < header.glyph.len) normal.index = ch;
  262. normalPrint("index");
  263. normal.to = false;
  264. return;
  265. }
  266. switch (ch) {
  267. break; case 'q': {
  268. if (!normal.modified) exit(EX_OK);
  269. mode = Discard;
  270. }
  271. break; case 'w': {
  272. fileWrite();
  273. printf("write: %s\n", path);
  274. normal.modified = false;
  275. }
  276. break; case '-': if (normal.scale) normal.scale--; frameClear();
  277. break; case '+': normal.scale++;
  278. break; case 'h': normalDec(1); normalPrint("index");
  279. break; case 'l': normalInc(1); normalPrint("index");
  280. break; case 'k': normalDec(NormalCols); normalPrint("index");
  281. break; case 'j': normalInc(NormalCols); normalPrint("index");
  282. break; case 'f': normal.from = normal.index; normal.to = true;
  283. break; case 047: normal.index = normal.from; normalPrint("index");
  284. break; case 'y': {
  285. if (!edit.copy) edit.copy = malloc(header.glyph.size);
  286. if (!edit.copy) err(EX_OSERR, "malloc");
  287. memcpy(edit.copy, glyph(normal.index), header.glyph.size);
  288. normalPrint("copy");
  289. }
  290. break; case 'e': {
  291. normal.modified = true;
  292. edit.index = normal.index;
  293. if (!edit.undo) edit.undo = malloc(header.glyph.size);
  294. if (!edit.undo) err(EX_OSERR, "malloc");
  295. memcpy(edit.undo, glyph(edit.index), header.glyph.size);
  296. mode = Edit;
  297. frameClear();
  298. }
  299. break; case 'i': mode = Preview; frameClear();
  300. }
  301. }
  302. static void drawEdit(void) {
  303. drawGlyph(0, 0, edit.scale, edit.index, false, edit.x, edit.y);
  304. drawBorder(header.glyph.width, header.glyph.height, edit.scale);
  305. drawGlyph(
  306. header.glyph.width << edit.scale,
  307. header.glyph.height << edit.scale,
  308. 0,
  309. edit.index, false, -1, -1
  310. );
  311. }
  312. static void inputEdit(char ch) {
  313. switch (ch) {
  314. break; case Esc: mode = Normal; frameClear();
  315. break; case '-': if (edit.scale) edit.scale--; frameClear();
  316. break; case '+': edit.scale++;
  317. break; case 'h': if (edit.x) edit.x--;
  318. break; case 'l': if (edit.x + 1 < header.glyph.width) edit.x++;
  319. break; case 'k': if (edit.y) edit.y--;
  320. break; case 'j': if (edit.y + 1 < header.glyph.height) edit.y++;
  321. break; case ' ': bitFlip(edit.index, edit.x, edit.y);
  322. break; case 'r': {
  323. for (uint32_t y = 0; y < header.glyph.height; ++y) {
  324. for (uint32_t x = 0; x < header.glyph.width; ++x) {
  325. bitFlip(edit.index, x, y);
  326. }
  327. }
  328. }
  329. break; case 'H': {
  330. for (uint32_t x = 0; x < header.glyph.width; ++x) {
  331. for (uint32_t y = 0; y < header.glyph.height; ++y) {
  332. if (x + 1 < header.glyph.width) {
  333. bitSet(edit.index, x, y, bitGet(edit.index, x + 1, y));
  334. } else {
  335. bitSet(edit.index, x, y, 0);
  336. }
  337. }
  338. }
  339. }
  340. break; case 'L': {
  341. uint32_t width = header.glyph.width;
  342. for (uint32_t x = width - 1; x < width; --x) {
  343. for (uint32_t y = 0; y < header.glyph.height; ++y) {
  344. if (x - 1 < width) {
  345. bitSet(edit.index, x, y, bitGet(edit.index, x - 1, y));
  346. } else {
  347. bitSet(edit.index, x, y, 0);
  348. }
  349. }
  350. }
  351. }
  352. break; case 'K': {
  353. for (uint32_t y = 0; y < header.glyph.height; ++y) {
  354. for (uint32_t x = 0; x < header.glyph.width; ++x) {
  355. if (y + 1 < header.glyph.height) {
  356. bitSet(edit.index, x, y, bitGet(edit.index, x, y + 1));
  357. } else {
  358. bitSet(edit.index, x, y, 0);
  359. }
  360. }
  361. }
  362. }
  363. break; case 'J': {
  364. uint32_t height = header.glyph.height;
  365. for (uint32_t y = height - 1; y < height; --y) {
  366. for (uint32_t x = 0; x < header.glyph.width; ++x) {
  367. if (y - 1 < height) {
  368. bitSet(edit.index, x, y, bitGet(edit.index, x, y - 1));
  369. } else {
  370. bitSet(edit.index, x, y, 0);
  371. }
  372. }
  373. }
  374. }
  375. break; case 'p': {
  376. memcpy(glyph(edit.index), edit.copy, header.glyph.size);
  377. }
  378. break; case 'u': {
  379. memcpy(glyph(edit.index), edit.undo, header.glyph.size);
  380. }
  381. }
  382. }
  383. enum { PreviewRows = 8, PreviewCols = 64 };
  384. static struct {
  385. uint32_t glyphs[PreviewRows * PreviewCols];
  386. uint32_t index;
  387. } preview;
  388. static void drawPreview(void) {
  389. for (uint32_t i = 0; i < PreviewRows * PreviewCols; ++i) {
  390. drawGlyph(
  391. header.glyph.width * (i % PreviewCols),
  392. header.glyph.height * (i / PreviewCols),
  393. 0,
  394. preview.glyphs[i], (i == preview.index), -1, -1
  395. );
  396. }
  397. }
  398. static void inputPreview(char ch) {
  399. switch (ch) {
  400. break; case Esc: mode = Normal; frameClear();
  401. break; case Del: {
  402. if (preview.index) preview.index--;
  403. preview.glyphs[preview.index] = 0;
  404. }
  405. break; case LF: {
  406. uint32_t tail = PreviewCols - (preview.index % PreviewCols);
  407. memset(
  408. &preview.glyphs[preview.index],
  409. 0, sizeof(preview.glyphs[0]) * tail
  410. );
  411. preview.index += tail;
  412. }
  413. break; default: preview.glyphs[preview.index++] = ch;
  414. }
  415. preview.index %= PreviewRows * PreviewCols;
  416. }
  417. static void drawDiscard(void) {
  418. printf("discard modifications? ");
  419. fflush(stdout);
  420. }
  421. static void inputDiscard(char ch) {
  422. printf("%c\n", ch);
  423. if (ch == 'Y' || ch == 'y') exit(EX_OK);
  424. mode = Normal;
  425. }
  426. static void draw(void) {
  427. switch (mode) {
  428. break; case Normal: drawNormal();
  429. break; case Edit: drawEdit();
  430. break; case Preview: drawPreview();
  431. break; case Discard: drawDiscard();
  432. }
  433. }
  434. static void input(char ch) {
  435. switch (mode) {
  436. break; case Normal: inputNormal(ch);
  437. break; case Edit: inputEdit(ch);
  438. break; case Preview: inputPreview(ch);
  439. break; case Discard: inputDiscard(ch);
  440. }
  441. }
  442. static struct termios saveTerm;
  443. static void restoreTerm(void) {
  444. tcsetattr(STDIN_FILENO, TCSADRAIN, &saveTerm);
  445. }
  446. int main(int argc, char *argv[]) {
  447. setlocale(LC_CTYPE, "");
  448. uint32_t newLen = 256;
  449. uint32_t newWidth = 8;
  450. uint32_t newHeight = 16;
  451. uint32_t setHeight = 0;
  452. int opt;
  453. while (0 < (opt = getopt(argc, argv, "H:g:h:w:"))) {
  454. switch (opt) {
  455. break; case 'H': setHeight = strtoul(optarg, NULL, 0);
  456. break; case 'g': newLen = strtoul(optarg, NULL, 0);
  457. break; case 'h': newHeight = strtoul(optarg, NULL, 0);
  458. break; case 'w': newWidth = strtoul(optarg, NULL, 0);
  459. break; default: return EX_USAGE;
  460. }
  461. }
  462. if (!newLen || !newWidth || !newHeight) return EX_USAGE;
  463. if (optind == argc) return EX_USAGE;
  464. path = strdup(argv[optind]);
  465. fileRead(newLen, newWidth, newHeight);
  466. if (setHeight) {
  467. if (setHeight < header.glyph.height) {
  468. errx(EX_CONFIG, "cannot decrease height");
  469. }
  470. uint32_t setSize = bytes(header.glyph.width) * setHeight;
  471. uint8_t *setGlyphs = calloc(header.glyph.len, setSize);
  472. for (uint32_t i = 0; i < header.glyph.len; ++i) {
  473. memcpy(&setGlyphs[setSize * i], glyph(i), header.glyph.size);
  474. }
  475. free(glyphs);
  476. glyphs = setGlyphs;
  477. header.glyph.height = setHeight;
  478. header.glyph.size = setSize;
  479. normal.modified = true;
  480. }
  481. frameOpen();
  482. frameClear();
  483. int error = tcgetattr(STDIN_FILENO, &saveTerm);
  484. if (error) err(EX_IOERR, "tcgetattr");
  485. atexit(restoreTerm);
  486. struct termios term = saveTerm;
  487. term.c_lflag &= ~(ICANON | ECHO);
  488. error = tcsetattr(STDIN_FILENO, TCSADRAIN, &term);
  489. if (error) err(EX_IOERR, "tcsetattr");
  490. for (;;) {
  491. draw();
  492. char ch;
  493. ssize_t size = read(STDIN_FILENO, &ch, 1);
  494. if (size < 0) err(EX_IOERR, "read");
  495. if (!size) return EX_SOFTWARE;
  496. input(ch);
  497. }
  498. }