dmenu is so small window, I want a bigger one

发布于:2025-09-06 ⋅ 阅读:(21) ⋅ 点赞:(0)

rewrite my menu launcher in raylib

// rmenu.c
#include "raylib.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>
#ifdef __linux__
#include <sys/wait.h>
#include <unistd.h>
#include <signal.h>
#include <fcntl.h> // 新增:解决open函数和O_RDWR定义问题
#endif
// 配置
#define WIDTH 800
#define HEIGHT 600
#define PADDING 15
#define FONT_SIZE 50
#define MAX_ITEMS 4000
#define MAX_INPUT_LEN 256
#define MAX_VISIBLE 10
#define SYSTEM_FONT_PATH "/usr/share/fonts/truetype/dejavu/DejaVuSansMono.ttf"

// 全局状态
typedef struct
{
    char text[256];
    bool is_match;
} MenuItem;

MenuItem items[MAX_ITEMS];
int item_count = 0;
char input_str[MAX_INPUT_LEN] = "";
int selected_idx = 0;
int scroll_offset = 0;
bool running = true;
size_t cursor = 0;
Font app_font;
Font default_font;
pid_t child_pid = -1;
bool command_executing = false;
bool window_should_close = false;

// 辅助函数:获取文件名
const char *get_basename(const char *path)
{
    const char *slash = strrchr(path, '/');
    return (slash) ? (slash + 1) : path;
}

// 读取标准输入项目
void read_items_from_stdin()
{
    char buf[512];
    item_count = 0;
    while (running && item_count < MAX_ITEMS)
    {
        ssize_t read_len = read(STDIN_FILENO, buf, sizeof(buf) - 1);
        if (read_len <= 0)
        {
            if (read_len < 0 && errno == EINTR)
                continue;
            break;
        }

        buf[read_len] = '\0';
        char *line = strtok(buf, "\n");
        while (line != NULL && item_count < MAX_ITEMS)
        {
            size_t len = strlen(line);
            while (len > 0 && isspace((unsigned char)line[len - 1]))
                line[--len] = '\0';

            if (len > 0)
            {
                strncpy(items[item_count].text, line, sizeof(items[item_count].text) - 1);
                items[item_count].text[sizeof(items[item_count].text) - 1] = '\0';
                items[item_count].is_match = true;
                item_count++;
            }
            line = strtok(NULL, "\n");
        }
    }
}

// 过滤项目
void filter_items()
{
    int input_len = strlen(input_str);
    if (input_len == 0)
    {
        for (int i = 0; i < item_count; i++)
            items[i].is_match = true;
        selected_idx = (item_count > 0) ? 0 : -1;
        scroll_offset = 0;
        return;
    }

    int match_count = 0;
    int first_match_idx = -1;
    for (int i = 0; i < item_count; i++)
    {
        const char *basename = get_basename(items[i].text);
        int basename_len = strlen(basename);
        bool match = (basename_len >= input_len);

        if (match)
        {
            for (int j = 0; j < input_len; j++)
            {
                if (tolower((unsigned char)input_str[j]) != tolower((unsigned char)basename[j]))
                {
                    match = false;
                    break;
                }
            }
        }

        items[i].is_match = match;
        if (match)
        {
            match_count++;
            if (first_match_idx == -1)
                first_match_idx = i;
        }
    }

    selected_idx = (match_count == 0) ? -1 : first_match_idx;
    int center_pos = MAX_VISIBLE / 2;
    scroll_offset = selected_idx - center_pos;
    if (scroll_offset < 0)
        scroll_offset = 0;
}

// 非阻塞检查子进程状态
void check_child_process()
{
    if (child_pid == -1)
        return;

    int status;
    pid_t result = waitpid(child_pid, &status, WNOHANG);
    if (result == child_pid || result == -1)
    {
        child_pid = -1;
        command_executing = false;
        window_should_close = true;
    }
}

// 命令执行逻辑(修复编译错误)
void execute_command(const char *cmd)
{
    if (!cmd || strlen(cmd) == 0 || command_executing)
        return;

    // 禁用信号干扰
    sigset_t mask, old_mask;
    sigemptyset(&mask);
    sigaddset(&mask, SIGCHLD);
    sigprocmask(SIG_BLOCK, &mask, &old_mask);

    pid_t pid = fork();
    if (pid == -1)
    {
        perror("fork failed");
        sigprocmask(SIG_SETMASK, &old_mask, NULL);
        return;
    }

    if (pid == 0)
    {
        // 子进程:重置信号掩码,脱离终端,执行命令
        sigprocmask(SIG_SETMASK, &old_mask, NULL);

        // 重定向标准输入输出到/dev/null(修复编译错误:已包含fcntl.h)
        int null_fd = open("/dev/null", O_RDWR);
        if (null_fd != -1)
        {
            dup2(null_fd, STDIN_FILENO);
            dup2(null_fd, STDOUT_FILENO);
            dup2(null_fd, STDERR_FILENO);
            if (null_fd > 2)
                close(null_fd);
        }

        // 拆分命令为参数数组
        char *args[64];
        int arg_idx = 0;
        char *token = strtok((char *)cmd, " ");
        while (token != NULL && arg_idx < 63)
        {
            args[arg_idx++] = token;
            token = strtok(NULL, " ");
        }
        args[arg_idx] = NULL;

        // 执行命令
        execvp(args[0], args);
        exit(EXIT_FAILURE);
    }
    else
    {
        // 父进程:记录子进程ID,标记命令执行中
        child_pid = pid;
        command_executing = true;
        sigprocmask(SIG_SETMASK, &old_mask, NULL);
    }
}

