FEAT: Lumos file searcher

This commit is contained in:
boreddevnl 2026-04-12 21:46:28 +02:00
parent 8dd756f25b
commit bb176f2193
10 changed files with 891 additions and 22 deletions

View file

@ -269,3 +269,7 @@ uint64_t mouse_handler(registers_t *regs) {
void ps2_init(void) { void ps2_init(void) {
mouse_init(); mouse_init();
} }
bool ps2_shift_pressed(void) {
return shift_pressed;
}

View file

@ -13,4 +13,6 @@ uint64_t timer_handler(registers_t *regs);
uint64_t keyboard_handler(registers_t *regs); uint64_t keyboard_handler(registers_t *regs);
uint64_t mouse_handler(registers_t *regs); uint64_t mouse_handler(registers_t *regs);
bool ps2_shift_pressed(void);
#endif #endif

477
src/sys/file_index.c Normal file
View file

@ -0,0 +1,477 @@
// Copyright (c) 2023-2026 Chris (boreddevnl)
// This software is released under the GNU General Public License v3.0. See LICENSE file for details.
// This header needs to maintain in any file it is present in, as per the GPL license terms.
#include "file_index.h"
#include "vfs.h"
#include "memory_manager.h"
#include "spinlock.h"
#include <stddef.h>
static file_index_t g_file_index = {0};
static spinlock_t g_index_lock = SPINLOCK_INIT;
static bool g_index_valid = false;
static int str_len(const char *s) {
int n = 0;
while (s[n]) n++;
return n;
}
static void str_copy(char *d, const char *s) {
while ((*d++ = *s++));
}
static int str_cmp(const char *a, const char *b) {
while (*a && *a == *b) { a++; b++; }
return (unsigned char)*a - (unsigned char)*b;
}
static void str_cat(char *d, const char *s) {
while (*d) d++;
str_copy(d, s);
}
static bool str_starts_with(const char *str, const char *prefix) {
while (*prefix) {
if (*str++ != *prefix++) return false;
}
return true;
}
static int fuzzy_match_score(const char *query, const char *filename) {
if (!query || !filename) return 0;
int score = 0;
int query_idx = 0;
int consecutive = 0;
for (int i = 0; filename[i] && query_idx < 256; i++) {
char fc = filename[i];
char qc = query[query_idx];
if (fc >= 'A' && fc <= 'Z') fc += 32;
if (qc >= 'A' && qc <= 'Z') qc += 32;
if (fc == qc) {
score += 10;
consecutive++;
if (consecutive > 1) score += 5;
query_idx++;
} else {
consecutive = 0;
}
}
if (query_idx < str_len(query)) {
return 0;
}
if (str_starts_with(filename, query)) {
score += 20;
}
return score;
}
static void index_walk_directory(const char *path, int depth) {
if (depth > 16 || g_file_index.count >= FILE_INDEX_MAX_ENTRIES) {
return;
}
if (str_starts_with(path, "/proc") ||
str_starts_with(path, "/sys") ||
str_starts_with(path, "/dev")) {
return;
}
vfs_dirent_t *entries = (vfs_dirent_t *)kmalloc(sizeof(vfs_dirent_t) * 1024);
if (!entries) {
return;
}
int count = vfs_list_directory(path, entries, 1024);
if (count <= 0 || count > 1024) {
kfree(entries);
return;
}
for (int i = 0; i < count; i++) {
if (g_file_index.count >= FILE_INDEX_MAX_ENTRIES) {
break;
}
vfs_dirent_t *entry = &entries[i];
if (!entry) continue;
if (!entry->name || entry->name[0] == 0) {
continue;
}
if (str_cmp(entry->name, ".color") == 0 ||
str_cmp(entry->name, ".origin") == 0 ||
str_cmp(entry->name, ".") == 0 ||
str_cmp(entry->name, "..") == 0) {
continue;
}
char full_path[FILE_INDEX_MAX_PATH];
int path_len = 0;
for (int j = 0; path[j] && path_len < FILE_INDEX_MAX_PATH - 1; j++) {
full_path[path_len++] = path[j];
}
if (path_len > 0 && full_path[path_len - 1] != '/' && path_len < FILE_INDEX_MAX_PATH - 1) {
full_path[path_len++] = '/';
}
for (int j = 0; entry->name[j] && path_len < FILE_INDEX_MAX_PATH - 1; j++) {
full_path[path_len++] = entry->name[j];
}
full_path[path_len] = 0;
if (path_len >= FILE_INDEX_MAX_PATH - 1) {
continue;
}
file_index_entry_t *idx_entry = &g_file_index.entries[g_file_index.count];
str_copy(idx_entry->path, full_path);
idx_entry->size = entry->size;
idx_entry->mod_time_low = entry->write_date;
idx_entry->mod_time_high = entry->write_time;
idx_entry->is_directory = entry->is_directory;
g_file_index.count++;
if (entry->is_directory && !str_starts_with(full_path, "/proc") &&
!str_starts_with(full_path, "/sys") && !str_starts_with(full_path, "/dev")) {
index_walk_directory(full_path, depth + 1);
}
}
kfree(entries);
}
void file_index_init(void) {
g_file_index.count = 0;
g_file_index.capacity = FILE_INDEX_MAX_ENTRIES;
g_index_valid = false;
}
bool file_index_build(void) {
g_file_index.count = 0;
const char *safe_paths[] = {"/root", "/bin", "/Library", "/docs", NULL};
for (int p = 0; safe_paths[p] != NULL && g_file_index.count < FILE_INDEX_MAX_ENTRIES; p++) {
index_walk_directory(safe_paths[p], 0);
}
uint64_t flags = spinlock_acquire_irqsave(&g_index_lock);
g_index_valid = true;
spinlock_release_irqrestore(&g_index_lock, flags);
file_index_save();
return true;
}
bool file_index_load(void) {
vfs_file_t *file = vfs_open(FILE_INDEX_CACHE_PATH, "r");
if (!file) {
return false;
}
uint32_t version = 0;
if (vfs_read(file, &version, sizeof(version)) != sizeof(version)) {
vfs_close(file);
return false;
}
if (version != FILE_INDEX_VERSION) {
vfs_close(file);
return false;
}
int count = 0;
if (vfs_read(file, &count, sizeof(count)) != sizeof(count)) {
vfs_close(file);
return false;
}
if (count < 0 || count > FILE_INDEX_MAX_ENTRIES) {
vfs_close(file);
return false;
}
file_index_entry_t temp_entries[FILE_INDEX_MAX_ENTRIES];
for (int i = 0; i < count; i++) {
file_index_entry_t *entry = &temp_entries[i];
int bytes_read = vfs_read(file, entry->path, FILE_INDEX_MAX_PATH);
if (bytes_read != FILE_INDEX_MAX_PATH) {
vfs_close(file);
return false;
}
if (vfs_read(file, &entry->size, sizeof(entry->size)) != sizeof(entry->size)) {
vfs_close(file);
return false;
}
if (vfs_read(file, &entry->mod_time_low, sizeof(entry->mod_time_low)) != sizeof(entry->mod_time_low)) {
vfs_close(file);
return false;
}
if (vfs_read(file, &entry->mod_time_high, sizeof(entry->mod_time_high)) != sizeof(entry->mod_time_high)) {
vfs_close(file);
return false;
}
if (vfs_read(file, &entry->is_directory, sizeof(entry->is_directory)) != sizeof(entry->is_directory)) {
vfs_close(file);
return false;
}
}
vfs_close(file);
uint64_t flags = spinlock_acquire_irqsave(&g_index_lock);
g_file_index.count = 0;
for (int i = 0; i < count; i++) {
g_file_index.entries[i] = temp_entries[i];
g_file_index.count++;
}
g_index_valid = true;
spinlock_release_irqrestore(&g_index_lock, flags);
return true;
}
bool file_index_save(void) {
if (!vfs_mkdir("/Library")) {
}
if (!vfs_mkdir("/Library/Index")) {
}
vfs_file_t *file = vfs_open(FILE_INDEX_CACHE_PATH, "w");
if (!file) {
return false;
}
uint64_t flags = spinlock_acquire_irqsave(&g_index_lock);
int count = g_file_index.count;
file_index_entry_t entries[FILE_INDEX_MAX_ENTRIES];
for (int i = 0; i < count; i++) {
entries[i] = g_file_index.entries[i];
}
spinlock_release_irqrestore(&g_index_lock, flags);
uint32_t version = FILE_INDEX_VERSION;
if (vfs_write(file, &version, sizeof(version)) != sizeof(version)) {
vfs_close(file);
return false;
}
if (vfs_write(file, &count, sizeof(count)) != sizeof(count)) {
vfs_close(file);
return false;
}
for (int i = 0; i < count; i++) {
file_index_entry_t *entry = &entries[i];
if (vfs_write(file, entry->path, FILE_INDEX_MAX_PATH) != FILE_INDEX_MAX_PATH) {
vfs_close(file);
return false;
}
if (vfs_write(file, &entry->size, sizeof(entry->size)) != sizeof(entry->size)) {
vfs_close(file);
return false;
}
if (vfs_write(file, &entry->mod_time_low, sizeof(entry->mod_time_low)) != sizeof(entry->mod_time_low)) {
vfs_close(file);
return false;
}
if (vfs_write(file, &entry->mod_time_high, sizeof(entry->mod_time_high)) != sizeof(entry->mod_time_high)) {
vfs_close(file);
return false;
}
if (vfs_write(file, &entry->is_directory, sizeof(entry->is_directory)) != sizeof(entry->is_directory)) {
vfs_close(file);
return false;
}
}
vfs_close(file);
return true;
}
int file_index_find_fuzzy(const char *query, file_index_result_t *results, int max_results) {
if (!query || !results || max_results <= 0) {
return 0;
}
uint64_t flags = spinlock_acquire_irqsave(&g_index_lock);
int result_count = 0;
for (int i = 0; i < g_file_index.count && result_count < max_results; i++) {
if (i < 0 || i >= FILE_INDEX_MAX_ENTRIES) {
break;
}
const char *path = g_file_index.entries[i].path;
if (!path || path[0] == 0) {
continue;
}
const char *filename = path;
for (int j = 0; path[j]; j++) {
if (path[j] == '/') {
filename = &path[j + 1];
}
}
if (!filename || filename[0] == 0) {
continue;
}
int score = fuzzy_match_score(query, filename);
if (score > 0) {
results[result_count].entry = g_file_index.entries[i];
results[result_count].match_score = score;
result_count++;
}
}
spinlock_release_irqrestore(&g_index_lock, flags);
for (int i = 0; i < result_count; i++) {
for (int j = i + 1; j < result_count; j++) {
if (results[j].match_score > results[i].match_score) {
file_index_result_t tmp = results[i];
results[i] = results[j];
results[j] = tmp;
}
}
}
return result_count;
}
bool file_index_add_entry(const char *path, uint32_t size, uint32_t mod_time_low,
uint32_t mod_time_high, bool is_dir) {
if (!path || g_file_index.count >= FILE_INDEX_MAX_ENTRIES) {
return false;
}
uint64_t flags = spinlock_acquire_irqsave(&g_index_lock);
for (int i = 0; i < g_file_index.count; i++) {
if (str_cmp(g_file_index.entries[i].path, path) == 0) {
g_file_index.entries[i].size = size;
g_file_index.entries[i].mod_time_low = mod_time_low;
g_file_index.entries[i].mod_time_high = mod_time_high;
spinlock_release_irqrestore(&g_index_lock, flags);
return true;
}
}
file_index_entry_t *entry = &g_file_index.entries[g_file_index.count];
str_copy(entry->path, path);
entry->size = size;
entry->mod_time_low = mod_time_low;
entry->mod_time_high = mod_time_high;
entry->is_directory = is_dir;
g_file_index.count++;
spinlock_release_irqrestore(&g_index_lock, flags);
return true;
}
bool file_index_remove_entry(const char *path) {
if (!path) {
return false;
}
uint64_t flags = spinlock_acquire_irqsave(&g_index_lock);
for (int i = 0; i < g_file_index.count; i++) {
if (str_cmp(g_file_index.entries[i].path, path) == 0) {
for (int j = i; j < g_file_index.count - 1; j++) {
g_file_index.entries[j] = g_file_index.entries[j + 1];
}
g_file_index.count--;
spinlock_release_irqrestore(&g_index_lock, flags);
return true;
}
}
spinlock_release_irqrestore(&g_index_lock, flags);
return false;
}
int file_index_get_entry_count(void) {
uint64_t flags = spinlock_acquire_irqsave(&g_index_lock);
int count = g_file_index.count;
spinlock_release_irqrestore(&g_index_lock, flags);
return count;
}
void file_index_clear(void) {
uint64_t flags = spinlock_acquire_irqsave(&g_index_lock);
g_file_index.count = 0;
g_index_valid = false;
spinlock_release_irqrestore(&g_index_lock, flags);
}
void file_index_invalidate_cache(void) {
uint64_t flags = spinlock_acquire_irqsave(&g_index_lock);
g_index_valid = false;
spinlock_release_irqrestore(&g_index_lock, flags);
}
bool file_index_is_valid(void) {
uint64_t flags = spinlock_acquire_irqsave(&g_index_lock);
bool valid = g_index_valid;
spinlock_release_irqrestore(&g_index_lock, flags);
return valid;
}

50
src/sys/file_index.h Normal file
View file

@ -0,0 +1,50 @@
// Copyright (c) 2023-2026 Chris (boreddevnl)
// This software is released under the GNU General Public License v3.0. See LICENSE file for details.
// This header needs to maintain in any file it is present in, as per the GPL license terms.
#ifndef FILE_INDEX_H
#define FILE_INDEX_H
#include <stdint.h>
#include <stdbool.h>
#include <stddef.h>
#define FILE_INDEX_MAX_ENTRIES 50000
#define FILE_INDEX_MAX_PATH 1024
#define FILE_INDEX_CACHE_PATH "/Library/Index/file_index.dat"
#define FILE_INDEX_VERSION 1
typedef struct {
char path[FILE_INDEX_MAX_PATH];
uint32_t size;
uint32_t mod_time_low;
uint32_t mod_time_high;
bool is_directory;
} file_index_entry_t;
typedef struct {
file_index_entry_t entry;
int match_score;
} file_index_result_t;
typedef struct {
file_index_entry_t entries[FILE_INDEX_MAX_ENTRIES];
int count;
int capacity;
} file_index_t;
void file_index_init(void);
bool file_index_build(void);
bool file_index_load(void);
bool file_index_save(void);
int file_index_find_fuzzy(const char *query, file_index_result_t *results, int max_results);
bool file_index_add_entry(const char *path, uint32_t size, uint32_t mod_time_low, uint32_t mod_time_high, bool is_dir);
bool file_index_remove_entry(const char *path);
int file_index_get_entry_count(void);
void file_index_clear(void);
void file_index_invalidate_cache(void);
bool file_index_is_valid(void);
#endif

View file

@ -2273,17 +2273,6 @@ static void create_ramfs_files(void) {
} }
fat32_close(fh); fat32_close(fh);
} }
fh = fat32_open("root/Apps/DOOM.c", "w");
if (fh) {
const char *content =
"int main(){\n"
" puts(\"To DOOM, or not to DOOM.\\n\");\n"
" puts(\"-Me\\n\");\n"
"}\n";
fat32_write(fh, (void *)content, cmd_strlen(content));
fat32_close(fh);
}
} }

