1#include <string.h>
2#include <stdlib.h>
3
4#include <X11/Xatom.h>
5#include <X11/Xproto.h>
6#include <X11/Xutil.h>
7
8#include "glitch.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 XSetForeground(wm.dpy, DefaultGC(wm.dpy, wm.screen), wm.xft_bg_color.pixel);
31 XFillRectangle(wm.dpy, wm.cycle_win, DefaultGC(wm.dpy, wm.screen), x_offset, y_offset, box_size, box_size);
32 } else {
33 XSetForeground(wm.dpy, DefaultGC(wm.dpy, wm.screen), WhitePixel(wm.dpy, wm.screen));
34 XFillRectangle(wm.dpy, wm.cycle_win, DefaultGC(wm.dpy, wm.screen), x_offset, y_offset, box_size, box_size);
35 }
36
37 // Get Program Name
38 char *prog_name = NULL;
39 XClassHint ch;
40 if (XGetClassHint(wm.dpy, w, &ch)) {
41 prog_name = ch.res_class;
42 if (prog_name) {
43 char *dash = strchr(prog_name, '-');
44 if (dash) *dash = '\0';
45 }
46 if (ch.res_name) XFree(ch.res_name);
47 }
48
49 // Get Window Title
50 char *win_title = NULL;
51 if (!XFetchName(wm.dpy, w, &win_title)) {
52 Atom utf8_string = XInternAtom(wm.dpy, "UTF8_STRING", False);
53 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 **)&win_title);
54 }
55
56 XftDraw *draw = XftDrawCreate(wm.dpy, wm.cycle_win, DefaultVisual(wm.dpy, wm.screen), wm.cmap);
57 if (draw) {
58 XftColor *color = is_selected ? &wm.xft_color : &wm.xft_root_bg_color;
59
60 if (prog_name) {
61 char truncated[9];
62 strncpy(truncated, prog_name, 8);
63 truncated[8] = '\0';
64 XftDrawStringUtf8(draw, color, wm.font, x_offset + 10, y_offset + 70, (const FcChar8 *)truncated, strlen(truncated));
65 }
66
67 if (win_title) {
68 char truncated[9];
69 strncpy(truncated, win_title, 8);
70 truncated[8] = '\0';
71 XftDrawStringUtf8(draw, color, wm.font, x_offset + 10, y_offset + 90, (const FcChar8 *)truncated, strlen(truncated));
72 }
73 XftDrawDestroy(draw);
74 }
75
76 if (prog_name) XFree(prog_name);
77 if (win_title) XFree(win_title);
78
79 x_offset += box_size;
80 }
81 XFlush(wm.dpy);
82}
83
84void end_cycling(void) {
85 if (!wm.is_cycling) return;
86
87 wm.is_cycling = 0;
88 if (wm.cycle_win) {
89 XDestroyWindow(wm.dpy, wm.cycle_win);
90 wm.cycle_win = None;
91 }
92 if (wm.cycle_clients) {
93 free(wm.cycle_clients);
94 wm.cycle_clients = NULL;
95 }
96 wm.cycle_count = 0;
97 wm.active_cycle_index = -1;
98
99 XUngrabKeyboard(wm.dpy, CurrentTime);
100 log_message(stdout, LOG_DEBUG, "Ended window cycling");
101}
102
103void cycle_active_window(const Arg *arg) {
104 // If not already cycling, initialize
105 if (!wm.is_cycling) {
106 wm.is_cycling = 1;
107
108 // Grab keyboard to catch Alt release (key release)
109 // We grab it on the root window.
110 XGrabKeyboard(wm.dpy, wm.root, True, GrabModeAsync, GrabModeAsync, CurrentTime);
111
112 // Count clients - allocate extra to handle new windows mapping during this process
113 int count = 0;
114 Client *c = wm.clients;
115 while (c) {
116 count++;
117 c = c->next;
118 }
119
120 if (count == 0) {
121 end_cycling();
122 return;
123 }
124
125 wm.cycle_clients = malloc(sizeof(Window) * (count + 10));
126 wm.cycle_count = 0;
127 wm.active_cycle_index = 0;
128
129 // Filter for current desktop and mapped windows
130 c = wm.clients;
131 int current_window_index = -1;
132
133 while (c) {
134 Window w = c->window;
135
136 unsigned long desktop = 0;
137 Atom actual_type;
138 int actual_format;
139 unsigned long nitems, bytes_after;
140 unsigned char *prop = NULL;
141
142 XErrorHandler old = XSetErrorHandler(ignore_x_error);
143 int status = XGetWindowProperty(wm.dpy, w, _NET_WM_DESKTOP, 0, 1, False, XA_CARDINAL, &actual_type, &actual_format, &nitems, &bytes_after, &prop);
144 XSync(wm.dpy, False);
145 XSetErrorHandler(old);
146
147 int on_current_desktop = 0;
148 if (status == Success && prop && nitems > 0) {
149 desktop = *(unsigned long *)prop;
150 if (desktop == wm.current_desktop) {
151 on_current_desktop = 1;
152 }
153 }
154 if (prop) XFree(prop);
155
156 if (on_current_desktop && !is_sticky(w)) {
157 XWindowAttributes wa;
158 XGetWindowAttributes(wm.dpy, w, &wa);
159 if (wa.map_state == IsViewable) {
160 wm.cycle_clients[wm.cycle_count] = w;
161 if (w == wm.active) {
162 current_window_index = wm.cycle_count;
163 }
164 wm.cycle_count++;
165 }
166 }
167 c = c->next;
168 }
169
170 if (wm.cycle_count == 0) {
171 end_cycling();
172 return;
173 }
174
175 wm.active_cycle_index = (current_window_index + 1) % wm.cycle_count;
176
177 // Create switcher window
178 int box_size = 100;
179
180 int width = (box_size * wm.cycle_count);
181 int height = box_size;
182 int screen_width = DisplayWidth(wm.dpy, wm.screen);
183 int screen_height = DisplayHeight(wm.dpy, wm.screen);
184 int x = (screen_width - width) / 2;
185 int y = (screen_height * 2) / 3;
186
187 XSetWindowAttributes wa;
188 wa.override_redirect = True;
189 wa.background_pixel = BlackPixel(wm.dpy, wm.screen);
190 wa.border_pixel = BlackPixel(wm.dpy, wm.screen);
191
192 wm.cycle_win = XCreateWindow(wm.dpy, wm.root, x, y, width, height, 0, CopyFromParent, InputOutput, CopyFromParent, CWOverrideRedirect | CWBackPixel | CWBorderPixel, &wa);
193
194 XMapRaised(wm.dpy, wm.cycle_win);
195 } else {
196 // Already cycling, just move selection
197 int delta = (arg->i == 0) ? 1 : -1;
198 wm.active_cycle_index = (wm.active_cycle_index + delta + wm.cycle_count) % wm.cycle_count;
199 }
200
201 draw_switcher();
202}