diff options
| author | Mitja Felicijan <mitja.felicijan@gmail.com> | 2026-01-24 17:17:21 +0100 |
|---|---|---|
| committer | Mitja Felicijan <mitja.felicijan@gmail.com> | 2026-01-24 17:17:21 +0100 |
| commit | 288f12d36843b6e404adb35857fcd87943e63944 (patch) | |
| tree | 50f58dc9c6c1e8240707d9349876d465d3fb75d8 /switcher.c | |
| download | glitch-288f12d36843b6e404adb35857fcd87943e63944.tar.gz | |
Engage!
Diffstat (limited to 'switcher.c')
| -rw-r--r-- | switcher.c | 187 |
1 files changed, 187 insertions, 0 deletions
diff --git a/switcher.c b/switcher.c new file mode 100644 index 0000000..e14554a --- /dev/null +++ b/switcher.c | |||
| @@ -0,0 +1,187 @@ | |||
| 1 | #include <string.h> | ||
| 2 | #include <stdlib.h> | ||
| 3 | |||
| 4 | #include <X11/Xatom.h> | ||
| 5 | #include <X11/Xproto.h> | ||
| 6 | |||
| 7 | #include "glitch.h" | ||
| 8 | #include "config.h" | ||
| 9 | |||
| 10 | extern WindowManager wm; | ||
| 11 | |||
| 12 | static void draw_switcher(void) { | ||
| 13 | if (!wm.cycle_win || wm.cycle_count == 0) return; | ||
| 14 | |||
| 15 | XSetWindowAttributes wa; | ||
| 16 | wa.background_pixel = WhitePixel(wm.dpy, wm.screen); | ||
| 17 | XChangeWindowAttributes(wm.dpy, wm.cycle_win, CWBackPixel, &wa); | ||
| 18 | XClearWindow(wm.dpy, wm.cycle_win); | ||
| 19 | |||
| 20 | int box_size = 100; | ||
| 21 | int x_offset = 0; | ||
| 22 | int y_offset = 0; | ||
| 23 | |||
| 24 | for (int i = 0; i < wm.cycle_count; i++) { | ||
| 25 | Window w = wm.cycle_clients[i]; | ||
| 26 | int is_selected = (i == wm.active_cycle_index); | ||
| 27 | |||
| 28 | // Draw box background | ||
| 29 | if (is_selected) { | ||
| 30 | // Use blue color for active background (wm.xft_bg_color is usually blue from config) | ||
| 31 | XSetForeground(wm.dpy, DefaultGC(wm.dpy, wm.screen), wm.xft_bg_color.pixel); | ||
| 32 | XFillRectangle(wm.dpy, wm.cycle_win, DefaultGC(wm.dpy, wm.screen), x_offset, y_offset, box_size, box_size); | ||
| 33 | } else { | ||
| 34 | XSetForeground(wm.dpy, DefaultGC(wm.dpy, wm.screen), WhitePixel(wm.dpy, wm.screen)); | ||
| 35 | XFillRectangle(wm.dpy, wm.cycle_win, DefaultGC(wm.dpy, wm.screen), x_offset, y_offset, box_size, box_size); | ||
| 36 | } | ||
| 37 | |||
| 38 | // Draw Window Name | ||
| 39 | char *name = NULL; | ||
| 40 | Atom utf8_string = XInternAtom(wm.dpy, "UTF8_STRING", False); | ||
| 41 | if (XFetchName(wm.dpy, w, &name) || XGetWindowProperty(wm.dpy, w, XInternAtom(wm.dpy, "_NET_WM_NAME", False), 0, (~0L), False, utf8_string, &(Atom){0}, &(int){0}, &(unsigned long){0}, &(unsigned long){0}, (unsigned char **)&name) == Success) { | ||
| 42 | if (name) { | ||
| 43 | // Selected: White text. Unselected: Black text. | ||
| 44 | XftColor *color = is_selected ? &wm.xft_color : &wm.xft_root_bg_color; | ||
| 45 | // NOTE: wm.xft_color is "white" (indicator_fg_color), wm.xft_root_bg_color is "black". | ||
| 46 | |||
| 47 | XftDraw *draw = XftDrawCreate(wm.dpy, wm.cycle_win, DefaultVisual(wm.dpy, wm.screen), wm.cmap); | ||
| 48 | if (draw) { | ||
| 49 | if (strlen(name) > 8) { | ||
| 50 | char truncated[9]; | ||
| 51 | strncpy(truncated, name, 8); | ||
| 52 | truncated[8] = '\0'; | ||
| 53 | XftDrawStringUtf8(draw, color, wm.font, x_offset + 10, y_offset + 90, (const FcChar8 *)truncated, strlen(truncated)); | ||
| 54 | } else { | ||
| 55 | XftDrawStringUtf8(draw, color, wm.font, x_offset + 10, y_offset + 90, (const FcChar8 *)name, strlen(name)); | ||
| 56 | } | ||
| 57 | XftDrawDestroy(draw); | ||
| 58 | } | ||
| 59 | XFree(name); | ||
| 60 | } | ||
| 61 | } | ||
| 62 | |||
| 63 | x_offset += box_size; | ||
| 64 | } | ||
| 65 | XFlush(wm.dpy); | ||
| 66 | } | ||
| 67 | |||
| 68 | void end_cycling(void) { | ||
| 69 | if (!wm.is_cycling) return; | ||
| 70 | |||
| 71 | wm.is_cycling = 0; | ||
| 72 | if (wm.cycle_win) { | ||
| 73 | XDestroyWindow(wm.dpy, wm.cycle_win); | ||
| 74 | wm.cycle_win = None; | ||
| 75 | } | ||
| 76 | if (wm.cycle_clients) { | ||
| 77 | free(wm.cycle_clients); | ||
| 78 | wm.cycle_clients = NULL; | ||
| 79 | } | ||
| 80 | wm.cycle_count = 0; | ||
| 81 | wm.active_cycle_index = -1; | ||
| 82 | |||
| 83 | XUngrabKeyboard(wm.dpy, CurrentTime); | ||
| 84 | log_message(stdout, LOG_DEBUG, "Ended window cycling"); | ||
| 85 | } | ||
| 86 | |||
| 87 | void cycle_active_window(const Arg *arg) { | ||
| 88 | // If not already cycling, initialize | ||
| 89 | if (!wm.is_cycling) { | ||
| 90 | wm.is_cycling = 1; | ||
| 91 | |||
| 92 | // Grab keyboard to catch Alt release (key release) | ||
| 93 | // We grab it on the root window. | ||
| 94 | XGrabKeyboard(wm.dpy, wm.root, True, GrabModeAsync, GrabModeAsync, CurrentTime); | ||
| 95 | |||
| 96 | // Count clients | ||
| 97 | int count = 0; | ||
| 98 | Client *c = wm.clients; | ||
| 99 | while (c) { | ||
| 100 | count++; | ||
| 101 | c = c->next; | ||
| 102 | } | ||
| 103 | |||
| 104 | if (count == 0) { | ||
| 105 | end_cycling(); | ||
| 106 | return; | ||
| 107 | } | ||
| 108 | |||
| 109 | wm.cycle_clients = malloc(sizeof(Window) * count); | ||
| 110 | wm.cycle_count = 0; | ||
| 111 | wm.active_cycle_index = 0; | ||
| 112 | |||
| 113 | // Filter for current desktop and mapped windows | ||
| 114 | c = wm.clients; | ||
| 115 | int current_window_index = -1; | ||
| 116 | |||
| 117 | while (c) { | ||
| 118 | Window w = c->window; | ||
| 119 | |||
| 120 | unsigned long desktop = 0; | ||
| 121 | Atom actual_type; | ||
| 122 | int actual_format; | ||
| 123 | unsigned long nitems, bytes_after; | ||
| 124 | unsigned char *prop = NULL; | ||
| 125 | |||
| 126 | XErrorHandler old = XSetErrorHandler(ignore_x_error); | ||
| 127 | int status = XGetWindowProperty(wm.dpy, w, _NET_WM_DESKTOP, 0, 1, False, XA_CARDINAL, &actual_type, &actual_format, &nitems, &bytes_after, &prop); | ||
| 128 | XSync(wm.dpy, False); | ||
| 129 | XSetErrorHandler(old); | ||
| 130 | |||
| 131 | int on_current_desktop = 0; | ||
| 132 | if (status == Success && prop && nitems > 0) { | ||
| 133 | desktop = *(unsigned long *)prop; | ||
| 134 | if (desktop == wm.current_desktop) { | ||
| 135 | on_current_desktop = 1; | ||
| 136 | } | ||
| 137 | } | ||
| 138 | if (prop) XFree(prop); | ||
| 139 | if (is_sticky(w)) on_current_desktop = 1; | ||
| 140 | |||
| 141 | if (on_current_desktop) { | ||
| 142 | XWindowAttributes wa; | ||
| 143 | XGetWindowAttributes(wm.dpy, w, &wa); | ||
| 144 | if (wa.map_state == IsViewable) { | ||
| 145 | wm.cycle_clients[wm.cycle_count] = w; | ||
| 146 | if (w == wm.active) { | ||
| 147 | current_window_index = wm.cycle_count; | ||
| 148 | } | ||
| 149 | wm.cycle_count++; | ||
| 150 | } | ||
| 151 | } | ||
| 152 | c = c->next; | ||
| 153 | } | ||
| 154 | |||
| 155 | if (wm.cycle_count == 0) { | ||
| 156 | end_cycling(); | ||
| 157 | return; | ||
| 158 | } | ||
| 159 | |||
| 160 | wm.active_cycle_index = (current_window_index + 1) % wm.cycle_count; | ||
| 161 | |||
| 162 | // Create switcher window | ||
| 163 | int box_size = 100; | ||
| 164 | |||
| 165 | int width = (box_size * wm.cycle_count); | ||
| 166 | int height = box_size; | ||
| 167 | int screen_width = DisplayWidth(wm.dpy, wm.screen); | ||
| 168 | int screen_height = DisplayHeight(wm.dpy, wm.screen); | ||
| 169 | int x = (screen_width - width) / 2; | ||
| 170 | int y = (screen_height * 2) / 3; | ||
| 171 | |||
| 172 | XSetWindowAttributes wa; | ||
| 173 | wa.override_redirect = True; | ||
| 174 | wa.background_pixel = BlackPixel(wm.dpy, wm.screen); | ||
| 175 | wa.border_pixel = BlackPixel(wm.dpy, wm.screen); | ||
| 176 | |||
| 177 | wm.cycle_win = XCreateWindow(wm.dpy, wm.root, x, y, width, height, 0, CopyFromParent, InputOutput, CopyFromParent, CWOverrideRedirect | CWBackPixel | CWBorderPixel, &wa); | ||
| 178 | |||
| 179 | XMapRaised(wm.dpy, wm.cycle_win); | ||
| 180 | } else { | ||
| 181 | // Already cycling, just move selection | ||
| 182 | int delta = (arg->i == 0) ? 1 : -1; | ||
| 183 | wm.active_cycle_index = (wm.active_cycle_index + delta + wm.cycle_count) % wm.cycle_count; | ||
| 184 | } | ||
| 185 | |||
| 186 | draw_switcher(); | ||
| 187 | } | ||
