rewrite my menu launcher in raylib
#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>
#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);
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
{
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);
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)
{
}
}
int main()
{
struct sigaction sa;
memset(&sa, 0, sizeof(sa));
sa.sa_handler = sigchld_handler;
sa.sa_flags = SA_RESTART;
sigaction(SIGCHLD, &sa, NULL);
SetTraceLogLevel(LOG_NONE);
InitWindow(WIDTH, HEIGHT, "rmenu");
SetTargetFPS(60);
SetExitKey(0);
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;
}
}
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
#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;
}
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;
}
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
#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);
}
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();
}
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();
}
else if (ctrl_pressed && event.key.key == SDLK_K)
{
input_str[cursor] = '\0';
filter_items();
}
else if (ctrl_pressed && event.key.key == SDLK_U)
{
input_str[0] = '\0';
cursor = 0;
filter_items();
}
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;
}
}
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;
}
}
else if (event.key.key == SDLK_RIGHT || (ctrl_pressed && event.key.key == SDLK_F))
{
if (cursor < strlen(input_str))
cursor++;
}
else if (event.key.key == SDLK_LEFT || (ctrl_pressed && event.key.key == SDLK_B))
{
if (cursor > 0)
cursor--;
}
else if (event.key.key == SDLK_HOME || (ctrl_pressed && event.key.key == SDLK_A))
{
cursor = 0;
}
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:
- thanks : suckless.org