The repository formerly known as dotfiles
您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

578 行
15KB

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