// 输入处理
void handle_input()
{
    if (command_executing)
        return;

    bool ctrl_pressed = IsKeyDown(KEY_LEFT_CONTROL) || IsKeyDown(KEY_RIGHT_CONTROL);

    if (IsKeyPressed(KEY_ESCAPE) || window_should_close)
    {
        running = false;
        return;
    }

    // 鼠标滚轮导航
    float wheel_move = GetMouseWheelMove();
    if (wheel_move != 0 && selected_idx != -1)
    {
        int center_pos = MAX_VISIBLE / 2;
        if (wheel_move > 0)
        {
            int prev_idx = selected_idx - 1;
            while (prev_idx >= 0 && !items[prev_idx].is_match)
                prev_idx--;
            if (prev_idx >= 0)
            {
                selected_idx = prev_idx;
                scroll_offset = selected_idx - center_pos;
                scroll_offset = (scroll_offset < 0) ? 0 : scroll_offset;
            }
        }
        else
        {
            int next_idx = selected_idx + 1;
            while (next_idx < item_count && !items[next_idx].is_match)
                next_idx++;
            if (next_idx < item_count)
            {
                selected_idx = next_idx;
                scroll_offset = selected_idx - center_pos;
                scroll_offset = (scroll_offset < 0) ? 0 : scroll_offset;
            }
        }
    }

    // 键盘字符输入
    int key = GetCharPressed();
    while (key > 0)
    {
        if (key >= 32 && key <= 126 && strlen(input_str) < MAX_INPUT_LEN - 1)
        {
            memmove(&input_str[cursor + 1], &input_str[cursor], MAX_INPUT_LEN - cursor - 1);
            input_str[cursor++] = (char)key;
            filter_items();
        }
        key = GetCharPressed();
    }

    // 退格键
    if (IsKeyPressed(KEY_BACKSPACE) && cursor > 0)
    {
        cursor--;
        memmove(&input_str[cursor], &input_str[cursor + 1], MAX_INPUT_LEN - cursor - 1);
        filter_items();
    }

    // 方向键导航
    int center_pos = MAX_VISIBLE / 2;
    if ((IsKeyPressed(KEY_DOWN) || (ctrl_pressed && IsKeyPressed(KEY_N))) && selected_idx != -1)
    {
        int next_idx = selected_idx + 1;
        while (next_idx < item_count && !items[next_idx].is_match)
            next_idx++;
        if (next_idx < item_count)
        {
            selected_idx = next_idx;
            scroll_offset = selected_idx - center_pos;
            scroll_offset = (scroll_offset < 0) ? 0 : scroll_offset;
        }
    }

    if ((IsKeyPressed(KEY_UP) || (ctrl_pressed && IsKeyPressed(KEY_P))) && selected_idx != -1)
    {
        int prev_idx = selected_idx - 1;
        while (prev_idx >= 0 && !items[prev_idx].is_match)
            prev_idx--;
        if (prev_idx >= 0)
        {
            selected_idx = prev_idx;
            scroll_offset = selected_idx - center_pos;
            scroll_offset = (scroll_offset < 0) ? 0 : scroll_offset;
        }
    }

    // 光标导航
    if (IsKeyPressed(KEY_RIGHT) || (ctrl_pressed && IsKeyPressed(KEY_F)))
        cursor = (cursor < strlen(input_str)) ? cursor + 1 : cursor;
    if (IsKeyPressed(KEY_LEFT) || (ctrl_pressed && IsKeyPressed(KEY_B)))
        cursor = (cursor > 0) ? cursor - 1 : cursor;
    if (IsKeyPressed(KEY_HOME) || (ctrl_pressed && IsKeyPressed(KEY_A)))
        cursor = 0;
    if (IsKeyPressed(KEY_END) || (ctrl_pressed && IsKeyPressed(KEY_E)))
        cursor = strlen(input_str);

    // 删除键和Ctrl+K
    if ((IsKeyPressed(KEY_DELETE) || (ctrl_pressed && IsKeyPressed(KEY_D))) && cursor < strlen(input_str))
    {
        memmove(&input_str[cursor], &input_str[cursor + 1], MAX_INPUT_LEN - cursor - 1);
        filter_items();
    }

    if (ctrl_pressed && IsKeyPressed(KEY_K))
    {
        input_str[cursor] = '\0';
        filter_items();
    }

    // 执行命令
    if (IsKeyPressed(KEY_ENTER))
    {
        const char *cmd = NULL;
        if (selected_idx != -1 && items[selected_idx].is_match)
            cmd = items[selected_idx].text;
        else if (strlen(input_str) > 0)
            cmd = input_str;

        if (cmd)
            execute_command(cmd);
    }
}

// 渲染函数
void render_main_window()
{
    BeginDrawing();
    ClearBackground(BLACK);

    // 输入框
    DrawRectangle(PADDING, PADDING, WIDTH - 2 * PADDING, FONT_SIZE + 12, (Color){40, 40, 40, 255});

    // 提示文本
    const char *prompt_text = "Filter > ";
    DrawTextEx(app_font, prompt_text, (Vector2){PADDING + 8, PADDING + 6}, FONT_SIZE, 1, (Color){255, 255, 255, 255});
    float prompt_width = MeasureTextEx(app_font, prompt_text, FONT_SIZE, 1).x;

    // 用户输入文本
    DrawTextEx(app_font, input_str, (Vector2){PADDING + 8 + prompt_width, PADDING + 6}, FONT_SIZE, 1, (Color){255, 204, 0, 255});

    // 文本光标
    static double last_cursor_time = 0;
    static bool cursor_visible = true;
    double current_time = GetTime();
    if (current_time - last_cursor_time > 0.5)
    {
        cursor_visible = !cursor_visible;
        last_cursor_time = current_time;
    }
    if (cursor_visible && !command_executing)
    {
        char cursor_substr[MAX_INPUT_LEN];
        strncpy(cursor_substr, input_str, cursor);
        cursor_substr[cursor] = '\0';
        float cursor_x = prompt_width + PADDING + 8 + MeasureTextEx(app_font, cursor_substr, FONT_SIZE, 1).x;
        DrawRectangle(cursor_x, PADDING + 6, 2, FONT_SIZE, (Color){255, 255, 255, 255});
    }

    // 分隔线
    int separator_y = PADDING + FONT_SIZE + 20;
    DrawLine(PADDING, separator_y, WIDTH - PADDING, separator_y, (Color){80, 80, 80, 255});

    // 渲染过滤后的项目
    int y_offset = separator_y + 10;
    int visible_count = 0;
    int match_count = 0;

    for (int i = 0; i < item_count; i++)
        if (items[i].is_match)
            match_count++;

    for (int i = 0; i < item_count && visible_count < MAX_VISIBLE; i++)
    {
        if (items[i].is_match)
        {
            if (i < scroll_offset)
                continue;

            // 高亮选中项
            if (i == selected_idx)
                DrawRectangle(PADDING, y_offset - 2, WIDTH - 2 * PADDING, FONT_SIZE + 8, (Color){0, 102, 153, 204});

            // 绘制项目文本
            char display_text[256];
            strncpy(display_text, items[i].text, sizeof(display_text) - 1);
            display_text[sizeof(display_text) - 1] = '\0';
            DrawTextEx(app_font, display_text, (Vector2){PADDING + 10, y_offset}, FONT_SIZE, 1, (Color){255, 255, 255, 255});

            y_offset += FONT_SIZE + 12;
            visible_count++;
        }
    }

    // 状态提示
    if (command_executing)
    {
        const char *exec_hint = "Executing...";
        float hint_width = MeasureTextEx(app_font, exec_hint, 16, 1).x;
        DrawTextEx(app_font, exec_hint,
                   (Vector2){PADDING + hint_width, HEIGHT - PADDING - 20},
                   16, 1, (Color){255, 153, 0, 255});
    }
    else if (match_count > MAX_VISIBLE)
    {
        const char *scroll_hint = "PgUp/PgDn: Nav | Wheel: Scroll | Enter: Run | Esc: Quit";
        float hint_width = MeasureTextEx(app_font, scroll_hint, 16, 1).x;
        DrawTextEx(app_font, scroll_hint,
                   (Vector2){WIDTH - PADDING - hint_width - 5, HEIGHT - PADDING - 20},
                   16, 1, (Color){0, 204, 102, 255});
    }

    EndDrawing();
}

// 信号处理函数
void sigchld_handler(int signum)
{
    int status;
    while (waitpid(-1, &status, WNOHANG) > 0)
    {
        // leave it
    }
}

