Add tiling and floating mode

Author Mitja Felicijan <mitja.felicijan@gmail.com> 2026-04-15 16:35:36 +0200
Committer Mitja Felicijan <mitja.felicijan@gmail.com> 2026-04-15 16:35:36 +0200
Commit 893e4ab58fce2918480501f999313000b6de6249 (patch)
-rw-r--r-- config.def.h 5
-rw-r--r-- glitch.h 20
-rw-r--r-- manager.c 108
-rw-r--r-- widgets.c 53
4 files changed, 181 insertions, 5 deletions
diff --git a/config.def.h b/config.def.h
...
28
static const char *mic_muted_bg_color = "#222222";
28
static const char *mic_muted_bg_color = "#222222";
29
static const char *mic_active_fg_color = "white";
29
static const char *mic_active_fg_color = "white";
30
static const char *mic_muted_fg_color = "white";
30
static const char *mic_muted_fg_color = "white";
  
31
static const char *layout_tile_bg_color = "darkgreen";
  
32
static const char *layout_float_bg_color = "#333333";
  
33
static const char *layout_tile_fg_color = "white";
  
34
static const char *layout_float_fg_color = "white";
31
  
35
  
32
static Shortcut shortcuts[] = {
36
static Shortcut shortcuts[] = {
33
	/* Mask                 KeySym                    Shell command */
37
	/* Mask                 KeySym                    Shell command */
...
87
	{ MODKEY | ShiftMask,   XK_r,       reload,              { 0 }        },
91
	{ MODKEY | ShiftMask,   XK_r,       reload,              { 0 }        },
88
	{ MODKEY,               XK_c,       center_window,       { 0 }        },
92
	{ MODKEY,               XK_c,       center_window,       { 0 }        },
89
	{ MODKEY,               XK_m,       toggle_mic_mute,     { 0 }        },
93
	{ MODKEY,               XK_m,       toggle_mic_mute,     { 0 }        },
  
94
	{ MODKEY,               XK_space,   toggle_layout,       { 0 }        },
90
	{ MODKEY | ShiftMask,   XK_q,       quit,                { 0 }        },
95
	{ MODKEY | ShiftMask,   XK_q,       quit,                { 0 }        },
91
	{ MODKEY,               XK_q,       close_window,        { 0 }        },
96
	{ MODKEY,               XK_q,       close_window,        { 0 }        },
92
};
97
};
...
diff --git a/glitch.h b/glitch.h
...
32
	LOG_ERROR,
32
	LOG_ERROR,
33
} LogLevel;
33
} LogLevel;
34
  
34
  
  
35
typedef enum {
  
36
	LAYOUT_FLOATING,
  
37
	LAYOUT_TILING,
  
38
} LayoutMode;
  
39
  
