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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574
  1. /* Copyright (C) 2018 C. 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, uint32_t index,
  174. uint32_t selectX, uint32_t selectY, uint32_t guideX, uint32_t guideY
  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 (selectX & 1 << glyphX && selectY & 1 << glyphY) fill ^= 0x77;
  186. if (guideX & 1 << glyphX || guideY & 1 << glyphY) fill ^= 0x3300;
  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. uint32_t guideX;
  231. uint32_t guideY;
  232. uint8_t *undo;
  233. uint8_t *copy;
  234. } edit = {
  235. .scale = 4,
  236. };
  237. static const uint32_t NormalCols = 32;
  238. static void drawNormal(void) {
  239. for (uint32_t i = 0; i < header.glyph.len; ++i) {
  240. drawGlyph(
  241. header.glyph.width * (i % NormalCols),
  242. header.glyph.height * (i / NormalCols),
  243. normal.scale, i,
  244. -(i == normal.index), -(i == normal.index), 0, 0
  245. );
  246. }
  247. }
  248. static void normalDec(uint32_t n) {
  249. if (normal.index >= n) normal.index -= n;
  250. }
  251. static void normalInc(uint32_t n) {
  252. if (normal.index + n < header.glyph.len) normal.index += n;
  253. }
  254. static void normalPrint(const char *prefix) {
  255. if (normal.index <= 256) {
  256. printf("%s: %02X '%lc'\n", prefix, normal.index, CP437[normal.index]);
  257. } else {
  258. printf("%s: %02X\n", prefix, normal.index);
  259. }
  260. }
  261. static void inputNormal(char ch) {
  262. if (normal.to) {
  263. if (ch < header.glyph.len) normal.index = ch;
  264. normalPrint("index");
  265. normal.to = false;
  266. return;
  267. }
  268. switch (ch) {
  269. break; case 'q': {
  270. if (!normal.modified) exit(EX_OK);
  271. mode = Discard;
  272. }
  273. break; case 'w': {
  274. fileWrite();
  275. printf("write: %s\n", path);
  276. normal.modified = false;
  277. }
  278. break; case '-': if (normal.scale) normal.scale--; frameClear();
  279. break; case '+': normal.scale++;
  280. break; case 'h': normalDec(1); normalPrint("index");
  281. break; case 'l': normalInc(1); normalPrint("index");
  282. break; case 'k': normalDec(NormalCols); normalPrint("index");
  283. break; case 'j': normalInc(NormalCols); normalPrint("index");
  284. break; case 'f': normal.from = normal.index; normal.to = true;
  285. break; case 047: normal.index = normal.from; normalPrint("index");
  286. break; case 'y': {
  287. if (!edit.copy) edit.copy = malloc(header.glyph.size);
  288. if (!edit.copy) err(EX_OSERR, "malloc");
  289. memcpy(edit.copy, glyph(normal.index), header.glyph.size);
  290. normalPrint("copy");
  291. }
  292. break; case 'e': {
  293. normal.modified = true;
  294. edit.index = normal.index;
  295. if (!edit.undo) edit.undo = malloc(header.glyph.size);
  296. if (!edit.undo) err(EX_OSERR, "malloc");
  297. memcpy(edit.undo, glyph(edit.index), header.glyph.size);
  298. mode = Edit;
  299. frameClear();
  300. }
  301. break; case 'i': mode = Preview; frameClear();
  302. }
  303. }
  304. static void drawEdit(void) {
  305. drawGlyph(
  306. 0, 0, edit.scale, edit.index,
  307. 1 << edit.x, 1 << edit.y, edit.guideX, edit.guideY
  308. );
  309. drawBorder(header.glyph.width, header.glyph.height, edit.scale);
  310. drawGlyph(
  311. header.glyph.width << edit.scale,
  312. header.glyph.height << edit.scale,
  313. 0, edit.index,
  314. 0, 0, 0, 0
  315. );
  316. }
  317. static void inputEdit(char ch) {
  318. switch (ch) {
  319. break; case Esc: mode = Normal; frameClear();
  320. break; case '-': if (edit.scale) edit.scale--; frameClear();
  321. break; case '+': edit.scale++;
  322. break; case 'g': edit.guideY ^= 1 << edit.y;
  323. break; case 'G': edit.guideX ^= 1 << edit.x;
  324. break; case 'h': if (edit.x) edit.x--;
  325. break; case 'l': if (edit.x + 1 < header.glyph.width) edit.x++;
  326. break; case 'k': if (edit.y) edit.y--;
  327. break; case 'j': if (edit.y + 1 < header.glyph.height) edit.y++;
  328. break; case ' ': bitFlip(edit.index, edit.x, edit.y);
  329. break; case 'r': {
  330. for (uint32_t y = 0; y < header.glyph.height; ++y) {
  331. for (uint32_t x = 0; x < header.glyph.width; ++x) {
  332. bitFlip(edit.index, x, y);
  333. }
  334. }
  335. }
  336. break; case 'H': {
  337. for (uint32_t x = 0; x < header.glyph.width; ++x) {
  338. for (uint32_t y = 0; y < header.glyph.height; ++y) {
  339. if (x + 1 < header.glyph.width) {
  340. bitSet(edit.index, x, y, bitGet(edit.index, x + 1, y));
  341. } else {
  342. bitSet(edit.index, x, y, 0);
  343. }
  344. }
  345. }
  346. }
  347. break; case 'L': {
  348. uint32_t width = header.glyph.width;
  349. for (uint32_t x = width - 1; x < width; --x) {
  350. for (uint32_t y = 0; y < header.glyph.height; ++y) {
  351. if (x - 1 < width) {
  352. bitSet(edit.index, x, y, bitGet(edit.index, x - 1, y));
  353. } else {
  354. bitSet(edit.index, x, y, 0);
  355. }
  356. }
  357. }
  358. }
  359. break; case 'K': {
  360. for (uint32_t y = 0; y < header.glyph.height; ++y) {
  361. for (uint32_t x = 0; x < header.glyph.width; ++x) {
  362. if (y + 1 < header.glyph.height) {
  363. bitSet(edit.index, x, y, bitGet(edit.index, x, y + 1));
  364. } else {
  365. bitSet(edit.index, x, y, 0);
  366. }
  367. }
  368. }
  369. }
  370. break; case 'J': {
  371. uint32_t height = header.glyph.height;
  372. for (uint32_t y = height - 1; y < height; --y) {
  373. for (uint32_t x = 0; x < header.glyph.width; ++x) {
  374. if (y - 1 < height) {
  375. bitSet(edit.index, x, y, bitGet(edit.index, x, y - 1));
  376. } else {
  377. bitSet(edit.index, x, y, 0);
  378. }
  379. }
  380. }
  381. }
  382. break; case 'p': {
  383. if (!edit.copy) break;
  384. memcpy(glyph(edit.index), edit.copy, header.glyph.size);
  385. }
  386. break; case 'u': {
  387. if (!edit.undo) break;
  388. memcpy(glyph(edit.index), edit.undo, header.glyph.size);
  389. }
  390. }
  391. }
  392. enum { PreviewRows = 8, PreviewCols = 64 };
  393. static struct {
  394. uint32_t glyphs[PreviewRows * PreviewCols];
  395. uint32_t index;
  396. } preview;
  397. static void drawPreview(void) {
  398. for (uint32_t i = 0; i < PreviewRows * PreviewCols; ++i) {
  399. drawGlyph(
  400. header.glyph.width * (i % PreviewCols),
  401. header.glyph.height * (i / PreviewCols),
  402. 0, preview.glyphs[i],
  403. -(i == preview.index), -(i == preview.index), 0, 0
  404. );
  405. }
  406. }
  407. static void inputPreview(char ch) {
  408. switch (ch) {
  409. break; case Esc: mode = Normal; frameClear();
  410. break; case Del: {
  411. if (preview.index) preview.index--;
  412. preview.glyphs[preview.index] = 0;
  413. }
  414. break; case LF: {
  415. uint32_t tail = PreviewCols - (preview.index % PreviewCols);
  416. memset(
  417. &preview.glyphs[preview.index],
  418. 0, sizeof(preview.glyphs[0]) * tail
  419. );
  420. preview.index += tail;
  421. }
  422. break; default: preview.glyphs[preview.index++] = ch;
  423. }
  424. preview.index %= PreviewRows * PreviewCols;
  425. }
  426. static void drawDiscard(void) {
  427. printf("discard modifications? ");
  428. fflush(stdout);
  429. }
  430. static void inputDiscard(char ch) {
  431. printf("%c\n", ch);
  432. if (ch == 'Y' || ch == 'y') exit(EX_OK);
  433. mode = Normal;
  434. }
  435. static void draw(void) {
  436. switch (mode) {
  437. break; case Normal: drawNormal();
  438. break; case Edit: drawEdit();
  439. break; case Preview: drawPreview();
  440. break; case Discard: drawDiscard();
  441. }
  442. }
  443. static void input(char ch) {
  444. switch (mode) {
  445. break; case Normal: inputNormal(ch);
  446. break; case Edit: inputEdit(ch);
  447. break; case Preview: inputPreview(ch);
  448. break; case Discard: inputDiscard(ch);
  449. }
  450. }
  451. static struct termios saveTerm;
  452. static void restoreTerm(void) {
  453. tcsetattr(STDIN_FILENO, TCSADRAIN, &saveTerm);
  454. }
  455. int main(int argc, char *argv[]) {
  456. setlocale(LC_CTYPE, "");
  457. uint32_t newLen = 256;
  458. uint32_t newWidth = 8;
  459. uint32_t newHeight = 16;
  460. uint32_t setHeight = 0;
  461. int opt;
  462. while (0 < (opt = getopt(argc, argv, "H:g:h:w:"))) {
  463. switch (opt) {
  464. break; case 'H': setHeight = strtoul(optarg, NULL, 0);
  465. break; case 'g': newLen = strtoul(optarg, NULL, 0);
  466. break; case 'h': newHeight = strtoul(optarg, NULL, 0);
  467. break; case 'w': newWidth = strtoul(optarg, NULL, 0);
  468. break; default: return EX_USAGE;
  469. }
  470. }
  471. if (!newLen || !newWidth || !newHeight) return EX_USAGE;
  472. if (optind == argc) return EX_USAGE;
  473. path = strdup(argv[optind]);
  474. fileRead(newLen, newWidth, newHeight);
  475. if (setHeight) {
  476. if (setHeight < header.glyph.height) {
  477. errx(EX_CONFIG, "cannot decrease height");
  478. }
  479. uint32_t setSize = bytes(header.glyph.width) * setHeight;
  480. uint8_t *setGlyphs = calloc(header.glyph.len, setSize);
  481. for (uint32_t i = 0; i < header.glyph.len; ++i) {
  482. memcpy(&setGlyphs[setSize * i], glyph(i), header.glyph.size);
  483. }
  484. free(glyphs);
  485. glyphs = setGlyphs;
  486. header.glyph.height = setHeight;
  487. header.glyph.size = setSize;
  488. normal.modified = true;
  489. }
  490. frameOpen();
  491. frameClear();
  492. int error = tcgetattr(STDIN_FILENO, &saveTerm);
  493. if (error) err(EX_IOERR, "tcgetattr");
  494. atexit(restoreTerm);
  495. struct termios term = saveTerm;
  496. term.c_lflag &= ~(ICANON | ECHO);
  497. error = tcsetattr(STDIN_FILENO, TCSADRAIN, &term);
  498. if (error) err(EX_IOERR, "tcsetattr");
  499. for (;;) {
  500. draw();
  501. char ch;
  502. ssize_t size = read(STDIN_FILENO, &ch, 1);
  503. if (size < 0) err(EX_IOERR, "read");
  504. if (!size) return EX_SOFTWARE;
  505. input(ch);
  506. }
  507. }