aboutsummaryrefslogtreecommitdiff
path: root/main.c
diff options
context:
space:
mode:
authorAnna (navi) Figueiredo Gomes <navi@vlhl.dev>2023-11-09 16:58:59 +0100
committerAnna (navi) Figueiredo Gomes <navi@vlhl.dev>2023-11-13 03:54:48 +0100
commitb61f6f4d53803ecf83d9d6152a48bc39c1f2dd42 (patch)
treef8f68b649c39e61aceb1afdae8eaa5afb081ffd0 /main.c
init
Signed-off-by: Anna (navi) Figueiredo Gomes <navi@vlhl.dev>
Diffstat (limited to 'main.c')
-rw-r--r--main.c391
1 files changed, 391 insertions, 0 deletions
diff --git a/main.c b/main.c
new file mode 100644
index 0000000..1f33800
--- /dev/null
+++ b/main.c
@@ -0,0 +1,391 @@
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+#include <dirent.h>
+#include <time.h>
+#include <notcurses/notcurses.h>
+
+#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 <starfile>] [-c <color>] [-p <padding>]\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(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");
+}