35
typedef struct {
40
typedef struct {
36
	unsigned long normal_active;
41
	unsigned long normal_active;
37
	unsigned long normal_inactive;
42
	unsigned long normal_inactive;
...
77
	XftColor xft_mic_muted_bg;
82
	XftColor xft_mic_muted_bg;
78
	XftColor xft_mic_active_fg;
83
	XftColor xft_mic_active_fg;
79
	XftColor xft_mic_muted_fg;
84
	XftColor xft_mic_muted_fg;
80
	
85
	XftColor xft_layout_tile_bg;
  
86
	XftColor xft_layout_float_bg;
  
87
	XftColor xft_layout_tile_fg;
  
88
	XftColor xft_layout_float_fg;
  
89
  
81
	unsigned long last_widget_update;
90
	unsigned long last_widget_update;
82
	Client *clients;
91
	Client *clients;
83
	
92
  
84
	int is_cycling;
93
	int is_cycling;
85
	Window cycle_win;
94
	Window cycle_win;
86
	Window *cycle_clients;
95
	Window *cycle_clients;
87
	int cycle_count;
96
	int cycle_count;
88
	int active_cycle_index;
97
	int active_cycle_index;
  
98
  
  
99
	// Layout management
  
100
	LayoutMode layout_modes[NUM_DESKTOPS + 1]; // 1-indexed for convenience
89
  
101
  
90
	// PulseAudio
102
	// PulseAudio
91
	pa_threaded_mainloop *pa_mainloop;
103
	pa_threaded_mainloop *pa_mainloop;
...
172
void widget_desktop_indicator(void);
184
void widget_desktop_indicator(void);
173
void widget_datetime(void);
185
void widget_datetime(void);
174
void widget_mic_indicator(void);
186
void widget_mic_indicator(void);
  
187
void widget_layout_indicator(void);
175
void redraw_widgets(void);
188
void redraw_widgets(void);
176
  
189
  
177
void init_audio(void);
190
void init_audio(void);
178
void deinit_audio(void);
191
void deinit_audio(void);
179
void toggle_mic_mute(const Arg *arg);
192
void toggle_mic_mute(const Arg *arg);
  
193
  
  
194
void apply_tiling_layout(void);
  
195
void toggle_layout(const Arg *arg);
180
  
196
  
181
#endif // GLITCH_H
197
#endif // GLITCH_H
diff --git a/manager.c b/manager.c
...
253
	XChangeProperty(wm.dpy, wm.root, _NET_CURRENT_DESKTOP, XA_CARDINAL, 32, PropModeReplace, (unsigned char *)&current_desktop, 1);
253
	XChangeProperty(wm.dpy, wm.root, _NET_CURRENT_DESKTOP, XA_CARDINAL, 32, PropModeReplace, (unsigned char *)&current_desktop, 1);
254
	log_message(stdout, LOG_DEBUG, "Registering %d desktops", NUM_DESKTOPS);
254
	log_message(stdout, LOG_DEBUG, "Registering %d desktops", NUM_DESKTOPS);
255
  
255
  
  
256
	// Initialize layout modes.
  
257
	for (int i = 0; i <= NUM_DESKTOPS; i++) {
  
258
		wm.layout_modes[i] = LAYOUT_FLOATING;
  
259
	}
  
260
  
256
	// Initialize colormap early as it's needed for Xft.
261
	// Initialize colormap early as it's needed for Xft.
257
	wm.cmap = DefaultColormap(wm.dpy, wm.screen);
262
	wm.cmap = DefaultColormap(wm.dpy, wm.screen);
258
  
263
  
...
308
		XftColorAllocValue(wm.dpy, visual, wm.cmap, &render_color, &wm.xft_mic_muted_fg);
313
		XftColorAllocValue(wm.dpy, visual, wm.cmap, &render_color, &wm.xft_mic_muted_fg);
309
	}
314
	}
310
  
315
  
  
316
	if (!XftColorAllocName(wm.dpy, visual, wm.cmap, layout_tile_bg_color, &wm.xft_layout_tile_bg)) {
  
317
		log_message(stdout, LOG_WARNING, "Failed to allocate color %s, falling back to dark green", layout_tile_bg_color);
  
318
		XRenderColor render_color = {0x0000, 0x6400, 0x0000, 0xFFFF};
  
319
		XftColorAllocValue(wm.dpy, visual, wm.cmap, &render_color, &wm.xft_layout_tile_bg);
  
320
	}
  
321
  
  
322
	if (!XftColorAllocName(wm.dpy, visual, wm.cmap, layout_float_bg_color, &wm.xft_layout_float_bg)) {
  
323
		log_message(stdout, LOG_WARNING, "Failed to allocate color %s, falling back to gray", layout_float_bg_color);
  
324
		XRenderColor render_color = {0x3333, 0x3333, 0x3333, 0xFFFF};
  
325
		XftColorAllocValue(wm.dpy, visual, wm.cmap, &render_color, &wm.xft_layout_float_bg);
  
326
	}
  