int main()
{
    // 设置SIGCHLD信号处理
    struct sigaction sa;
    memset(&sa, 0, sizeof(sa));
    sa.sa_handler = sigchld_handler;
    sa.sa_flags = SA_RESTART;
    sigaction(SIGCHLD, &sa, NULL);

    // 初始化raylib
    SetTraceLogLevel(LOG_NONE);
    InitWindow(WIDTH, HEIGHT, "rmenu");
    SetTargetFPS(60);
    SetExitKey(0);

    // 字体加载(修复Font.id问题:移除id检查)
    default_font = GetFontDefault();
    app_font = LoadFont(SYSTEM_FONT_PATH);
    bool is_custom_font = false;
    if (app_font.baseSize == 0)
    {
        fprintf(stderr, "Warning: Using default font (system font load failed)\n");
        app_font = default_font;
    }
    else
    {
        is_custom_font = true; // 标记是否为自定义加载的字体
    }

    // 读取输入项目
    read_items_from_stdin();
    filter_items();

    // 主循环
    while (running && !WindowShouldClose())
    {
        // 检查子进程状态
        check_child_process();

        // 处理输入
        handle_input();

        // 渲染窗口
        render_main_window();

        // 命令执行完成后关闭窗口
        if (window_should_close)
        {
            running = false;
            break;
        }
    }

    // 资源清理(修复Font.id问题:根据标记释放自定义字体)
    if (is_custom_font)
    {
        UnloadFont(app_font);
    }

    // 回收子进程
    if (child_pid != -1)
    {
        int status;
        waitpid(child_pid, &status, WNOHANG);
    }

    CloseWindow();
    return 0;
}
```
# rewrite in x11
```c
// xmenu.c
#include <X11/Xlib.h>
#include <X11/Xft/Xft.h>
#include <X11/keysym.h>
#include <X11/Xatom.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdbool.h>
#include <math.h>
#include <time.h>

#define WIDTH 800
#define HEIGHT 400
#define PADDING 15
#define MAX_ITEMS 4000
#define MAX_INPUT_LEN 256
#define MAX_VISIBLE 8
#define FONT_SIZE 28
#define HINT_FONT_SIZE 20
#define FONT_NAME "DejaVu Sans Mono"
#define TARGET_FPS 60
#define FRAME_TIME_US (1000000 / TARGET_FPS)

#define POPUP_POSITION_TOP 1
#define POPUP_POSITION_BOTTOM 0
#define DEFAULT_POPUP_POSITION POPUP_POSITION_TOP

#define COLOR_BG 0x000000
#define COLOR_INPUT_BG 0x282828
#define COLOR_INPUT_BG_ALPHA 0.9
#define COLOR_SEPARATOR 0x505050
#define COLOR_TEXT 0xFFFFFF
#define COLOR_HINT 0x8A8A8A
#define COLOR_SELECTION 0x006699

typedef struct
{
    char text[512];
    char basename[256];
    bool is_match;
} MenuItem;

MenuItem items[MAX_ITEMS];
int item_count = 0;
char input_str[MAX_INPUT_LEN] = "";
size_t cursor = 0;
int selected_idx = 0;
int scroll_offset = 0;
bool running = true;
bool redraw_needed = true;
bool command_executed = false;
static pid_t child_pid = -1;

Display *dpy;
Window win;
GC gc;
Visual *vis;
Colormap cmap;
Pixmap backbuffer;
XftFont *main_font;
XftFont *hint_font;
XftColor color_bg;
XftColor color_input_bg;
XftColor color_separator;
XftColor color_text;
XftColor color_hint;
XftColor color_selection;
int screen;
int screen_width, screen_height;
int window_x, window_y;

const char *get_basename(const char *path)
{
    const char *slash = strrchr(path, '/');
    return (slash) ? (slash + 1) : path;
}

bool init_colors()
{
    if (!XftColorAllocName(dpy, vis, cmap, "#000000", &color_bg))
        return false;
    if (!XftColorAllocName(dpy, vis, cmap, "#282828", &color_input_bg))
        return false;
    if (!XftColorAllocName(dpy, vis, cmap, "#505050", &color_separator))
        return false;
    if (!XftColorAllocName(dpy, vis, cmap, "#FFFFFF", &color_text))
        return false;
    if (!XftColorAllocName(dpy, vis, cmap, "#00ff00", &color_hint))
        return false;
    if (!XftColorAllocName(dpy, vis, cmap, "#006699", &color_selection))
        return false;
    return true;
}

bool init_fonts()
{
    char main_font_str[256];
    snprintf(main_font_str, sizeof(main_font_str), "%s:pixelsize=%d", FONT_NAME, FONT_SIZE);
    main_font = XftFontOpenName(dpy, screen, main_font_str);
    if (!main_font)
    {
        fprintf(stderr, "无法加载主字体: %s\n", main_font_str);
        return false;
    }

    char hint_font_str[256];
    snprintf(hint_font_str, sizeof(hint_font_str), "%s:pixelsize=%d", FONT_NAME, HINT_FONT_SIZE);
    hint_font = XftFontOpenName(dpy, screen, hint_font_str);
    if (!hint_font)
    {
        fprintf(stderr, "无法加载提示字体: %s\n", hint_font_str);
        XftFontClose(dpy, main_font);
        return false;
    }
    return true;
}

bool init_x11()
{
    dpy = XOpenDisplay(NULL);
    if (!dpy)
    {
        fprintf(stderr, "无法打开X显示\n");
        return false;
    }

    screen = DefaultScreen(dpy);
    vis = DefaultVisual(dpy, screen);
    cmap = DefaultColormap(dpy, screen);
    screen_width = DisplayWidth(dpy, screen);
    screen_height = DisplayHeight(dpy, screen);

    window_x = (screen_width - WIDTH) / 2;
#if DEFAULT_POPUP_POSITION == POPUP_POSITION_TOP
    window_y = 0;
#else
    window_y = screen_height - HEIGHT;
#endif

    XSetWindowAttributes attr;
    attr.override_redirect = True;
    attr.background_pixel = BlackPixel(dpy, screen);
    attr.event_mask = ExposureMask | KeyPressMask | ButtonPressMask |
                      ButtonReleaseMask | PointerMotionMask | ButtonMotionMask;

    win = XCreateWindow(
        dpy, DefaultRootWindow(dpy),
        window_x, window_y, WIDTH, HEIGHT,
        0, DefaultDepth(dpy, screen),
        InputOutput, vis,
        CWOverrideRedirect | CWBackPixel | CWEventMask, &attr);

    backbuffer = XCreatePixmap(
        dpy, win, WIDTH, HEIGHT,
        DefaultDepth(dpy, screen));

    gc = XCreateGC(dpy, backbuffer, 0, NULL);
    if (!gc)
    {
        fprintf(stderr, "无法创建GC\n");
        XCloseDisplay(dpy);
        return false;
    }

    Atom net_wm_state = XInternAtom(dpy, "_NET_WM_STATE", False);
    Atom net_wm_state_above = XInternAtom(dpy, "_NET_WM_STATE_ABOVE", False);
    XChangeProperty(
        dpy, win, net_wm_state, XA_ATOM, 32,
        PropModeReplace, (unsigned char *)&net_wm_state_above, 1);

    if (!init_colors() || !init_fonts())
    {
        if (gc)
            XFreeGC(dpy, gc);
        if (backbuffer)
            XFreePixmap(dpy, backbuffer);
        if (win)
            XDestroyWindow(dpy, win);
        XCloseDisplay(dpy);
        return false;
    }

    XMapWindow(dpy, win);
    XSetInputFocus(dpy, win, RevertToParent, CurrentTime);
    XFlush(dpy);
    return true;
}

int get_main_text_width(const char *str)
{
    if (!str || !*str)
        return 0;
    XGlyphInfo info;
    XftTextExtentsUtf8(dpy, main_font, (const FcChar8 *)str, strlen(str), &info);
    return info.width;
}

int get_hint_text_width(const char *str)
{
    if (!str || !*str)
        return 0;
    XGlyphInfo info;
    XftTextExtentsUtf8(dpy, hint_font, (const FcChar8 *)str, strlen(str), &info);
    return info.width;
}

