summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMitja Felicijan <mitja.felicijan@gmail.com>2026-04-15 18:35:41 +0200
committerMitja Felicijan <mitja.felicijan@gmail.com>2026-04-15 18:35:41 +0200
commit6330c2827935a67ee03f18baf9cf4a7a77684760 (patch)
tree36f454ff7c8de804987b1b4af21272ca7363d005
parentf214499d3d5be0bc3582f8de5d2567b8767fef87 (diff)
downloadglitch-6330c2827935a67ee03f18baf9cf4a7a77684760.tar.gz
Merged rofi launcher alternative
-rw-r--r--Makefile2
-rw-r--r--config.def.h15
-rw-r--r--glitch.h26
-rw-r--r--launcher.c262
-rw-r--r--manager.c50
5 files changed, 351 insertions, 4 deletions
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
@@ -56,6 +56,11 @@ typedef struct Client {
} Client;
typedef struct {
+ char *name;
+ char *exec;
+} LauncherItem;
+
+typedef struct {
Display *dpy;
Window root;
Window active;
@@ -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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <dirent.h>
+#include <ctype.h>
+
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+#include <X11/keysym.h>
+
+#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;