327
  
  
328
	if (!XftColorAllocName(wm.dpy, visual, wm.cmap, layout_tile_fg_color, &wm.xft_layout_tile_fg)) {
  
329
		log_message(stdout, LOG_WARNING, "Failed to allocate color %s, falling back to white", layout_tile_fg_color);
  
330
		XRenderColor render_color = {0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF};
  
331
		XftColorAllocValue(wm.dpy, visual, wm.cmap, &render_color, &wm.xft_layout_tile_fg);
  
332
	}
  
333
  
  
334
	if (!XftColorAllocName(wm.dpy, visual, wm.cmap, layout_float_fg_color, &wm.xft_layout_float_fg)) {
  
335
		log_message(stdout, LOG_WARNING, "Failed to allocate color %s, falling back to white", layout_float_fg_color);
  
336
		XRenderColor render_color = {0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF};
  
337
		XftColorAllocValue(wm.dpy, visual, wm.cmap, &render_color, &wm.xft_layout_float_fg);
  
338
	}
  
339
  
311
	wm.running = 1;
340
	wm.running = 1;
312
  
341
  
313
	scan_windows();
342
	scan_windows();
...
641
	grab_buttons(window);
670
	grab_buttons(window);
642
  
671
  
643
	add_client(window);
672
	add_client(window);
644
	
673
	apply_tiling_layout();
  
674
  
645
	XSync(wm.dpy, False);
675
	XSync(wm.dpy, False);
646
	XSetErrorHandler(old);
676
	XSetErrorHandler(old);
647
  
677
  
...
663
	}
693
	}
664
  
694
  
665
	log_message(stdout, LOG_DEBUG, "Window 0x%lx unmapped", window);
695
	log_message(stdout, LOG_DEBUG, "Window 0x%lx unmapped", window);
  
696
	apply_tiling_layout();
666
	update_client_list();
697
	update_client_list();
667
}
698
}
668
  
699
  
...
673
		wm.active = None;
704
		wm.active = None;
674
	}
705
	}
675
	remove_client(window);
706
	remove_client(window);
  
707
	apply_tiling_layout();
676
	log_message(stdout, LOG_DEBUG, "Window 0x%lx destroyed", window);
708
	log_message(stdout, LOG_DEBUG, "Window 0x%lx destroyed", window);
677
	update_client_list();
709
	update_client_list();
678
}
710
}
...
938
  
970
  
939
	set_active_window(new_active, CurrentTime);
971
	set_active_window(new_active, CurrentTime);
940
	set_active_border(new_active);
972
	set_active_border(new_active);
  
973
	apply_tiling_layout();
941
  
974
  
942
	widget_desktop_indicator();
975
	widget_desktop_indicator();
943
	widget_datetime();
976
	widget_datetime();
...
956
	XUnmapWindow(wm.dpy, wm.active);
989
	XUnmapWindow(wm.dpy, wm.active);
957
  
990
  
958
	wm.active = None;
991
	wm.active = None;
  
992
	apply_tiling_layout();
959
	widget_desktop_indicator();
993
	widget_desktop_indicator();
960
	widget_datetime();
994
	widget_datetime();
961
	log_message(stdout, LOG_DEBUG, "Moved window to desktop %d", arg->i);
995
	log_message(stdout, LOG_DEBUG, "Moved window to desktop %d", arg->i);
...
1612
	wm.restart = 1;
1646
	wm.restart = 1;
1613
	log_message(stdout, LOG_DEBUG, "Reload window manager");
1647
	log_message(stdout, LOG_DEBUG, "Reload window manager");
1614
}
1648
}
  
