about summary refs log tree commit diff stats
path: root/main.c
blob: 16ebbaef7e739f16d526c9c6fc93fe26485fd07c (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
/*
 * (C)opyright MMVI Anselm R. Garbe <garbeam at gmail dot com>
 * See LICENSE file for license details.
 */

#include "dwm.h"
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/select.h>
#include <X11/cursorfont.h>
#include <X11/Xatom.h>
#include <X11/Xproto.h>

/* static */

static int (*xerrorxlib)(Display *, XErrorEvent *);
static Bool otherwm;

static void
cleanup()
{
	while(sel) {
		resize(sel, True, TopLeft);
		unmanage(sel);
	}
	XSetInputFocus(dpy, PointerRoot, RevertToPointerRoot, CurrentTime);
}

static void
scan()
{
	unsigned int i, num;
	Window *wins, d1, d2;
	XWindowAttributes wa;

	if(XQueryTree(dpy, root, &d1, &d2, &wins, &num)) {
		for(i = 0; i < num; i++) {
			if(!XGetWindowAttributes(dpy, wins[i], &wa))
				continue;
			if(wa.override_redirect || XGetTransientForHint(dpy, wins[i], &d1))
				continue;
			if(wa.map_state == IsViewable)
				manage(wins[i], &wa);
		}
	}
	if(wins)
		XFree(wins);
}

static int
win_property(Window w, Atom a, Atom t, long l, unsigned char **prop)
{
	int status, format;
	unsigned long res, extra;
	Atom real;

	status = XGetWindowProperty(dpy, w, a, 0L, l, False, t, &real, &format,
			&res, &extra, prop);

	if(status != Success || *prop == 0) {
		return 0;
	}
	if(res == 0) {
		free((void *) *prop);
	}
	return res;
}

/*
 * Startup Error handler to check if another window manager
 * is already running.
 */
static int
xerrorstart(Display *dsply, XErrorEvent *ee)
{
	otherwm = True;
	return -1;
}

/* extern */

char stext[1024];
Bool *seltag;
int screen, sx, sy, sw, sh, bx, by, bw, bh, mw;
unsigned int ntags;
Atom wmatom[WMLast], netatom[NetLast];
Bool running = True;
Bool issel = True;
Client *clients = NULL;
Client *sel = NULL;
Cursor cursor[CurLast];
Display *dpy;
DC dc = {0};
Window root, barwin;

int
getproto(Window w)
{
	int protos = 0;
	int i;
	long res;
	Atom *protocols;

	res = win_property(w, wmatom[WMProtocols], XA_ATOM, 20L,
			((unsigned char **)&protocols));
	if(res <= 0) {
		return protos;
	}
	for(i = 0; i < res; i++) {
		if(protocols[i] == wmatom[WMDelete])
			protos |= PROTODELWIN;
	}
	free((char *) protocols);
	return protos;
}

void
sendevent(Window w, Atom a, long value)
{
	XEvent e;

	e.type = ClientMessage;
	e.xclient.window = w;
	e.xclient.message_type = a;
	e.xclient.format = 32;
	e.xclient.data.l[0] = value;
	e.xclient.data.l[1] = CurrentTime;
	XSendEvent(dpy, w, False, NoEventMask, &e);
	XSync(dpy, False);
}

void
quit(Arg *arg)
{
	running = False;
}

/*
 * There's no way to check accesses to destroyed windows, thus those cases are
 * ignored (especially on UnmapNotify's).  Other types of errors call Xlibs
 * default error handler, which calls exit().
 */
int
xerror(Display *dpy, XErrorEvent *ee)
{
	if(ee->error_code == BadWindow
	|| (ee->request_code == X_SetInputFocus && ee->error_code == BadMatch)
	|| (ee->request_code == X_PolyText8 && ee->error_code == BadDrawable)
	|| (ee->request_code == X_PolyFillRectangle && ee->error_code == BadDrawable)
	|| (ee->request_code == X_PolySegment && ee->error_code == BadDrawable)
	|| (ee->request_code == X_ConfigureWindow && ee->error_code == BadMatch)
	|| (ee->request_code == X_GrabKey && ee->error_code == BadAccess))
		return 0;
	fprintf(stderr, "dwm: fatal error: request code=%d, error code=%d\n",
		ee->request_code, ee->error_code);
	return xerrorxlib(dpy, ee); /* may call exit() */
}

int
main(int argc, char *argv[])
{
	int i, xfd;
	unsigned int mask;
	fd_set rd;
	Bool readin = True;
	Window w;
	XEvent ev;
	XSetWindowAttributes wa;

	if(argc == 2 && !strncmp("-v", argv[1], 3)) {
		fputs("dwm-"VERSION", (C)opyright MMVI Anselm R. Garbe\n", stdout);
		exit(EXIT_SUCCESS);
	}
	else if(argc != 1)
		eprint("usage: dwm [-v]\n");

	dpy = XOpenDisplay(0);
	if(!dpy)
		eprint("dwm: cannot open display\n");

	xfd = ConnectionNumber(dpy);
	screen = DefaultScreen(dpy);
	root = RootWindow(dpy, screen);

	otherwm = False;
	XSetErrorHandler(xerrorstart);
	/* this causes an error if some other window manager is running */
	XSelectInput(dpy, root, SubstructureRedirectMask);
	XSync(dpy, False);

	if(otherwm)
		eprint("dwm: another window manager is already running\n");

	XSetErrorHandler(NULL);
	xerrorxlib = XSetErrorHandler(xerror);

	/* init atoms */
	wmatom[WMProtocols] = XInternAtom(dpy, "WM_PROTOCOLS", False);
	wmatom[WMDelete] = XInternAtom(dpy, "WM_DELETE_WINDOW", False);
	netatom[NetSupported] = XInternAtom(dpy, "_NET_SUPPORTED", False);
	netatom[NetWMName] = XInternAtom(dpy, "_NET_WM_NAME", False);
	XChangeProperty(dpy, root, netatom[NetSupported], XA_ATOM, 32,
			PropModeReplace, (unsigned char *) netatom, NetLast);

	/* init cursors */
	cursor[CurNormal] = XCreateFontCursor(dpy, XC_left_ptr);
	cursor[CurResize] = XCreateFontCursor(dpy, XC_sizing);
	cursor[CurMove] = XCreateFontCursor(dpy, XC_fleur);

	grabkeys();
	initrregs();

	for(ntags = 0; tags[ntags]; ntags++);
	seltag = emallocz(sizeof(Bool) * ntags);
	seltag[DEFTAG] = True;

	/* style */
	dc.bg = getcolor(BGCOLOR);
	dc.fg = getcolor(FGCOLOR);
	dc.border = getcolor(BORDERCOLOR);
	setfont(FONT);

	sx = sy = 0;
	sw = DisplayWidth(dpy, screen);
	sh = DisplayHeight(dpy, screen);
	mw = (sw * MASTERW) / 100;

	wa.override_redirect = 1;
	wa.background_pixmap = ParentRelative;
	wa.event_mask = ButtonPressMask | ExposureMask;

	bx = by = 0;
	bw = sw;
	dc.h = bh = dc.font.height + 4;
	barwin = XCreateWindow(dpy, root, bx, by, bw, bh, 0, DefaultDepth(dpy, screen),
			CopyFromParent, DefaultVisual(dpy, screen),
			CWOverrideRedirect | CWBackPixmap | CWEventMask, &wa);
	XDefineCursor(dpy, barwin, cursor[CurNormal]);
	XMapRaised(dpy, barwin);

	dc.drawable = XCreatePixmap(dpy, root, sw, bh, DefaultDepth(dpy, screen));
	dc.gc = XCreateGC(dpy, root, 0, 0);

	strcpy(stext, "dwm-"VERSION);
	drawstatus();

	issel = XQueryPointer(dpy, root, &w, &w, &i, &i, &i, &i, &mask);

	wa.event_mask = SubstructureRedirectMask | EnterWindowMask | LeaveWindowMask;
	wa.cursor = cursor[CurNormal];
	XChangeWindowAttributes(dpy, root, CWEventMask | CWCursor, &wa);

	scan();

	/* main event loop, also reads status text from stdin */
	XSync(dpy, False);
	while(running) {
		FD_ZERO(&rd);
		if(readin)
			FD_SET(STDIN_FILENO, &rd);
		FD_SET(xfd, &rd);

		i = select(xfd + 1, &rd, NULL, NULL, NULL);
		if(i == -1 && errno == EINTR)
			continue;
		if(i < 0)
			eprint("select failed\n");
		else if(i > 0) {
			if(FD_ISSET(xfd, &rd)) {
				while(XPending(dpy)) {
					XNextEvent(dpy, &ev);
					if(handler[ev.type])
						(handler[ev.type])(&ev); /* call handler */
				}
			}
			if(readin && FD_ISSET(STDIN_FILENO, &rd)) {
				readin = NULL != fgets(stext, sizeof(stext), stdin);
				if(readin)
					stext[strlen(stext) - 1] = 0;
				else 
					strcpy(stext, "broken pipe");
				drawstatus();
			}
		}
	}

	cleanup();
	XCloseDisplay(dpy);

	return 0;
}
lass="w"> right break-if at-right? column <- add column, 1 *screen <- put *screen, cursor-column:offset, column } ] scenario print-character-at-top-left [ local-scope fake-screen:&:screen <- new-fake-screen 3/width, 2/height run [ a:char <- copy 97/a fake-screen <- print fake-screen, a:char cell:&:@:screen-cell <- get *fake-screen, data:offset 1:@:screen-cell/raw <- copy *cell ] memory-should-contain [ 1 <- 6 # width*height 2 <- 97 # 'a' 3 <- 7 # white # rest of screen is empty 4 <- 0 ] ] scenario print-character-at-fractional-coordinate [ local-scope fake-screen:&:screen <- new-fake-screen 3/width, 2/height a:char <- copy 97/a run [ move-cursor fake-screen, 0.5, 0 fake-screen <- print fake-screen, a:char cell:&:@:screen-cell <- get *fake-screen, data:offset 1:@:screen-cell/raw <- copy *cell ] memory-should-contain [ 1 <- 6 # width*height 2 <- 97 # 'a' 3 <- 7 # white # rest of screen is empty 4 <- 0 ] ] scenario print-character-in-color [ local-scope fake-screen:&:screen <- new-fake-screen 3/width, 2/height run [ a:char <- copy 97/a fake-screen <- print fake-screen, a:char, 1/red cell:&:@:screen-cell <- get *fake-screen, data:offset 1:@:screen-cell/raw <- copy *cell ] memory-should-contain [ 1 <- 6 # width*height 2 <- 97 # 'a' 3 <- 1 # red # rest of screen is empty 4 <- 0 ] ] scenario print-backspace-character [ local-scope fake-screen:&:screen <- new-fake-screen 3/width, 2/height a:char <- copy 97/a fake-screen <- print fake-screen, a run [ backspace:char <- copy 8/backspace fake-screen <- print fake-screen, backspace 10:num/raw <- get *fake-screen, cursor-column:offset cell:&:@:screen-cell <- get *fake-screen, data:offset 11:@:screen-cell/raw <- copy *cell ] memory-should-contain [ 10 <- 0 # cursor column 11 <- 6 # width*height 12 <- 32 # space, not 'a' 13 <- 7 # white # rest of screen is empty 14 <- 0 ] ] scenario print-extra-backspace-character [ local-scope fake-screen:&:screen <- new-fake-screen 3/width, 2/height a:char <- copy 97/a fake-screen <- print fake-screen, a run [ backspace:char <- copy 8/backspace fake-screen <- print fake-screen, backspace fake-screen <- print fake-screen, backspace # cursor already at left margin 1:num/raw <- get *fake-screen, cursor-column:offset cell:&:@:screen-cell <- get *fake-screen, data:offset 3:@:screen-cell/raw <- copy *cell ] memory-should-contain [ 1 <- 0 # cursor column 3 <- 6 # width*height 4 <- 32 # space, not 'a' 5 <- 7 # white # rest of screen is empty 6 <- 0 ] ] scenario print-character-at-right-margin [ # fill top row of screen with text local-scope fake-screen:&:screen <- new-fake-screen 2/width, 2/height a:char <- copy 97/a fake-screen <- print fake-screen, a b:char <- copy 98/b fake-screen <- print fake-screen, b run [ # cursor now at right margin c:char <- copy 99/c fake-screen <- print fake-screen, c 10:num/raw <- get *fake-screen, cursor-column:offset cell:&:@:screen-cell <- get *fake-screen, data:offset 11:@:screen-cell/raw <- copy *cell ] memory-should-contain [ 10 <- 1 # cursor column 11 <- 4 # width*height 12 <- 97 # 'a' 13 <- 7 # white 14 <- 99 # 'c' over 'b' 15 <- 7 # white # rest of screen is empty 16 <- 0 ] ] scenario print-newline-character [ local-scope fake-screen:&:screen <- new-fake-screen 3/width, 2/height a:char <- copy 97/a fake-screen <- print fake-screen, a run [ newline:char <- copy 10/newline fake-screen <- print fake-screen, newline 10:num/raw <- get *fake-screen, cursor-row:offset 11:num/raw <- get *fake-screen, cursor-column:offset cell:&:@:screen-cell <- get *fake-screen, data:offset 12:@:screen-cell/raw <- copy *cell ] memory-should-contain [ 10 <- 1 # cursor row 11 <- 0 # cursor column 12 <- 6 # width*height 13 <- 97 # 'a' 14 <- 7 # white # rest of screen is empty 15 <- 0 ] ] scenario print-newline-at-bottom-line [ local-scope fake-screen:&:screen <- new-fake-screen 3/width, 2/height newline:char <- copy 10/newline fake-screen <- print fake-screen, newline fake-screen <- print fake-screen, newline run [ # cursor now at bottom of screen fake-screen <- print fake-screen, newline 10:num/raw <- get *fake-screen, cursor-row:offset 11:num/raw <- get *fake-screen, cursor-column:offset ] # doesn't move further down memory-should-contain [ 10 <- 1 # cursor row 11 <- 0 # cursor column ] ] scenario print-character-at-bottom-right [ local-scope fake-screen:&:screen <- new-fake-screen 2/width, 2/height newline:char <- copy 10/newline fake-screen <- print fake-screen, newline a:char <- copy 97/a fake-screen <- print fake-screen, a b:char <- copy 98/b fake-screen <- print fake-screen, b c:char <- copy 99/c fake-screen <- print fake-screen, c fake-screen <- print fake-screen, newline run [ # cursor now at bottom right d:char <- copy 100/d fake-screen <- print fake-screen, d 10:num/raw <- get *fake-screen, cursor-row:offset 11:num/raw <- get *fake-screen, cursor-column:offset cell:&:@:screen-cell <- get *fake-screen, data:offset 20:@:screen-cell/raw <- copy *cell ] memory-should-contain [ 10 <- 1 # cursor row 11 <- 1 # cursor column 20 <- 4 # width*height 21 <- 0 # unused 22 <- 7 # white 23 <- 0 # unused 24 <- 7 # white 25 <- 97 # 'a' 26 <- 7 # white 27 <- 100 # 'd' over 'b' and 'c' and newline 28 <- 7 # white # rest of screen is empty 29 <- 0 ] ] def clear-line screen:&:screen -> screen:&:screen [ local-scope load-ingredients space:char <- copy 0/nul { break-if screen # real screen clear-line-on-display return } # fake screen width:num <- get *screen, num-columns:offset column:num <- get *screen, cursor-column:offset original-column:num <- copy column # space over the entire line { right:num <- subtract width, 1 done?:bool <- greater-or-equal column, right break-if done? print screen, space column <- add column, 1 loop } # now back to where the cursor was *screen <- put *screen, cursor-column:offset, original-column ] def clear-line-until screen:&:screen, right:num/inclusive -> screen:&:screen [ local-scope load-ingredients _, column:num <- cursor-position screen space:char <- copy 32/space bg-color:num, bg-color-found?:bool <- next-ingredient { # default bg-color to black break-if bg-color-found? bg-color <- copy 0/black } { done?:bool <- greater-than column, right break-if done? screen <- print screen, space, 7/white, bg-color # foreground color is mostly unused except if the cursor shows up at this cell column <- add column, 1 loop } ] def cursor-position screen:&:screen -> row:num, column:num [ local-scope load-ingredients { break-if screen # real screen row, column <- cursor-position-on-display return } # fake screen row:num <- get *screen, cursor-row:offset column:num <- get *screen, cursor-column:offset ] def move-cursor screen:&:screen, new-row:num, new-column:num -> screen:&:screen [ local-scope load-ingredients { break-if screen # real screen move-cursor-on-display new-row, new-column return } # fake screen *screen <- put *screen, cursor-row:offset, new-row *screen <- put *screen, cursor-column:offset, new-column ] scenario clear-line-erases-printed-characters [ local-scope fake-screen:&:screen <- new-fake-screen 3/width, 2/height # print a character a:char <- copy 97/a fake-screen <- print fake-screen, a # move cursor to start of line fake-screen <- move-cursor fake-screen, 0/row, 0/column run [ fake-screen <- clear-line fake-screen cell:&:@:screen-cell <- get *fake-screen, data:offset 10:@:screen-cell/raw <- copy *cell ] # screen should be blank memory-should-contain [ 10 <- 6 # width*height 11 <- 0 12 <- 7 13 <- 0 14 <- 7 15 <- 0 16 <- 7 17 <- 0 18 <- 7 19 <- 0 20 <- 7 21 <- 0 22 <- 7 ] ] def cursor-down screen:&:screen -> screen:&:screen [ local-scope load-ingredients { break-if screen # real screen move-cursor-down-on-display return } # fake screen height:num <- get *screen, num-rows:offset row:num <- get *screen, cursor-row:offset max:num <- subtract height, 1 at-bottom?:bool <- greater-or-equal row, max return-if at-bottom? row <- add row, 1 *screen <- put *screen, cursor-row:offset, row ] def cursor-up screen:&:screen -> screen:&:screen [ local-scope load-ingredients { break-if screen # real screen move-cursor-up-on-display return } # fake screen row:num <- get *screen, cursor-row:offset at-top?:bool <- lesser-or-equal row, 0 return-if at-top? row <- subtract row, 1 *screen <- put *screen, cursor-row:offset, row ] def cursor-right screen:&:screen -> screen:&:screen [ local-scope load-ingredients { break-if screen # real screen move-cursor-right-on-display return } # fake screen width:num <- get *screen, num-columns:offset column:num <- get *screen, cursor-column:offset max:num <- subtract width, 1 at-bottom?:bool <- greater-or-equal column, max return-if at-bottom? column <- add column, 1 *screen <- put *screen, cursor-column:offset, column ] def cursor-left screen:&:screen -> screen:&:screen [ local-scope load-ingredients { break-if screen # real screen move-cursor-left-on-display return } # fake screen column:num <- get *screen, cursor-column:offset at-top?:bool <- lesser-or-equal column, 0 return-if at-top? column <- subtract column, 1 *screen <- put *screen, cursor-column:offset, column ] def cursor-to-start-of-line screen:&:screen -> screen:&:screen [ local-scope load-ingredients row:num <- cursor-position screen column:num <- copy 0 screen <- move-cursor screen, row, column ] def cursor-to-next-line screen:&:screen -> screen:&:screen [ local-scope load-ingredients screen <- cursor-down screen screen <- cursor-to-start-of-line screen ] def move-cursor-to-column screen:&:screen, column:num -> screen:&:screen [ local-scope load-ingredients row:num, _ <- cursor-position screen move-cursor screen, row, column ] def screen-width screen:&:screen -> width:num [ local-scope load-ingredients { break-unless screen # fake screen width <- get *screen, num-columns:offset return } # real screen width <- display-width ] def screen-height screen:&:screen -> height:num [ local-scope load-ingredients { break-unless screen # fake screen height <- get *screen, num-rows:offset return } # real screen height <- display-height ] def hide-cursor screen:&:screen -> screen:&:screen [ local-scope load-ingredients return-if screen # fake screen; do nothing # real screen hide-cursor-on-display ] def show-cursor screen:&:screen -> screen:&:screen [ local-scope load-ingredients return-if screen # fake screen; do nothing # real screen show-cursor-on-display ] def hide-screen screen:&:screen -> screen:&:screen [ local-scope load-ingredients return-if screen # fake screen; do nothing # real screen hide-display ] def show-screen screen:&:screen -> screen:&:screen [ local-scope load-ingredients return-if screen # fake screen; do nothing # real screen show-display ] def print screen:&:screen, s:text -> screen:&:screen [ local-scope load-ingredients color:num, color-found?:bool <- next-ingredient { # default color to white break-if color-found? color <- copy 7/white } bg-color:num, bg-color-found?:bool <- next-ingredient { # default bg-color to black break-if bg-color-found? bg-color <- copy 0/black } len:num <- length *s i:num <- copy 0 { done?:bool <- greater-or-equal i, len break-if done? c:char <- index *s, i print screen, c, color, bg-color i <- add i, 1 loop } ] scenario print-text-stops-at-right-margin [ local-scope fake-screen:&:screen <- new-fake-screen 3/width, 2/height run [ fake-screen <- print fake-screen, [abcd] cell:&:@:screen-cell <- get *fake-screen, data:offset 10:@:screen-cell/raw <- copy *cell ] memory-should-contain [ 10 <- 6 # width*height 11 <- 97 # 'a' 12 <- 7 # white 13 <- 98 # 'b' 14 <- 7 # white 15 <- 100 # 'd' overwrites 'c' 16 <- 7 # white # rest of screen is empty 17 <- 0 ] ] def print screen:&:screen, n:num -> screen:&:screen [ local-scope load-ingredients color:num, color-found?:bool <- next-ingredient { # default color to white break-if color-found? color <- copy 7/white } bg-color:num, bg-color-found?:bool <- next-ingredient { # default bg-color to black break-if bg-color-found? bg-color <- copy 0/black } # todo: other bases besides decimal s:text <- to-text n screen <- print screen, s, color, bg-color ] def print screen:&:screen, n:bool -> screen:&:screen [ local-scope load-ingredients color:num, color-found?:bool <- next-ingredient { # default color to white break-if color-found? color <- copy 7/white } bg-color:num, bg-color-found?:bool <- next-ingredient { # default bg-color to black break-if bg-color-found? bg-color <- copy 0/black } n2:num <- copy n screen <- print screen, n2, color, bg-color ] def print screen:&:screen, n:&:_elem -> screen:&:screen [ local-scope load-ingredients color:num, color-found?:bool <- next-ingredient { # default color to white break-if color-found? color <- copy 7/white } bg-color:num, bg-color-found?:bool <- next-ingredient { # default bg-color to black break-if bg-color-found? bg-color <- copy 0/black } n2:num <- copy n screen <- print screen, n2, color, bg-color ]