void draw_rect(int x, int y, int width, int height, XftColor *color, float alpha)
{
    if (alpha >= 1.0f)
    {
        XSetForeground(dpy, gc, color->pixel);
        XFillRectangle(dpy, backbuffer, gc, x, y, width, height);
        return;
    }

    XftDraw *draw = XftDrawCreate(dpy, backbuffer, vis, cmap);
    XftColor transp_color = *color;
    transp_color.color.alpha = (unsigned short)(alpha * 65535);

    XftDrawRect(draw, &transp_color, x, y, width, height);
    XftDrawDestroy(draw);
}

void draw_main_text(int x, int y, const char *str, XftColor *color)
{
    if (!str || !*str)
        return;
    y += main_font->ascent;
    XftDraw *draw = XftDrawCreate(dpy, backbuffer, vis, cmap);
    XftDrawStringUtf8(draw, color, main_font, x, y, (const FcChar8 *)str, strlen(str));
    XftDrawDestroy(draw);
}

void draw_hint_text(int x, int y, const char *str, XftColor *color)
{
    if (!str || !*str)
        return;
    y += hint_font->ascent;
    XftDraw *draw = XftDrawCreate(dpy, backbuffer, vis, cmap);
    XftDrawStringUtf8(draw, color, hint_font, x, y, (const FcChar8 *)str, strlen(str));
    XftDrawDestroy(draw);
}

void draw_cursor(int x, int y)
{
    y += main_font->ascent;
    draw_rect(x, y - main_font->height, 2, main_font->height, &color_text, 1.0f);
}

void read_items_from_stdin()
{
    char buf[512];
    item_count = 0;
    while (fgets(buf, sizeof(buf), stdin) && item_count < MAX_ITEMS)
    {
        size_t len = strlen(buf);
        while (len > 0 && isspace((unsigned char)buf[len - 1]))
            buf[--len] = '\0';

        strncpy(items[item_count].text, buf, sizeof(items[item_count].text) - 1);
        items[item_count].text[sizeof(items[item_count].text) - 1] = '\0';

        const char *basename = get_basename(buf);
        strncpy(items[item_count].basename, basename, sizeof(items[item_count].basename) - 1);
        items[item_count].basename[sizeof(items[item_count].basename) - 1] = '\0';

        items[item_count].is_match = true;
        item_count++;
    }
    redraw_needed = true;
}

void filter_items()
{
    int input_len = strlen(input_str);
    if (input_len == 0)
    {
        for (int i = 0; i < item_count; i++)
            items[i].is_match = true;
        selected_idx = 0;
        scroll_offset = 0;
        redraw_needed = true;
        return;
    }

    int match_count = 0;
    int first_match_idx = -1;
    for (int i = 0; i < item_count; i++)
    {
        const char *basename = items[i].basename;
        int basename_len = strlen(basename);

        bool match = (basename_len >= input_len);
        if (match)
        {
            for (int j = 0; j < input_len; j++)
            {
                if (tolower((unsigned char)input_str[j]) != tolower((unsigned char)basename[j]))
                {
                    match = false;
                    break;
                }
            }
        }

        items[i].is_match = match;
        if (match)
        {
            match_count++;
            if (first_match_idx == -1)
                first_match_idx = i;
        }
    }

    selected_idx = (match_count == 0) ? -1 : first_match_idx;
    int center_pos = MAX_VISIBLE / 2;
    scroll_offset = selected_idx - center_pos;
    if (scroll_offset < 0)
        scroll_offset = 0;

    redraw_needed = true;
}

void execute_command(const char *cmd)
{
    if (!cmd || strlen(cmd) == 0)
        return;

    if (child_pid != -1 || command_executed)
        return;

    pid_t pid = fork();
    if (pid == 0)
    {
        char *args[] = {(char *)cmd, NULL};
        freopen("/dev/null", "r", stdin);
        freopen("/dev/null", "w", stdout);
        freopen("/dev/null", "w", stderr);
        execvp(args[0], args);
        exit(EXIT_FAILURE);
    }
    else if (pid > 0)
    {
        child_pid = pid;
        command_executed = true;
        running = false;
    }
}

void check_child_process()
{
    if (child_pid == -1)
        return;

    int status;
    pid_t result = waitpid(child_pid, &status, WNOHANG);
    if (result == child_pid || result == -1)
    {
        child_pid = -1;
    }
}

int get_cursor_x_pos(int start_x)
{
    if (cursor == 0)
        return start_x;

    char temp[MAX_INPUT_LEN];
    strncpy(temp, input_str, cursor);
    temp[cursor] = '\0';
    return start_x + get_main_text_width(temp);
}

void handle_key_press(XKeyEvent *ev)
{
    if (command_executed)
        return;

    KeySym keysym = XKeycodeToKeysym(dpy, ev->keycode, 0);
    char buf[32];
    int len = XLookupString(ev, buf, sizeof(buf), &keysym, NULL);
    bool ctrl_pressed = (ev->state & ControlMask) != 0;

    if (ctrl_pressed)
    {
        switch (keysym)
        {
        case XK_h:
            if (cursor > 0)
            {
                cursor--;
                memmove(&input_str[cursor], &input_str[cursor + 1], MAX_INPUT_LEN - cursor - 1);
                filter_items();
            }
            return;

        case XK_d:
            if (cursor < strlen(input_str))
            {
                memmove(&input_str[cursor], &input_str[cursor + 1], MAX_INPUT_LEN - cursor - 1);
                filter_items();
            }
            return;

        case XK_k:
            input_str[cursor] = '\0';
            filter_items();
            return;

        case XK_u:
            input_str[0] = '\0';
            cursor = 0;
            filter_items();
            return;

        case XK_n:
            if (selected_idx != -1)
            {
                int next_idx = selected_idx + 1;
                while (next_idx < item_count && !items[next_idx].is_match)
                    next_idx++;
                if (next_idx < item_count)
                {
                    selected_idx = next_idx;
                    scroll_offset = selected_idx - (MAX_VISIBLE / 2);
                    if (scroll_offset < 0)
                        scroll_offset = 0;
                    redraw_needed = true;
                }
            }
            return;

        case XK_p:
            if (selected_idx != -1)
            {
                int prev_idx = selected_idx - 1;
                while (prev_idx >= 0 && !items[prev_idx].is_match)
                    prev_idx--;
                if (prev_idx >= 0)
                {
                    selected_idx = prev_idx;
                    scroll_offset = selected_idx - (MAX_VISIBLE / 2);
                    if (scroll_offset < 0)
                        scroll_offset = 0;
                    redraw_needed = true;
                }
            }
            return;

        case XK_f:
            if (cursor < strlen(input_str))
            {
                cursor++;
                redraw_needed = true;
            }
            return;

        case XK_b:
            if (cursor > 0)
            {
                cursor--;
                redraw_needed = true;
            }
            return;

        case XK_a:
            cursor = 0;
            redraw_needed = true;
            return;

        case XK_e:
            cursor = strlen(input_str);
            redraw_needed = true;
            return;
        }
    }

    switch (keysym)
    {
    case XK_Escape:
        running = false;
        break;

    case XK_Return:
    case XK_KP_Enter:
    {
        const char *cmd = NULL;
        if (selected_idx != -1 && items[selected_idx].is_match)
            cmd = items[selected_idx].text;
        else if (strlen(input_str) > 0)
            cmd = input_str;
        if (cmd)
            execute_command(cmd);
        break;
    }

    case XK_BackSpace:
        if (cursor > 0)
        {
            cursor--;
            memmove(&input_str[cursor], &input_str[cursor + 1], MAX_INPUT_LEN - cursor - 1);
            filter_items();
        }
        break;

    case XK_Delete:
        if (cursor < strlen(input_str))
        {
            memmove(&input_str[cursor], &input_str[cursor + 1], MAX_INPUT_LEN - cursor - 1);
            filter_items();
        }
        break;

    case XK_Down:
        if (selected_idx != -1)
        {
            int next_idx = selected_idx + 1;
            while (next_idx < item_count && !items[next_idx].is_match)
                next_idx++;
            if (next_idx < item_count)
            {
                selected_idx = next_idx;
                scroll_offset = selected_idx - (MAX_VISIBLE / 2);
                if (scroll_offset < 0)
                    scroll_offset = 0;
                redraw_needed = true;
            }
        }
        break;

    case XK_Up:
        if (selected_idx != -1)
        {
            int prev_idx = selected_idx - 1;
            while (prev_idx >= 0 && !items[prev_idx].is_match)
                prev_idx--;
            if (prev_idx >= 0)
            {
                selected_idx = prev_idx;
                scroll_offset = selected_idx - (MAX_VISIBLE / 2);
                if (scroll_offset < 0)
                    scroll_offset = 0;
                redraw_needed = true;
            }
        }
        break;

    case XK_Right:
        if (cursor < strlen(input_str))
        {
            cursor++;
            redraw_needed = true;
        }
        break;

    case XK_Left:
        if (cursor > 0)
        {
            cursor--;
            redraw_needed = true;
        }
        break;

    case XK_Home:
        cursor = 0;
        redraw_needed = true;
        break;

    case XK_End:
        cursor = strlen(input_str);
        redraw_needed = true;
        break;

    default:
        if (len > 0 && buf[0] >= 32 && buf[0] <= 126 && strlen(input_str) < MAX_INPUT_LEN - 1)
        {
            memmove(&input_str[cursor + 1], &input_str[cursor], MAX_INPUT_LEN - cursor - 1);
            input_str[cursor++] = buf[0];
            filter_items();
        }
        break;
    }
}