1649
  
  
1650
void apply_tiling_layout(void) {
  
1651
	if (wm.layout_modes[wm.current_desktop] != LAYOUT_TILING) return;
  
1652
  
  
1653
	int n = 0;
  
1654
	for (Client *c = wm.clients; c; c = c->next) {
  
1655
		if (window_exists(c->window)) {
  
1656
			unsigned long desktop;
  
1657
			Atom actual_type;
  
1658
			int actual_format;
  
1659
			unsigned long nitems, bytes_after;
  
1660
			unsigned char *prop = NULL;
  
1661
  
  
1662
			int status = XGetWindowProperty(wm.dpy, c->window, _NET_WM_DESKTOP, 0, 1, False, XA_CARDINAL, &actual_type, &actual_format, &nitems, &bytes_after, &prop);
  
1663
			if (status == Success && prop && nitems > 0) {
  
1664
				desktop = *(unsigned long *)prop;
  
1665
				XFree(prop);
  
1666
				if (desktop == wm.current_desktop && !is_sticky(c->window) && !is_always_on_top(c->window) && !has_wm_state(c->window, _NET_WM_STATE_FULLSCREEN)) {
  
1667
					n++;
  
1668
				}
  
1669
			} else if (prop) {
  
1670
				XFree(prop);
  
1671
			}
  
1672
		}
  
1673
	}
  
1674
  
  
1675
	if (n == 0) return;
  
1676
  
  
1677
	int screen_width = DisplayWidth(wm.dpy, wm.screen);
  
1678
	int screen_height = DisplayHeight(wm.dpy, wm.screen);
  
1679
	int mw = (n > 1) ? screen_width / 3 : screen_width;
  
1680
	int i = 0;
  
1681
  
  
1682
	for (Client *c = wm.clients; c; c = c->next) {
  
1683
		if (window_exists(c->window)) {
  
1684
			unsigned long desktop;
  
1685
			Atom actual_type;
  
1686
			int actual_format;
  
1687
			unsigned long nitems, bytes_after;
  
1688
			unsigned char *prop = NULL;
  
1689
  
  
1690
			int status = XGetWindowProperty(wm.dpy, c->window, _NET_WM_DESKTOP, 0, 1, False, XA_CARDINAL, &actual_type, &actual_format, &nitems, &bytes_after, &prop);
  
1691
			if (status == Success && prop && nitems > 0) {
  
1692
				desktop = *(unsigned long *)prop;
  
1693
				XFree(prop);
  
1694
				if (desktop == wm.current_desktop && !is_sticky(c->window) && !is_always_on_top(c->window) && !has_wm_state(c->window, _NET_WM_STATE_FULLSCREEN)) {
  
1695
					if (n == 1) {
  
1696
						XMoveResizeWindow(wm.dpy, c->window, 0, 0, screen_width - 2 * border_size, screen_height - 2 * border_size);
  
1697
					} else if (i == 0) { // Master
  
1698
						XMoveResizeWindow(wm.dpy, c->window, 0, 0, mw - 2 * border_size, screen_height - 2 * border_size);
  
1699
					} else { // Stack
  
1700
						int h = screen_height / (n - 1);
  
1701
						XMoveResizeWindow(wm.dpy, c->window, mw, (i - 1) * h, screen_width - mw - 2 * border_size, h - 2 * border_size);
  
1702
					}
  
1703
					i++;
  
1704
				}
  
1705
			} else if (prop) {
  
1706
				XFree(prop);
  
1707
			}
  
1708
		}
  
1709
	}
  
1710
}
  
1711
  
  
1712
void toggle_layout(const Arg *arg) {
  
1713
	(void)arg;
  
1714
	wm.layout_modes[wm.current_desktop] = (wm.layout_modes[wm.current_desktop] == LAYOUT_TILING) ? LAYOUT_FLOATING : LAYOUT_TILING;
  
1715
	if (wm.layout_modes[wm.current_desktop] == LAYOUT_TILING) {
  
1716
		apply_tiling_layout();
  
1717
	}
  
1718
	redraw_widgets();
  
1719
	log_message(stdout, LOG_DEBUG, "Toggled layout for desktop %d to %s", wm.current_desktop, wm.layout_modes[wm.current_desktop] == LAYOUT_TILING ? "TILING" : "FLOATING");
  
1720
}
diff --git a/widgets.c b/widgets.c
...
44
	XftTextExtentsUtf8(wm.dpy, wm.font, (FcChar8 *)desktop_buf, strlen(desktop_buf), &desktop_extents);
