diff --git a/dir_nav b/dir_nav new file mode 100755 index 0000000..cba0efd Binary files /dev/null and b/dir_nav differ diff --git a/src/main.h b/src/main.h index 86e0efc..9dea81a 100644 --- a/src/main.h +++ b/src/main.h @@ -287,6 +287,7 @@ char* find_pad_by_prefix(const char* prefix); int show_pad_info(const char* chksum); void show_progress(uint64_t current, uint64_t total, time_t start_time); void format_time_remaining(double seconds, char* buffer, size_t buffer_size); +int is_escape_input(const char* input); //////////////////////////////////////////////////////////////////////////////// // FILE OPERATIONS diff --git a/src/ui.c b/src/ui.c index 6052366..520e263 100644 --- a/src/ui.c +++ b/src/ui.c @@ -184,7 +184,7 @@ int handle_encrypt_menu(void) { printf("\nSelect encryption type:\n"); printf(" 1. Text message\n"); printf(" 2. File\n"); - printf("Enter choice (1-2): "); + printf("Enter choice (1-2) or 'esc' to cancel: "); char choice_input[10]; if (!fgets(choice_input, sizeof(choice_input), stdin)) { @@ -192,6 +192,14 @@ int handle_encrypt_menu(void) { return 1; } + choice_input[strcspn(choice_input, "\n")] = 0; + + // Check for ESC/cancel + if (is_escape_input(choice_input)) { + printf("Returning to main menu...\n"); + return 0; + } + int choice = atoi(choice_input); if (choice == 1) { @@ -213,7 +221,7 @@ int handle_encrypt_menu(void) { printf("\nFile selection options:\n"); printf(" 1. Type file path directly\n"); printf(" 2. Use file manager\n"); - printf("Enter choice (1-2): "); + printf("Enter choice (1-2) or 'esc' to cancel: "); char file_choice[10]; char input_file[512]; @@ -223,25 +231,45 @@ int handle_encrypt_menu(void) { return 1; } + file_choice[strcspn(file_choice, "\n")] = 0; + + // Check for ESC/cancel + if (is_escape_input(file_choice)) { + printf("Returning to main menu...\n"); + return 0; + } + if (atoi(file_choice) == 2) { // Use file manager if (launch_file_manager(".", input_file, sizeof(input_file)) != 0) { printf("Falling back to manual file path entry.\n"); - printf("Enter input file path: "); + printf("Enter input file path (or 'esc' to cancel): "); if (!fgets(input_file, sizeof(input_file), stdin)) { printf("Error: Failed to read input\n"); return 1; } input_file[strcspn(input_file, "\n")] = 0; + + // Check for ESC/cancel + if (is_escape_input(input_file)) { + printf("Returning to main menu...\n"); + return 0; + } } } else { // Direct file path input - printf("Enter input file path: "); + printf("Enter input file path (or 'esc' to cancel): "); if (!fgets(input_file, sizeof(input_file), stdin)) { printf("Error: Failed to read input\n"); return 1; } input_file[strcspn(input_file, "\n")] = 0; + + // Check for ESC/cancel + if (is_escape_input(input_file)) { + printf("Returning to main menu...\n"); + return 0; + } } // Check if file exists @@ -263,14 +291,24 @@ int handle_encrypt_menu(void) { printf("\nSelect output format:\n"); printf(" 1. Binary (.otp) - preserves file permissions\n"); printf(" 2. ASCII (.otp.asc) - text-safe format\n"); - printf("Enter choice (1-2): "); + printf("Enter choice (1-2) or 'esc' to cancel: "); char format_input[10]; if (!fgets(format_input, sizeof(format_input), stdin)) { printf("Error: Failed to read input\n"); + free(selected_pad); return 1; } + format_input[strcspn(format_input, "\n")] = 0; + + // Check for ESC/cancel + if (is_escape_input(format_input)) { + printf("Returning to main menu...\n"); + free(selected_pad); + return 0; + } + int ascii_armor = (atoi(format_input) == 2) ? 1 : 0; // Generate default output filename with files directory and use enhanced input function @@ -309,6 +347,7 @@ int handle_decrypt_menu(void) { printf("\n"); print_centered_header("Smart Decrypt", 0); printf("Enter encrypted data (paste ASCII armor), file path, or press Enter to browse files:\n"); + printf("(Type 'esc' or 'q' to return to main menu)\n"); char input_line[MAX_LINE_LENGTH]; if (!fgets(input_line, sizeof(input_line), stdin)) { @@ -319,6 +358,12 @@ int handle_decrypt_menu(void) { // Remove newline input_line[strcspn(input_line, "\n")] = 0; + // Check for ESC/cancel + if (is_escape_input(input_line)) { + printf("Returning to main menu...\n"); + return 0; + } + // Trim leading whitespace to handle pasted content better char* trimmed_input = input_line; while (*trimmed_input == ' ' || *trimmed_input == '\t') { @@ -532,14 +577,24 @@ int handle_file_encrypt(void) { printf("\nSelect output format:\n"); printf(" 1. Binary (.otp) - preserves file permissions\n"); printf(" 2. ASCII (.otp.asc) - text-safe format\n"); - printf("Enter choice (1-2): "); + printf("Enter choice (1-2) or 'esc' to cancel: "); char format_input[10]; if (!fgets(format_input, sizeof(format_input), stdin)) { printf("Error: Failed to read input\n"); + free(selected_pad); return 1; } + format_input[strcspn(format_input, "\n")] = 0; + + // Check for ESC/cancel + if (is_escape_input(format_input)) { + printf("Returning to main menu...\n"); + free(selected_pad); + return 0; + } + int ascii_armor = (atoi(format_input) == 2) ? 1 : 0; // Generate default output filename @@ -564,6 +619,184 @@ int handle_file_encrypt(void) { return result; } +// Comparison function for qsort (case-insensitive) +static int compare_dir_strings(const void *a, const void *b) { + return strcasecmp(*(const char**)a, *(const char**)b); +} + +// Function to get list of subdirectories +static int get_subdirs(const char *path, char ***subdirs) { + DIR *dir = opendir(path); + if (!dir) return 0; + + int count = 0; + int capacity = 10; + *subdirs = malloc(capacity * sizeof(char*)); + + struct dirent *entry; + while ((entry = readdir(dir)) != NULL) { + if (entry->d_type == DT_DIR) { + // Skip . and .. + if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) + continue; + + if (count >= capacity) { + capacity *= 2; + *subdirs = realloc(*subdirs, capacity * sizeof(char*)); + } + (*subdirs)[count++] = strdup(entry->d_name); + } + } + closedir(dir); + + // Sort alphabetically + if (count > 0) { + qsort(*subdirs, count, sizeof(char*), compare_dir_strings); + } + + return count; +} + +// Function to get a single keypress without echo +static int getch_nav(void) { + struct termios oldt, newt; + int ch; + tcgetattr(STDIN_FILENO, &oldt); + newt = oldt; + newt.c_lflag &= ~(ICANON | ECHO); + tcsetattr(STDIN_FILENO, TCSANOW, &newt); + ch = getchar(); + tcsetattr(STDIN_FILENO, TCSANOW, &oldt); + return ch; +} + +// Navigate directories with arrow keys +static char* navigate_directory_interactive(void) { + char current_path[PATH_MAX]; + char original_path[PATH_MAX]; + getcwd(original_path, sizeof(original_path)); + getcwd(current_path, sizeof(current_path)); + + char **subdirs = NULL; + int subdir_count = 0; + int current_index = 0; + + printf("\nNavigate with arrow keys (UP/DOWN: cycle, RIGHT: enter, LEFT: back, ENTER: select, Q: cancel)\n"); + printf("\033[?25l"); // Hide cursor + + while (1) { + // Clear line and show current path + printf("\r\033[K%s", current_path); + + // If we have subdirectories and an index, show current selection + if (subdir_count > 0 && current_index < subdir_count) { + printf("/%s", subdirs[current_index]); + } + + fflush(stdout); + + int ch = getch_nav(); + + // Handle arrow keys (they come as escape sequences) + if (ch == 27) { // ESC sequence + getch_nav(); // Skip '[' + ch = getch_nav(); + + if (ch == 'A') { // UP arrow + // Load subdirs if not loaded + if (subdirs == NULL) { + subdir_count = get_subdirs(current_path, &subdirs); + current_index = 0; + } else if (subdir_count > 0) { + current_index = (current_index - 1 + subdir_count) % subdir_count; + } + } + else if (ch == 'B') { // DOWN arrow + // Load subdirs if not loaded + if (subdirs == NULL) { + subdir_count = get_subdirs(current_path, &subdirs); + current_index = 0; + } else if (subdir_count > 0) { + current_index = (current_index + 1) % subdir_count; + } + } + else if (ch == 'C') { // RIGHT arrow - go deeper + if (subdir_count > 0 && current_index < subdir_count) { + // Navigate into selected directory + char new_path[PATH_MAX]; + snprintf(new_path, sizeof(new_path), "%s/%s", current_path, subdirs[current_index]); + + if (chdir(new_path) == 0) { + getcwd(current_path, sizeof(current_path)); + + // Free old subdirs + for (int i = 0; i < subdir_count; i++) { + free(subdirs[i]); + } + free(subdirs); + subdirs = NULL; + + // Load subdirs of new directory and show first one + subdir_count = get_subdirs(current_path, &subdirs); + current_index = 0; + } + } + } + else if (ch == 'D') { // LEFT arrow - go up + if (chdir("..") == 0) { + getcwd(current_path, sizeof(current_path)); + + // Free old subdirs + for (int i = 0; i < subdir_count; i++) { + free(subdirs[i]); + } + free(subdirs); + subdirs = NULL; + subdir_count = 0; + current_index = 0; + } + } + } + else if (ch == '\n' || ch == '\r') { // ENTER - confirm selection + // If a subdirectory is displayed, navigate into it first + if (subdir_count > 0 && current_index < subdir_count) { + char new_path[PATH_MAX]; + snprintf(new_path, sizeof(new_path), "%s/%s", current_path, subdirs[current_index]); + + if (chdir(new_path) == 0) { + getcwd(current_path, sizeof(current_path)); + } + } + break; + } + else if (ch == 'q' || ch == 'Q') { // Q to quit + printf("\033[?25h\n"); // Show cursor + // Restore original directory + chdir(original_path); + // Clean up + for (int i = 0; i < subdir_count; i++) { + free(subdirs[i]); + } + free(subdirs); + return NULL; + } + } + + printf("\033[?25h\n"); // Show cursor + + // Clean up + for (int i = 0; i < subdir_count; i++) { + free(subdirs[i]); + } + free(subdirs); + + // Restore original directory before returning + char* result = strdup(current_path); + chdir(original_path); + + return result; +} + int handle_directory_encrypt(void) { printf("\n"); print_centered_header("Directory Encrypt", 0); @@ -571,8 +804,9 @@ int handle_directory_encrypt(void) { // Directory selection options printf("\nDirectory selection options:\n"); printf(" 1. Type directory path directly\n"); - printf(" 2. Use file manager (navigate to directory)\n"); - printf("Enter choice (1-2): "); + printf(" 2. Navigate with arrow keys\n"); + printf(" 3. Use file manager (navigate to directory)\n"); + printf("Enter choice (1-3) or 'esc' to cancel: "); char choice_input[10]; char dir_path[512]; @@ -582,25 +816,59 @@ int handle_directory_encrypt(void) { return 1; } - if (atoi(choice_input) == 2) { + choice_input[strcspn(choice_input, "\n")] = 0; + + // Check for ESC/cancel + if (is_escape_input(choice_input)) { + printf("Returning to main menu...\n"); + return 0; + } + + int choice = atoi(choice_input); + + if (choice == 2) { + // Use arrow key navigation + char* selected = navigate_directory_interactive(); + if (!selected) { + printf("Directory selection cancelled.\n"); + return 0; + } + strncpy(dir_path, selected, sizeof(dir_path) - 1); + dir_path[sizeof(dir_path) - 1] = '\0'; + free(selected); + printf("Selected: %s\n", dir_path); + } + else if (choice == 3) { // Use directory manager if (launch_directory_manager(".", dir_path, sizeof(dir_path)) != 0) { printf("Falling back to manual directory path entry.\n"); - printf("Enter directory path to encrypt: "); + printf("Enter directory path to encrypt (or 'esc' to cancel): "); if (!fgets(dir_path, sizeof(dir_path), stdin)) { printf("Error: Failed to read input\n"); return 1; } dir_path[strcspn(dir_path, "\n")] = 0; + + // Check for ESC/cancel + if (is_escape_input(dir_path)) { + printf("Returning to main menu...\n"); + return 0; + } } } else { // Direct directory path input - printf("Enter directory path to encrypt: "); + printf("Enter directory path to encrypt (or 'esc' to cancel): "); if (!fgets(dir_path, sizeof(dir_path), stdin)) { printf("Error: Failed to read input\n"); return 1; } dir_path[strcspn(dir_path, "\n")] = 0; + + // Check for ESC/cancel + if (is_escape_input(dir_path)) { + printf("Returning to main menu...\n"); + return 0; + } } // Check if directory exists diff --git a/src/util.c b/src/util.c index 4da0756..1d768fc 100644 --- a/src/util.c +++ b/src/util.c @@ -162,7 +162,8 @@ int launch_text_editor(const char* initial_content, char* result_buffer, size_t char* get_preferred_file_manager(void) { // Try file managers in order of preference - const char* file_managers[] = {"ranger", "fzf", "nnn", "lf", NULL}; + // fzf is first because it's more intuitive with fuzzy search + const char* file_managers[] = {"fzf", "ranger", "nnn", "lf", NULL}; for (int i = 0; file_managers[i] != NULL; i++) { char command[512]; @@ -178,7 +179,8 @@ char* get_preferred_file_manager(void) { int launch_file_manager(const char* start_directory, char* selected_file, size_t buffer_size) { char* fm = get_preferred_file_manager(); if (!fm) { - printf("No file manager found. Please install ranger, fzf, nnn, or lf.\n"); + printf("No file manager found. Please install fzf, ranger, nnn, or lf.\n"); + printf("Recommended: sudo apt install fzf\n"); printf("Falling back to manual file path entry.\n"); return 1; // Fall back to manual entry } @@ -190,6 +192,13 @@ int launch_file_manager(const char* start_directory, char* selected_file, size_t int result = 1; printf("Opening %s for file selection...\n", fm); + + // Show helpful instructions based on file manager + if (strcmp(fm, "fzf") == 0) { + printf("Instructions: Type to search, use arrow keys, press Enter to select\n"); + } else if (strcmp(fm, "ranger") == 0) { + printf("Instructions: Arrow keys or j/k to navigate, Enter or l to select, q to quit\n"); + } if (strcmp(fm, "ranger") == 0) { snprintf(command, sizeof(command), "cd '%s' && ranger --choosefile=%s", @@ -244,7 +253,8 @@ int launch_file_manager(const char* start_directory, char* selected_file, size_t int launch_directory_manager(const char* start_directory, char* selected_dir, size_t buffer_size) { char* fm = get_preferred_file_manager(); if (!fm) { - printf("No file manager found. Please install ranger, fzf, nnn, or lf.\n"); + printf("No file manager found. Please install fzf, ranger, nnn, or lf.\n"); + printf("Recommended: sudo apt install fzf\n"); printf("Falling back to manual directory path entry.\n"); return 1; // Fall back to manual entry } @@ -256,7 +266,13 @@ int launch_directory_manager(const char* start_directory, char* selected_dir, si int result = 1; printf("Opening %s for directory selection...\n", fm); - printf("Navigate INTO the directory you want to encrypt, then press 'q' to quit and select it.\n"); + + // Show helpful instructions based on file manager + if (strcmp(fm, "fzf") == 0) { + printf("Instructions: Type to search, use arrow keys, press Enter to select directory\n"); + } else if (strcmp(fm, "ranger") == 0) { + printf("Instructions: Navigate INTO the directory, then press 'q' to quit and select it\n"); + } if (strcmp(fm, "ranger") == 0) { snprintf(command, sizeof(command), "cd '%s' && ranger --choosedir=%s", @@ -318,6 +334,19 @@ int launch_directory_manager(const char* start_directory, char* selected_dir, si return 1; // Fall back to manual entry } +// Helper function to check if input contains ESC key +int is_escape_input(const char* input) { + // Check for ESC character (ASCII 27) or empty input after ESC + if (input && (input[0] == 27 || (input[0] == '\0' && strlen(input) == 0))) { + return 1; + } + // Also check for literal "esc" or "ESC" typed + if (input && (strcasecmp(input, "esc") == 0 || strcasecmp(input, "q") == 0)) { + return 1; + } + return 0; +} + // Stdin detection functions implementation int has_stdin_data(void) { // Check if stdin is a pipe/redirect (not a terminal)