#define _POSIX_C_SOURCE 200809L #include "cst_hermit_glyph.h" #include "os_compat.h" #include #include #include /* hermit_getenv lives in os_compat.c so light test binaries that link * os_compat without hermit_glyph still resolve HERMIT_/CST_ dual env. */ static int file_ok(const char *path) { os_stat_t st; return path && path[0] && os_lstat(path, &st) == 0 && st.is_reg && st.size > 0; } /* Prefer HERMIT_FONT_PATH, then common install locations. */ const char *hermit_font_path(void) { static char buf[512]; const char *e = hermit_getenv("FONT_PATH"); if (file_ok(e)) return e; const char *home = getenv("HOME"); if (home && home[0]) { snprintf(buf, sizeof(buf), "%s/.local/share/fonts/hermit-icons.ttf", home); if (file_ok(buf)) return buf; snprintf(buf, sizeof(buf), "%s/.local/share/hermit/hermit-icons.ttf", home); if (file_ok(buf)) return buf; } static const char *const cands[] = { "/usr/local/share/fonts/hermit/hermit-icons.ttf", "/usr/local/share/hermit/hermit-icons.ttf", "/usr/share/fonts/hermit/hermit-icons.ttf", "/usr/share/hermit/hermit-icons.ttf", "res/hermit-icons.ttf", NULL}; for (int i = 0; cands[i]; i++) if (file_ok(cands[i])) return cands[i]; return ""; } hermit_icon_mode_t hermit_icon_mode(void) { const char *e = hermit_getenv("ICONS"); if (!e[0] || !strcmp(e, "auto")) return HERMIT_ICON_AUTO; if (!strcmp(e, "font") || !strcmp(e, "ttf")) return HERMIT_ICON_FONT; return HERMIT_ICON_MINI; } hermit_icon_mode_t hermit_icon_mode_eff(int ascii) { if (ascii) return HERMIT_ICON_MINI; hermit_icon_mode_t m = hermit_icon_mode(); if (m != HERMIT_ICON_AUTO) return m; if (hermit_getenv("ICON_FONT")[0] == '1') return HERMIT_ICON_FONT; /* AUTO stays on the mini art track; font needs a terminal that loads * Hermit Icons (installed by make install / bootstrap). */ return HERMIT_ICON_MINI; } hermit_state_t hermit_state_from_ui(int busy, int ask, int error) { if (error) return HERMIT_ST_ERROR; if (ask) return HERMIT_ST_QUESTION; if (busy) return HERMIT_ST_CODING; return HERMIT_ST_IDLE; } wchar_t hermit_font_codepoint(hermit_state_t st, int frame, int with_shell) { if (st < 0 || st >= HERMIT_ST_N) st = HERMIT_ST_IDLE; if (frame < 0) frame = 0; frame %= HERMIT_FONT_FRAMES; int shell = with_shell ? 1 : 0; return (wchar_t)(HERMIT_FONT_PUA_BASE + (shell * HERMIT_ST_N + (int)st) * HERMIT_FONT_FRAMES + frame); } /* --- minimal badge ------------------------------------------------------- */ const char *hermit_mini_badge(hermit_state_t st, int frame, int ascii) { int f = frame < 0 ? 0 : frame; if (ascii) { switch (st) { case HERMIT_ST_QUESTION: return "||?"; case HERMIT_ST_ERROR: return "\\/!"; case HERMIT_ST_SUCCESS: return "||*"; case HERMIT_ST_THINKING: return (f % 2) ? "~|" : "|~"; case HERMIT_ST_CODING: case HERMIT_ST_IDLE: default: { static const char *const spin[] = {"||", ">_", "||", "-"}; return spin[f % 4]; } } } switch (st) { case HERMIT_ST_QUESTION: return "║║?"; case HERMIT_ST_ERROR: return "╲╱!"; case HERMIT_ST_SUCCESS: return "║║✦"; case HERMIT_ST_THINKING: return (f % 2) ? "║~" : "~║"; case HERMIT_ST_CODING: case HERMIT_ST_IDLE: default: { static const char *const spin[] = {"║║", ">_", "║║", "─"}; return spin[f % 4]; } } } /* --- home shell art (fixed grid, state+frame animated like HTML kit) ----- */ /* 6 rows x 12 cols. Roles: 0 body 1 shell 2 eye 3 leg 4 prompt 5 empty/mark */ #define ART_H 6 #define ART_W 12 /* Base grids use: * @ shell # body : open eye = blink >_ prompt | leg ? mark ~ claw * Eyes/prompt mutate per state+frame below. */ static void fill_art(hermit_state_t st, int with_shell, int frame, int ascii, char out[ART_H][ART_W + 1]) { static const char *const shell_base[ART_H] = { " @@@@@@@@ ", " @@ >_ @@ ", " @@@@@@@@ ", " #### ", " #::# ", " | | ", }; static const char *const bare_base[ART_H] = { " ", " ", " #### ", " #::# ", " #### ", " | | ", }; const char *const *src = with_shell ? shell_base : bare_base; int f = frame < 0 ? 0 : frame % HERMIT_FONT_FRAMES; for (int r = 0; r < ART_H; r++) { memcpy(out[r], src[r], ART_W); out[r][ART_W] = 0; } /* Eyes on body row (row 4 with shell, row 3 bare). */ int er = with_shell ? 4 : 3; int blink = (st == HERMIT_ST_IDLE || st == HERMIT_ST_QUESTION) && (f == 3 || f == 4); if (st == HERMIT_ST_CODING) { out[er][4] = '>'; out[er][5] = '_'; out[er][6] = ' '; out[er][7] = '#'; } else if (st == HERMIT_ST_SUCCESS) { out[er][5] = ascii ? '^' : '^'; out[er][6] = ascii ? '^' : '^'; } else if (st == HERMIT_ST_ERROR) { out[er][5] = 'x'; out[er][6] = 'x'; } else if (st == HERMIT_ST_THINKING) { int look = (f % 4) >= 2; out[er][5] = look ? ' ' : ':'; out[er][6] = look ? ':' : ' '; } else if (blink) { out[er][5] = '='; out[er][6] = '='; } /* Accents */ if (st == HERMIT_ST_QUESTION && with_shell) { out[1][10] = '?'; } else if (st == HERMIT_ST_THINKING) { out[er][9] = '~'; } else if (st == HERMIT_ST_ERROR && with_shell) { out[2][10] = '!'; } /* Soft breath: nudge body one cell on even frames for idle/coding. */ if ((st == HERMIT_ST_IDLE || st == HERMIT_ST_CODING) && (f & 1) && with_shell) { /* already subtle via blink; keep grid stable */ } if (ascii) { for (int r = 0; r < ART_H; r++) for (int c = 0; c < ART_W; c++) { char ch = out[r][c]; if (ch == '@') out[r][c] = '#'; else if (ch == ':' || ch == '=') out[r][c] = (ch == '=') ? '-' : ':'; } } } int hermit_shell_art(hermit_state_t st, int with_shell, int ascii, const char **lines, int max_lines) { return hermit_shell_art_frame(st, with_shell, ascii, 0, lines, max_lines); } int hermit_shell_art_frame(hermit_state_t st, int with_shell, int ascii, int frame, const char **lines, int max_lines) { static char buf[ART_H][ART_W + 1]; static char *ptrs[ART_H]; if (!lines || max_lines < 1) return 0; fill_art(st, with_shell, frame, ascii, buf); int n = ART_H < max_lines ? ART_H : max_lines; for (int i = 0; i < n; i++) { ptrs[i] = buf[i]; lines[i] = ptrs[i]; } return n; } int hermit_shell_cell_role(hermit_state_t st, int with_shell, int row, int col) { static char grid[ART_H][ART_W + 1]; fill_art(st, with_shell, 0, 0, grid); if (row < 0 || row >= ART_H || col < 0 || col >= ART_W) return 5; char c = grid[row][col]; if (c == ' ' || !c) return 5; if (c == '>' || c == '_') return 4; if (c == ':' || c == '=' || c == '^' || c == 'x') return 2; if (c == '|' ) return 3; if (c == '?' || c == '!' || c == '~') return 5; /* mark */ if (c == '@') return 1; if (with_shell && row <= 2) return 1; return 0; } int hermit_shell_art_width(void) { return ART_W; } int hermit_shell_art_height(void) { return ART_H; }