void handle_mouse_event(XButtonEvent *ev)
{
    if (command_executed)
        return;

    if (ev->window != win)
    {
        running = false;
        return;
    }

    // Mouse scroll handling (Button4 = scroll up, Button5 = scroll down)
    if (ev->type == ButtonPress && (ev->button == 4 || ev->button == 5))
    {
        if (selected_idx == -1)
            return;

        int target_idx = selected_idx;
        if (ev->button == 4)
        {
            target_idx = selected_idx - 1;
            while (target_idx >= 0 && !items[target_idx].is_match)
                target_idx--;
        }
        else if (ev->button == 5)
        {
            target_idx = selected_idx + 1;
            while (target_idx < item_count && !items[target_idx].is_match)
                target_idx++;
        }

        if (target_idx >= 0 && target_idx < item_count)
        {
            selected_idx = target_idx;
            scroll_offset = selected_idx - (MAX_VISIBLE / 2);
            if (scroll_offset < 0)
                scroll_offset = 0;
            redraw_needed = true;
        }
        return;
    }

    // Left-click to select menu item
    if (ev->type == ButtonPress && ev->button == Button1)
    {
        int input_box_y = PADDING;
        int input_box_height = FONT_SIZE + 12;
        int separator_y = input_box_y + input_box_height + 8;

        if (ev->y > separator_y + 10)
        {
            int item_y = separator_y + 10;
            int item_height = FONT_SIZE + 10;
            int click_item_index = (ev->y - item_y) / item_height;

            int visible_idx = 0;
            int match_idx = -1;
            for (int i = 0; i < item_count; i++)
            {
                if (items[i].is_match)
                {
                    if (visible_idx >= scroll_offset && visible_idx < scroll_offset + MAX_VISIBLE)
                    {
                        if (visible_idx - scroll_offset == click_item_index)
                        {
                            match_idx = i;
                            break;
                        }
                    }
                    visible_idx++;
                }
            }

            if (match_idx != -1)
            {
                selected_idx = match_idx;
                redraw_needed = true;
                execute_command(items[selected_idx].text);
            }
        }
    }
}

void handle_events()
{
    XEvent ev;
    while (XPending(dpy) > 0 && running)
    {
        XNextEvent(dpy, &ev);
        switch (ev.type)
        {
        case Expose:
            redraw_needed = true;
            break;

        case KeyPress:
            handle_key_press(&ev.xkey);
            break;

        case ButtonPress:
            handle_mouse_event(&ev.xbutton);
            break;
        }
    }
}

void render_offscreen()
{
    if (!redraw_needed)
        return;

    draw_rect(0, 0, WIDTH, HEIGHT, &color_bg, 1.0f);

    int input_box_x = PADDING;
    int input_box_y = PADDING;
    int input_box_width = WIDTH - 2 * PADDING;
    int input_box_height = FONT_SIZE + 12;
    draw_rect(input_box_x, input_box_y, input_box_width, input_box_height,
              &color_input_bg, COLOR_INPUT_BG_ALPHA);

    const char *prompt_text = "Filter >  ";
    int prompt_x = input_box_x + 8;
    int prompt_y = input_box_y + 6;
    draw_main_text(prompt_x, prompt_y, prompt_text, &color_text);
    int prompt_width = get_main_text_width(prompt_text);

    int input_x = prompt_x + prompt_width;
    draw_main_text(input_x, prompt_y, input_str, &color_text);

    int cursor_x = get_cursor_x_pos(input_x);
    draw_cursor(cursor_x, prompt_y);

    int separator_y = input_box_y + input_box_height + 8;
    draw_rect(PADDING, separator_y, WIDTH - 2 * PADDING, 1, &color_separator, 1.0f);

    int y_offset = separator_y + 10;
    int visible_count = 0;
    int match_count = 0;
    for (int i = 0; i < item_count; i++)
        if (items[i].is_match)
            match_count++;

    for (int i = 0; i < item_count && visible_count < MAX_VISIBLE; i++)
    {
        if (items[i].is_match)
        {
            if (i < scroll_offset)
                continue;

            if (i == selected_idx)
            {
                draw_rect(
                    PADDING, y_offset - 2,
                    WIDTH - 2 * PADDING, FONT_SIZE + 8,
                    &color_selection, 0.8f);
            }

            draw_main_text(PADDING + 10, y_offset, items[i].text, &color_text);

            y_offset += FONT_SIZE + 10;
            visible_count++;
        }
    }

    if (match_count > 0)
    {
        const char *hint_text = "PgUp/PgDn: Nav | Wheel: Scroll | Enter: Run | Esc: Quit";
        int hint_width = get_hint_text_width(hint_text);
        int hint_x = (WIDTH - hint_width) / 2;
        int hint_y = HEIGHT - PADDING - HINT_FONT_SIZE;
        draw_hint_text(hint_x, hint_y, hint_text, &color_hint);
    }

    redraw_needed = false;
}

void swap_buffers()
{
    XCopyArea(dpy, backbuffer, win, gc, 0, 0, WIDTH, HEIGHT, 0, 0);
    XFlush(dpy);
}

