about summary refs log tree commit diff stats
path: root/dwm.1
blob: 31a8d0b0028fdc21593bde03e5ba49780471eb9d (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
.TH DWM 1 dwm-VERSION
.SH NAME
dwm \- dynamic window manager
.SH SYNOPSIS
.B dwm
.RB [ \-v ]
.SH DESCRIPTION
dwm is a dynamic window manager for X. It manages windows in tiling and
floating modes. Either mode can be applied dynamically, optimizing the
environment for the application in use and the task performed.
.P
In tiling mode windows are managed in a master and stacking column. The master
column contains the window which currently needs most attention, whereas the
stacking column contains all other windows. In floating mode windows can be
resized and moved freely. Dialog windows are always managed floating,
regardless of the mode selected.
.P
Windows are grouped by tags. Each window can be tagged with one or multiple
tags. Selecting certain tags displays all windows with that tag.
.P
dwm contains a small status bar which displays all available tags, the mode,
the title of the focused window, and the text read from standard input. The
selected tags are highlighted with a different color, the tags of the focused
window are highlighted with a small point.
.P
dwm draws a 1-pixel border around windows to indicate the focus state.
Unfocused windows contain a small bar in front of them displaying their title.
.SH OPTIONS
.TP
.B \-v
prints version information to standard output, then exits.
.SH USAGE
.SS Status bar
.TP
.B Standard input
is read and displayed in the status text area.
.TP
.B Button1
click on a tag label to display all windows with that tag, click on the mode
label toggles between tiled and floating mode.
.TP
.B Button3
click on a tag label adds/removes all windows with that tag to/from the view.
.TP
.B Mod1-Button1
click on a tag label applies that tag to the focused window.
.TP
.B Mod1-Button3
click on a tag label adds/removes that tag to/from the focused window.
.SS Keyboard commands
.TP
.B Mod1-Shift-Return
Start
.BR xterm (1).
.TP
.B Mod1-Tab
Focus next window.
.TP
.B Mod1-Shift-Tab
Focus previous window.
.TP
.B Mod1-Return
Zoom current window to the master column (tiling mode only).
.TP
.B Mod1-m
Maximize current window.
.TP
.B Mod1-g
Grow current column (tiling mode only).
.TP
.B Mod1-s
Shrink current column (tiling mode only).
.TP
.B Mod1-Shift-[1..n]
Apply
.RB nth
tag to current window.
.TP
.B Mod1-Control-Shift-[1..n]
Add/remove
.B nth
tag to/from current window.
.TP
.B Mod1-Shift-c
Close focused window.
.TP
.B Mod1-space
Toggle between tiled and floating mode (affects all windows).
.TP
.B Mod1-[1..n]
View all windows with
.BR nth
tag.
.TP
.B Mod1-0
View all windows with any tag.
.TP
.B Mod1-Control-[1..n]
Add/remove all windows with
.BR nth
tag to/from the view.
.TP
.B Mod1-Shift-q
Quit dwm.
.SS Mouse commands
.TP
.B Mod1-Button1
Move current window while dragging (floating mode only).
.TP
.B Mod1-Button2
Zoom current window to the master column (tiling mode only).
.TP
.B Mod1-Button3
Resize current window while dragging (floating mode only).
.SH CUSTOMIZATION
dwm is customized by creating a custom config.h and (re)compiling the source
code. This keeps it fast, secure and simple.
.SH CAVEATS
The status bar may display
.BR "broken pipe"
when dwm has been started by
.BR xdm (1),
because it closes standard output before executing dwm.
.SH SEE ALSO
.BR dmenu (1)
8 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498
/*
 * (C)opyright MMVI Anselm R. Garbe <garbeam at gmail dot com>
 * (C)opyright MMVI Sander van Dijk <a dot h dot vandijk at gmail dot com>
 * See LICENSE file for license details.
 */

#include <ctype.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <time.h>
#include <unistd.h>
#include <X11/Xlib.h>
#include <X11/cursorfont.h>
#include <X11/Xutil.h>
#include <X11/keysym.h>

#include <blitz.h>
#include <cext.h>

typedef struct Item Item;

struct Item {
	Item *next;		/* traverses all items */
	Item *left, *right;	/* traverses items matching current search pattern */
	char *text;
};

static char *title = nil;
static Bool done = False;
static int ret = 0;
static char text[4096];
static BlitzColor selcolor;
static BlitzColor normcolor;
static Window win;
static XRectangle mrect;
static Item *allitem = nil;	/* first of all items */
static Item *item = nil;	/* first of pattern matching items */
static Item *sel = nil;
static Item *nextoff = nil;
static Item *prevoff = nil;
static Item *curroff = nil;
static int nitem = 0;
static unsigned int cmdw = 0;
static unsigned int twidth = 0;
static unsigned int cwidth = 0;
static Blitz blz = {0};
static BlitzBrush brush = {0};
static const int seek = 30;		/* 30px */

static void draw_menu(void);
static void handle_kpress(XKeyEvent * e);

static char version[] = "wmiimenu - " VERSION ", (C)opyright MMIV-MMVI Anselm R. Garbe\n";

static void
usage()
{
	fprintf(stderr, "%s", "usage: wmiimenu [-v] [-t <title>]\n");
	exit(1);
}

static void
update_offsets()
{
	unsigned int tw, w = cmdw + 2 * seek;

	if(!curroff)
		return;

	for(nextoff = curroff; nextoff; nextoff=nextoff->right) {
		tw = blitz_textwidth(brush.font, nextoff->text);
		if(tw > mrect.width / 3)
			tw = mrect.width / 3;
		w += tw + mrect.height;
		if(w > mrect.width)
			break;
	}

	w = cmdw + 2 * seek;
	for(prevoff = curroff; prevoff && prevoff->left; prevoff=prevoff->left) {
		tw = blitz_textwidth(brush.font, prevoff->left->text);
		if(tw > mrect.width / 3)
			tw = mrect.width / 3;
		w += tw + mrect.height;
		if(w > mrect.width)
			break;
	}
}

static void
update_items(char *pattern)
{
	unsigned int plen = strlen(pattern);
	Item *i, *j;

	if(!pattern)
		return;

	if(!title || *pattern)
		cmdw = cwidth;
	else
		cmdw = twidth;

	item = j = nil;
	nitem = 0;

	for(i = allitem; i; i=i->next)
		if(!plen || !strncmp(pattern, i->text, plen)) {
			if(!j)
				item = i;
			else
				j->right = i;
			i->left = j;
			i->right = nil;
			j = i;
			nitem++;
		}
	for(i = allitem; i; i=i->next)
		if(plen && strncmp(pattern, i->text, plen)
				&& strstr(i->text, pattern)) {
			if(!j)
				item = i;
			else
				j->right = i;
			i->left = j;
			i->right = nil;
			j = i;
			nitem++;
		}

	curroff = prevoff = nextoff = sel = item;

	update_offsets();
}

/* creates brush structs for brush mode drawing */
static void
draw_menu()
{
	unsigned int offx = 0;

	Item *i;

	brush.align = WEST;

	brush.rect = mrect;
	brush.rect.x = 0;
	brush.rect.y = 0;
	brush.color = normcolor;
	brush.border = False;
	blitz_draw_tile(&brush);

	/* print command */
	if(!title || text[0]) {
		brush.color = normcolor;
		cmdw = cwidth;
		if(cmdw && item)
			brush.rect.width = cmdw;
		blitz_draw_label(&brush, text);
	}
	else {
		cmdw = twidth;
		brush.color = selcolor;
		brush.rect.width = cmdw;
		blitz_draw_label(&brush, title);
	}
	offx += brush.rect.width;

	brush.align = CENTER;
	if(curroff) {
		brush.color = normcolor;
		brush.rect.x = offx;
		brush.rect.width = seek;
		offx += brush.rect.width;
		blitz_draw_label(&brush, (curroff && curroff->left) ? "<" : nil);

		/* determine maximum items */
		for(i = curroff; i != nextoff; i=i->right) {
			brush.color = normcolor;
			brush.border = False;
			brush.rect.x = offx;
			brush.rect.width = blitz_textwidth(brush.font, i->text);
			if(brush.rect.width > mrect.width / 3)
				brush.rect.width = mrect.width / 3;
			brush.rect.width += mrect.height;
			if(sel == i) {
				brush.color = selcolor;
				brush.border = True;
			}
			blitz_draw_label(&brush, i->text);
			offx += brush.rect.width;
		}

		brush.color = normcolor;
		brush.border = False;
		brush.rect.x = mrect.width - seek;
		brush.rect.width = seek;
		blitz_draw_label(&brush, nextoff ? ">" : nil);
	}
	XCopyArea(blz.dpy, brush.drawable, win, brush.gc, 0, 0, mrect.width,
			mrect.height, 0, 0);
	XSync(blz.dpy, False);
}

static void
handle_kpress(XKeyEvent * e)
{
	KeySym ksym;
	char buf[32];
	int num, prev_nitem;
	unsigned int i, len = strlen(text);

	buf[0] = 0;
	num = XLookupString(e, buf, sizeof(buf), &ksym, 0);

	if(IsFunctionKey(ksym) || IsKeypadKey(ksym)
			|| IsMiscFunctionKey(ksym) || IsPFKey(ksym)
			|| IsPrivateKeypadKey(ksym))
		return;

	/* first check if a control mask is omitted */
	if(e->state & ControlMask) {
		switch (ksym) {
		case XK_H:
		case XK_h:
			ksym = XK_BackSpace;
			break;
		case XK_I:
		case XK_i:
			ksym = XK_Tab;
			break;
		case XK_J:
		case XK_j:
			ksym = XK_Return;
			break;
		case XK_N:
		case XK_n:
			ksym = XK_Right;
			break;
		case XK_P:
		case XK_p:
			ksym = XK_Left;
			break;
		case XK_U:
		case XK_u:
			text[0] = 0;
			update_items(text);
			draw_menu();
			return;
			break;
		case XK_bracketleft:
			ksym = XK_Escape;
			break;
		default:	/* ignore other control sequences */
			return;
			break;
		}
	}
	switch (ksym) {
	case XK_Left:
		if(!(sel && sel->left))
			return;
		sel=sel->left;
		if(sel->right == curroff) {
			curroff = prevoff;
			update_offsets();
		}
		break;
	case XK_Tab:
		if(!sel)
			return;
		cext_strlcpy(text, sel->text, sizeof(text));
		update_items(text);
		break;
	case XK_Right:
		if(!(sel && sel->right))
			return;
		sel=sel->right;
		if(sel == nextoff) {
			curroff = nextoff;
			update_offsets();
		}
		break;
	case XK_Return:
		if(e->state & ShiftMask) {
			if(text)
				fprintf(stdout, "%s", text);
		}
		else if(sel)
			fprintf(stdout, "%s", sel->text);
		else if(text)
			fprintf(stdout, "%s", text);
		fflush(stdout);
		done = True;
		break;
	case XK_Escape:
		ret = 1;
		done = True;
		break;
	case XK_BackSpace:
		if((i = len)) {
			prev_nitem = nitem;
			do {
				text[--i] = 0;
				update_items(text);
			} while(i && nitem && prev_nitem == nitem);
			update_items(text);
		}
		break;
	default:
		if((num == 1) && !iscntrl((int) buf[0])) {
			buf[num] = 0;
			if(len > 0)
				cext_strlcat(text, buf, sizeof(text));
			else
				cext_strlcpy(text, buf, sizeof(text));
			update_items(text);
		}
	}
	draw_menu();
}

static char *
read_allitems()
{
	static char *maxname = nil;
	char *p, buf[1024];
	unsigned int len = 0, max = 0;
	Item *i, *new;

	i = nil;
	while(fgets(buf, sizeof(buf), stdin)) {
		len = strlen(buf);
		if (buf[len - 1] == '\n')
			buf[len - 1] = 0;
		p = cext_estrdup(buf);
		if(max < len) {
			maxname = p;
			max = len;
		}

		new = cext_emalloc(sizeof(Item));
		new->next = new->left = new->right = nil;
		new->text = p;
		if(!i)
			allitem = new;
		else 
			i->next = new;
		i = new;
	}

	return maxname;
}

int
main(int argc, char *argv[])
{
	int i;
	XSetWindowAttributes wa;
	char *maxname, *p;
	BlitzFont font = {0};
	GC gc;
	Drawable pmap;
	XEvent ev;

	/* command line args */
	for(i = 1; i < argc; i++) {
		if (argv[i][0] == '-')
			switch (argv[i][1]) {
			case 'v':
				fprintf(stdout, "%s", version);
				exit(0);
				break;
			case 't':
				if(++i < argc)
					title = argv[i];
				else
					usage();
				break;
			default:
				usage();
				break;
			}
		else
			usage();
	}

	blz.dpy = XOpenDisplay(0);
	if(!blz.dpy) {
		fprintf(stderr, "%s", "wmiimenu: cannot open dpy\n");
		exit(1);
	}
	blz.screen = DefaultScreen(blz.dpy);
	blz.root = RootWindow(blz.dpy, blz.screen);

	maxname = read_allitems();

	/* grab as early as possible, but after reading all items!!! */
	while(XGrabKeyboard
			(blz.dpy, blz.root, True, GrabModeAsync,
			 GrabModeAsync, CurrentTime) != GrabSuccess)
		usleep(1000);

	font.fontstr = getenv("WMII_FONT");
	if (!font.fontstr)
		font.fontstr = cext_estrdup(BLITZ_FONT);
	blitz_loadfont(&blz, &font);

	if((p = getenv("WMII_NORMCOLORS")))
		cext_strlcpy(normcolor.colstr, p, sizeof(normcolor.colstr));
	if(strlen(normcolor.colstr) != 23)
		cext_strlcpy(normcolor.colstr, BLITZ_NORMCOLORS, sizeof(normcolor.colstr));
	blitz_loadcolor(&blz, &normcolor);

	if((p = getenv("WMII_SELCOLORS")))
		cext_strlcpy(selcolor.colstr, p, sizeof(selcolor.colstr));
	if(strlen(selcolor.colstr) != 23)
		cext_strlcpy(selcolor.colstr, BLITZ_SELCOLORS, sizeof(selcolor.colstr));
	blitz_loadcolor(&blz, &selcolor);

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

	mrect.width = DisplayWidth(blz.dpy, blz.screen);
	mrect.height = font.ascent + font.descent + 4;
	mrect.y = DisplayHeight(blz.dpy, blz.screen) - mrect.height;
	mrect.x = 0;

	win = XCreateWindow(blz.dpy, blz.root, mrect.x, mrect.y,
			mrect.width, mrect.height, 0, DefaultDepth(blz.dpy, blz.screen),
			CopyFromParent, DefaultVisual(blz.dpy, blz.screen),
			CWOverrideRedirect | CWBackPixmap | CWEventMask, &wa);
	XDefineCursor(blz.dpy, win, XCreateFontCursor(blz.dpy, XC_xterm));
	XSync(blz.dpy, False);

	/* pixmap */
	gc = XCreateGC(blz.dpy, win, 0, 0);
	pmap = XCreatePixmap(blz.dpy, win, mrect.width, mrect.height,
			DefaultDepth(blz.dpy, blz.screen));

	XSync(blz.dpy, False);

	brush.blitz = &blz;
	brush.color = normcolor;
	brush.drawable = pmap;
	brush.gc = gc;
	brush.font = &font;

	if(maxname)
		cwidth = blitz_textwidth(brush.font, maxname) + mrect.height;
	if(cwidth > mrect.width / 3)
		cwidth = mrect.width / 3;

	if(title) {
		twidth = blitz_textwidth(brush.font, title) + mrect.height;
		if(twidth > mrect.width / 3)
			twidth = mrect.width / 3;
	}

	cmdw = title ? twidth : cwidth;

	text[0] = 0;
	update_items(text);
	XMapRaised(blz.dpy, win);
	draw_menu();
	XSync(blz.dpy, False);

	/* main event loop */
	while(!XNextEvent(blz.dpy, &ev)) {
		switch (ev.type) {
			case KeyPress:
				handle_kpress(&ev.xkey);
				break;
			case Expose:
				if(ev.xexpose.count == 0) {
					draw_menu();
				}
				break;
			default:
				break;
		}
		if(done)
			break;
	}

	XUngrabKeyboard(blz.dpy, CurrentTime);
	XFreePixmap(blz.dpy, pmap);
	XFreeGC(blz.dpy, gc);
	XDestroyWindow(blz.dpy, win);
	XCloseDisplay(blz.dpy);

	return ret;
}