commits
tags
#include "dirpicker.h"
#include <dirent.h>
#include <ncurses.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include "colors.h"
#include "icons.h"
#include "util.h"
#define MAX_ENTRIES 1024
#define NAME_CAP 256
static int cmp_names(const void *a, const void *b) {
return strcasecmp((const char *)a, (const char *)b);
}
static int list_subdirs(const char *dir, char entries[][NAME_CAP], int max) {
DIR *d = opendir(dir);
if (!d) return 0;
int n = 0;
struct dirent *de;
while (n < max && (de = readdir(d)) != NULL) {
if (de->d_name[0] == '.') continue;
char full[2048];
snprintf(full, sizeof(full), "%s/%s", dir, de->d_name);
struct stat st;
if (stat(full, &st) != 0 || !S_ISDIR(st.st_mode)) continue;
safe_strcpy(entries[n], NAME_CAP, de->d_name);
n++;
}
closedir(d);
qsort(entries, n, NAME_CAP, cmp_names);
return n;
}
static void nearest_existing_dir(const char *start, char *out, size_t cap) {
char path[1024];
safe_strcpy(path, sizeof(path), start);
struct stat st;
while (stat(path, &st) != 0 || !S_ISDIR(st.st_mode)) {
char *slash = strrchr(path, '/');
if (!slash || slash == path) {
safe_strcpy(path, sizeof(path), getenv("HOME") ? getenv("HOME") : "/");
break;
}
*slash = '\0';
if (path[0] == '\0') safe_strcpy(path, sizeof(path), "/");
}
safe_strcpy(out, cap, path);
}
static void go_to_parent(char *dir) {
if (strcmp(dir, "/") == 0) return;
char *slash = strrchr(dir, '/');
if (!slash) return;
if (slash == dir) {
dir[1] = '\0';
} else {
*slash = '\0';
}
}
int dirpicker_run(const char *start_path, char *chosen, size_t cap) {
char current_dir[1024];
nearest_existing_dir(start_path, current_dir, sizeof(current_dir));
static char entries[MAX_ENTRIES][NAME_CAP];
int entry_count = list_subdirs(current_dir, entries, MAX_ENTRIES);
int selected = 0;
int rows, cols;
getmaxyx(stdscr, rows, cols);
int win_h = rows * 4 / 5;
int win_w = cols * 4 / 5;
int win_y = (rows - win_h) / 2;
int win_x = (cols - win_w) / 2;
WINDOW *win = newwin(win_h, win_w, win_y, win_x);
keypad(win, TRUE);
int result = 0;
int running = 1;
while (running) {
werase(win);
draw_rounded_box(win, PAIR_BORDER_FOCUS);
wattron(win, A_BOLD);
mvwprintw(win, 0, 2, " Choose a folder to save downloads to ");
wattroff(win, A_BOLD);
mvwprintw(win, 1, 2, "Current: %.*s", win_w - 14, current_dir);
int list_top = 3;
int list_h = win_h - list_top - 3;
int start_idx = 0;
if (selected >= list_h) start_idx = selected - list_h + 1;
if (entry_count == 0) {
wattron(win, A_DIM);
mvwprintw(win, list_top, 4, "(no subfolders)");
wattroff(win, A_DIM);
}
for (int row = 0; row < list_h && start_idx + row < entry_count; row++) {
int idx = start_idx + row;
if (idx == selected) wattron(win, A_REVERSE);
wattron(win, COLOR_PAIR(PAIR_STATUS_PENDING));
mvwprintw(win, list_top + row, 2, "%s", ICON_FOLDER);
wattroff(win, COLOR_PAIR(PAIR_STATUS_PENDING));
mvwprintw(win, list_top + row, 4, "%.*s", win_w - 8, entries[idx]);
if (idx == selected) wattroff(win, A_REVERSE);
}
wattron(win, A_DIM);
mvwprintw(win, win_h - 2, 2,
"Up/Down move Enter/Right open Backspace/Left up s select Esc cancel");
wattroff(win, A_DIM);
wnoutrefresh(win);
doupdate();
int ch = wgetch(win);
switch (ch) {
case KEY_UP:
if (selected > 0) selected--;
break;
case KEY_DOWN:
if (selected < entry_count - 1) selected++;
break;
case KEY_RIGHT:
case '\n':
case '\r':
case KEY_ENTER:
if (entry_count > 0) {
char next[1024 + NAME_CAP];
snprintf(next, sizeof(next), "%s/%s", current_dir, entries[selected]);
safe_strcpy(current_dir, sizeof(current_dir), next);
entry_count = list_subdirs(current_dir, entries, MAX_ENTRIES);
selected = 0;
}
break;
case KEY_LEFT:
case KEY_BACKSPACE:
case 127:
case 8:
go_to_parent(current_dir);
entry_count = list_subdirs(current_dir, entries, MAX_ENTRIES);
selected = 0;
break;
case 's':
case 'S':
safe_strcpy(chosen, cap, current_dir);
result = 1;
running = 0;
break;
case 27: /* Esc */
running = 0;
break;
default:
break;
}
}
delwin(win);
return result;
}