void cleanup_x11()
{
    if (child_pid > 0)
    {
        int status;
        waitpid(child_pid, &status, WNOHANG);
    }

    if (main_font)
        XftFontClose(dpy, main_font);
    if (hint_font)
        XftFontClose(dpy, hint_font);

    XftColorFree(dpy, vis, cmap, &color_bg);
    XftColorFree(dpy, vis, cmap, &color_input_bg);
    XftColorFree(dpy, vis, cmap, &color_separator);
    XftColorFree(dpy, vis, cmap, &color_text);
    XftColorFree(dpy, vis, cmap, &color_hint);
    XftColorFree(dpy, vis, cmap, &color_selection);

    if (backbuffer)
        XFreePixmap(dpy, backbuffer);
    if (gc)
        XFreeGC(dpy, gc);
    if (win)
        XDestroyWindow(dpy, win);

    if (dpy)
        XCloseDisplay(dpy);
}

long get_current_time_us()
{
    struct timespec ts;
    clock_gettime(CLOCK_MONOTONIC, &ts);
    return ts.tv_sec * 1000000 + ts.tv_nsec / 1000;
}

int main()
{
    if (!init_x11())
    {
        fprintf(stderr, "X11初始化失败\n");
        return 1;
    }

    read_items_from_stdin();
    filter_items();

    long last_frame_time = get_current_time_us();
    while (running)
    {
        long current_time = get_current_time_us();

        handle_events();

        check_child_process();

        if (redraw_needed)
        {
            render_offscreen();
            swap_buffers();
        }

        long elapsed = get_current_time_us() - current_time;
        if (elapsed < FRAME_TIME_US)
            usleep(FRAME_TIME_US - elapsed);

        last_frame_time = get_current_time_us();
    }

    cleanup_x11();
    return 0;
}
```
# sdl3 version 
```c
// sdlmenu.c
#include <SDL3/SDL.h>
#include <ft2build.h>
#include FT_FREETYPE_H
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdbool.h>

#define WIDTH 1024
#define HEIGHT 768
#define PADDING 15
#define MAX_ITEMS 4000
#define MAX_INPUT_LEN 256
#define MAX_VISIBLE 10
#define FONT_SIZE 36
#define HINT_FONT_SIZE 20
#define FONT_PATH "/usr/share/fonts/truetype/dejavu/DejaVuSansMono.ttf"

typedef struct
{
    char text[512];
    char basename[256];
    bool is_match;
} MenuItem;

MenuItem items[MAX_ITEMS];
int item_count = 0;
char input_str[MAX_INPUT_LEN] = "";
size_t cursor = 0;
int selected_idx = 0;
int scroll_offset = 0;
bool running = true;
pid_t child_pid = -1;

SDL_Window *window = NULL;
SDL_Renderer *renderer = NULL;
FT_Library ft;
FT_Face main_face;
FT_Face hint_face;
SDL_Texture *main_glyphs[128] = {NULL};
SDL_Texture *hint_glyphs[128] = {NULL};
#define EMPTY_GLYPH_SENTINEL (SDL_Texture *)-1

const char *get_basename(const char *path);
int get_main_string_width(const char *str);
int get_hint_string_width(const char *str);
bool load_glyphs(FT_Face face, SDL_Texture **glyphs, int size);
bool init_freetype();
void draw_main_char(int x, int y, char c);
int draw_main_string(int x, int y, const char *str);
void draw_hint_char(int x, int y, char c);
int draw_hint_string(int x, int y, const char *str);
void read_items_from_stdin();
void filter_items();
void execute_command(const char *cmd);
void check_child_process();
void handle_input();
void render();
void cleanup();

const char *get_basename(const char *path)
{
    const char *slash = strrchr(path, '/');
    return (slash) ? (slash + 1) : path;
}

int get_main_string_width(const char *str)
{
    int width = 0;
    for (int i = 0; str[i]; i++)
    {
        FT_Load_Char(main_face, str[i], FT_LOAD_DEFAULT);
        width += main_face->glyph->advance.x >> 6;
    }
    return width;
}

int get_hint_string_width(const char *str)
{
    int width = 0;
    for (int i = 0; str[i]; i++)
    {
        FT_Load_Char(hint_face, str[i], FT_LOAD_DEFAULT);
        width += hint_face->glyph->advance.x >> 6;
    }
    return width;
}

bool load_glyphs(FT_Face face, SDL_Texture **glyphs, int size)
{
    FT_Set_Pixel_Sizes(face, 0, size);

    for (unsigned char c = 32; c <= 126; c++)
    {
        if (FT_Load_Char(face, c, FT_LOAD_RENDER))
        {
            fprintf(stderr, "ERROR: Missing glyph '%c' (size: %d)\n", c, size);
            return false;
        }

        FT_Bitmap *bitmap = &face->glyph->bitmap;

        if (bitmap->width == 0 || bitmap->rows == 0)
        {
            glyphs[c] = EMPTY_GLYPH_SENTINEL;
            continue;
        }

        SDL_Surface *surface = SDL_CreateSurface(
            bitmap->width, bitmap->rows, SDL_PIXELFORMAT_RGBA32);
        if (!surface)
        {
            fprintf(stderr, "ERROR: Surface failed for glyph '%c'\n", c);
            return false;
        }

        SDL_FillSurfaceRect(surface, NULL, 0x00000000);
        Uint32 *pixels = (Uint32 *)surface->pixels;
        for (int y = 0; y < bitmap->rows; y++)
        {
            for (int x = 0; x < bitmap->width; x++)
            {
                Uint8 alpha = bitmap->buffer[y * bitmap->pitch + x];
                if (alpha > 0)
                {
                    pixels[y * surface->w + x] = 0xFFFFFFFF;
                    pixels[y * surface->w + x] |= (alpha << 24);
                }
            }
        }

        glyphs[c] = SDL_CreateTextureFromSurface(renderer, surface);
        if (!glyphs[c])
        {
            fprintf(stderr, "ERROR: Texture failed for glyph '%c'\n", c);
            SDL_DestroySurface(surface);
            return false;
        }
        SDL_SetTextureBlendMode(glyphs[c], SDL_BLENDMODE_BLEND);
        SDL_DestroySurface(surface);
    }
    return true;
}

bool init_freetype()
{
    if (FT_Init_FreeType(&ft))
    {
        fprintf(stderr, "FreeType initialization failed\n");
        return false;
    }

    if (FT_New_Face(ft, FONT_PATH, 0, &main_face))
    {
        fprintf(stderr, "Could not load main font: %s\n", FONT_PATH);
        FT_Done_FreeType(ft);
        return false;
    }

    if (FT_New_Face(ft, FONT_PATH, 0, &hint_face))
    {
        fprintf(stderr, "Could not load hint font: %s\n", FONT_PATH);
        FT_Done_Face(main_face);
        FT_Done_FreeType(ft);
        return false;
    }

    if (!load_glyphs(main_face, main_glyphs, FONT_SIZE))
    {
        FT_Done_Face(hint_face);
        FT_Done_Face(main_face);
        FT_Done_FreeType(ft);
        return false;
    }

    if (!load_glyphs(hint_face, hint_glyphs, HINT_FONT_SIZE))
    {
        FT_Done_Face(hint_face);
        FT_Done_Face(main_face);
        FT_Done_FreeType(ft);
        return false;
    }

    return true;
}