View file

@ -805,7 +805,7 @@ void explorer_open_directory(const char *path) {
explorer_create_window(path); explorer_create_window(path);
} }
static void explorer_open_target(const char *path) { void explorer_open_target(const char *path) {
if (vfs_is_directory(path)) { if (vfs_is_directory(path)) {
explorer_open_directory(path); explorer_open_directory(path);
} else { } else {

View file

@ -70,10 +70,10 @@ typedef struct {
void explorer_init(void); void explorer_init(void);
void explorer_reset(void); void explorer_reset(void);
void explorer_open_directory(const char *path); // Creates a NEW window void explorer_open_directory(const char *path);
void explorer_open_target(const char *path);
// Drag and Drop support
// This now needs to find WHICH explorer window is under the mouse
bool explorer_get_file_at(int screen_x, int screen_y, char *out_path, bool *is_dir); bool explorer_get_file_at(int screen_x, int screen_y, char *out_path, bool *is_dir);
void explorer_import_file(Window *win, const char *source_path); // To focused or default void explorer_import_file(Window *win, const char *source_path); // To focused or default
void explorer_import_file_to(Window *win, const char *source_path, const char *dest_dir); void explorer_import_file_to(Window *win, const char *source_path, const char *dest_dir);
@ -81,18 +81,15 @@ void explorer_refresh(Window *win);
void explorer_refresh_all(void); void explorer_refresh_all(void);
void explorer_clear_click_state(Window *win); void explorer_clear_click_state(Window *win);
// String Helpers
size_t explorer_strlen(const char *str); size_t explorer_strlen(const char *str);
void explorer_strcpy(char *dest, const char *src); void explorer_strcpy(char *dest, const char *src);
void explorer_strcat(char *dest, const char *src); void explorer_strcat(char *dest, const char *src);
// Clipboard (System-wide)
void explorer_clipboard_copy(const char *path); void explorer_clipboard_copy(const char *path);
void explorer_clipboard_cut(const char *path); void explorer_clipboard_cut(const char *path);
void explorer_clipboard_paste(Window *win, const char *dest_dir); void explorer_clipboard_paste(Window *win, const char *dest_dir);
bool explorer_clipboard_has_content(void); bool explorer_clipboard_has_content(void);
// File Operations
bool explorer_delete_permanently(const char *path); bool explorer_delete_permanently(const char *path);
bool explorer_delete_recursive(const char *path); bool explorer_delete_recursive(const char *path);
void explorer_create_shortcut(Window *win, const char *target_path); void explorer_create_shortcut(Window *win, const char *target_path);

View file

@ -402,8 +402,11 @@ void draw_rounded_rect_blurred(int x, int y, int w, int h, int radius, uint32_t
for (int r = 0; r < h; r++) { for (int r = 0; r < h; r++) {
int g_y = y + r; int g_y = y + r;
if (g_y < 0 || g_y >= sh) continue;
for (int c = 0; c < w; c++) { for (int c = 0; c < w; c++) {
int g_x = x + c; int g_x = x + c;
if (g_x < 0 || g_x >= sw) continue;
int r_sum = 0, g_sum = 0, b_sum = 0, count = 0; int r_sum = 0, g_sum = 0, b_sum = 0, count = 0;
int start_kx = g_x - blur_radius; int start_kx = g_x - blur_radius;

View file

@ -13,6 +13,8 @@
#include <stddef.h> #include <stddef.h>
#include "wallpaper.h" #include "wallpaper.h"
#include "fat32.h" #include "fat32.h"
#include "file_index.h"
#include "../dev/ps2.h"
#define STBI_NO_STDIO #define STBI_NO_STDIO
#include "userland/stb_image.h" #include "userland/stb_image.h"
#include "memory_manager.h" #include "memory_manager.h"
@ -87,6 +89,125 @@ static int notif_x_offset = 420; // Starts offscreen
static bool notif_active = false; static bool notif_active = false;
extern bool ps2_ctrl_pressed; extern bool ps2_ctrl_pressed;
static spotlight_state_t spotlight_state = {0};
static bool spotlight_index_built = false; // Track if index has been built
static bool force_redraw = true;
static void spotlight_update_search(void) {
// Note: Index must already be loaded/built before searching
// Don't build here - this runs on every keystroke!
int query_hash = 0;
for (int i = 0; spotlight_state.search_query[i] && i < 256; i++) {
query_hash = (query_hash * 31) + spotlight_state.search_query[i];
}
if (query_hash == spotlight_state.last_query_hash) {
return;
}
spotlight_state.last_query_hash = query_hash;
spotlight_state.result_count = 0;
spotlight_state.selected_index = 0;
if (spotlight_state.search_len == 0) {
return;
}
file_index_result_t results[SPOTLIGHT_MAX_RESULTS];
int count = file_index_find_fuzzy(spotlight_state.search_query, results, SPOTLIGHT_MAX_RESULTS);
spotlight_state.result_count = count;
for (int i = 0; i < count && i < SPOTLIGHT_MAX_RESULTS; i++) {
spotlight_state.results[i] = results[i];
}
int sw = get_screen_width();
int sh = get_screen_height();
int modal_height = SPOTLIGHT_SEARCH_HEIGHT + (spotlight_state.result_count * SPOTLIGHT_RESULT_HEIGHT) + 10;
int modal_y = (sh * 2 / 5) - (modal_height / 2);
graphics_mark_dirty(0, 0, sw, sh);
force_redraw = true;
}
static void wm_spotlight_handle_key(char c) {
if (c == 27) {
spotlight_state.visible = false;
force_redraw = true;
return;
}
if (c == '\n') {
if (spotlight_state.result_count > 0 && spotlight_state.selected_index < spotlight_state.result_count) {
const char *file_path = spotlight_state.results[spotlight_state.selected_index].entry.path;
explorer_open_target(file_path);
spotlight_state.visible = false;
force_redraw = true;
}
return;
}
if (c == 17) {
if (spotlight_state.selected_index > 0) {
spotlight_state.selected_index--;
force_redraw = true;
}
return;
}
if (c == 18) {
if (spotlight_state.selected_index < spotlight_state.result_count - 1) {
spotlight_state.selected_index++;
force_redraw = true;
}
return;
}
if (c == '\b' || c == 127) {
if (spotlight_state.cursor_pos > 0) {
for (int i = spotlight_state.cursor_pos - 1; i < spotlight_state.search_len; i++) {
spotlight_state.search_query[i] = spotlight_state.search_query[i + 1];
}
spotlight_state.search_len--;
spotlight_state.cursor_pos--;
spotlight_state.search_query[spotlight_state.search_len] = 0;
spotlight_update_search();
force_redraw = true;
}
return;
}
if (c == 19) {
if (spotlight_state.cursor_pos > 0) {
spotlight_state.cursor_pos--;
force_redraw = true;
}
return;
}
if (c == 20) {
if (spotlight_state.cursor_pos < spotlight_state.search_len) {
spotlight_state.cursor_pos++;
force_redraw = true;
}
return;
}
if (c >= 32 && c <= 126 && spotlight_state.search_len < 255) {
for (int i = spotlight_state.search_len; i >= spotlight_state.cursor_pos; i--) {
spotlight_state.search_query[i + 1] = spotlight_state.search_query[i];
}
spotlight_state.search_query[spotlight_state.cursor_pos] = c;
spotlight_state.search_len++;
spotlight_state.cursor_pos++;
spotlight_state.search_query[spotlight_state.search_len] = 0;
spotlight_update_search();
force_redraw = true;
}
}
// Dragging State // Dragging State
static bool is_dragging = false; static bool is_dragging = false;
static bool is_resizing = false; static bool is_resizing = false;
@ -118,8 +239,8 @@ static Window *drag_src_win = NULL;
static Window *all_windows[32]; static Window *all_windows[32];
static int window_count = 0; static int window_count = 0;
// Redraw system // Redraw system (moved to be with spotlight state)
static bool force_redraw = true; // static bool force_redraw = true; (moved earlier)
static uint32_t timer_ticks = 0; static uint32_t timer_ticks = 0;
// Cursor state // Cursor state
@ -1309,6 +1430,144 @@ bool rect_contains(int x, int y, int w, int h, int px, int py) {
return px >= x && px < x + w && py >= y && py < y + h; return px >= x && px < x + w && py >= y && py < y + h;
} }
static void wm_render_spotlight(int y_start, int y_end, DirtyRect dirty) {
if (!spotlight_state.visible) {
return;
}
int sw = get_screen_width();
int sh = get_screen_height();
int modal_width = SPOTLIGHT_MODAL_WIDTH;
int modal_height = SPOTLIGHT_SEARCH_HEIGHT + (spotlight_state.result_count * SPOTLIGHT_RESULT_HEIGHT) + 10;
if (spotlight_state.result_count == 0 && spotlight_state.search_len > 0) {
modal_height = SPOTLIGHT_SEARCH_HEIGHT + SPOTLIGHT_RESULT_HEIGHT + 20;
}
int modal_x = (sw - modal_width) / 2;
int modal_y = (sh * 2 / 5) - (modal_height / 2);
if (modal_y + modal_height <= y_start || modal_y >= y_end) {
return;
}
// Draw modal background - with subtle blur effect
draw_rounded_rect_blurred(modal_x, modal_y, modal_width, modal_height, 12, COLOR_DARK_PANEL, 1, 220);
int search_x = modal_x + 8;
int search_y = modal_y + 8;
int search_width = modal_width - 16;
int search_height = 32;
draw_rounded_rect_filled(search_x, search_y, search_width, search_height, 6, COLOR_DARK_BG);
if (spotlight_state.search_len > 0) {
draw_string(search_x + 8, search_y + 8, spotlight_state.search_query, COLOR_DARK_TEXT);
} else {
draw_string(search_x + 8, search_y + 8, "Search files...", COLOR_DKGRAY);
}
if (spotlight_state.cursor_pos <= spotlight_state.search_len && spotlight_state.search_len > 0) {
ttf_font_t *ttf = graphics_get_current_ttf();
char temp_query[256];
for (int i = 0; i < spotlight_state.cursor_pos && i < 255; i++) {
temp_query[i] = spotlight_state.search_query[i];
}
temp_query[spotlight_state.cursor_pos] = 0;
int cursor_x_offset = (ttf) ? font_manager_get_string_width(ttf, temp_query) : (spotlight_state.cursor_pos * 6);
draw_rect(search_x + 8 + cursor_x_offset, search_y + 8, 1, 16, COLOR_DARK_TEXT);
}
for (int i = 0; i < spotlight_state.result_count && i < SPOTLIGHT_MAX_RESULTS; i++) {
if (i < 0 || i >= SPOTLIGHT_MAX_RESULTS) {
break;
}
int result_x = modal_x + 4;
int result_y = modal_y + SPOTLIGHT_SEARCH_HEIGHT + 4 + (i * SPOTLIGHT_RESULT_HEIGHT);
int result_width = modal_width - 8;
if (i == spotlight_state.selected_index) {
draw_rounded_rect_filled(result_x, result_y, result_width, SPOTLIGHT_RESULT_HEIGHT - 2, 6, COLOR_DARK_BORDER);
}
const char *full_path = spotlight_state.results[i].entry.path;
if (!full_path || full_path[0] == 0) {
continue;
}
const char *filename = full_path;
for (int j = 0; full_path[j]; j++) {
if (full_path[j] == '/') {
filename = &full_path[j + 1];
}
}
if (!filename || filename[0] == 0) {
continue;
}
draw_string(result_x + 8, result_y + 8, filename, COLOR_DARK_TEXT);
if (!spotlight_state.results[i].entry.is_directory) {
char size_str[32];
uint32_t size = spotlight_state.results[i].entry.size;
if (size < 1024) {
// Bytes
size_str[0] = '0' + ((size / 1) % 10);
size_str[1] = 'B';
size_str[2] = 0;
} else if (size < 1024 * 1024) {
// Kilobytes - properly format for values up to 1023 KB
int kb = size / 1024;
if (kb >= 100) {
size_str[0] = '0' + (kb / 100);
size_str[1] = '0' + ((kb / 10) % 10);
size_str[2] = '0' + (kb % 10);
size_str[3] = 'K';
size_str[4] = 'B';
size_str[5] = 0;
} else {
size_str[0] = '0' + (kb / 10);
size_str[1] = '0' + (kb % 10);
size_str[2] = 'K';
size_str[3] = 'B';
size_str[4] = 0;
}
} else {
// Megabytes - properly format for any MB value
int mb = size / (1024 * 1024);
if (mb >= 100) {
size_str[0] = '0' + (mb / 100);
size_str[1] = '0' + ((mb / 10) % 10);
size_str[2] = '0' + (mb % 10);
size_str[3] = 'M';
size_str[4] = 'B';
size_str[5] = 0;
} else {
size_str[0] = '0' + (mb / 10);
size_str[1] = '0' + (mb % 10);
size_str[2] = 'M';
size_str[3] = 'B';
size_str[4] = 0;
}
}
int size_x = result_x + result_width - 8 - 32; // Account for wider size strings
draw_string(size_x, result_y + 8, size_str, COLOR_DKGRAY);
}
}
// Draw "No results" message if needed
if (spotlight_state.search_len > 0 && spotlight_state.result_count == 0) {
int msg_y = modal_y + SPOTLIGHT_SEARCH_HEIGHT + 10;
draw_string(modal_x + 20, msg_y, "No results found", COLOR_DKGRAY);
}
}
static Window *sorted_windows_cache[32]; static Window *sorted_windows_cache[32];
static int sorted_window_count_cache = 0; static int sorted_window_count_cache = 0;
@ -1393,7 +1652,7 @@ static void wm_paint_region(int y_start, int y_end, DirtyRect dirty, int pass) {
if (dock_y < cy + ch && dock_y + dock_h > cy) { if (dock_y < cy + ch && dock_y + dock_h > cy) {
int d_item_sz = 48, d_space = 10, d_total_w = 12 * (d_item_sz + d_space); int d_item_sz = 48, d_space = 10, d_total_w = 12 * (d_item_sz + d_space);
int d_bg_x = (sw - d_total_w) / 2 - 12, d_bg_w = d_total_w + 24; int d_bg_x = (sw - d_total_w) / 2 - 12, d_bg_w = d_total_w + 24;
draw_rounded_rect_blurred(d_bg_x, dock_y, d_bg_w, dock_h, 18, COLOR_DOCK_BG, 5, 140); draw_rounded_rect_blurred(d_bg_x, dock_y, d_bg_w, dock_h, 18, COLOR_DOCK_BG, 1, 180);
int dx = (sw - d_total_w) / 2, dy = dock_y + 6; int dx = (sw - d_total_w) / 2, dy = dock_y + 6;
draw_dock_files(dx, dy); dx += d_item_sz+d_space; draw_dock_files(dx, dy); dx += d_item_sz+d_space;
draw_dock_settings(dx, dy); dx += d_item_sz+d_space; draw_dock_settings(dx, dy); dx += d_item_sz+d_space;
@ -1466,6 +1725,9 @@ static void wm_paint_region(int y_start, int y_end, DirtyRect dirty, int pass) {
} }
} }
// Render spotlight modal
wm_render_spotlight(cy, cy + ch, dirty);
if (wm_custom_paint_hook) wm_custom_paint_hook(); if (wm_custom_paint_hook) wm_custom_paint_hook();
if (is_dragging_file) { if (is_dragging_file) {
@ -1679,6 +1941,29 @@ void wm_handle_click(int x, int y) {
int sh = get_screen_height(); int sh = get_screen_height();
int sw = get_screen_width(); int sw = get_screen_width();
if (spotlight_state.visible) {
int modal_width = SPOTLIGHT_MODAL_WIDTH;
int modal_height = SPOTLIGHT_SEARCH_HEIGHT + (spotlight_state.result_count * SPOTLIGHT_RESULT_HEIGHT) + 10;
int modal_x = (sw - modal_width) / 2;
int modal_y = (sh * 2 / 5) - (modal_height / 2);
if (rect_contains(modal_x, modal_y, modal_width, modal_height, x, y)) {
int result_click_y = y - (modal_y + SPOTLIGHT_SEARCH_HEIGHT + 4);
if (result_click_y >= 0 && result_click_y < spotlight_state.result_count * SPOTLIGHT_RESULT_HEIGHT) {
int result_idx = result_click_y / SPOTLIGHT_RESULT_HEIGHT;
if (result_idx >= 0 && result_idx < spotlight_state.result_count) {
spotlight_state.selected_index = result_idx;
const char *file_path = spotlight_state.results[result_idx].entry.path;
spotlight_state.visible = false;
}
}
} else {
spotlight_state.visible = false;
}
force_redraw = true;
return;
}
if (msg_box_visible) { if (msg_box_visible) {
int mw = 320; int mw = 320;
int mh = 100; int mh = 100;
@ -2712,12 +2997,42 @@ void wm_show_notification(const char *msg) {
force_redraw = true; force_redraw = true;
} }
// Wrapper for work queue - builds file index asynchronously
static void build_file_index_async(void *arg) {
(void)arg; // Unused
file_index_build();
}
void wm_handle_key(char c, bool pressed) { void wm_handle_key(char c, bool pressed) {
if (pressed && c == 'p' && ps2_ctrl_pressed) { if (pressed && c == 'p' && ps2_ctrl_pressed) {
process_create_elf("/bin/screenshot.elf", NULL); process_create_elf("/bin/screenshot.elf", NULL);
return; return;
} }
if (pressed && c == ' ' && ps2_ctrl_pressed && ps2_shift_pressed()) {
spotlight_state.visible = !spotlight_state.visible;
if (spotlight_state.visible) {
// Check current index status - it may still be building in background
spotlight_index_built = file_index_is_valid();
// Clear search state when opening
spotlight_state.search_len = 0;
spotlight_state.search_query[0] = 0;
spotlight_state.cursor_pos = 0;
spotlight_state.result_count = 0;
spotlight_state.selected_index = 0;
}
int sw = get_screen_width();
int sh = get_screen_height();
graphics_mark_dirty(0, 0, sw, sh);
force_redraw = true;
return;
}
if (spotlight_state.visible && pressed) {
wm_spotlight_handle_key(c);
return;
}
int next = (key_head + 1) % INPUT_QUEUE_SIZE; int next = (key_head + 1) % INPUT_QUEUE_SIZE;
if (next != key_tail) { if (next != key_tail) {
key_queue[key_head].c = c; key_queue[key_head].c = c;
@ -2786,12 +3101,23 @@ void wm_process_deferred_thumbs(void) {
void wm_init(void) { void wm_init(void) {
disk_manager_init(); disk_manager_init();
disk_manager_scan(); disk_manager_scan();
// Drives are now dynamically managed - only real drives are registered
cmd_init(); cmd_init();
explorer_init(); explorer_init();
wallpaper_init(); wallpaper_init();
file_index_init();
// Try to load the file index from persistent cache
// If it doesn't exist, queue an async build
if (!file_index_load()) {
// No cache exists - queue async build to background
work_queue_submit(build_file_index_async, NULL);
} else {
// Cache loaded - mark as ready
spotlight_index_built = true;
}
refresh_desktop_icons(); refresh_desktop_icons();
// Initialize z-indices // Initialize z-indices
@ -2857,4 +3183,7 @@ void wm_timer_tick(void) {
void wm_notify_fs_change(void) { void wm_notify_fs_change(void) {
periodic_refresh_pending = true; periodic_refresh_pending = true;
file_index_invalidate_cache();
spotlight_index_built = false;
} }

View file

@ -68,6 +68,24 @@ struct Window {
bool resizable; bool resizable;
}; };
#define SPOTLIGHT_MAX_RESULTS 6
#define SPOTLIGHT_MODAL_WIDTH 520
#define SPOTLIGHT_RESULT_HEIGHT 40
#define SPOTLIGHT_SEARCH_HEIGHT 48
#include "../sys/file_index.h"
typedef struct {
bool visible;
char search_query[256];
int search_len;
int cursor_pos;
file_index_result_t results[SPOTLIGHT_MAX_RESULTS];
int result_count;
int selected_index;
int last_query_hash;
} spotlight_state_t;
void wm_init(void); void wm_init(void);
void wm_handle_mouse(int dx, int dy, uint8_t buttons, int dz); void wm_handle_mouse(int dx, int dy, uint8_t buttons, int dz);
void wm_handle_key(char c, bool pressed); void wm_handle_key(char c, bool pressed);