aboutsummaryrefslogtreecommitdiff
path: root/switcher.c
diff options
context:
space:
mode:
authorMitja Felicijan <mitja.felicijan@gmail.com>2026-01-24 17:17:21 +0100
committerMitja Felicijan <mitja.felicijan@gmail.com>2026-01-24 17:17:21 +0100
commit288f12d36843b6e404adb35857fcd87943e63944 (patch)
tree50f58dc9c6c1e8240707d9349876d465d3fb75d8 /switcher.c
downloadglitch-288f12d36843b6e404adb35857fcd87943e63944.tar.gz
Engage!
Diffstat (limited to 'switcher.c')
-rw-r--r--switcher.c187
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
10extern WindowManager wm;
11
12static 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
68void 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
87void 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}