44
	XftTextExtentsUtf8(wm.dpy, wm.font, (FcChar8 *)desktop_buf, strlen(desktop_buf), &desktop_extents);
45
	int desktop_size = (wm.font->height > desktop_extents.width ? wm.font->height : desktop_extents.width) + padding * 2;
45
	int desktop_size = (wm.font->height > desktop_extents.width ? wm.font->height : desktop_extents.width) + padding * 2;
46
  
46
  
  
47
	// Layout indicator size
  
48
	LayoutMode mode = wm.layout_modes[wm.current_desktop];
  
49
	const char *layout_buf = (mode == LAYOUT_TILING) ? "T" : "F";
  
50
	XGlyphInfo layout_extents;
  
51
	XftTextExtentsUtf8(wm.dpy, wm.font, (FcChar8 *)layout_buf, strlen(layout_buf), &layout_extents);
  
52
	int layout_size_w = (wm.font->height > layout_extents.width ? wm.font->height : layout_extents.width) + padding * 2;
  
53
  
47
	const char *buf = "MIC";
54
	const char *buf = "MIC";
48
	XGlyphInfo extents;
55
	XGlyphInfo extents;
49
	XftTextExtentsUtf8(wm.dpy, wm.font, (FcChar8 *)buf, strlen(buf), &extents);
56
	XftTextExtentsUtf8(wm.dpy, wm.font, (FcChar8 *)buf, strlen(buf), &extents);
50
  
57
  
51
	int size_w = extents.width + padding * 4;
58
	int size_w = extents.width + padding * 4;
52
	int size_h = desktop_size;
59
	int size_h = desktop_size;
53
	int x = screen_width - desktop_size - size_w - 20;
60
	int x = screen_width - desktop_size - layout_size_w - size_w - 20;
54
	int y = 10;
61
	int y = 10;
55
  
62
  
56
	XftColor *bg = wm.mic_muted ? &wm.xft_mic_muted_bg : &wm.xft_mic_active_bg;
63
	XftColor *bg = wm.mic_muted ? &wm.xft_mic_muted_bg : &wm.xft_mic_active_bg;
...
66
	XftDrawStringUtf8(wm.xft_draw, fg, wm.font, text_x, text_y, (FcChar8 *)buf, strlen(buf));
73
	XftDrawStringUtf8(wm.xft_draw, fg, wm.font, text_x, text_y, (FcChar8 *)buf, strlen(buf));
67
}
74
}
68
  
75
  
  
76
void widget_layout_indicator(void) {
  
77
	int screen_width = DisplayWidth(wm.dpy, wm.screen);
  
78
	int padding = 3;
  
79
  
  
80
	// Desktop indicator size
  
81
	char desktop_buf[8];
  
82
	snprintf(desktop_buf, sizeof(desktop_buf), "%u", wm.current_desktop);
  
83
	XGlyphInfo desktop_extents;
  
84
	XftTextExtentsUtf8(wm.dpy, wm.font, (FcChar8 *)desktop_buf, strlen(desktop_buf), &desktop_extents);
  
85
	int desktop_size = (wm.font->height > desktop_extents.width ? wm.font->height : desktop_extents.width) + padding * 2;
  
86
  
  
87
	LayoutMode mode = wm.layout_modes[wm.current_desktop];
  
88
	const char *buf = (mode == LAYOUT_TILING) ? "T" : "F";
  
89
	XGlyphInfo extents;
  
90
	XftTextExtentsUtf8(wm.dpy, wm.font, (FcChar8 *)buf, strlen(buf), &extents);
  
91
  
  
92
	int size_w = (wm.font->height > extents.width ? wm.font->height : extents.width) + padding * 2;
  
93
	int size_h = desktop_size;
  
94
	int x = screen_width - desktop_size - size_w - 15;
  
95
	int y = 10;
  
96
  
  
97
	XftColor *bg = (mode == LAYOUT_TILING) ? &wm.xft_layout_tile_bg : &wm.xft_layout_float_bg;
  
98
	XftColor *fg = (mode == LAYOUT_TILING) ? &wm.xft_layout_tile_fg : &wm.xft_layout_float_fg;
  
99
  
  
100
	// Draw the background.
  
101
	XftDrawRect(wm.xft_draw, bg, x, y, size_w, size_h);
  
102
  
  
103
	// Center the text.
  
104
	int text_x = x + (size_w - extents.width) / 2 + extents.x;
  
105
	int text_y = y + (size_h - wm.font->ascent - wm.font->descent) / 2 + wm.font->ascent;
  
106
  
  
107
	XftDrawStringUtf8(wm.xft_draw, fg, wm.font, text_x, text_y, (FcChar8 *)buf, strlen(buf));
  
108
}
  
