#include #include #include #include #include #include #ifndef DATADIR #define DATADIR "/usr/local" #endif #define STARBOX_HEIGHT 12 #define STARBOX_WIDTH 24 #define INFOBOX_WIDTH 40 #define STARBOX_MAX_TITLE ((STARBOX_WIDTH - 2) / 2) char *pick_constellation(void) { char *xdg_dir = getenv("XDG_DATA_HOME"); if (!xdg_dir) xdg_dir = "~/.local/share"; size_t len = snprintf(NULL, 0, "%s/%s", xdg_dir, "stargaze"); char *user_dir = malloc(++len); snprintf(user_dir, len, "%s/%s", xdg_dir, "stargaze"); const char * const dirs[] = { "./stargaze/", user_dir, DATADIR "/stargaze/", }; char **files = calloc(10, sizeof(*files)); size_t nfiles = 0, max_files = 10; for (size_t i = 0; i < sizeof(dirs) / sizeof(*dirs); i++) { DIR *dir = opendir(dirs[i]); if (!dir) continue; struct dirent *dirent; while ((dirent = readdir(dir))) { if (dirent->d_type != DT_REG) continue; if (nfiles + 1 > max_files) files = (max_files += 10, realloc(files, sizeof(*files) * max_files)); size_t len = snprintf(NULL, 0, "%s/%s", dirs[i], dirent->d_name); files[nfiles] = malloc(++len); snprintf(files[nfiles++], len, "%s/%s", dirs[i], dirent->d_name); } closedir(dir); } char *out = NULL; if (nfiles > 0) { srand(time(NULL)); int choice = rand() % nfiles; out = files[choice]; files[choice] = files[--nfiles]; } for (size_t i = 0; i < nfiles; i++) free(files[i]); free(files); free(user_dir); return out; } char *name_to_fullwidth(const char *name, size_t len) { if (!name) return NULL; char *new_name = calloc((len * 3) + 1, sizeof(char)); size_t j = 0; for (size_t i = 0; i < len; i++) { if (isalnum(name[i])) { uint32_t codepoint = name[i] + 0xFEE0; new_name[j++] = 0xE0 | (codepoint >> 12); new_name[j++] = 0x80 | ((codepoint >> 6) & 0x3F); new_name[j++] = 0x80 | (codepoint & 0x3F); } else { new_name[j++] = name[i]; } } new_name[j] = '\0'; return new_name; } enum attibute_types { STAR_ATTR_QUADRANT, STAR_ATTR_ASCENSION, STAR_ATTR_DECLINATION, STAR_ATTR_AREA, STAR_ATTR_STARS, STAR_ATTR_TOTAL }; struct constellation { char *name[2]; size_t name_len[2]; char *display_name[2]; struct attribute { char *key; char *shortname; char *symbol; char *value; } attributes[STAR_ATTR_TOTAL]; size_t longest_attr; size_t n_stars, max_stars; struct point { size_t x, y; } stars[]; }; struct constellation *parse_constellation(const char *path) { FILE *fp = NULL; if (!(fp = fopen(path, "r"))) { return NULL; } struct constellation *constellation = calloc(1, sizeof(*constellation) + sizeof(*constellation->stars) * 10); constellation->max_stars = 10; char *line = NULL; size_t line_bufsiz; size_t len; while ((len = getline(&line, &line_bufsiz, fp)) != -1) { line[len - 1] = '\0'; char *data = strchr(line, ' '); size_t datalen = len - (data - line); if (!data) continue; *data++ = '\0'; if (!*data) continue; if (strcmp(line, "star") == 0) { size_t x, y; if (sscanf(data, "%ld %ld", &x, &y) != 2) continue; if (x >= 20 || y >= 20) continue; if (constellation->n_stars + 1 < constellation->max_stars) { constellation->max_stars += 5; constellation = realloc(constellation, sizeof(*constellation) + sizeof(*constellation->stars) * constellation->max_stars); } constellation->stars[constellation->n_stars++] = (struct point) { .x = x, .y = y }; continue; } else if (strcmp(line, "name") == 0) { if (constellation->name[0]) continue; constellation->name[0] = strdup(data); constellation->name_len[0] = datalen; char *spc; if (constellation->name_len[0] >= STARBOX_MAX_TITLE && (spc = strchr(constellation->name[0], ' '))) { *spc = '\0'; constellation->name[1] = spc + 1; constellation->name_len[1] = constellation->name_len[0] - (constellation->name[1] - constellation->name[0]); constellation->name_len[0] -= constellation->name_len[1]; } constellation->display_name[0] = name_to_fullwidth(constellation->name[0], constellation->name_len[0] > STARBOX_MAX_TITLE ? STARBOX_MAX_TITLE : constellation->name_len[0]); constellation->display_name[1] = name_to_fullwidth(constellation->name[1], constellation->name_len[1] > STARBOX_MAX_TITLE ? STARBOX_MAX_TITLE : constellation->name_len[1]); } else if (strcmp(line, "quadrant") == 0) { if (constellation->attributes[STAR_ATTR_QUADRANT].value) continue; constellation->attributes[STAR_ATTR_QUADRANT] = (struct attribute) { .shortname = "Quad", .key = "Quadrant", .symbol = "Q", .value = strdup(data), }; } else if (strcmp(line, "ascension") == 0) { if (constellation->attributes[STAR_ATTR_ASCENSION].value) continue; constellation->attributes[STAR_ATTR_ASCENSION] = (struct attribute) { .shortname = "Asc", .key = "Ascension", .symbol = "α", .value = strdup(data), }; } else if (strcmp(line, "declination") == 0) { if (constellation->attributes[STAR_ATTR_DECLINATION].value) continue; constellation->attributes[STAR_ATTR_DECLINATION] = (struct attribute) { .shortname = "Decl", .key = "Declination", .symbol = "δ", .value = strdup(data), }; } else if (strcmp(line, "area") == 0) { if (constellation->attributes[STAR_ATTR_AREA].value) continue; constellation->attributes[STAR_ATTR_AREA] = (struct attribute) { .shortname = "Area", .key = "Area", .symbol = "A", .value = strdup(data), }; } else if (strcmp(line, "n_stars") == 0) { if (constellation->attributes[STAR_ATTR_STARS].value) continue; constellation->attributes[STAR_ATTR_STARS] = (struct attribute) { .shortname = "Stars", .key = "Main Stars", .symbol = "✦", .value = strdup(data), }; } } free(line); return constellation; } void constellation_free(struct constellation* constellation) { if (!constellation) return; free(constellation->name[0]); free(constellation->display_name[0]); free(constellation->display_name[1]); for (size_t i = 0; i < STAR_ATTR_TOTAL; i++) { free(constellation->attributes[i].value); } free(constellation); } void usage(const char* name) { fprintf(stderr, "usage: %s [-s|-S] [-f ] [-c ] [-p ]\n\ \n\ -f loads a specific starfile\n\ -c hex color for the stars and highligts\n\ -s | -S symbol or shortname form for the info box\n\ -p the padding between the star window and the info text\n\ \n\ starfiles are picked from ./stargaze, %s/share/stargaze and ${XDG_DATA_HOME}/stargaze (defaults to ~/.local/share)\n", name, DATADIR); } enum info_format { INFO_FULL, INFO_SHORT, INFO_SYM }; int main(int argc, char **argv) { char *starfile = NULL; char opt; enum info_format format = INFO_FULL; struct color { int r, g, b; bool set; } color; size_t padding = 5; while ((opt = getopt(argc, argv, "hf:Ssc:")) != -1) { switch (opt) { case 'h': usage(argv[0]); return EXIT_SUCCESS; case 'S': format = INFO_SHORT; break; case 's': format = INFO_SYM; break; case 'f': starfile = optarg; break; case 'c': if (!(color.set = sscanf(optarg, "%2x%2x%2x", &color.r, &color.g, &color.b) == 3)) fprintf(stderr, "%s is not a valid rgb hex color.\n", optarg); break; case 'p': padding = atoi(optarg); break; default: usage(argv[0]); return EXIT_FAILURE; } } if (!starfile) starfile = pick_constellation(); struct constellation *constellation = parse_constellation(starfile); if (!constellation) { fputs("failed to load constellation file\n", stderr); return EXIT_FAILURE; } struct notcurses_options opts = { .flags = NCOPTION_CLI_MODE | NCOPTION_SUPPRESS_BANNERS | NCOPTION_DRAIN_INPUT, }; struct notcurses *nc = notcurses_core_init(&opts, NULL); if (!nc) { fputs("failed to init notcurses\n", stderr); constellation_free(constellation); return EXIT_FAILURE; } struct ncplane *std = notcurses_stdplane(nc); unsigned cursor_y = ncplane_cursor_y(std); unsigned term_width = ncplane_dim_x(std); if (term_width <= STARBOX_WIDTH) { notcurses_stop(nc); constellation_free(constellation); fputs("terminal window\nis too small\n", stderr); return EXIT_FAILURE; } struct ncplane_options stargaze_opts = { .x = 0, .y = cursor_y, .rows = STARBOX_HEIGHT, .cols = term_width, .flags = NCPLANE_OPTION_AUTOGROW | NCPLANE_OPTION_VSCROLL, }; struct ncplane *stargaze = ncplane_create(std, &stargaze_opts); struct ncplane_options star_opts = { .x = 0, .y = 0, .rows = STARBOX_HEIGHT, .cols = STARBOX_WIDTH, }; struct ncplane *stars = ncplane_create(stargaze, &star_opts); struct ncplane_options info_box_opts = { .x = STARBOX_WIDTH, .y = NCALIGN_CENTER, .rows = STAR_ATTR_TOTAL + 2, // title and empty line .cols = ncplane_dim_x(stargaze) - STARBOX_WIDTH, .flags = NCPLANE_OPTION_VERALIGNED }; struct ncplane *info_box = ncplane_create(stargaze, &info_box_opts); ncplane_scrollup_child(std, stargaze); struct nccell box[6] = { 0 }; nccells_light_box(stars, 0, 0, &box[0], &box[1], &box[2], &box[3], &box[4], &box[5]); ncplane_box_sized(stars, &box[0], &box[1], &box[2], &box[3], &box[4], &box[5], STARBOX_HEIGHT, STARBOX_WIDTH, NCBOXGRAD_TOP | NCBOXGRAD_LEFT | NCBOXGRAD_RIGHT | NCBOXGRAD_BOTTOM); if (color.set) { ncplane_set_fg_rgb8(stars, color.r, color.g, color.b); ncplane_set_fg_rgb8(info_box, color.r, color.g, color.b); } ncplane_on_styles(info_box, NCSTYLE_BOLD); if (constellation->name[0]) { ncplane_putstr_aligned(stars, 0, NCALIGN_CENTER, constellation->display_name[0]); ncplane_putstr_yx(info_box, 0, padding, constellation->name[0]); } if (constellation->name[1]) { ncplane_putstr_aligned(stars, STARBOX_HEIGHT - 1, NCALIGN_CENTER, constellation->display_name[1]); ncplane_printf(info_box, " %s", constellation->name[1]); } for (size_t i = 0; i < constellation->n_stars; i++) ncplane_putstr_yx(stars, constellation->stars[i].y, constellation->stars[i].x, "✦"); size_t info_index = 2; for (size_t i = 0; i < STAR_ATTR_TOTAL; i++) { if (!constellation->attributes[i].value) continue; char *key = NULL; switch (format) { case INFO_FULL: key = constellation->attributes[i].key; break; case INFO_SHORT: key = constellation->attributes[i].shortname; break; case INFO_SYM: key = constellation->attributes[i].symbol; break; } if (color.set) ncplane_set_fg_rgb8(info_box, color.r, color.g, color.b); ncplane_on_styles(info_box, NCSTYLE_BOLD); ncplane_printf_yx(info_box, info_index++, padding, "%s: ", key); ncplane_set_fg_default(info_box); ncplane_off_styles(info_box, NCSTYLE_BOLD); ncplane_putstr(info_box, constellation->attributes[i].value); } notcurses_render(nc); constellation_free(constellation); notcurses_stop(nc); puts("\n"); }