void draw_main_char(int x, int y, char c)
{
    if (c < 32 || c > 126)
        return;
    if (main_glyphs[c] == EMPTY_GLYPH_SENTINEL)
        return;
    if (!main_glyphs[c])
        return;

    FT_Load_Char(main_face, c, FT_LOAD_DEFAULT);
    int x_off = main_face->glyph->bitmap_left;
    int y_off = main_face->glyph->bitmap_top;

    SDL_FRect dst = {
        (float)(x + x_off),
        (float)(y + (FONT_SIZE - y_off)),
        (float)main_face->glyph->bitmap.width,
        (float)main_face->glyph->bitmap.rows};
    SDL_RenderTexture(renderer, main_glyphs[c], NULL, &dst);
}

int draw_main_string(int x, int y, const char *str)
{
    int start_x = x;
    for (int i = 0; str[i]; i++)
    {
        draw_main_char(x, y, str[i]);
        FT_Load_Char(main_face, str[i], FT_LOAD_DEFAULT);
        x += main_face->glyph->advance.x >> 6;
    }
    return x - start_x;
}

void draw_hint_char(int x, int y, char c)
{
    if (c < 32 || c > 126)
        return;
    if (hint_glyphs[c] == EMPTY_GLYPH_SENTINEL)
        return;
    if (!hint_glyphs[c])
        return;

    FT_Load_Char(hint_face, c, FT_LOAD_DEFAULT);
    int x_off = hint_face->glyph->bitmap_left;
    int y_off = hint_face->glyph->bitmap_top;

    SDL_FRect dst = {
        (float)(x + x_off),
        (float)(y + (HINT_FONT_SIZE - y_off)),
        (float)hint_face->glyph->bitmap.width,
        (float)hint_face->glyph->bitmap.rows};
    SDL_RenderTexture(renderer, hint_glyphs[c], NULL, &dst);
}

int draw_hint_string(int x, int y, const char *str)
{
    int start_x = x;
    for (int i = 0; str[i]; i++)
    {
        draw_hint_char(x, y, str[i]);
        FT_Load_Char(hint_face, str[i], FT_LOAD_DEFAULT);
        x += hint_face->glyph->advance.x >> 6;
    }
    return x - start_x;
}

void read_items_from_stdin()
{
    char buf[512];
    item_count = 0;
    while (fgets(buf, sizeof(buf), stdin) && item_count < MAX_ITEMS)
    {
        size_t len = strlen(buf);
        while (len > 0 && isspace((unsigned char)buf[len - 1]))
            buf[--len] = '\0';

        strncpy(items[item_count].text, buf, sizeof(items[item_count].text) - 1);
        items[item_count].text[sizeof(items[item_count].text) - 1] = '\0';

        const char *basename = get_basename(buf);
        strncpy(items[item_count].basename, basename, sizeof(items[item_count].basename) - 1);
        items[item_count].basename[sizeof(items[item_count].basename) - 1] = '\0';

        items[item_count].is_match = true;
        item_count++;
    }
}

void filter_items()
{
    int input_len = strlen(input_str);
    if (input_len == 0)
    {
        for (int i = 0; i < item_count; i++)
            items[i].is_match = true;
        selected_idx = 0;
        scroll_offset = 0;
        return;
    }

    int match_count = 0;
    int first_match_idx = -1;
    for (int i = 0; i < item_count; i++)
    {
        const char *basename = items[i].basename;
        int basename_len = strlen(basename);

        bool match = (basename_len >= input_len);
        if (match)
        {
            for (int j = 0; j < input_len; j++)
            {
                if (tolower((unsigned char)input_str[j]) != tolower((unsigned char)basename[j]))
                {
                    match = false;
                    break;
                }
            }
        }

        items[i].is_match = match;
        if (match)
        {
            match_count++;
            if (first_match_idx == -1)
                first_match_idx = i;
        }
    }

    selected_idx = (match_count == 0) ? -1 : first_match_idx;
    int center_pos = MAX_VISIBLE / 2;
    scroll_offset = selected_idx - center_pos;
    if (scroll_offset < 0)
        scroll_offset = 0;
}

void execute_command(const char *cmd)
{
    if (!cmd || strlen(cmd) == 0 || child_pid != -1)
        return;

    pid_t pid = fork();
    if (pid == 0)
    {
        char *args[] = {(char *)cmd, NULL};
        freopen("/dev/null", "r", stdin);
        freopen("/dev/null", "w", stdout);
        freopen("/dev/null", "w", stderr);
        execvp(args[0], args);
        exit(EXIT_FAILURE);
    }
    else if (pid > 0)
    {
        child_pid = pid;
    }
}

void check_child_process()
{
    if (child_pid == -1)
        return;

    int status;
    pid_t result = waitpid(child_pid, &status, WNOHANG);
    if (result == child_pid || result == -1)
    {
        child_pid = -1;
        running = false;
    }
}

void handle_input()
{
    SDL_Event event;
    bool ctrl_pressed = false;

    while (SDL_PollEvent(&event))
    {
        switch (event.type)
        {
        case SDL_EVENT_QUIT:
            running = false;
            break;

        case SDL_EVENT_KEY_DOWN:
            ctrl_pressed = (event.key.mod & SDL_KMOD_CTRL) != 0;

            if (event.key.key == SDLK_ESCAPE)
            {
                running = false;
                return;
            }
            else if (event.key.key == SDLK_RETURN)
            {
                const char *cmd = NULL;
                if (selected_idx != -1 && items[selected_idx].is_match)
                    cmd = items[selected_idx].text;
                else if (strlen(input_str) > 0)
                    cmd = input_str;
                if (cmd)
                    execute_command(cmd);
            }
            // Use SDLK_H (uppercase) instead of SDLK_h
            else if ((event.key.key == SDLK_BACKSPACE || (ctrl_pressed && event.key.key == SDLK_H)) && cursor > 0)
            {
                cursor--;
                memmove(&input_str[cursor], &input_str[cursor + 1], MAX_INPUT_LEN - cursor - 1);
                filter_items();
            }
            // Use SDLK_D instead of SDLK_d
            else if ((event.key.key == SDLK_DELETE || (ctrl_pressed && event.key.key == SDLK_D)) && cursor < strlen(input_str))
            {
                memmove(&input_str[cursor], &input_str[cursor + 1], MAX_INPUT_LEN - cursor - 1);
                filter_items();
            }
            // Use SDLK_K instead of SDLK_k
            else if (ctrl_pressed && event.key.key == SDLK_K)
            {
                input_str[cursor] = '\0';
                filter_items();
            }
            // Use SDLK_U instead of SDLK_u
            else if (ctrl_pressed && event.key.key == SDLK_U)
            {
                input_str[0] = '\0';
                cursor = 0;
                filter_items();
            }
            // Use SDLK_N instead of SDLK_n
            else if (event.key.key == SDLK_DOWN || (ctrl_pressed && event.key.key == SDLK_N))
            {
                if (selected_idx == -1)
                    return;
                int next_idx = selected_idx + 1;
                while (next_idx < item_count && !items[next_idx].is_match)
                    next_idx++;
                if (next_idx < item_count)
                {
                    selected_idx = next_idx;
                    int center_pos = MAX_VISIBLE / 2;
                    scroll_offset = selected_idx - center_pos;
                    if (scroll_offset < 0)
                        scroll_offset = 0;
                }
            }
            // Use SDLK_P instead of SDLK_p
            else if (event.key.key == SDLK_UP || (ctrl_pressed && event.key.key == SDLK_P))
            {
                if (selected_idx == -1)
                    return;
                int prev_idx = selected_idx - 1;
                while (prev_idx >= 0 && !items[prev_idx].is_match)
                    prev_idx--;
                if (prev_idx >= 0)
                {
                    selected_idx = prev_idx;
                    int center_pos = MAX_VISIBLE / 2;
                    scroll_offset = selected_idx - center_pos;
                    if (scroll_offset < 0)
                        scroll_offset = 0;
                }
            }
            // Use SDLK_F instead of SDLK_f
            else if (event.key.key == SDLK_RIGHT || (ctrl_pressed && event.key.key == SDLK_F))
            {
                if (cursor < strlen(input_str))
                    cursor++;
            }
            // Use SDLK_B instead of SDLK_b
            else if (event.key.key == SDLK_LEFT || (ctrl_pressed && event.key.key == SDLK_B))
            {
                if (cursor > 0)
                    cursor--;
            }
            // Use SDLK_A instead of SDLK_a
            else if (event.key.key == SDLK_HOME || (ctrl_pressed && event.key.key == SDLK_A))
            {
                cursor = 0;
            }
            // Use SDLK_E instead of SDLK_e
            else if (event.key.key == SDLK_END || (ctrl_pressed && event.key.key == SDLK_E))
            {
                cursor = strlen(input_str);
            }
            break;

        case SDL_EVENT_MOUSE_WHEEL:
        {
            int center_pos = MAX_VISIBLE / 2;
            if (event.wheel.y > 0)
            {
                if (selected_idx == -1)
                    return;
                int prev_idx = selected_idx - 1;
                while (prev_idx >= 0 && !items[prev_idx].is_match)
                    prev_idx--;
                if (prev_idx >= 0)
                {
                    selected_idx = prev_idx;
                    scroll_offset = selected_idx - center_pos;
                    if (scroll_offset < 0)
                        scroll_offset = 0;
                }
            }
            else if (event.wheel.y < 0)
            {
                if (selected_idx == -1)
                    return;
                int next_idx = selected_idx + 1;
                while (next_idx < item_count && !items[next_idx].is_match)
                    next_idx++;
                if (next_idx < item_count)
                {
                    selected_idx = next_idx;
                    scroll_offset = selected_idx - center_pos;
                    if (scroll_offset < 0)
                        scroll_offset = 0;
                }
            }
            break;
        }

        case SDL_EVENT_TEXT_INPUT:
            if (strlen(input_str) < MAX_INPUT_LEN - 1 && event.text.text[0] >= 32 && event.text.text[0] <= 126)
            {
                memmove(&input_str[cursor + 1], &input_str[cursor], MAX_INPUT_LEN - cursor - 1);
                input_str[cursor++] = event.text.text[0];
                filter_items();
            }
            break;
        }
    }
}