109
  
69
void widget_datetime(void) {
110
void widget_datetime(void) {
70
	int screen_width = DisplayWidth(wm.dpy, wm.screen);
111
	int screen_width = DisplayWidth(wm.dpy, wm.screen);
71
	int padding = 3;
112
	int padding = 3;
...
77
	XftTextExtentsUtf8(wm.dpy, wm.font, (FcChar8 *)desktop_buf, strlen(desktop_buf), &desktop_extents);
118
	XftTextExtentsUtf8(wm.dpy, wm.font, (FcChar8 *)desktop_buf, strlen(desktop_buf), &desktop_extents);
78
	int desktop_size = (wm.font->height > desktop_extents.width ? wm.font->height : desktop_extents.width) + padding * 2;
119
	int desktop_size = (wm.font->height > desktop_extents.width ? wm.font->height : desktop_extents.width) + padding * 2;
79
  
120
  
  
121
	// Layout indicator size
  
122
	LayoutMode mode = wm.layout_modes[wm.current_desktop];
  
123
	const char *layout_buf = (mode == LAYOUT_TILING) ? "T" : "F";
  
124
	XGlyphInfo layout_extents;
  
125
	XftTextExtentsUtf8(wm.dpy, wm.font, (FcChar8 *)layout_buf, strlen(layout_buf), &layout_extents);
  
126
	int layout_size_w = (wm.font->height > layout_extents.width ? wm.font->height : layout_extents.width) + padding * 2;
  
127
  
80
	// Mic indicator size
128
	// Mic indicator size
81
	const char *mic_buf = "MIC";
129
	const char *mic_buf = "MIC";
82
	XGlyphInfo mic_extents;
130
	XGlyphInfo mic_extents;
83
	XftTextExtentsUtf8(wm.dpy, wm.font, (FcChar8 *)mic_buf, strlen(mic_buf), &mic_extents);
131
	XftTextExtentsUtf8(wm.dpy, wm.font, (FcChar8 *)mic_buf, strlen(mic_buf), &mic_extents);
84
	int mic_size_w = mic_extents.width + padding * 4;
132
	int mic_size_w = mic_extents.width + padding * 4;
85
  
133
  
86
	int offset_x = desktop_size + mic_size_w + 40;
134
	int offset_x = desktop_size + layout_size_w + mic_size_w + 35;
87
  
135
  
88
	char time_buf[64];
136
	char time_buf[64];
89
	time_t now = time(NULL);
137
	time_t now = time(NULL);
...
107
  
155
  
108
void redraw_widgets(void) {
156
void redraw_widgets(void) {
109
	widget_desktop_indicator();
157
	widget_desktop_indicator();
  
158
	widget_layout_indicator();
110
	widget_mic_indicator();
159
	widget_mic_indicator();
111
	widget_datetime();
160
	widget_datetime();
112
}
161
}