From 6330c2827935a67ee03f18baf9cf4a7a77684760 Mon Sep 17 00:00:00 2001 From: Mitja Felicijan Date: Wed, 15 Apr 2026 18:35:41 +0200 Subject: Merged rofi launcher alternative --- Makefile | 2 +- config.def.h | 15 +++- glitch.h | 26 ++++++ launcher.c | 262 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ manager.c | 50 ++++++++++++ 5 files changed, 351 insertions(+), 4 deletions(-) create mode 100644 launcher.c diff --git a/Makefile b/Makefile index 796b17b..8dc8245 100644 --- a/Makefile +++ b/Makefile @@ -15,7 +15,7 @@ endif all: glitch -glitch: main.c logging.c manager.c widgets.c switcher.c audio.c +glitch: main.c logging.c manager.c widgets.c switcher.c audio.c launcher.c $(CC) $(CFLAGS) $(INCLUDES) -o $@ $^ $(LDFLAGS) config.h: diff --git a/config.def.h b/config.def.h index a10d0f9..71efcdd 100644 --- a/config.def.h +++ b/config.def.h @@ -21,6 +21,7 @@ static const char *on_top_active_border_color = "orange"; static const char *on_top_inactive_border_color = "darkorange"; static const char *widget_font = "Berkeley Mono:size=7:bold"; +static const char *launcher_font_name = "Berkeley Mono:size=9:bold"; static const char *time_format = "%A %d.%m.%Y %H:%M:%S"; static const char *indicator_fg_color = "white"; static const char *indicator_bg_color = "blue"; @@ -33,12 +34,19 @@ static const char *layout_float_bg_color = "#333333"; static const char *layout_tile_fg_color = "white"; static const char *layout_float_fg_color = "white"; +static const char *launcher_bg_color = "black"; +static const char *launcher_border_color = "khaki"; +static const char *launcher_fg_color = "white"; +static const char *launcher_hl_bg_color = "khaki"; +static const char *launcher_hl_fg_color = "black"; +static int launcher_width = 800; +static int launcher_height = 600; + static Shortcut shortcuts[] = { /* Mask KeySym Shell command */ - { MODKEY, XK_Return, "st -f \"Berkeley Mono:style=Bold:size=10\" -g 80x40" }, - { MODKEY, XK_p, "rofi -show drun -theme ~/.black.rasi" }, + { MODKEY, XK_Return, "alacritty" }, { ControlMask, XK_Escape, "sh -c 'maim -s | xclip -selection clipboard -t image/png'" }, - { MODKEY, XK_w, "/home/m/Applications/brave --new-window" }, + { MODKEY, XK_w, "brave --new-window" }, { MODKEY, XK_e, "thunar" }, { MODKEY, XK_s, "xmagnify -s 1000 -z 3" }, { MODKEY, XK_r, "simplescreenrecorder" }, @@ -92,6 +100,7 @@ static Keybinds keybinds[] = { { MODKEY, XK_c, center_window, { 0 } }, { MODKEY, XK_m, toggle_mic_mute, { 0 } }, { MODKEY, XK_space, toggle_layout, { 0 } }, + { MODKEY, XK_p, toggle_launcher, { 0 } }, { MODKEY | ShiftMask, XK_q, quit, { 0 } }, { MODKEY, XK_q, close_window, { 0 } }, }; diff --git a/glitch.h b/glitch.h index c5dc272..c7bcf3f 100644 --- a/glitch.h +++ b/glitch.h @@ -55,6 +55,11 @@ typedef struct Client { int has_saved_state; } Client; +typedef struct { + char *name; + char *exec; +} LauncherItem; + typedef struct { Display *dpy; Window root; @@ -76,6 +81,7 @@ typedef struct { unsigned int current_desktop; XftFont *font; + XftFont *launcher_font; XftDraw *xft_draw; XftColor xft_color; XftColor xft_bg_color; @@ -90,6 +96,12 @@ typedef struct { XftColor xft_layout_tile_fg; XftColor xft_layout_float_fg; + XftColor xft_launcher_bg; + XftColor xft_launcher_border; + XftColor xft_launcher_fg; + XftColor xft_launcher_hl_bg; + XftColor xft_launcher_hl_fg; + unsigned long last_widget_update; Client *clients; @@ -106,6 +118,16 @@ typedef struct { pa_threaded_mainloop *pa_mainloop; pa_context *pa_ctx; int mic_muted; + + // Launcher + int launcher_active; + Window launcher_win; + LauncherItem *launcher_items; + int launcher_items_count; + LauncherItem **launcher_filtered; + int launcher_filtered_count; + char launcher_search[256]; + int launcher_selected; } WindowManager; typedef struct { @@ -197,4 +219,8 @@ void toggle_mic_mute(const Arg *arg); void apply_tiling_layout(void); void toggle_layout(const Arg *arg); +void toggle_launcher(const Arg *arg); +void launcher_handle_key(void); +void launcher_draw(void); + #endif // GLITCH_H diff --git a/launcher.c b/launcher.c new file mode 100644 index 0000000..9c77958 --- /dev/null +++ b/launcher.c @@ -0,0 +1,262 @@ +#define _GNU_SOURCE +#define _POSIX_C_SOURCE 200809L +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "glitch.h" +#include "config.h" + +extern WindowManager wm; + +static void launcher_filter(void); + +static char *trim_whitespace(char *str) { + char *end; + while (isspace((unsigned char)*str)) str++; + if (*str == 0) return str; + end = str + strlen(str) - 1; + while (end > str && isspace((unsigned char)*end)) end--; + end[1] = '\0'; + return str; +} + +static void load_applications(void) { + const char *system_dirs[] = { + "/usr/share/applications", + "/usr/local/share/applications" + }; + + char home_dir[1024]; + char *home = getenv("HOME"); + if (home) { + snprintf(home_dir, sizeof(home_dir), "%s/.local/share/applications", home); + } + + if (wm.launcher_items) { + for (int i = 0; i < wm.launcher_items_count; i++) { + free(wm.launcher_items[i].name); + free(wm.launcher_items[i].exec); + } + free(wm.launcher_items); + wm.launcher_items = NULL; + wm.launcher_items_count = 0; + } + + int capacity = 100; + wm.launcher_items = malloc(sizeof(LauncherItem) * capacity); + + for (int d = -1; d < (int)LENGTH(system_dirs); d++) { + const char *path_to_open; + if (d == -1) { + if (!home) continue; + path_to_open = home_dir; + } else { + path_to_open = system_dirs[d]; + } + + DIR *dir = opendir(path_to_open); + if (!dir) continue; + + struct dirent *entry; + while ((entry = readdir(dir))) { + if (strstr(entry->d_name, ".desktop")) { + char desktop_path[2048]; + snprintf(desktop_path, sizeof(desktop_path), "%s/%s", path_to_open, entry->d_name); + + FILE *f = fopen(desktop_path, "r"); + if (!f) continue; + + char line[1024]; + char *name = NULL; + char *exec = NULL; + int no_display = 0; + int in_desktop_entry = 0; + + while (fgets(line, sizeof(line), f)) { + char *trimmed = trim_whitespace(line); + if (trimmed[0] == '[' && strstr(trimmed, "[Desktop Entry]")) { + in_desktop_entry = 1; + continue; + } + if (trimmed[0] == '[' && !strstr(trimmed, "[Desktop Entry]")) { + in_desktop_entry = 0; + } + + if (!in_desktop_entry) continue; + + if (strncmp(trimmed, "Name=", 5) == 0 && !name) { + name = strdup(trim_whitespace(trimmed + 5)); + } else if (strncmp(trimmed, "Exec=", 5) == 0 && !exec) { + char *e = strdup(trimmed + 5); + char *percent = strchr(e, '%'); + if (percent) *percent = '\0'; + exec = strdup(trim_whitespace(e)); + free(e); + } else if (strncmp(trimmed, "NoDisplay=true", 14) == 0) { + no_display = 1; + } + } + fclose(f); + + if (name && exec && !no_display) { + if (wm.launcher_items_count >= capacity) { + capacity *= 2; + wm.launcher_items = realloc(wm.launcher_items, sizeof(LauncherItem) * capacity); + } + wm.launcher_items[wm.launcher_items_count].name = name; + wm.launcher_items[wm.launcher_items_count].exec = exec; + wm.launcher_items_count++; + } else { + if (name) free(name); + if (exec) free(exec); + } + } + } + closedir(dir); + } +} + +void toggle_launcher(const Arg *arg) { + (void)arg; + if (wm.launcher_active) { + wm.launcher_active = 0; + XUnmapWindow(wm.dpy, wm.launcher_win); + XUngrabKeyboard(wm.dpy, CurrentTime); + return; + } + + if (!wm.launcher_items) { + load_applications(); + } + + wm.launcher_active = 1; + wm.launcher_search[0] = '\0'; + wm.launcher_selected = 0; + launcher_filter(); + + int screen_width = DisplayWidth(wm.dpy, wm.screen); + int screen_height = DisplayHeight(wm.dpy, wm.screen); + int win_width = launcher_width; + int win_height = launcher_height; + int x = (screen_width - win_width) / 2; + int y = (screen_height - win_height) / 2; + + if (!wm.launcher_win) { + XSetWindowAttributes wa; + wa.override_redirect = True; + wa.background_pixel = wm.xft_launcher_bg.pixel; + wa.border_pixel = wm.xft_launcher_border.pixel; + wm.launcher_win = XCreateWindow(wm.dpy, wm.root, x, y, win_width, win_height, 2, + DefaultDepth(wm.dpy, wm.screen), CopyFromParent, + DefaultVisual(wm.dpy, wm.screen), + CWOverrideRedirect | CWBackPixel | CWBorderPixel, &wa); + } else { + XMoveWindow(wm.dpy, wm.launcher_win, x, y); + } + + XMapRaised(wm.dpy, wm.launcher_win); + XGrabKeyboard(wm.dpy, wm.launcher_win, True, GrabModeAsync, GrabModeAsync, CurrentTime); + launcher_draw(); +} + +static void launcher_filter(void) { + if (wm.launcher_filtered) free(wm.launcher_filtered); + wm.launcher_filtered = malloc(sizeof(LauncherItem *) * wm.launcher_items_count); + wm.launcher_filtered_count = 0; + + for (int i = 0; i < wm.launcher_items_count; i++) { + if (wm.launcher_search[0] == '\0' || + strcasestr(wm.launcher_items[i].name, wm.launcher_search)) { + wm.launcher_filtered[wm.launcher_filtered_count++] = &wm.launcher_items[i]; + } + } + + if (wm.launcher_selected >= wm.launcher_filtered_count) { + wm.launcher_selected = wm.launcher_filtered_count > 0 ? wm.launcher_filtered_count - 1 : 0; + } +} + +void launcher_handle_key(void) { + KeySym keysym = XLookupKeysym(&wm.ev.xkey, 0); + int len = strlen(wm.launcher_search); + + if (keysym == XK_Escape) { + toggle_launcher(NULL); + return; + } else if (keysym == XK_BackSpace) { + if (len > 0) { + wm.launcher_search[len - 1] = '\0'; + wm.launcher_selected = 0; + launcher_filter(); + } + } else if (keysym == XK_Return) { + if (wm.launcher_filtered_count > 0 && wm.launcher_selected < wm.launcher_filtered_count) { + execute_shortcut(wm.launcher_filtered[wm.launcher_selected]->exec); + toggle_launcher(NULL); + return; + } + } else if (keysym == XK_Up) { + if (wm.launcher_selected > 0) wm.launcher_selected--; + } else if (keysym == XK_Down) { + if (wm.launcher_selected < wm.launcher_filtered_count - 1) wm.launcher_selected++; + } else { + char buf[32]; + int n = XLookupString(&wm.ev.xkey, buf, sizeof(buf), NULL, NULL); + if (n > 0 && len + n < (int)sizeof(wm.launcher_search)) { + memcpy(wm.launcher_search + len, buf, n); + wm.launcher_search[len + n] = '\0'; + wm.launcher_selected = 0; + launcher_filter(); + } + } + + launcher_draw(); +} + +void launcher_draw(void) { + if (!wm.launcher_win) return; + + XftDraw *draw = XftDrawCreate(wm.dpy, wm.launcher_win, DefaultVisual(wm.dpy, wm.screen), wm.cmap); + XWindowAttributes wa; + XGetWindowAttributes(wm.dpy, wm.launcher_win, &wa); + + // Clear background + XftDrawRect(draw, &wm.xft_launcher_bg, 0, 0, wa.width, wa.height); + + int x = 20; + int y = 30; + int row_height = wm.launcher_font->height + 10; + + // Draw search bar + char search_display[300]; + snprintf(search_display, sizeof(search_display), "> %s", wm.launcher_search); + XftDrawStringUtf8(draw, &wm.xft_launcher_fg, wm.launcher_font, x, y, (FcChar8 *)search_display, strlen(search_display)); + + y += row_height + 10; // Extra padding below input + + // Draw items + int start_idx = 0; + if (wm.launcher_selected >= 10) { + start_idx = wm.launcher_selected - 9; + } + + for (int i = start_idx; i < wm.launcher_filtered_count && i < start_idx + 15; i++) { + if (i == wm.launcher_selected) { + XftDrawRect(draw, &wm.xft_launcher_hl_bg, 0, y - wm.launcher_font->ascent - 5, wa.width, row_height); + XftDrawStringUtf8(draw, &wm.xft_launcher_hl_fg, wm.launcher_font, x, y, (FcChar8 *)wm.launcher_filtered[i]->name, strlen(wm.launcher_filtered[i]->name)); + } else { + XftDrawStringUtf8(draw, &wm.xft_launcher_fg, wm.launcher_font, x, y, (FcChar8 *)wm.launcher_filtered[i]->name, strlen(wm.launcher_filtered[i]->name)); + } + y += row_height; + } + + XftDrawDestroy(draw); + XFlush(wm.dpy); +} diff --git a/manager.c b/manager.c index dde091e..a38c1cb 100644 --- a/manager.c +++ b/manager.c @@ -317,6 +317,12 @@ void init_window_manager(void) { wm.font = XftFontOpenName(wm.dpy, wm.screen, "fixed"); } + wm.launcher_font = XftFontOpenName(wm.dpy, wm.screen, launcher_font_name); + if (!wm.launcher_font) { + log_message(stdout, LOG_WARNING, "Failed to load launcher font %s, falling back to fixed", launcher_font_name); + wm.launcher_font = XftFontOpenName(wm.dpy, wm.screen, "fixed"); + } + Visual *visual = DefaultVisual(wm.dpy, wm.screen); // Create XftDraw for the root window. @@ -386,6 +392,27 @@ void init_window_manager(void) { XftColorAllocValue(wm.dpy, visual, wm.cmap, &render_color, &wm.xft_layout_float_fg); } + if (!XftColorAllocName(wm.dpy, visual, wm.cmap, launcher_bg_color, &wm.xft_launcher_bg)) { + XRenderColor render_color = {0x0000, 0x0000, 0x0000, 0xFFFF}; + XftColorAllocValue(wm.dpy, visual, wm.cmap, &render_color, &wm.xft_launcher_bg); + } + if (!XftColorAllocName(wm.dpy, visual, wm.cmap, launcher_border_color, &wm.xft_launcher_border)) { + XRenderColor render_color = {0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF}; + XftColorAllocValue(wm.dpy, visual, wm.cmap, &render_color, &wm.xft_launcher_border); + } + if (!XftColorAllocName(wm.dpy, visual, wm.cmap, launcher_fg_color, &wm.xft_launcher_fg)) { + XRenderColor render_color = {0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF}; + XftColorAllocValue(wm.dpy, visual, wm.cmap, &render_color, &wm.xft_launcher_fg); + } + if (!XftColorAllocName(wm.dpy, visual, wm.cmap, launcher_hl_bg_color, &wm.xft_launcher_hl_bg)) { + XRenderColor render_color = {0x8000, 0x8000, 0x0000, 0xFFFF}; + XftColorAllocValue(wm.dpy, visual, wm.cmap, &render_color, &wm.xft_launcher_hl_bg); + } + if (!XftColorAllocName(wm.dpy, visual, wm.cmap, launcher_hl_fg_color, &wm.xft_launcher_hl_fg)) { + XRenderColor render_color = {0x0000, 0x0000, 0x0000, 0xFFFF}; + XftColorAllocValue(wm.dpy, visual, wm.cmap, &render_color, &wm.xft_launcher_hl_fg); + } + wm.running = 1; // Grab keys for keybinds. @@ -488,9 +515,27 @@ void deinit_window_manager(void) { XftColorFree(wm.dpy, DefaultVisual(wm.dpy, wm.screen), wm.cmap, &wm.xft_mic_muted_bg); XftColorFree(wm.dpy, DefaultVisual(wm.dpy, wm.screen), wm.cmap, &wm.xft_mic_active_fg); XftColorFree(wm.dpy, DefaultVisual(wm.dpy, wm.screen), wm.cmap, &wm.xft_mic_muted_fg); + + XftColorFree(wm.dpy, DefaultVisual(wm.dpy, wm.screen), wm.cmap, &wm.xft_launcher_bg); + XftColorFree(wm.dpy, DefaultVisual(wm.dpy, wm.screen), wm.cmap, &wm.xft_launcher_border); + XftColorFree(wm.dpy, DefaultVisual(wm.dpy, wm.screen), wm.cmap, &wm.xft_launcher_fg); + XftColorFree(wm.dpy, DefaultVisual(wm.dpy, wm.screen), wm.cmap, &wm.xft_launcher_hl_bg); + XftColorFree(wm.dpy, DefaultVisual(wm.dpy, wm.screen), wm.cmap, &wm.xft_launcher_hl_fg); + XftDrawDestroy(wm.xft_draw); + if (wm.launcher_win) XDestroyWindow(wm.dpy, wm.launcher_win); + if (wm.launcher_items) { + for (int i = 0; i < wm.launcher_items_count; i++) { + free(wm.launcher_items[i].name); + free(wm.launcher_items[i].exec); + } + free(wm.launcher_items); + } + if (wm.launcher_filtered) free(wm.launcher_filtered); + XftFontClose(wm.dpy, wm.font); + XftFontClose(wm.dpy, wm.launcher_font); XFreeCursor(wm.dpy, wm.cursor_default); XFreeCursor(wm.dpy, wm.cursor_move); XFreeCursor(wm.dpy, wm.cursor_resize); @@ -1062,6 +1107,11 @@ void handle_button_release(void) { // https://tronche.com/gui/x/xlib/events/keyboard-pointer/keyboard-pointer.html void handle_key_press(void) { + if (wm.launcher_active) { + launcher_handle_key(); + return; + } + log_message(stdout, LOG_DEBUG, ">> Key pressed > active window 0x%lx", wm.ev.xkey.subwindow); if (wm.ev.type != KeyPress) return; -- cgit v1.2.3