void render()
{
    SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
    SDL_RenderClear(renderer);

    SDL_FRect input_rect = {
        (float)PADDING,
        (float)PADDING,
        (float)(WIDTH - 2 * PADDING),
        (float)(FONT_SIZE + 12)};
    SDL_SetRenderDrawColor(renderer, 50, 50, 50, 255);
    SDL_RenderFillRect(renderer, &input_rect);

    const char *prompt_text = "Filter > ";
    int prompt_x = PADDING + 8;
    int prompt_y = PADDING + 6;
    draw_main_string(prompt_x, prompt_y, prompt_text);
    int prompt_width = get_main_string_width(prompt_text);

    int input_x = prompt_x + prompt_width;
    draw_main_string(input_x, prompt_y, input_str);

    int cursor_x = input_x;
    for (size_t i = 0; i < cursor; i++)
    {
        FT_Load_Char(main_face, input_str[i], FT_LOAD_DEFAULT);
        cursor_x += main_face->glyph->advance.x >> 6;
    }
    SDL_FRect cursor_rect = {
        (float)cursor_x,
        (float)prompt_y,
        2.0f,
        (float)FONT_SIZE};
    SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255);
    SDL_RenderFillRect(renderer, &cursor_rect);

    int separator_y = PADDING + FONT_SIZE + 20;
    SDL_SetRenderDrawColor(renderer, 100, 100, 100, 255);
    SDL_RenderLine(renderer,
                   (float)PADDING, (float)separator_y,
                   (float)(WIDTH - PADDING), (float)separator_y);

    int y_offset = separator_y + 10;
    int visible_count = 0;
    int match_count = 0;
    for (int i = 0; i < item_count; i++)
        if (items[i].is_match)
            match_count++;

    for (int i = 0; i < item_count && visible_count < MAX_VISIBLE; i++)
    {
        if (items[i].is_match)
        {
            if (i < scroll_offset)
                continue;

            if (i == selected_idx)
            {
                SDL_SetRenderDrawColor(renderer, 0, 85, 119, 255);
                SDL_FRect highlight_rect = {
                    (float)PADDING,
                    (float)(y_offset - 2),
                    (float)(WIDTH - 2 * PADDING),
                    (float)(FONT_SIZE + 8)};
                SDL_RenderFillRect(renderer, &highlight_rect);
            }

            draw_main_string(PADDING + 10, y_offset, items[i].text);
            y_offset += FONT_SIZE + 12;
            visible_count++;
        }
    }

    if (match_count > 0)
    {
        const char *hint_text = "Controls: PgUp/PgDn = Nav | Ctrl+N/P = Nav | Enter = Run | Esc = Quit";
        int hint_x = PADDING + 10;
        int hint_y = y_offset + 10;
        draw_hint_string(hint_x, hint_y, hint_text);
    }

    SDL_RenderPresent(renderer);
}

void cleanup()
{
    SDL_StopTextInput(window);

    for (int c = 32; c <= 126; c++)
    {
        if (main_glyphs[c] != NULL && main_glyphs[c] != EMPTY_GLYPH_SENTINEL)
            SDL_DestroyTexture(main_glyphs[c]);
    }

    for (int c = 32; c <= 126; c++)
    {
        if (hint_glyphs[c] != NULL && hint_glyphs[c] != EMPTY_GLYPH_SENTINEL)
            SDL_DestroyTexture(hint_glyphs[c]);
    }

    FT_Done_Face(hint_face);
    FT_Done_Face(main_face);
    FT_Done_FreeType(ft);
    SDL_DestroyRenderer(renderer);
    SDL_DestroyWindow(window);
    SDL_Quit();
}

int main()
{
    if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS) < 0)
    {
        fprintf(stderr, "SDL_Init failed: %s\n", SDL_GetError());
        return 1;
    }

    window = SDL_CreateWindow("sdlmenu", WIDTH, HEIGHT, 0);
    if (!window)
    {
        fprintf(stderr, "Window failed: %s\n", SDL_GetError());
        SDL_Quit();
        return 1;
    }

    renderer = SDL_CreateRenderer(window, NULL);
    if (!renderer)
    {
        fprintf(stderr, "Renderer failed: %s\n", SDL_GetError());
        SDL_DestroyWindow(window);
        SDL_Quit();
        return 1;
    }

    SDL_StartTextInput(window);

    if (!init_freetype())
    {
        cleanup();
        return 1;
    }

    read_items_from_stdin();
    filter_items();

    while (running)
    {
        handle_input();
        check_child_process();
        render();
        SDL_Delay(16);
    }

    cleanup();
    return 0;
}
```
# repo
- https://gitee.com/EEPPEE_admin/rmenu
- thanks : suckless.org


网站公告

今日签到

点亮在社区的每一天
去签到