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}