release: Hermit 1.0.0 — home layout, icon font, install
Compact the home parliament chip, animate crab states like the HTML kit, ship hermit-icons.ttf (sbix) + frames, and install the font from make/ bootstrap/package paths. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+145
-41
@@ -1,5 +1,6 @@
|
||||
#define _POSIX_C_SOURCE 200809L
|
||||
#include "cst_hermit_glyph.h"
|
||||
#include "os_compat.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
@@ -18,6 +19,40 @@ const char *hermit_getenv(const char *suffix) {
|
||||
return e ? e : "";
|
||||
}
|
||||
|
||||
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"))
|
||||
@@ -33,9 +68,10 @@ hermit_icon_mode_t hermit_icon_mode_eff(int ascii) {
|
||||
hermit_icon_mode_t m = hermit_icon_mode();
|
||||
if (m != HERMIT_ICON_AUTO)
|
||||
return m;
|
||||
/* AUTO: font only when forced path exists or HERMIT_ICON_FONT=1. */
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -101,67 +137,135 @@ const char *hermit_mini_badge(hermit_state_t st, int frame, int ascii) {
|
||||
}
|
||||
}
|
||||
|
||||
/* --- home shell art (fixed grid) ----------------------------------------- */
|
||||
/* 6 rows x 14 cols. Roles: 0 body 1 shell 2 eye 3 leg 4 prompt 5 empty/mark */
|
||||
/* --- 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 14
|
||||
#define ART_W 12
|
||||
|
||||
static const char *const ART_SHELL[ART_H] = {
|
||||
" ######## ", /* shell top */
|
||||
" ## >_ ## ", /* shell + prompt */
|
||||
" ########## ", /* shell bottom */
|
||||
" ###### ", /* body */
|
||||
" #::## ", /* eyes :: on body */
|
||||
" | | ", /* legs */
|
||||
};
|
||||
/* Base grids use:
|
||||
* @ shell # body : open eye = blink >_ prompt | leg ? mark ~ claw
|
||||
* Eyes/prompt mutate per state+frame below. */
|
||||
|
||||
static const char *const ART_NOSHELL[ART_H] = {
|
||||
" ",
|
||||
" ",
|
||||
" ###### ",
|
||||
" #::## ",
|
||||
" ###### ",
|
||||
" | | ",
|
||||
};
|
||||
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;
|
||||
}
|
||||
|
||||
static const char *const ART_SHELL_ASC[ART_H] = {
|
||||
" ######## ",
|
||||
" ## >_ ## ",
|
||||
" ########## ",
|
||||
" ###### ",
|
||||
" #::## ",
|
||||
" | | ",
|
||||
};
|
||||
/* 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) {
|
||||
(void)st;
|
||||
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;
|
||||
const char *const *src =
|
||||
with_shell ? (ascii ? ART_SHELL_ASC : ART_SHELL) : ART_NOSHELL;
|
||||
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++)
|
||||
lines[i] = src[i];
|
||||
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) {
|
||||
(void)st;
|
||||
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;
|
||||
const char *const *src = with_shell ? ART_SHELL : ART_NOSHELL;
|
||||
char c = src[row][col];
|
||||
char c = grid[row][col];
|
||||
if (c == ' ' || !c)
|
||||
return 5;
|
||||
if (c == '>' || c == '_')
|
||||
return 4;
|
||||
if (c == ':')
|
||||
if (c == ':' || c == '=' || c == '^' || c == 'x')
|
||||
return 2;
|
||||
if (c == '|')
|
||||
if (c == '|' )
|
||||
return 3;
|
||||
if (c == '?' || c == '!' || c == '~')
|
||||
return 5; /* mark */
|
||||
if (c == '@')
|
||||
return 1;
|
||||
if (with_shell && row <= 2)
|
||||
return 1; /* shell */
|
||||
return 0; /* body */
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int hermit_shell_art_width(void) { return ART_W; }
|
||||
int hermit_shell_art_height(void) { return ART_H; }
|
||||
|
||||
@@ -2721,6 +2721,8 @@ static int hermit_role_pair(int role) {
|
||||
return COLORS >= 256 ? CP_MUTED : CP_ERROR; /* brick legs */
|
||||
case 4:
|
||||
return CP_SGR_CYAN; /* >_ */
|
||||
case 5:
|
||||
return CP_ASSIST; /* ? ! claw mark */
|
||||
default:
|
||||
return CP_MUTED;
|
||||
}
|
||||
@@ -2743,15 +2745,31 @@ static void draw_hermit_icon(WINDOW *win, int y0, int x0, hermit_state_t st,
|
||||
}
|
||||
}
|
||||
const char *lines[8];
|
||||
int n = hermit_shell_art(st, with_shell, g_ascii, lines, 8);
|
||||
int n = hermit_shell_art_frame(st, with_shell, g_ascii, frame, lines, 8);
|
||||
for (int r = 0; r < n; r++) {
|
||||
const char *row = lines[r];
|
||||
for (int c = 0; row[c]; c++) {
|
||||
if (row[c] == ' ')
|
||||
char raw = row[c];
|
||||
if (raw == ' ')
|
||||
continue;
|
||||
char ch[2] = {row[c], 0};
|
||||
int pair = hermit_role_pair(hermit_shell_cell_role(st, with_shell, r, c));
|
||||
draw_ansi_at(win, y0 + r, x0 + c, 1, ch, pair, 0, 0);
|
||||
char draw = (raw == '@') ? '#' : raw;
|
||||
int role = 0;
|
||||
if (raw == '@')
|
||||
role = 1;
|
||||
else if (raw == '#' )
|
||||
role = (with_shell && r <= 2) ? 1 : 0;
|
||||
else if (raw == '>' || raw == '_')
|
||||
role = 4;
|
||||
else if (raw == ':' || raw == '=' || raw == '^' || raw == 'x')
|
||||
role = 2;
|
||||
else if (raw == '|')
|
||||
role = 3;
|
||||
else if (raw == '?' || raw == '!' || raw == '~')
|
||||
role = 5;
|
||||
else
|
||||
role = hermit_shell_cell_role(st, with_shell, r, c);
|
||||
char ch[2] = {draw, 0};
|
||||
draw_ansi_at(win, y0 + r, x0 + c, 1, ch, hermit_role_pair(role), 0, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2804,10 +2822,12 @@ static void draw_home(tui_t *t) {
|
||||
const char *lb = hermit_getenv("LOGO");
|
||||
want_block = lb[0] && !strcmp(lb, "block");
|
||||
}
|
||||
/* Crab on the right of the brand stack when there is room. */
|
||||
int crab_w = 14, crab_h = 6;
|
||||
hermit_state_t crab_st = hermit_state_from_ui(
|
||||
t->busy, t->ask_pending || t->awaiting_perm, 0);
|
||||
int crab_w = hermit_shell_art_width();
|
||||
int crab_h = hermit_shell_art_height();
|
||||
int crab_x = w - crab_w - 2;
|
||||
int show_crab = crab_x > LOGO_W + 4 && box_top >= crab_h + 1;
|
||||
int show_crab = crab_x > 20 && box_top >= crab_h + 1 && w >= 48;
|
||||
if (want_block && !(g_ascii || !g_utf8 || g_logo_text) &&
|
||||
box_top - LOGO_H - 2 >= 0 && w >= LOGO_W) {
|
||||
int x0 = show_crab ? (crab_x - LOGO_W) / 2 : (w - LOGO_W) / 2;
|
||||
@@ -2822,7 +2842,7 @@ static void draw_home(tui_t *t) {
|
||||
} else {
|
||||
const char *mark = "HERMIT";
|
||||
int mw = (int)strlen(mark);
|
||||
int x = show_crab ? (crab_x - mw) / 2 : (w - mw) / 2;
|
||||
int x = (w - mw) / 2;
|
||||
if (x < 0)
|
||||
x = 0;
|
||||
if (brand_y >= 1)
|
||||
@@ -2830,32 +2850,42 @@ static void draw_home(tui_t *t) {
|
||||
draw_ansi_at(t->logw, brand_y, x, mw, mark, CP_ACCENT, 1, 0);
|
||||
}
|
||||
if (show_crab) {
|
||||
int cy = box_top - crab_h;
|
||||
int cy = brand_y;
|
||||
if (cy + crab_h > box_top)
|
||||
cy = box_top - crab_h;
|
||||
if (cy < 0)
|
||||
cy = 0;
|
||||
draw_hermit_icon(t->logw, cy, crab_x, HERMIT_ST_IDLE, 1, t->spin);
|
||||
draw_hermit_icon(t->logw, cy, crab_x, crab_st, 1, t->spin);
|
||||
}
|
||||
int ty = brand_y + 1;
|
||||
if (ty < box_top) {
|
||||
const char *tag =
|
||||
g_ascii ? "code . plan . parliament" : "code · plan · parliament";
|
||||
int tw = (int)strlen(tag);
|
||||
int x = show_crab ? (crab_x - tw) / 2 : (w - tw) / 2;
|
||||
int x = (w - tw) / 2;
|
||||
if (x < 0)
|
||||
x = 0;
|
||||
draw_ansi_at(t->logw, ty, x, tw, tag, CP_MUTED, 0, 0);
|
||||
}
|
||||
/* Compact parliament chip — never dump full status_text on the home. */
|
||||
if (cst_parliament_is_active() && ty + 1 < box_top) {
|
||||
char *st = cst_parliament_status_text();
|
||||
if (st && st[0]) {
|
||||
char chip[160];
|
||||
snprintf(chip, sizeof(chip), "%s %s", g_ascii ? "*" : "◆", st);
|
||||
int cw = vis_cols(chip);
|
||||
if (cw > w - 2)
|
||||
cw = w - 2;
|
||||
draw_ansi_at(t->logw, ty + 1, (w - cw) / 2, cw, chip, CP_ACCENT, 0, 0);
|
||||
}
|
||||
free(st);
|
||||
cst_parliament_meta_t m;
|
||||
memset(&m, 0, sizeof(m));
|
||||
cst_parliament_load(&m);
|
||||
char sid[64] = {0};
|
||||
int sess = cst_parliament_session_ok(sid, sizeof(sid));
|
||||
int chair = sess && cst_parliament_is_chair();
|
||||
char chip[96];
|
||||
snprintf(chip, sizeof(chip), "%s parliament · rev=%lu · %s",
|
||||
g_ascii ? "*" : "◆", (unsigned long)m.revision,
|
||||
!sess ? "login" : chair ? "chair" : "member");
|
||||
int cw = vis_cols(chip);
|
||||
if (cw > w - 4)
|
||||
cw = w - 4;
|
||||
int cx = (w - cw) / 2;
|
||||
if (cx < 0)
|
||||
cx = 0;
|
||||
draw_ansi_at(t->logw, ty + 1, cx, cw, chip, CP_ACCENT, 0, 0);
|
||||
}
|
||||
int right = t->in_x - t->log_x + t->in_w;
|
||||
if (box_bot < h) {
|
||||
|
||||
Reference in New Issue
Block a user