aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorphilw <dscr@duck.com>2025-04-15 01:41:05 +0200
committerphilw <dscr@duck.com>2025-04-15 01:41:05 +0200
commit8146021a2ea4130cfb0d2bf846b451ec538b1bb6 (patch)
tree3a946ef5ee4769bf4eae89b4e77858cc67b8060b /src
parent515b7f3b4f048b29325e3e38f0f4a2ef898e8daa (diff)
downloaddwm-8146021a2ea4130cfb0d2bf846b451ec538b1bb6.tar.gz
dwm-8146021a2ea4130cfb0d2bf846b451ec538b1bb6.zip
Refactor the project
Refactored core logic to improve readability and maintainability. Documented the 'transient.c' file to provide clear explanations of its purpose and functions. Updated the 'util.c' file with necessary comments and improvements to existing code. Documented the 'util.h' header file to clarify function prototypes and usage. This update should improve the overall code quality and make it easier for future development. Signed-off-by: philw <dscr@duck.com>
Diffstat (limited to 'src')
-rw-r--r--src/config/config.def.h129
-rw-r--r--src/config/config.h129
-rw-r--r--src/config/config.mk40
-rw-r--r--src/core/dwm.c2735
-rw-r--r--src/drw/drw.c425
-rw-r--r--src/drw/drw.h64
-rw-r--r--src/transient/transient.c87
-rw-r--r--src/util/util.c62
-rw-r--r--src/util/util.h59
9 files changed, 3730 insertions, 0 deletions
diff --git a/src/config/config.def.h b/src/config/config.def.h
new file mode 100644
index 0000000..e6d32c5
--- /dev/null
+++ b/src/config/config.def.h
@@ -0,0 +1,129 @@
1// #include <cstddef>
2static const unsigned int borderpx = 0;
3static const unsigned int snap = 32;
4static const unsigned int systraypinning = 0;
5static const unsigned int systrayonleft = 0;
6static const unsigned int systrayspacing = 2;
7static const unsigned int gappih = 5;
8static const unsigned int gappiv = 5;
9static const unsigned int gappoh = 5;
10static const unsigned int gappov = 5;
11
12static const int showsystray = 1;
13static const int systraypinningfailfirst = 1;
14static const int smartgaps = 0;
15static const int swallowfloating = 1;
16static const int showbar = 0;
17static const int topbar = 1;
18
19static const char *fonts[] = {"monospace:size=10"};
20static const char dmenufont[] = "monospace:size=10";
21static const char col_gray1[] = "#222222";
22static const char col_gray2[] = "#444444";
23static const char col_gray3[] = "#bbbbbb";
24static const char col_gray4[] = "#eeeeee";
25static const char col_cyan[] = "#005577";
26static const char norm_fg[] = "#383a42";
27static const char norm_bg[] = "#e1e3ea";
28static const char norm_border[] = "#3f3f40";
29
30static const char sel_fg[] = "#383a42";
31static const char sel_bg[] = "#e1e3ea";
32static const char sel_border[] = "#225588";
33
34static const char urg_fg[] = "#225588";
35static const char urg_bg[] = "#e1e3ea";
36static const char urg_border[] = "#000c18";
37
38static const char *colors[][3] = {
39 [SchemeNorm] = {norm_fg, norm_bg, norm_border}, // unfocused wins
40 [SchemeSel] = {sel_fg, sel_bg, sel_border}, // the focused win
41};
42
43/* tagging */
44static const char *tags[] = {"1", "2", "3", "4", "5", "6", "7", "8", "9"};
45
46static const Rule rules[] = {
47 /* class instance title tags mask isfloating isterminal
48 noswallow monitor */
49 {"St", NULL, NULL, 0, 0, 1, 0, -1},
50 {NULL, NULL, "Event Tester", 0, 0, 0, 1, -1}, /* xev */
51};
52
53static const float mfact = 0.50;
54static const int nmaster = 1;
55static const int resizehints = 0;
56static const int lockfullscreen = 1;
57
58static const Layout layouts[] = {
59 /* symbol arrange function */
60 {"[]=", tile}, /* first entry is default */
61 {"><>", NULL}, /* no layout function means floating behavior */
62 // {"[M]", monocle},
63};
64
65/* key definitions */
66#define MODKEY Mod4Mask
67#define TAGKEYS(KEY, TAG) \
68 {MODKEY, KEY, view, {.ui = 1 << TAG}}, \
69 {MODKEY | ControlMask, KEY, toggleview, {.ui = 1 << TAG}}, \
70 {MODKEY | ShiftMask, KEY, tag, {.ui = 1 << TAG}}, \
71 {MODKEY | ControlMask | ShiftMask, KEY, toggletag, {.ui = 1 << TAG}},
72
73/* commands */
74static char dmenumon[2] = "0";
75
76static const char *dmenucmd[] = {"dmenucmd.sh", NULL};
77static const char *termcmd[] = {"st", NULL};
78static const char *screenshotcmd[] = {"screenshotsel.sh", NULL};
79
80static const Key keys[] = {
81 /* modifier key function argument */
82 {MODKEY, XK_space, spawn, {.v = dmenucmd}},
83 {MODKEY, XK_Return, spawn, {.v = termcmd}},
84 {MODKEY | ShiftMask, XK_s, spawn, {.v = screenshotcmd}},
85 {MODKEY, XK_b, togglebar, {0}},
86 {MODKEY, XK_q, killclient, {0}},
87 {MODKEY, XK_f, togglefullscr, {0}},
88 {MODKEY, XK_l, setlayout, {0}},
89 {MODKEY | ShiftMask, XK_space, togglefloating, {0}},
90 TAGKEYS(XK_1, 0) TAGKEYS(XK_2, 1) TAGKEYS(XK_3, 2) TAGKEYS(XK_4, 3)
91 TAGKEYS(XK_5, 4) TAGKEYS(XK_6, 5) TAGKEYS(XK_7, 6) TAGKEYS(XK_8, 7)
92 TAGKEYS(XK_9, 8){MODKEY | ShiftMask, XK_q, quit, {0}},
93 {0,
94 XF86XK_AudioMute,
95 spawn,
96 {.v = (const char *[]){"wpctl", "set-mute", "@DEFAULT_AUDIO_SINK@",
97 "toggle"}}},
98 {0,
99 XF86XK_AudioMicMute,
100 spawn,
101 {.v = (const char *[]){"wpctl", "set-mute", "54", "toggle"}}},
102 {0,
103 XF86XK_AudioRaiseVolume,
104 spawn,
105 {.v = (const char *[]){"wpctl", "set-volume", "@DEFAULT_AUDIO_SINK@",
106 "10%+"}}},
107 {0,
108 XF86XK_AudioLowerVolume,
109 spawn,
110 {.v = (const char *[]){"wpctl", "set-volume", "@DEFAULT_AUDIO_SINK@",
111 "10%-"}}},
112};
113
114static const Button buttons[] = {
115 /* click event mask button function argument */
116 {ClkLtSymbol, 0, Button1, setlayout, {0}},
117 {ClkLtSymbol, 0, Button3, setlayout, {.v = &layouts[2]}},
118 {ClkTagBar, MODKEY, Button1, tag, {0}},
119 {ClkTagBar, MODKEY, Button3, toggletag, {0}},
120 {ClkWinTitle, 0, Button2, zoom, {0}},
121 {ClkStatusText, 0, Button2, spawn, {.v = termcmd}},
122 {ClkClientWin, MODKEY, Button1, movemouse, {0}},
123 {ClkClientWin, MODKEY, Button2, togglefloating, {0}},
124 {ClkClientWin, MODKEY, Button3, resizemouse, {0}},
125 {ClkTagBar, 0, Button1, view, {0}},
126 {ClkTagBar, 0, Button3, toggleview, {0}},
127 {ClkTagBar, MODKEY, Button1, tag, {0}},
128 {ClkTagBar, MODKEY, Button3, toggletag, {0}},
129};
diff --git a/src/config/config.h b/src/config/config.h
new file mode 100644
index 0000000..e6d32c5
--- /dev/null
+++ b/src/config/config.h
@@ -0,0 +1,129 @@
1// #include <cstddef>
2static const unsigned int borderpx = 0;
3static const unsigned int snap = 32;
4static const unsigned int systraypinning = 0;
5static const unsigned int systrayonleft = 0;
6static const unsigned int systrayspacing = 2;
7static const unsigned int gappih = 5;
8static const unsigned int gappiv = 5;
9static const unsigned int gappoh = 5;
10static const unsigned int gappov = 5;
11
12static const int showsystray = 1;
13static const int systraypinningfailfirst = 1;
14static const int smartgaps = 0;
15static const int swallowfloating = 1;
16static const int showbar = 0;
17static const int topbar = 1;
18
19static const char *fonts[] = {"monospace:size=10"};
20static const char dmenufont[] = "monospace:size=10";
21static const char col_gray1[] = "#222222";
22static const char col_gray2[] = "#444444";
23static const char col_gray3[] = "#bbbbbb";
24static const char col_gray4[] = "#eeeeee";
25static const char col_cyan[] = "#005577";
26static const char norm_fg[] = "#383a42";
27static const char norm_bg[] = "#e1e3ea";
28static const char norm_border[] = "#3f3f40";
29
30static const char sel_fg[] = "#383a42";
31static const char sel_bg[] = "#e1e3ea";
32static const char sel_border[] = "#225588";
33
34static const char urg_fg[] = "#225588";
35static const char urg_bg[] = "#e1e3ea";
36static const char urg_border[] = "#000c18";
37
38static const char *colors[][3] = {
39 [SchemeNorm] = {norm_fg, norm_bg, norm_border}, // unfocused wins
40 [SchemeSel] = {sel_fg, sel_bg, sel_border}, // the focused win
41};
42
43/* tagging */
44static const char *tags[] = {"1", "2", "3", "4", "5", "6", "7", "8", "9"};
45
46static const Rule rules[] = {
47 /* class instance title tags mask isfloating isterminal
48 noswallow monitor */
49 {"St", NULL, NULL, 0, 0, 1, 0, -1},
50 {NULL, NULL, "Event Tester", 0, 0, 0, 1, -1}, /* xev */
51};
52
53static const float mfact = 0.50;
54static const int nmaster = 1;
55static const int resizehints = 0;
56static const int lockfullscreen = 1;
57
58static const Layout layouts[] = {
59 /* symbol arrange function */
60 {"[]=", tile}, /* first entry is default */
61 {"><>", NULL}, /* no layout function means floating behavior */
62 // {"[M]", monocle},
63};
64
65/* key definitions */
66#define MODKEY Mod4Mask
67#define TAGKEYS(KEY, TAG) \
68 {MODKEY, KEY, view, {.ui = 1 << TAG}}, \
69 {MODKEY | ControlMask, KEY, toggleview, {.ui = 1 << TAG}}, \
70 {MODKEY | ShiftMask, KEY, tag, {.ui = 1 << TAG}}, \
71 {MODKEY | ControlMask | ShiftMask, KEY, toggletag, {.ui = 1 << TAG}},
72
73/* commands */
74static char dmenumon[2] = "0";
75
76static const char *dmenucmd[] = {"dmenucmd.sh", NULL};
77static const char *termcmd[] = {"st", NULL};
78static const char *screenshotcmd[] = {"screenshotsel.sh", NULL};
79
80static const Key keys[] = {
81 /* modifier key function argument */
82 {MODKEY, XK_space, spawn, {.v = dmenucmd}},
83 {MODKEY, XK_Return, spawn, {.v = termcmd}},
84 {MODKEY | ShiftMask, XK_s, spawn, {.v = screenshotcmd}},
85 {MODKEY, XK_b, togglebar, {0}},
86 {MODKEY, XK_q, killclient, {0}},
87 {MODKEY, XK_f, togglefullscr, {0}},
88 {MODKEY, XK_l, setlayout, {0}},
89 {MODKEY | ShiftMask, XK_space, togglefloating, {0}},
90 TAGKEYS(XK_1, 0) TAGKEYS(XK_2, 1) TAGKEYS(XK_3, 2) TAGKEYS(XK_4, 3)
91 TAGKEYS(XK_5, 4) TAGKEYS(XK_6, 5) TAGKEYS(XK_7, 6) TAGKEYS(XK_8, 7)
92 TAGKEYS(XK_9, 8){MODKEY | ShiftMask, XK_q, quit, {0}},
93 {0,
94 XF86XK_AudioMute,
95 spawn,
96 {.v = (const char *[]){"wpctl", "set-mute", "@DEFAULT_AUDIO_SINK@",
97 "toggle"}}},
98 {0,
99 XF86XK_AudioMicMute,
100 spawn,
101 {.v = (const char *[]){"wpctl", "set-mute", "54", "toggle"}}},
102 {0,
103 XF86XK_AudioRaiseVolume,
104 spawn,
105 {.v = (const char *[]){"wpctl", "set-volume", "@DEFAULT_AUDIO_SINK@",
106 "10%+"}}},
107 {0,
108 XF86XK_AudioLowerVolume,
109 spawn,
110 {.v = (const char *[]){"wpctl", "set-volume", "@DEFAULT_AUDIO_SINK@",
111 "10%-"}}},
112};
113
114static const Button buttons[] = {
115 /* click event mask button function argument */
116 {ClkLtSymbol, 0, Button1, setlayout, {0}},
117 {ClkLtSymbol, 0, Button3, setlayout, {.v = &layouts[2]}},
118 {ClkTagBar, MODKEY, Button1, tag, {0}},
119 {ClkTagBar, MODKEY, Button3, toggletag, {0}},
120 {ClkWinTitle, 0, Button2, zoom, {0}},
121 {ClkStatusText, 0, Button2, spawn, {.v = termcmd}},
122 {ClkClientWin, MODKEY, Button1, movemouse, {0}},
123 {ClkClientWin, MODKEY, Button2, togglefloating, {0}},
124 {ClkClientWin, MODKEY, Button3, resizemouse, {0}},
125 {ClkTagBar, 0, Button1, view, {0}},
126 {ClkTagBar, 0, Button3, toggleview, {0}},
127 {ClkTagBar, MODKEY, Button1, tag, {0}},
128 {ClkTagBar, MODKEY, Button3, toggletag, {0}},
129};
diff --git a/src/config/config.mk b/src/config/config.mk
new file mode 100644
index 0000000..3935516
--- /dev/null
+++ b/src/config/config.mk
@@ -0,0 +1,40 @@
1# dwm version
2VERSION = 6.4
3
4# Customize below to fit your system
5
6# paths
7PREFIX = /usr/local
8MANPREFIX = ${PREFIX}/share/man
9
10X11INC = /usr/X11R6/include
11X11LIB = /usr/X11R6/lib
12
13# Xinerama, comment if you don't want it
14XINERAMALIBS = -lXinerama
15XINERAMAFLAGS = -DXINERAMA
16
17# freetype
18FREETYPELIBS = -lfontconfig -lXft
19FREETYPEINC = /usr/include/freetype2
20# OpenBSD (uncomment)
21#FREETYPEINC = ${X11INC}/freetype2
22#MANPREFIX = ${PREFIX}/man
23#KVMLIB = -lkvm
24
25# includes and libs
26INCS = -I${X11INC} -I${FREETYPEINC}
27LIBS = -L${X11LIB} -lX11 ${XINERAMALIBS} ${FREETYPELIBS} -lX11-xcb -lxcb -lxcb-res ${KVMLIB}
28
29# flags
30CPPFLAGS = -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_XOPEN_SOURCE=700L -DVERSION=\"${VERSION}\" ${XINERAMAFLAGS}
31#CFLAGS = -g -std=c99 -pedantic -Wall -O0 ${INCS} ${CPPFLAGS}
32CFLAGS = -std=c99 -pedantic -Wall -Wno-deprecated-declarations -Os ${INCS} ${CPPFLAGS}
33LDFLAGS = ${LIBS}
34
35# Solaris
36#CFLAGS = -fast ${INCS} -DVERSION=\"${VERSION}\"
37#LDFLAGS = ${LIBS}
38
39# compiler and linker
40CC = gcc
diff --git a/src/core/dwm.c b/src/core/dwm.c
new file mode 100644
index 0000000..6082987
--- /dev/null
+++ b/src/core/dwm.c
@@ -0,0 +1,2735 @@
1/* See LICENSE file for copyright and license details.
2 *
3 * dynamic window manager is designed like any other X client as well. It is
4 * driven through handling X events. In contrast to other X clients, a window
5 * manager selects for SubstructureRedirectMask on the root window, to receive
6 * events about window (dis-)appearance. Only one X connection at a time is
7 * allowed to select for this event mask.
8 *
9 * The event handlers of dwm are organized in an array which is accessed
10 * whenever a new event has been fetched. This allows event dispatching
11 * in O(1) time.
12 *
13 * Each child of the root window is called a client, except windows which have
14 * set the override_redirect flag. Clients are organized in a linked client
15 * list on each monitor, the focus history is remembered through a stack list
16 * on each monitor. Each client contains a bit array to indicate the tags of a
17 * client.
18 *
19 * Keys and tagging rules are organized as arrays and defined in config.h.
20 *
21 * To understand everything else, start reading main().
22 */
23#include <X11/Xatom.h>
24#include <X11/Xlib.h>
25#include <X11/Xproto.h>
26#include <X11/Xutil.h>
27#include <X11/cursorfont.h>
28#include <X11/keysym.h>
29#include <errno.h>
30#include <locale.h>
31#include <signal.h>
32#include <stdarg.h>
33#include <stdio.h>
34#include <stdlib.h>
35#include <string.h>
36#include <sys/types.h>
37#include <sys/wait.h>
38#include <unistd.h>
39#ifdef XINERAMA
40#include <X11/extensions/Xinerama.h>
41#endif /* XINERAMA */
42#include <X11/Xft/Xft.h>
43#include <X11/Xlib-xcb.h>
44#include <xcb/res.h>
45#ifdef __OpenBSD__
46#include <kvm.h>
47#include <sys/sysctl.h>
48#endif /* __OpenBSD */
49
50#include "../drw/drw.h"
51#include "../util/util.h"
52#include <X11/XF86keysym.h>
53
54/* macros */
55#define BUTTONMASK (ButtonPressMask | ButtonReleaseMask)
56#define CLEANMASK(mask) \
57 (mask & ~(numlockmask | LockMask) & \
58 (ShiftMask | ControlMask | Mod1Mask | Mod2Mask | Mod3Mask | Mod4Mask | \
59 Mod5Mask))
60#define INTERSECT(x, y, w, h, m) \
61 (MAX(0, MIN((x) + (w), (m)->wx + (m)->ww) - MAX((x), (m)->wx)) * \
62 MAX(0, MIN((y) + (h), (m)->wy + (m)->wh) - MAX((y), (m)->wy)))
63#define ISVISIBLE(C) ((C->tags & C->mon->tagset[C->mon->seltags]))
64#define LENGTH(X) (sizeof X / sizeof X[0])
65#define MOUSEMASK (BUTTONMASK | PointerMotionMask)
66#define WIDTH(X) ((X)->w + 2 * (X)->bw)
67#define HEIGHT(X) ((X)->h + 2 * (X)->bw)
68#define TAGMASK ((1 << LENGTH(tags)) - 1)
69#define TEXTW(X) (drw_fontset_getwidth(drw, (X)) + lrpad)
70
71#define SYSTEM_TRAY_REQUEST_DOCK 0
72/* XEMBED messages */
73#define XEMBED_EMBEDDED_NOTIFY 0
74#define XEMBED_WINDOW_ACTIVATE 1
75#define XEMBED_FOCUS_IN 4
76#define XEMBED_MODALITY_ON 10
77#define XEMBED_MAPPED (1 << 0)
78#define XEMBED_WINDOW_ACTIVATE 1
79#define XEMBED_WINDOW_DEACTIVATE 2
80#define VERSION_MAJOR 0
81#define VERSION_MINOR 0
82#define XEMBED_EMBEDDED_VERSION (VERSION_MAJOR << 16) | VERSION_MINOR
83
84/* enums */
85enum { CurNormal, CurResize, CurMove, CurLast }; /* cursor */
86enum { SchemeNorm, SchemeSel }; /* color schemes */
87enum {
88 NetSupported,
89 NetWMName,
90 NetWMState,
91 NetWMCheck,
92 NetSystemTray,
93 NetSystemTrayOP,
94 NetSystemTrayOrientation,
95 NetSystemTrayOrientationHorz,
96 NetWMFullscreen,
97 NetActiveWindow,
98 NetWMWindowType,
99 NetWMWindowTypeDialog,
100 NetClientList,
101 NetLast
102}; /* EWMH atoms */
103enum { Manager, Xembed, XembedInfo, XLast }; /* Xembed atoms */
104enum {
105 WMProtocols,
106 WMDelete,
107 WMState,
108 WMTakeFocus,
109 WMLast
110}; /* default atoms */
111enum {
112 ClkTagBar,
113 ClkLtSymbol,
114 ClkStatusText,
115 ClkWinTitle,
116 ClkClientWin,
117 ClkRootWin,
118 ClkLast
119}; /* clicks */
120
121typedef union {
122 int i;
123 unsigned int ui;
124 float f;
125 const void *v;
126} Arg;
127
128typedef struct {
129 unsigned int click;
130 unsigned int mask;
131 unsigned int button;
132 void (*func)(const Arg *arg);
133 const Arg arg;
134} Button;
135
136typedef struct Monitor Monitor;
137typedef struct Client Client;
138struct Client {
139 char name[256];
140 float mina, maxa;
141 int x, y, w, h;
142 int oldx, oldy, oldw, oldh;
143 int basew, baseh, incw, inch, maxw, maxh, minw, minh, hintsvalid;
144 int bw, oldbw;
145 unsigned int tags;
146 int isfixed, isfloating, isurgent, neverfocus, oldstate, isfullscreen,
147 isterminal, noswallow;
148 pid_t pid;
149 Client *next;
150 Client *snext;
151 Client *swallowing;
152 Monitor *mon;
153 Window win;
154};
155
156typedef struct {
157 unsigned int mod;
158 KeySym keysym;
159 void (*func)(const Arg *);
160 const Arg arg;
161} Key;
162
163typedef struct {
164 const char *symbol;
165 void (*arrange)(Monitor *);
166} Layout;
167
168struct Monitor {
169 char ltsymbol[16];
170 float mfact;
171 int nmaster;
172 int num;
173 int by; /* bar geometry */
174 int mx, my, mw, mh; /* screen size */
175 int wx, wy, ww, wh; /* window area */
176 int gappih; /* horizontal gap between windows */
177 int gappiv; /* vertical gap between windows */
178 int gappoh; /* horizontal outer gaps */
179 int gappov; /* vertical outer gaps */
180 unsigned int seltags;
181 unsigned int sellt;
182 unsigned int tagset[2];
183 int showbar;
184 int topbar;
185 Client *clients;
186 Client *sel;
187 Client *stack;
188 Monitor *next;
189 Window barwin;
190 const Layout *lt[2];
191};
192
193typedef struct {
194 const char *class;
195 const char *instance;
196 const char *title;
197 unsigned int tags;
198 int isfloating;
199 int isterminal;
200 int noswallow;
201 int monitor;
202} Rule;
203
204typedef struct Systray Systray;
205struct Systray {
206 Window win;
207 Client *icons;
208};
209
210/* function declarations */
211static void applyrules(Client *c);
212static int applysizehints(Client *c, int *x, int *y, int *w, int *h,
213 int interact);
214static void arrange(Monitor *m);
215static void arrangemon(Monitor *m);
216static void attach(Client *c);
217static void attachstack(Client *c);
218static void buttonpress(XEvent *e);
219static void checkotherwm(void);
220static void cleanup(void);
221static void cleanupmon(Monitor *mon);
222static void clientmessage(XEvent *e);
223static void configure(Client *c);
224static void configurenotify(XEvent *e);
225static void configurerequest(XEvent *e);
226static Monitor *createmon(void);
227static void destroynotify(XEvent *e);
228static void detach(Client *c);
229static void detachstack(Client *c);
230static Monitor *dirtomon(int dir);
231static void drawbar(Monitor *m);
232static void drawbars(void);
233static void enternotify(XEvent *e);
234static void expose(XEvent *e);
235static void focus(Client *c);
236static void focusin(XEvent *e);
237static void focusmon(const Arg *arg);
238static void focusstack(const Arg *arg);
239static Atom getatomprop(Client *c, Atom prop);
240static int getrootptr(int *x, int *y);
241static long getstate(Window w);
242static unsigned int getsystraywidth();
243static int gettextprop(Window w, Atom atom, char *text, unsigned int size);
244static void grabbuttons(Client *c, int focused);
245static void grabkeys(void);
246static void incnmaster(const Arg *arg);
247static void keypress(XEvent *e);
248static void killclient(const Arg *arg);
249static void manage(Window w, XWindowAttributes *wa);
250static void mappingnotify(XEvent *e);
251static void maprequest(XEvent *e);
252static void monocle(Monitor *m);
253static void motionnotify(XEvent *e);
254static void movemouse(const Arg *arg);
255static Client *nexttiled(Client *c);
256static void pop(Client *c);
257static void propertynotify(XEvent *e);
258static void quit(const Arg *arg);
259static Monitor *recttomon(int x, int y, int w, int h);
260static void removesystrayicon(Client *i);
261static void resize(Client *c, int x, int y, int w, int h, int interact);
262static void resizebarwin(Monitor *m);
263static void resizeclient(Client *c, int x, int y, int w, int h);
264static void resizemouse(const Arg *arg);
265static void resizerequest(XEvent *e);
266static void restack(Monitor *m);
267static void run(void);
268static void scan(void);
269static int sendevent(Window w, Atom proto, int m, long d0, long d1, long d2,
270 long d3, long d4);
271static void sendmon(Client *c, Monitor *m);
272static void setclientstate(Client *c, long state);
273static void setfocus(Client *c);
274static void setfullscreen(Client *c, int fullscreen);
275static void setgaps(int oh, int ov, int ih, int iv);
276static void incrgaps(const Arg *arg);
277static void incrigaps(const Arg *arg);
278static void incrogaps(const Arg *arg);
279static void incrohgaps(const Arg *arg);
280static void incrovgaps(const Arg *arg);
281static void incrihgaps(const Arg *arg);
282static void incrivgaps(const Arg *arg);
283static void togglegaps(const Arg *arg);
284static void defaultgaps(const Arg *arg);
285static void setlayout(const Arg *arg);
286static void setmfact(const Arg *arg);
287static void setup(void);
288static void seturgent(Client *c, int urg);
289static void showhide(Client *c);
290static void spawn(const Arg *arg);
291static Monitor *systraytomon(Monitor *m);
292static void tag(const Arg *arg);
293static void tagmon(const Arg *arg);
294static void tile(Monitor *m);
295static void togglebar(const Arg *arg);
296static void togglefloating(const Arg *arg);
297static void togglefullscr(const Arg *arg);
298static void toggletag(const Arg *arg);
299static void toggleview(const Arg *arg);
300static void unfocus(Client *c, int setfocus);
301static void unmanage(Client *c, int destroyed);
302static void unmapnotify(XEvent *e);
303static void updatebarpos(Monitor *m);
304static void updatebars(void);
305static void updateclientlist(void);
306static int updategeom(void);
307static void updatenumlockmask(void);
308static void updatesystray(void);
309static void updatesystrayicongeom(Client *i, int w, int h);
310static void updatesystrayiconstate(Client *i, XPropertyEvent *ev);
311static void updatesizehints(Client *c);
312static void updatestatus(void);
313static void updatetitle(Client *c);
314static void updatewindowtype(Client *c);
315static void updatewmhints(Client *c);
316static void view(const Arg *arg);
317static Client *wintoclient(Window w);
318static Monitor *wintomon(Window w);
319static Client *wintosystrayicon(Window w);
320static int xerror(Display *dpy, XErrorEvent *ee);
321static int xerrordummy(Display *dpy, XErrorEvent *ee);
322static int xerrorstart(Display *dpy, XErrorEvent *ee);
323static void zoom(const Arg *arg);
324
325static pid_t getparentprocess(pid_t p);
326static int isdescprocess(pid_t p, pid_t c);
327static Client *swallowingclient(Window w);
328static Client *termforwin(const Client *c);
329static pid_t winpid(Window w);
330
331/* variables */
332static Systray *systray = NULL;
333static const char broken[] = "broken";
334static char stext[256];
335static int screen;
336static int sw, sh; /* X display screen geometry width, height */
337static int bh; /* bar height */
338static int enablegaps = 1; /* enables gaps, used by togglegaps */
339static int lrpad; /* sum of left and right padding for text */
340static int (*xerrorxlib)(Display *, XErrorEvent *);
341static unsigned int numlockmask = 0;
342static void (*handler[LASTEvent])(XEvent *) = {
343 [ButtonPress] = buttonpress,
344 [ClientMessage] = clientmessage,
345 [ConfigureRequest] = configurerequest,
346 [ConfigureNotify] = configurenotify,
347 [DestroyNotify] = destroynotify,
348 [EnterNotify] = enternotify,
349 [Expose] = expose,
350 [FocusIn] = focusin,
351 [KeyPress] = keypress,
352 [MappingNotify] = mappingnotify,
353 [MapRequest] = maprequest,
354 [MotionNotify] = motionnotify,
355 [PropertyNotify] = propertynotify,
356 [ResizeRequest] = resizerequest,
357 [UnmapNotify] = unmapnotify};
358
359static Atom wmatom[WMLast], netatom[NetLast], xatom[XLast];
360static int running = 1;
361static Cur *cursor[CurLast];
362static Clr **scheme;
363static Display *dpy;
364static Drw *drw;
365static Monitor *mons, *selmon;
366static Window root, wmcheckwin;
367
368static xcb_connection_t *xcon;
369
370/* configuration, allows nested code to access above variables */
371#include "../config/config.h"
372
373/* compile-time check if all tags fit into an unsigned int bit array. */
374struct NumTags {
375 char limitexceeded[LENGTH(tags) > 31 ? -1 : 1];
376};
377
378/* function implementations */
379void applyrules(Client *c) {
380 const char *class, *instance;
381 unsigned int i;
382 const Rule *r;
383 Monitor *m;
384 XClassHint ch = {NULL, NULL};
385
386 /* rule matching */
387 c->isfloating = 0;
388 c->tags = 0;
389 XGetClassHint(dpy, c->win, &ch);
390 class = ch.res_class ? ch.res_class : broken;
391 instance = ch.res_name ? ch.res_name : broken;
392
393 for (i = 0; i < LENGTH(rules); i++) {
394 r = &rules[i];
395 if ((!r->title || strstr(c->name, r->title)) &&
396 (!r->class || strstr(class, r->class)) &&
397 (!r->instance || strstr(instance, r->instance))) {
398 c->isterminal = r->isterminal;
399 c->noswallow = r->noswallow;
400 c->isfloating = r->isfloating;
401 c->tags |= r->tags;
402 for (m = mons; m && m->num != r->monitor; m = m->next)
403 ;
404 if (m)
405 c->mon = m;
406 }
407 }
408 if (ch.res_class)
409 XFree(ch.res_class);
410 if (ch.res_name)
411 XFree(ch.res_name);
412 c->tags =
413 c->tags & TAGMASK ? c->tags & TAGMASK : c->mon->tagset[c->mon->seltags];
414}
415
416int applysizehints(Client *c, int *x, int *y, int *w, int *h, int interact) {
417 int baseismin;
418 Monitor *m = c->mon;
419
420 /* set minimum possible */
421 *w = MAX(1, *w);
422 *h = MAX(1, *h);
423 if (interact) {
424 if (*x > sw)
425 *x = sw - WIDTH(c);
426 if (*y > sh)
427 *y = sh - HEIGHT(c);
428 if (*x + *w + 2 * c->bw < 0)
429 *x = 0;
430 if (*y + *h + 2 * c->bw < 0)
431 *y = 0;
432 } else {
433 if (*x >= m->wx + m->ww)
434 *x = m->wx + m->ww - WIDTH(c);
435 if (*y >= m->wy + m->wh)
436 *y = m->wy + m->wh - HEIGHT(c);
437 if (*x + *w + 2 * c->bw <= m->wx)
438 *x = m->wx;
439 if (*y + *h + 2 * c->bw <= m->wy)
440 *y = m->wy;
441 }
442 if (*h < bh)
443 *h = bh;
444 if (*w < bh)
445 *w = bh;
446 if (resizehints || c->isfloating || !c->mon->lt[c->mon->sellt]->arrange) {
447 if (!c->hintsvalid)
448 updatesizehints(c);
449 /* see last two sentences in ICCCM 4.1.2.3 */
450 baseismin = c->basew == c->minw && c->baseh == c->minh;
451 if (!baseismin) { /* temporarily remove base dimensions */
452 *w -= c->basew;
453 *h -= c->baseh;
454 }
455 /* adjust for aspect limits */
456 if (c->mina > 0 && c->maxa > 0) {
457 if (c->maxa < (float)*w / *h)
458 *w = *h * c->maxa + 0.5;
459 else if (c->mina < (float)*h / *w)
460 *h = *w * c->mina + 0.5;
461 }
462 if (baseismin) { /* increment calculation requires this */
463 *w -= c->basew;
464 *h -= c->baseh;
465 }
466 /* adjust for increment value */
467 if (c->incw)
468 *w -= *w % c->incw;
469 if (c->inch)
470 *h -= *h % c->inch;
471 /* restore base dimensions */
472 *w = MAX(*w + c->basew, c->minw);
473 *h = MAX(*h + c->baseh, c->minh);
474 if (c->maxw)
475 *w = MIN(*w, c->maxw);
476 if (c->maxh)
477 *h = MIN(*h, c->maxh);
478 }
479 return *x != c->x || *y != c->y || *w != c->w || *h != c->h;
480}
481
482void arrange(Monitor *m) {
483 if (m)
484 showhide(m->stack);
485 else
486 for (m = mons; m; m = m->next)
487 showhide(m->stack);
488 if (m) {
489 arrangemon(m);
490 restack(m);
491 } else
492 for (m = mons; m; m = m->next)
493 arrangemon(m);
494}
495
496void arrangemon(Monitor *m) {
497 strncpy(m->ltsymbol, m->lt[m->sellt]->symbol, sizeof m->ltsymbol);
498 if (m->lt[m->sellt]->arrange)
499 m->lt[m->sellt]->arrange(m);
500}
501
502void attach(Client *c) {
503 c->next = c->mon->clients;
504 c->mon->clients = c;
505}
506
507void attachstack(Client *c) {
508 c->snext = c->mon->stack;
509 c->mon->stack = c;
510}
511
512void swallow(Client *p, Client *c) {
513
514 if (c->noswallow || c->isterminal)
515 return;
516 if (c->noswallow && !swallowfloating && c->isfloating)
517 return;
518
519 detach(c);
520 detachstack(c);
521
522 setclientstate(c, WithdrawnState);
523 XUnmapWindow(dpy, p->win);
524
525 p->swallowing = c;
526 c->mon = p->mon;
527
528 Window w = p->win;
529 p->win = c->win;
530 c->win = w;
531 updatetitle(p);
532 XMoveResizeWindow(dpy, p->win, p->x, p->y, p->w, p->h);
533 arrange(p->mon);
534 configure(p);
535 updateclientlist();
536}
537
538void unswallow(Client *c) {
539 c->win = c->swallowing->win;
540
541 free(c->swallowing);
542 c->swallowing = NULL;
543
544 /* unfullscreen the client */
545 setfullscreen(c, 0);
546 updatetitle(c);
547 arrange(c->mon);
548 XMapWindow(dpy, c->win);
549 XMoveResizeWindow(dpy, c->win, c->x, c->y, c->w, c->h);
550 setclientstate(c, NormalState);
551 focus(NULL);
552 arrange(c->mon);
553}
554
555void buttonpress(XEvent *e) {
556 unsigned int i, x, click;
557 Arg arg = {0};
558 Client *c;
559 Monitor *m;
560 XButtonPressedEvent *ev = &e->xbutton;
561
562 click = ClkRootWin;
563 /* focus monitor if necessary */
564 if ((m = wintomon(ev->window)) && m != selmon) {
565 unfocus(selmon->sel, 1);
566 selmon = m;
567 focus(NULL);
568 }
569 if (ev->window == selmon->barwin) {
570 i = x = 0;
571 unsigned int occ = 0;
572 for (c = m->clients; c; c = c->next)
573 occ |= c->tags;
574 do {
575 /* Do not reserve space for vacant tags */
576 if (!(occ & 1 << i || m->tagset[m->seltags] & 1 << i))
577 continue;
578 x += TEXTW(tags[i]);
579 } while (ev->x >= x && ++i < LENGTH(tags));
580 if (i < LENGTH(tags)) {
581 click = ClkTagBar;
582 arg.ui = 1 << i;
583 } else if (ev->x < x + TEXTW(selmon->ltsymbol))
584 click = ClkLtSymbol;
585 else if (ev->x > selmon->ww - (int)TEXTW(stext) - getsystraywidth())
586 click = ClkStatusText;
587 else
588 click = ClkWinTitle;
589 } else if ((c = wintoclient(ev->window))) {
590 focus(c);
591 restack(selmon);
592 XAllowEvents(dpy, ReplayPointer, CurrentTime);
593 click = ClkClientWin;
594 }
595 for (i = 0; i < LENGTH(buttons); i++)
596 if (click == buttons[i].click && buttons[i].func &&
597 buttons[i].button == ev->button &&
598 CLEANMASK(buttons[i].mask) == CLEANMASK(ev->state))
599 buttons[i].func(
600 click == ClkTagBar && buttons[i].arg.i == 0 ? &arg : &buttons[i].arg);
601}
602
603void checkotherwm(void) {
604 xerrorxlib = XSetErrorHandler(xerrorstart);
605 /* this causes an error if some other window manager is running */
606 XSelectInput(dpy, DefaultRootWindow(dpy), SubstructureRedirectMask);
607 XSync(dpy, False);
608 XSetErrorHandler(xerror);
609 XSync(dpy, False);
610}
611
612void cleanup(void) {
613 Arg a = {.ui = ~0};
614 Layout foo = {"", NULL};
615 Monitor *m;
616 size_t i;
617
618 view(&a);
619 selmon->lt[selmon->sellt] = &foo;
620 for (m = mons; m; m = m->next)
621 while (m->stack)
622 unmanage(m->stack, 0);
623 XUngrabKey(dpy, AnyKey, AnyModifier, root);
624 while (mons)
625 cleanupmon(mons);
626
627 if (showsystray) {
628 XUnmapWindow(dpy, systray->win);
629 XDestroyWindow(dpy, systray->win);
630 free(systray);
631 }
632
633 for (i = 0; i < CurLast; i++)
634 drw_cur_free(drw, cursor[i]);
635 for (i = 0; i < LENGTH(colors); i++)
636 free(scheme[i]);
637 free(scheme);
638 XDestroyWindow(dpy, wmcheckwin);
639 drw_free(drw);
640 XSync(dpy, False);
641 XSetInputFocus(dpy, PointerRoot, RevertToPointerRoot, CurrentTime);
642 XDeleteProperty(dpy, root, netatom[NetActiveWindow]);
643}
644
645void cleanupmon(Monitor *mon) {
646 Monitor *m;
647
648 if (mon == mons)
649 mons = mons->next;
650 else {
651 for (m = mons; m && m->next != mon; m = m->next)
652 ;
653 m->next = mon->next;
654 }
655 XUnmapWindow(dpy, mon->barwin);
656 XDestroyWindow(dpy, mon->barwin);
657 free(mon);
658}
659
660void clientmessage(XEvent *e) {
661 XWindowAttributes wa;
662 XSetWindowAttributes swa;
663 XClientMessageEvent *cme = &e->xclient;
664 Client *c = wintoclient(cme->window);
665
666 if (showsystray && cme->window == systray->win &&
667 cme->message_type == netatom[NetSystemTrayOP]) {
668 /* add systray icons */
669 if (cme->data.l[1] == SYSTEM_TRAY_REQUEST_DOCK) {
670 if (!(c = (Client *)calloc(1, sizeof(Client))))
671 die("fatal: could not malloc() %u bytes\n", sizeof(Client));
672 if (!(c->win = cme->data.l[2])) {
673 free(c);
674 return;
675 }
676 c->mon = selmon;
677 c->next = systray->icons;
678 systray->icons = c;
679 if (!XGetWindowAttributes(dpy, c->win, &wa)) {
680 /* use sane defaults */
681 wa.width = bh;
682 wa.height = bh;
683 wa.border_width = 0;
684 }
685 c->x = c->oldx = c->y = c->oldy = 0;
686 c->w = c->oldw = wa.width;
687 c->h = c->oldh = wa.height;
688 c->oldbw = wa.border_width;
689 c->bw = 0;
690 c->isfloating = True;
691 /* reuse tags field as mapped status */
692 c->tags = 1;
693 updatesizehints(c);
694 updatesystrayicongeom(c, wa.width, wa.height);
695 XAddToSaveSet(dpy, c->win);
696 XSelectInput(dpy, c->win,
697 StructureNotifyMask | PropertyChangeMask |
698 ResizeRedirectMask);
699 XReparentWindow(dpy, c->win, systray->win, 0, 0);
700 /* use parents background color */
701 swa.background_pixel = scheme[SchemeNorm][ColBg].pixel;
702 XChangeWindowAttributes(dpy, c->win, CWBackPixel, &swa);
703 sendevent(c->win, netatom[Xembed], StructureNotifyMask, CurrentTime,
704 XEMBED_EMBEDDED_NOTIFY, 0, systray->win,
705 XEMBED_EMBEDDED_VERSION);
706 /* FIXME not sure if I have to send these events, too */
707 sendevent(c->win, netatom[Xembed], StructureNotifyMask, CurrentTime,
708 XEMBED_FOCUS_IN, 0, systray->win, XEMBED_EMBEDDED_VERSION);
709 sendevent(c->win, netatom[Xembed], StructureNotifyMask, CurrentTime,
710 XEMBED_WINDOW_ACTIVATE, 0, systray->win,
711 XEMBED_EMBEDDED_VERSION);
712 sendevent(c->win, netatom[Xembed], StructureNotifyMask, CurrentTime,
713 XEMBED_MODALITY_ON, 0, systray->win, XEMBED_EMBEDDED_VERSION);
714 XSync(dpy, False);
715 resizebarwin(selmon);
716 updatesystray();
717 setclientstate(c, NormalState);
718 }
719 return;
720 }
721
722 if (!c)
723 return;
724 if (cme->message_type == netatom[NetWMState]) {
725 if (cme->data.l[1] == netatom[NetWMFullscreen] ||
726 cme->data.l[2] == netatom[NetWMFullscreen])
727 setfullscreen(c, (cme->data.l[0] == 1 /* _NET_WM_STATE_ADD */
728 || (cme->data.l[0] == 2 /* _NET_WM_STATE_TOGGLE */
729 && !c->isfullscreen)));
730 } else if (cme->message_type == netatom[NetActiveWindow]) {
731 if (c != selmon->sel && !c->isurgent)
732 seturgent(c, 1);
733 }
734}
735
736void configure(Client *c) {
737 XConfigureEvent ce;
738
739 ce.type = ConfigureNotify;
740 ce.display = dpy;
741 ce.event = c->win;
742 ce.window = c->win;
743 ce.x = c->x;
744 ce.y = c->y;
745 ce.width = c->w;
746 ce.height = c->h;
747 ce.border_width = c->bw;
748 ce.above = None;
749 ce.override_redirect = False;
750 XSendEvent(dpy, c->win, False, StructureNotifyMask, (XEvent *)&ce);
751}
752
753void configurenotify(XEvent *e) {
754 Monitor *m;
755 Client *c;
756 XConfigureEvent *ev = &e->xconfigure;
757 int dirty;
758
759 /* TODO: updategeom handling sucks, needs to be simplified */
760 if (ev->window == root) {
761 dirty = (sw != ev->width || sh != ev->height);
762 sw = ev->width;
763 sh = ev->height;
764 if (updategeom() || dirty) {
765 drw_resize(drw, sw, bh);
766 updatebars();
767 for (m = mons; m; m = m->next) {
768 for (c = m->clients; c; c = c->next)
769 if (c->isfullscreen)
770 resizeclient(c, m->mx, m->my, m->mw, m->mh);
771 resizebarwin(m);
772 }
773 focus(NULL);
774 arrange(NULL);
775 }
776 }
777}
778
779void configurerequest(XEvent *e) {
780 Client *c;
781 Monitor *m;
782 XConfigureRequestEvent *ev = &e->xconfigurerequest;
783 XWindowChanges wc;
784
785 if ((c = wintoclient(ev->window))) {
786 if (ev->value_mask & CWBorderWidth)
787 c->bw = ev->border_width;
788 else if (c->isfloating || !selmon->lt[selmon->sellt]->arrange) {
789 m = c->mon;
790 if (ev->value_mask & CWX) {
791 c->oldx = c->x;
792 c->x = m->mx + ev->x;
793 }
794 if (ev->value_mask & CWY) {
795 c->oldy = c->y;
796 c->y = m->my + ev->y;
797 }
798 if (ev->value_mask & CWWidth) {
799 c->oldw = c->w;
800 c->w = ev->width;
801 }
802 if (ev->value_mask & CWHeight) {
803 c->oldh = c->h;
804 c->h = ev->height;
805 }
806 if ((c->x + c->w) > m->mx + m->mw && c->isfloating)
807 c->x = m->mx + (m->mw / 2 - WIDTH(c) / 2); /* center in x direction */
808 if ((c->y + c->h) > m->my + m->mh && c->isfloating)
809 c->y = m->my + (m->mh / 2 - HEIGHT(c) / 2); /* center in y direction */
810 if ((ev->value_mask & (CWX | CWY)) &&
811 !(ev->value_mask & (CWWidth | CWHeight)))
812 configure(c);
813 if (ISVISIBLE(c))
814 XMoveResizeWindow(dpy, c->win, c->x, c->y, c->w, c->h);
815 } else
816 configure(c);
817 } else {
818 wc.x = ev->x;
819 wc.y = ev->y;
820 wc.width = ev->width;
821 wc.height = ev->height;
822 wc.border_width = ev->border_width;
823 wc.sibling = ev->above;
824 wc.stack_mode = ev->detail;
825 XConfigureWindow(dpy, ev->window, ev->value_mask, &wc);
826 }
827 XSync(dpy, False);
828}
829
830Monitor *createmon(void) {
831 Monitor *m;
832
833 m = ecalloc(1, sizeof(Monitor));
834 m->tagset[0] = m->tagset[1] = 1;
835 m->mfact = mfact;
836 m->nmaster = nmaster;
837 m->showbar = showbar;
838 m->topbar = topbar;
839 m->gappih = gappih;
840 m->gappiv = gappiv;
841 m->gappoh = gappoh;
842 m->gappov = gappov;
843 m->lt[0] = &layouts[0];
844 m->lt[1] = &layouts[1 % LENGTH(layouts)];
845 strncpy(m->ltsymbol, layouts[0].symbol, sizeof m->ltsymbol);
846 return m;
847}
848
849void destroynotify(XEvent *e) {
850 Client *c;
851 XDestroyWindowEvent *ev = &e->xdestroywindow;
852
853 if ((c = wintoclient(ev->window)))
854 unmanage(c, 1);
855 else if ((c = wintosystrayicon(ev->window))) {
856 removesystrayicon(c);
857 resizebarwin(selmon);
858 updatesystray();
859 } else if ((c = swallowingclient(ev->window)))
860 unmanage(c->swallowing, 1);
861}
862
863void detach(Client *c) {
864 Client **tc;
865
866 for (tc = &c->mon->clients; *tc && *tc != c; tc = &(*tc)->next)
867 ;
868 *tc = c->next;
869}
870
871void detachstack(Client *c) {
872 Client **tc, *t;
873
874 for (tc = &c->mon->stack; *tc && *tc != c; tc = &(*tc)->snext)
875 ;
876 *tc = c->snext;
877
878 if (c == c->mon->sel) {
879 for (t = c->mon->stack; t && !ISVISIBLE(t); t = t->snext)
880 ;
881 c->mon->sel = t;
882 }
883}
884
885Monitor *dirtomon(int dir) {
886 Monitor *m = NULL;
887
888 if (dir > 0) {
889 if (!(m = selmon->next))
890 m = mons;
891 } else if (selmon == mons)
892 for (m = mons; m->next; m = m->next)
893 ;
894 else
895 for (m = mons; m->next != selmon; m = m->next)
896 ;
897 return m;
898}
899
900void drawbar(Monitor *m) {
901 int x, w, tw = 0, stw = 0;
902 int boxs = drw->fonts->h / 9;
903 int boxw = drw->fonts->h / 6 + 2;
904 unsigned int i, occ = 0, urg = 0;
905 Client *c;
906
907 if (!m->showbar)
908 return;
909
910 if (showsystray && m == systraytomon(m) && !systrayonleft)
911 stw = getsystraywidth();
912
913 /* draw status first so it can be overdrawn by tags later */
914 if (m == selmon) { /* status is only drawn on selected monitor */
915 drw_setscheme(drw, scheme[SchemeNorm]);
916 tw = TEXTW(stext) - lrpad / 2 + 2; /* 2px extra right padding */
917 drw_text(drw, m->ww - tw - stw, 0, tw, bh, lrpad / 2 - 2, stext, 0);
918 }
919
920 resizebarwin(m);
921 for (c = m->clients; c; c = c->next) {
922 occ |= c->tags;
923 if (c->isurgent)
924 urg |= c->tags;
925 }
926 x = 0;
927 for (i = 0; i < LENGTH(tags); i++) {
928 /* Do not draw vacant tags */
929 if (!(occ & 1 << i || m->tagset[m->seltags] & 1 << i))
930 continue;
931 w = TEXTW(tags[i]);
932 drw_setscheme(
933 drw, scheme[m->tagset[m->seltags] & 1 << i ? SchemeSel : SchemeNorm]);
934 drw_text(drw, x, 0, w, bh, lrpad / 2, tags[i], urg & 1 << i);
935 if (occ & 1 << i)
936 drw_rect(drw, x + boxs, boxs, boxw, boxw,
937 m == selmon && selmon->sel && selmon->sel->tags & 1 << i,
938 urg & 1 << i);
939 x += w;
940 }
941 w = TEXTW(m->ltsymbol);
942 drw_setscheme(drw, scheme[SchemeNorm]);
943 x = drw_text(drw, x, 0, w, bh, lrpad / 2, m->ltsymbol, 0);
944
945 if ((w = m->ww - tw - stw - x) > bh) {
946 if (m->sel) {
947 drw_setscheme(drw, scheme[m == selmon ? SchemeSel : SchemeNorm]);
948 drw_text(drw, x, 0, w, bh, lrpad / 2, m->sel->name, 0);
949 if (m->sel->isfloating)
950 drw_rect(drw, x + boxs, boxs, boxw, boxw, m->sel->isfixed, 0);
951 } else {
952 drw_setscheme(drw, scheme[SchemeNorm]);
953 drw_rect(drw, x, 0, w, bh, 1, 1);
954 }
955 }
956 drw_map(drw, m->barwin, 0, 0, m->ww - stw, bh);
957}
958
959void drawbars(void) {
960 Monitor *m;
961
962 for (m = mons; m; m = m->next)
963 drawbar(m);
964}
965
966void enternotify(XEvent *e) {
967 Client *c;
968 Monitor *m;
969 XCrossingEvent *ev = &e->xcrossing;
970
971 if ((ev->mode != NotifyNormal || ev->detail == NotifyInferior) &&
972 ev->window != root)
973 return;
974 c = wintoclient(ev->window);
975 m = c ? c->mon : wintomon(ev->window);
976 if (m != selmon) {
977 unfocus(selmon->sel, 1);
978 selmon = m;
979 } else if (!c || c == selmon->sel)
980 return;
981 focus(c);
982}
983
984void expose(XEvent *e) {
985 Monitor *m;
986 XExposeEvent *ev = &e->xexpose;
987
988 if (ev->count == 0 && (m = wintomon(ev->window))) {
989 drawbar(m);
990 if (m == selmon)
991 updatesystray();
992 }
993}
994
995void focus(Client *c) {
996 if (!c || !ISVISIBLE(c))
997 for (c = selmon->stack; c && !ISVISIBLE(c); c = c->snext)
998 ;
999 if (selmon->sel && selmon->sel != c)
1000 unfocus(selmon->sel, 0);
1001 if (c) {
1002 if (c->mon != selmon)
1003 selmon = c->mon;
1004 if (c->isurgent)
1005 seturgent(c, 0);
1006 detachstack(c);
1007 attachstack(c);
1008 grabbuttons(c, 1);
1009 XSetWindowBorder(dpy, c->win, scheme[SchemeSel][ColBorder].pixel);
1010 setfocus(c);
1011 } else {
1012 XSetInputFocus(dpy, root, RevertToPointerRoot, CurrentTime);
1013 XDeleteProperty(dpy, root, netatom[NetActiveWindow]);
1014 }
1015 selmon->sel = c;
1016 drawbars();
1017}
1018
1019/* there are some broken focus acquiring clients needing extra handling */
1020void focusin(XEvent *e) {
1021 XFocusChangeEvent *ev = &e->xfocus;
1022
1023 if (selmon->sel && ev->window != selmon->sel->win)
1024 setfocus(selmon->sel);
1025}
1026
1027void focusmon(const Arg *arg) {
1028 Monitor *m;
1029
1030 if (!mons->next)
1031 return;
1032 if ((m = dirtomon(arg->i)) == selmon)
1033 return;
1034 unfocus(selmon->sel, 0);
1035 selmon = m;
1036 focus(NULL);
1037}
1038
1039void focusstack(const Arg *arg) {
1040 Client *c = NULL, *i;
1041
1042 if (!selmon->sel || (selmon->sel->isfullscreen && lockfullscreen))
1043 return;
1044 if (arg->i > 0) {
1045 for (c = selmon->sel->next; c && !ISVISIBLE(c); c = c->next)
1046 ;
1047 if (!c)
1048 for (c = selmon->clients; c && !ISVISIBLE(c); c = c->next)
1049 ;
1050 } else {
1051 for (i = selmon->clients; i != selmon->sel; i = i->next)
1052 if (ISVISIBLE(i))
1053 c = i;
1054 if (!c)
1055 for (; i; i = i->next)
1056 if (ISVISIBLE(i))
1057 c = i;
1058 }
1059 if (c) {
1060 focus(c);
1061 restack(selmon);
1062 }
1063}
1064
1065Atom getatomprop(Client *c, Atom prop) {
1066 int di;
1067 unsigned long dl;
1068 unsigned char *p = NULL;
1069 Atom da, atom = None;
1070
1071 /* FIXME getatomprop should return the number of items and a pointer to
1072 * the stored data instead of this workaround */
1073 Atom req = XA_ATOM;
1074 if (prop == xatom[XembedInfo])
1075 req = xatom[XembedInfo];
1076
1077 if (XGetWindowProperty(dpy, c->win, prop, 0L, sizeof atom, False, req, &da,
1078 &di, &dl, &dl, &p) == Success &&
1079 p) {
1080 atom = *(Atom *)p;
1081 if (da == xatom[XembedInfo] && dl == 2)
1082 atom = ((Atom *)p)[1];
1083 XFree(p);
1084 }
1085 return atom;
1086}
1087
1088unsigned int getsystraywidth() {
1089 unsigned int w = 0;
1090 Client *i;
1091 if (showsystray)
1092 for (i = systray->icons; i; w += i->w + systrayspacing, i = i->next)
1093 ;
1094 return w ? w + systrayspacing : 1;
1095}
1096
1097int getrootptr(int *x, int *y) {
1098 int di;
1099 unsigned int dui;
1100 Window dummy;
1101
1102 return XQueryPointer(dpy, root, &dummy, &dummy, x, y, &di, &di, &dui);
1103}
1104
1105long getstate(Window w) {
1106 int format;
1107 long result = -1;
1108 unsigned char *p = NULL;
1109 unsigned long n, extra;
1110 Atom real;
1111
1112 if (XGetWindowProperty(dpy, w, wmatom[WMState], 0L, 2L, False,
1113 wmatom[WMState], &real, &format, &n, &extra,
1114 (unsigned char **)&p) != Success)
1115 return -1;
1116 if (n != 0)
1117 result = *p;
1118 XFree(p);
1119 return result;
1120}
1121
1122int gettextprop(Window w, Atom atom, char *text, unsigned int size) {
1123 char **list = NULL;
1124 int n;
1125 XTextProperty name;
1126
1127 if (!text || size == 0)
1128 return 0;
1129 text[0] = '\0';
1130 if (!XGetTextProperty(dpy, w, &name, atom) || !name.nitems)
1131 return 0;
1132 if (name.encoding == XA_STRING) {
1133 strncpy(text, (char *)name.value, size - 1);
1134 } else if (XmbTextPropertyToTextList(dpy, &name, &list, &n) >= Success &&
1135 n > 0 && *list) {
1136 strncpy(text, *list, size - 1);
1137 XFreeStringList(list);
1138 }
1139 text[size - 1] = '\0';
1140 XFree(name.value);
1141 return 1;
1142}
1143
1144void grabbuttons(Client *c, int focused) {
1145 updatenumlockmask();
1146 {
1147 unsigned int i, j;
1148 unsigned int modifiers[] = {0, LockMask, numlockmask,
1149 numlockmask | LockMask};
1150 XUngrabButton(dpy, AnyButton, AnyModifier, c->win);
1151 if (!focused)
1152 XGrabButton(dpy, AnyButton, AnyModifier, c->win, False, BUTTONMASK,
1153 GrabModeSync, GrabModeSync, None, None);
1154 for (i = 0; i < LENGTH(buttons); i++)
1155 if (buttons[i].click == ClkClientWin)
1156 for (j = 0; j < LENGTH(modifiers); j++)
1157 XGrabButton(dpy, buttons[i].button, buttons[i].mask | modifiers[j],
1158 c->win, False, BUTTONMASK, GrabModeAsync, GrabModeSync,
1159 None, None);
1160 }
1161}
1162
1163void grabkeys(void) {
1164 updatenumlockmask();
1165 {
1166 unsigned int i, j, k;
1167 unsigned int modifiers[] = {0, LockMask, numlockmask,
1168 numlockmask | LockMask};
1169 int start, end, skip;
1170 KeySym *syms;
1171
1172 XUngrabKey(dpy, AnyKey, AnyModifier, root);
1173 XDisplayKeycodes(dpy, &start, &end);
1174 syms = XGetKeyboardMapping(dpy, start, end - start + 1, &skip);
1175 if (!syms)
1176 return;
1177 for (k = start; k <= end; k++)
1178 for (i = 0; i < LENGTH(keys); i++)
1179 /* skip modifier codes, we do that ourselves */
1180 if (keys[i].keysym == syms[(k - start) * skip])
1181 for (j = 0; j < LENGTH(modifiers); j++)
1182 XGrabKey(dpy, k, keys[i].mod | modifiers[j], root, True,
1183 GrabModeAsync, GrabModeAsync);
1184 XFree(syms);
1185 }
1186}
1187
1188void incnmaster(const Arg *arg) {
1189 selmon->nmaster = MAX(selmon->nmaster + arg->i, 0);
1190 arrange(selmon);
1191}
1192
1193#ifdef XINERAMA
1194static int isuniquegeom(XineramaScreenInfo *unique, size_t n,
1195 XineramaScreenInfo *info) {
1196 while (n--)
1197 if (unique[n].x_org == info->x_org && unique[n].y_org == info->y_org &&
1198 unique[n].width == info->width && unique[n].height == info->height)
1199 return 0;
1200 return 1;
1201}
1202#endif /* XINERAMA */
1203
1204void keypress(XEvent *e) {
1205 unsigned int i;
1206 KeySym keysym;
1207 XKeyEvent *ev;
1208
1209 ev = &e->xkey;
1210 keysym = XKeycodeToKeysym(dpy, (KeyCode)ev->keycode, 0);
1211 for (i = 0; i < LENGTH(keys); i++)
1212 if (keysym == keys[i].keysym &&
1213 CLEANMASK(keys[i].mod) == CLEANMASK(ev->state) && keys[i].func)
1214 keys[i].func(&(keys[i].arg));
1215}
1216
1217void killclient(const Arg *arg) {
1218 if (!selmon->sel)
1219 return;
1220
1221 if (!sendevent(selmon->sel->win, wmatom[WMDelete], NoEventMask,
1222 wmatom[WMDelete], CurrentTime, 0, 0, 0)) {
1223 XGrabServer(dpy);
1224 XSetErrorHandler(xerrordummy);
1225 XSetCloseDownMode(dpy, DestroyAll);
1226 XKillClient(dpy, selmon->sel->win);
1227 XSync(dpy, False);
1228 XSetErrorHandler(xerror);
1229 XUngrabServer(dpy);
1230 }
1231}
1232
1233void manage(Window w, XWindowAttributes *wa) {
1234 Client *c, *t = NULL, *term = NULL;
1235 Window trans = None;
1236 XWindowChanges wc;
1237
1238 c = ecalloc(1, sizeof(Client));
1239 c->win = w;
1240 c->pid = winpid(w);
1241 /* geometry */
1242 c->x = c->oldx = wa->x;
1243 c->y = c->oldy = wa->y;
1244 c->w = c->oldw = wa->width;
1245 c->h = c->oldh = wa->height;
1246 c->oldbw = wa->border_width;
1247
1248 updatetitle(c);
1249 if (XGetTransientForHint(dpy, w, &trans) && (t = wintoclient(trans))) {
1250 c->mon = t->mon;
1251 c->tags = t->tags;
1252 } else {
1253 c->mon = selmon;
1254 applyrules(c);
1255 term = termforwin(c);
1256 }
1257
1258 if (c->x + WIDTH(c) > c->mon->wx + c->mon->ww)
1259 c->x = c->mon->wx + c->mon->ww - WIDTH(c);
1260 if (c->y + HEIGHT(c) > c->mon->wy + c->mon->wh)
1261 c->y = c->mon->wy + c->mon->wh - HEIGHT(c);
1262 c->x = MAX(c->x, c->mon->wx);
1263 c->y = MAX(c->y, c->mon->wy);
1264 c->bw = borderpx;
1265
1266 wc.border_width = c->bw;
1267 XConfigureWindow(dpy, w, CWBorderWidth, &wc);
1268 XSetWindowBorder(dpy, w, scheme[SchemeNorm][ColBorder].pixel);
1269 configure(c); /* propagates border_width, if size doesn't change */
1270 updatewindowtype(c);
1271 updatesizehints(c);
1272 updatewmhints(c);
1273 XSelectInput(dpy, w,
1274 EnterWindowMask | FocusChangeMask | PropertyChangeMask |
1275 StructureNotifyMask);
1276 grabbuttons(c, 0);
1277 if (!c->isfloating)
1278 c->isfloating = c->oldstate = trans != None || c->isfixed;
1279 if (c->isfloating)
1280 XRaiseWindow(dpy, c->win);
1281 attach(c);
1282 attachstack(c);
1283 XChangeProperty(dpy, root, netatom[NetClientList], XA_WINDOW, 32,
1284 PropModeAppend, (unsigned char *)&(c->win), 1);
1285 XMoveResizeWindow(dpy, c->win, c->x + 2 * sw, c->y, c->w,
1286 c->h); /* some windows require this */
1287 setclientstate(c, NormalState);
1288 if (c->mon == selmon)
1289 unfocus(selmon->sel, 0);
1290 c->mon->sel = c;
1291 arrange(c->mon);
1292 XMapWindow(dpy, c->win);
1293 if (term)
1294 swallow(term, c);
1295 focus(NULL);
1296}
1297
1298void mappingnotify(XEvent *e) {
1299 XMappingEvent *ev = &e->xmapping;
1300
1301 XRefreshKeyboardMapping(ev);
1302 if (ev->request == MappingKeyboard)
1303 grabkeys();
1304}
1305
1306void maprequest(XEvent *e) {
1307 static XWindowAttributes wa;
1308 XMapRequestEvent *ev = &e->xmaprequest;
1309
1310 Client *i;
1311 if ((i = wintosystrayicon(ev->window))) {
1312 sendevent(i->win, netatom[Xembed], StructureNotifyMask, CurrentTime,
1313 XEMBED_WINDOW_ACTIVATE, 0, systray->win, XEMBED_EMBEDDED_VERSION);
1314 resizebarwin(selmon);
1315 updatesystray();
1316 }
1317
1318 if (!XGetWindowAttributes(dpy, ev->window, &wa) || wa.override_redirect)
1319 return;
1320 if (!wintoclient(ev->window))
1321 manage(ev->window, &wa);
1322}
1323
1324void monocle(Monitor *m) {
1325 unsigned int n = 0;
1326 Client *c;
1327
1328 for (c = m->clients; c; c = c->next)
1329 if (ISVISIBLE(c))
1330 n++;
1331 if (n > 0) /* override layout symbol */
1332 snprintf(m->ltsymbol, sizeof m->ltsymbol, "[%d]", n);
1333 for (c = nexttiled(m->clients); c; c = nexttiled(c->next))
1334 resize(c, m->wx, m->wy, m->ww - 2 * c->bw, m->wh - 2 * c->bw, 0);
1335}
1336
1337void motionnotify(XEvent *e) {
1338 static Monitor *mon = NULL;
1339 Monitor *m;
1340 XMotionEvent *ev = &e->xmotion;
1341
1342 if (ev->window != root)
1343 return;
1344 if ((m = recttomon(ev->x_root, ev->y_root, 1, 1)) != mon && mon) {
1345 unfocus(selmon->sel, 1);
1346 selmon = m;
1347 focus(NULL);
1348 }
1349 mon = m;
1350}
1351
1352void movemouse(const Arg *arg) {
1353 int x, y, ocx, ocy, nx, ny;
1354 Client *c;
1355 Monitor *m;
1356 XEvent ev;
1357 Time lasttime = 0;
1358
1359 if (!(c = selmon->sel))
1360 return;
1361 if (c->isfullscreen) /* no support moving fullscreen windows by mouse */
1362 return;
1363 restack(selmon);
1364 ocx = c->x;
1365 ocy = c->y;
1366 if (XGrabPointer(dpy, root, False, MOUSEMASK, GrabModeAsync, GrabModeAsync,
1367 None, cursor[CurMove]->cursor, CurrentTime) != GrabSuccess)
1368 return;
1369 if (!getrootptr(&x, &y))
1370 return;
1371 do {
1372 XMaskEvent(dpy, MOUSEMASK | ExposureMask | SubstructureRedirectMask, &ev);
1373 switch (ev.type) {
1374 case ConfigureRequest:
1375 case Expose:
1376 case MapRequest:
1377 handler[ev.type](&ev);
1378 break;
1379 case MotionNotify:
1380 if ((ev.xmotion.time - lasttime) <= (1000 / 60))
1381 continue;
1382 lasttime = ev.xmotion.time;
1383
1384 nx = ocx + (ev.xmotion.x - x);
1385 ny = ocy + (ev.xmotion.y - y);
1386 if (abs(selmon->wx - nx) < snap)
1387 nx = selmon->wx;
1388 else if (abs((selmon->wx + selmon->ww) - (nx + WIDTH(c))) < snap)
1389 nx = selmon->wx + selmon->ww - WIDTH(c);
1390 if (abs(selmon->wy - ny) < snap)
1391 ny = selmon->wy;
1392 else if (abs((selmon->wy + selmon->wh) - (ny + HEIGHT(c))) < snap)
1393 ny = selmon->wy + selmon->wh - HEIGHT(c);
1394 if (!c->isfloating && selmon->lt[selmon->sellt]->arrange &&
1395 (abs(nx - c->x) > snap || abs(ny - c->y) > snap))
1396 togglefloating(NULL);
1397 if (!selmon->lt[selmon->sellt]->arrange || c->isfloating)
1398 resize(c, nx, ny, c->w, c->h, 1);
1399 break;
1400 }
1401 } while (ev.type != ButtonRelease);
1402 XUngrabPointer(dpy, CurrentTime);
1403 if ((m = recttomon(c->x, c->y, c->w, c->h)) != selmon) {
1404 sendmon(c, m);
1405 selmon = m;
1406 focus(NULL);
1407 }
1408}
1409
1410Client *nexttiled(Client *c) {
1411 for (; c && (c->isfloating || !ISVISIBLE(c)); c = c->next)
1412 ;
1413 return c;
1414}
1415
1416void pop(Client *c) {
1417 detach(c);
1418 attach(c);
1419 focus(c);
1420 arrange(c->mon);
1421}
1422
1423void propertynotify(XEvent *e) {
1424 Client *c;
1425 Window trans;
1426 XPropertyEvent *ev = &e->xproperty;
1427
1428 if ((c = wintosystrayicon(ev->window))) {
1429 if (ev->atom == XA_WM_NORMAL_HINTS) {
1430 updatesizehints(c);
1431 updatesystrayicongeom(c, c->w, c->h);
1432 } else
1433 updatesystrayiconstate(c, ev);
1434 resizebarwin(selmon);
1435 updatesystray();
1436 }
1437
1438 if ((ev->window == root) && (ev->atom == XA_WM_NAME))
1439 updatestatus();
1440 else if (ev->state == PropertyDelete)
1441 return; /* ignore */
1442 else if ((c = wintoclient(ev->window))) {
1443 switch (ev->atom) {
1444 default:
1445 break;
1446 case XA_WM_TRANSIENT_FOR:
1447 if (!c->isfloating && (XGetTransientForHint(dpy, c->win, &trans)) &&
1448 (c->isfloating = (wintoclient(trans)) != NULL))
1449 arrange(c->mon);
1450 break;
1451 case XA_WM_NORMAL_HINTS:
1452 c->hintsvalid = 0;
1453 break;
1454 case XA_WM_HINTS:
1455 updatewmhints(c);
1456 drawbars();
1457 break;
1458 }
1459 if (ev->atom == XA_WM_NAME || ev->atom == netatom[NetWMName]) {
1460 updatetitle(c);
1461 if (c == c->mon->sel)
1462 drawbar(c->mon);
1463 }
1464 if (ev->atom == netatom[NetWMWindowType])
1465 updatewindowtype(c);
1466 }
1467}
1468
1469void quit(const Arg *arg) { running = 0; }
1470
1471Monitor *recttomon(int x, int y, int w, int h) {
1472 Monitor *m, *r = selmon;
1473 int a, area = 0;
1474
1475 for (m = mons; m; m = m->next)
1476 if ((a = INTERSECT(x, y, w, h, m)) > area) {
1477 area = a;
1478 r = m;
1479 }
1480 return r;
1481}
1482
1483void removesystrayicon(Client *i) {
1484 Client **ii;
1485
1486 if (!showsystray || !i)
1487 return;
1488 for (ii = &systray->icons; *ii && *ii != i; ii = &(*ii)->next)
1489 ;
1490 if (ii)
1491 *ii = i->next;
1492 free(i);
1493}
1494
1495void resize(Client *c, int x, int y, int w, int h, int interact) {
1496 if (applysizehints(c, &x, &y, &w, &h, interact))
1497 resizeclient(c, x, y, w, h);
1498}
1499
1500void resizebarwin(Monitor *m) {
1501 unsigned int w = m->ww;
1502 if (showsystray && m == systraytomon(m) && !systrayonleft)
1503 w -= getsystraywidth();
1504 XMoveResizeWindow(dpy, m->barwin, m->wx, m->by, w, bh);
1505}
1506
1507void resizeclient(Client *c, int x, int y, int w, int h) {
1508 XWindowChanges wc;
1509
1510 c->oldx = c->x;
1511 c->x = wc.x = x;
1512 c->oldy = c->y;
1513 c->y = wc.y = y;
1514 c->oldw = c->w;
1515 c->w = wc.width = w;
1516 c->oldh = c->h;
1517 c->h = wc.height = h;
1518 wc.border_width = c->bw;
1519 XConfigureWindow(dpy, c->win, CWX | CWY | CWWidth | CWHeight | CWBorderWidth,
1520 &wc);
1521 configure(c);
1522 XSync(dpy, False);
1523}
1524
1525void resizerequest(XEvent *e) {
1526 XResizeRequestEvent *ev = &e->xresizerequest;
1527 Client *i;
1528
1529 if ((i = wintosystrayicon(ev->window))) {
1530 updatesystrayicongeom(i, ev->width, ev->height);
1531 resizebarwin(selmon);
1532 updatesystray();
1533 }
1534}
1535
1536void resizemouse(const Arg *arg) {
1537 int ocx, ocy, nw, nh;
1538 Client *c;
1539 Monitor *m;
1540 XEvent ev;
1541 Time lasttime = 0;
1542
1543 if (!(c = selmon->sel))
1544 return;
1545 if (c->isfullscreen) /* no support resizing fullscreen windows by mouse */
1546 return;
1547 restack(selmon);
1548 ocx = c->x;
1549 ocy = c->y;
1550 if (XGrabPointer(dpy, root, False, MOUSEMASK, GrabModeAsync, GrabModeAsync,
1551 None, cursor[CurResize]->cursor, CurrentTime) != GrabSuccess)
1552 return;
1553 XWarpPointer(dpy, None, c->win, 0, 0, 0, 0, c->w + c->bw - 1,
1554 c->h + c->bw - 1);
1555 do {
1556 XMaskEvent(dpy, MOUSEMASK | ExposureMask | SubstructureRedirectMask, &ev);
1557 switch (ev.type) {
1558 case ConfigureRequest:
1559 case Expose:
1560 case MapRequest:
1561 handler[ev.type](&ev);
1562 break;
1563 case MotionNotify:
1564 if ((ev.xmotion.time - lasttime) <= (1000 / 60))
1565 continue;
1566 lasttime = ev.xmotion.time;
1567
1568 nw = MAX(ev.xmotion.x - ocx - 2 * c->bw + 1, 1);
1569 nh = MAX(ev.xmotion.y - ocy - 2 * c->bw + 1, 1);
1570 if (c->mon->wx + nw >= selmon->wx &&
1571 c->mon->wx + nw <= selmon->wx + selmon->ww &&
1572 c->mon->wy + nh >= selmon->wy &&
1573 c->mon->wy + nh <= selmon->wy + selmon->wh) {
1574 if (!c->isfloating && selmon->lt[selmon->sellt]->arrange &&
1575 (abs(nw - c->w) > snap || abs(nh - c->h) > snap))
1576 togglefloating(NULL);
1577 }
1578 if (!selmon->lt[selmon->sellt]->arrange || c->isfloating)
1579 resize(c, c->x, c->y, nw, nh, 1);
1580 break;
1581 }
1582 } while (ev.type != ButtonRelease);
1583 XWarpPointer(dpy, None, c->win, 0, 0, 0, 0, c->w + c->bw - 1,
1584 c->h + c->bw - 1);
1585 XUngrabPointer(dpy, CurrentTime);
1586 while (XCheckMaskEvent(dpy, EnterWindowMask, &ev))
1587 ;
1588 if ((m = recttomon(c->x, c->y, c->w, c->h)) != selmon) {
1589 sendmon(c, m);
1590 selmon = m;
1591 focus(NULL);
1592 }
1593}
1594
1595void restack(Monitor *m) {
1596 Client *c;
1597 XEvent ev;
1598 XWindowChanges wc;
1599
1600 drawbar(m);
1601 if (!m->sel)
1602 return;
1603 if (m->sel->isfloating || !m->lt[m->sellt]->arrange)
1604 XRaiseWindow(dpy, m->sel->win);
1605 if (m->lt[m->sellt]->arrange) {
1606 wc.stack_mode = Below;
1607 wc.sibling = m->barwin;
1608 for (c = m->stack; c; c = c->snext)
1609 if (!c->isfloating && ISVISIBLE(c)) {
1610 XConfigureWindow(dpy, c->win, CWSibling | CWStackMode, &wc);
1611 wc.sibling = c->win;
1612 }
1613 }
1614 XSync(dpy, False);
1615 while (XCheckMaskEvent(dpy, EnterWindowMask, &ev))
1616 ;
1617}
1618
1619void run(void) {
1620 XEvent ev;
1621 /* main event loop */
1622 XSync(dpy, False);
1623 while (running && !XNextEvent(dpy, &ev))
1624 if (handler[ev.type])
1625 handler[ev.type](&ev); /* call handler */
1626}
1627
1628void scan(void) {
1629 unsigned int i, num;
1630 Window d1, d2, *wins = NULL;
1631 XWindowAttributes wa;
1632
1633 if (XQueryTree(dpy, root, &d1, &d2, &wins, &num)) {
1634 for (i = 0; i < num; i++) {
1635 if (!XGetWindowAttributes(dpy, wins[i], &wa) || wa.override_redirect ||
1636 XGetTransientForHint(dpy, wins[i], &d1))
1637 continue;
1638 if (wa.map_state == IsViewable || getstate(wins[i]) == IconicState)
1639 manage(wins[i], &wa);
1640 }
1641 for (i = 0; i < num; i++) { /* now the transients */
1642 if (!XGetWindowAttributes(dpy, wins[i], &wa))
1643 continue;
1644 if (XGetTransientForHint(dpy, wins[i], &d1) &&
1645 (wa.map_state == IsViewable || getstate(wins[i]) == IconicState))
1646 manage(wins[i], &wa);
1647 }
1648 if (wins)
1649 XFree(wins);
1650 }
1651}
1652
1653void sendmon(Client *c, Monitor *m) {
1654 if (c->mon == m)
1655 return;
1656 unfocus(c, 1);
1657 detach(c);
1658 detachstack(c);
1659 c->mon = m;
1660 c->tags = m->tagset[m->seltags]; /* assign tags of target monitor */
1661 attach(c);
1662 attachstack(c);
1663 focus(NULL);
1664 arrange(NULL);
1665}
1666
1667void setclientstate(Client *c, long state) {
1668 long data[] = {state, None};
1669
1670 XChangeProperty(dpy, c->win, wmatom[WMState], wmatom[WMState], 32,
1671 PropModeReplace, (unsigned char *)data, 2);
1672}
1673
1674int sendevent(Window w, Atom proto, int mask, long d0, long d1, long d2,
1675 long d3, long d4) {
1676 int n;
1677 Atom *protocols, mt;
1678 int exists = 0;
1679 XEvent ev;
1680
1681 if (proto == wmatom[WMTakeFocus] || proto == wmatom[WMDelete]) {
1682 mt = wmatom[WMProtocols];
1683 if (XGetWMProtocols(dpy, w, &protocols, &n)) {
1684 while (!exists && n--)
1685 exists = protocols[n] == proto;
1686 XFree(protocols);
1687 }
1688 } else {
1689 exists = True;
1690 mt = proto;
1691 }
1692
1693 if (exists) {
1694 ev.type = ClientMessage;
1695 ev.xclient.window = w;
1696 ev.xclient.message_type = mt;
1697 ev.xclient.format = 32;
1698 ev.xclient.data.l[0] = d0;
1699 ev.xclient.data.l[1] = d1;
1700 ev.xclient.data.l[2] = d2;
1701 ev.xclient.data.l[3] = d3;
1702 ev.xclient.data.l[4] = d4;
1703 XSendEvent(dpy, w, False, mask, &ev);
1704 }
1705 return exists;
1706}
1707
1708void setfocus(Client *c) {
1709 if (!c->neverfocus) {
1710 XSetInputFocus(dpy, c->win, RevertToPointerRoot, CurrentTime);
1711 XChangeProperty(dpy, root, netatom[NetActiveWindow], XA_WINDOW, 32,
1712 PropModeReplace, (unsigned char *)&(c->win), 1);
1713 }
1714 sendevent(c->win, wmatom[WMTakeFocus], NoEventMask, wmatom[WMTakeFocus],
1715 CurrentTime, 0, 0, 0);
1716}
1717
1718void setfullscreen(Client *c, int fullscreen) {
1719 if (fullscreen && !c->isfullscreen) {
1720 XChangeProperty(dpy, c->win, netatom[NetWMState], XA_ATOM, 32,
1721 PropModeReplace, (unsigned char *)&netatom[NetWMFullscreen],
1722 1);
1723 c->isfullscreen = 1;
1724 c->oldstate = c->isfloating;
1725 c->oldbw = c->bw;
1726 c->bw = 0;
1727 c->isfloating = 1;
1728 resizeclient(c, c->mon->mx, c->mon->my, c->mon->mw, c->mon->mh);
1729 XRaiseWindow(dpy, c->win);
1730 } else if (!fullscreen && c->isfullscreen) {
1731 XChangeProperty(dpy, c->win, netatom[NetWMState], XA_ATOM, 32,
1732 PropModeReplace, (unsigned char *)0, 0);
1733 c->isfullscreen = 0;
1734 c->isfloating = c->oldstate;
1735 c->bw = c->oldbw;
1736 c->x = c->oldx;
1737 c->y = c->oldy;
1738 c->w = c->oldw;
1739 c->h = c->oldh;
1740 resizeclient(c, c->x, c->y, c->w, c->h);
1741 arrange(c->mon);
1742 }
1743}
1744
1745void setgaps(int oh, int ov, int ih, int iv) {
1746 if (oh < 0)
1747 oh = 0;
1748 if (ov < 0)
1749 ov = 0;
1750 if (ih < 0)
1751 ih = 0;
1752 if (iv < 0)
1753 iv = 0;
1754
1755 selmon->gappoh = oh;
1756 selmon->gappov = ov;
1757 selmon->gappih = ih;
1758 selmon->gappiv = iv;
1759 arrange(selmon);
1760}
1761
1762void togglegaps(const Arg *arg) {
1763 enablegaps = !enablegaps;
1764 arrange(selmon);
1765}
1766
1767void defaultgaps(const Arg *arg) { setgaps(gappoh, gappov, gappih, gappiv); }
1768
1769void incrgaps(const Arg *arg) {
1770 setgaps(selmon->gappoh + arg->i, selmon->gappov + arg->i,
1771 selmon->gappih + arg->i, selmon->gappiv + arg->i);
1772}
1773
1774void incrigaps(const Arg *arg) {
1775 setgaps(selmon->gappoh, selmon->gappov, selmon->gappih + arg->i,
1776 selmon->gappiv + arg->i);
1777}
1778
1779void incrogaps(const Arg *arg) {
1780 setgaps(selmon->gappoh + arg->i, selmon->gappov + arg->i, selmon->gappih,
1781 selmon->gappiv);
1782}
1783
1784void incrohgaps(const Arg *arg) {
1785 setgaps(selmon->gappoh + arg->i, selmon->gappov, selmon->gappih,
1786 selmon->gappiv);
1787}
1788
1789void incrovgaps(const Arg *arg) {
1790 setgaps(selmon->gappoh, selmon->gappov + arg->i, selmon->gappih,
1791 selmon->gappiv);
1792}
1793
1794void incrihgaps(const Arg *arg) {
1795 setgaps(selmon->gappoh, selmon->gappov, selmon->gappih + arg->i,
1796 selmon->gappiv);
1797}
1798
1799void incrivgaps(const Arg *arg) {
1800 setgaps(selmon->gappoh, selmon->gappov, selmon->gappih,
1801 selmon->gappiv + arg->i);
1802}
1803
1804void setlayout(const Arg *arg) {
1805 if (!arg || !arg->v || arg->v != selmon->lt[selmon->sellt])
1806 selmon->sellt ^= 1;
1807 if (arg && arg->v)
1808 selmon->lt[selmon->sellt] = (Layout *)arg->v;
1809 strncpy(selmon->ltsymbol, selmon->lt[selmon->sellt]->symbol,
1810 sizeof selmon->ltsymbol);
1811 if (selmon->sel)
1812 arrange(selmon);
1813 else
1814 drawbar(selmon);
1815}
1816
1817/* arg > 1.0 will set mfact absolutely */
1818void setmfact(const Arg *arg) {
1819 float f;
1820
1821 if (!arg || !selmon->lt[selmon->sellt]->arrange)
1822 return;
1823 f = arg->f < 1.0 ? arg->f + selmon->mfact : arg->f - 1.0;
1824 if (f < 0.05 || f > 0.95)
1825 return;
1826 selmon->mfact = f;
1827 arrange(selmon);
1828}
1829
1830void setup(void) {
1831 int i;
1832 XSetWindowAttributes wa;
1833 Atom utf8string;
1834 struct sigaction sa;
1835
1836 /* do not transform children into zombies when they terminate */
1837 sigemptyset(&sa.sa_mask);
1838 sa.sa_flags = SA_NOCLDSTOP | SA_NOCLDWAIT | SA_RESTART;
1839 sa.sa_handler = SIG_IGN;
1840 sigaction(SIGCHLD, &sa, NULL);
1841
1842 /* clean up any zombies (inherited from .xinitrc etc) immediately */
1843 while (waitpid(-1, NULL, WNOHANG) > 0)
1844 ;
1845
1846 /* init screen */
1847 screen = DefaultScreen(dpy);
1848 sw = DisplayWidth(dpy, screen);
1849 sh = DisplayHeight(dpy, screen);
1850 root = RootWindow(dpy, screen);
1851 drw = drw_create(dpy, screen, root, sw, sh);
1852 if (!drw_fontset_create(drw, fonts, LENGTH(fonts)))
1853 die("no fonts could be loaded.");
1854 lrpad = drw->fonts->h;
1855 bh = drw->fonts->h + 2;
1856 updategeom();
1857 /* init atoms */
1858 utf8string = XInternAtom(dpy, "UTF8_STRING", False);
1859 wmatom[WMProtocols] = XInternAtom(dpy, "WM_PROTOCOLS", False);
1860 wmatom[WMDelete] = XInternAtom(dpy, "WM_DELETE_WINDOW", False);
1861 wmatom[WMState] = XInternAtom(dpy, "WM_STATE", False);
1862 wmatom[WMTakeFocus] = XInternAtom(dpy, "WM_TAKE_FOCUS", False);
1863 netatom[NetActiveWindow] = XInternAtom(dpy, "_NET_ACTIVE_WINDOW", False);
1864 netatom[NetSupported] = XInternAtom(dpy, "_NET_SUPPORTED", False);
1865 netatom[NetSystemTray] = XInternAtom(dpy, "_NET_SYSTEM_TRAY_S0", False);
1866 netatom[NetSystemTrayOP] = XInternAtom(dpy, "_NET_SYSTEM_TRAY_OPCODE", False);
1867 netatom[NetSystemTrayOrientation] =
1868 XInternAtom(dpy, "_NET_SYSTEM_TRAY_ORIENTATION", False);
1869 netatom[NetSystemTrayOrientationHorz] =
1870 XInternAtom(dpy, "_NET_SYSTEM_TRAY_ORIENTATION_HORZ", False);
1871 netatom[NetWMName] = XInternAtom(dpy, "_NET_WM_NAME", False);
1872 netatom[NetWMState] = XInternAtom(dpy, "_NET_WM_STATE", False);
1873 netatom[NetWMCheck] = XInternAtom(dpy, "_NET_SUPPORTING_WM_CHECK", False);
1874 netatom[NetWMFullscreen] =
1875 XInternAtom(dpy, "_NET_WM_STATE_FULLSCREEN", False);
1876 netatom[NetWMWindowType] = XInternAtom(dpy, "_NET_WM_WINDOW_TYPE", False);
1877 netatom[NetWMWindowTypeDialog] =
1878 XInternAtom(dpy, "_NET_WM_WINDOW_TYPE_DIALOG", False);
1879 netatom[NetClientList] = XInternAtom(dpy, "_NET_CLIENT_LIST", False);
1880 xatom[Manager] = XInternAtom(dpy, "MANAGER", False);
1881 xatom[Xembed] = XInternAtom(dpy, "_XEMBED", False);
1882 xatom[XembedInfo] = XInternAtom(dpy, "_XEMBED_INFO", False);
1883 /* init cursors */
1884 cursor[CurNormal] = drw_cur_create(drw, XC_left_ptr);
1885 cursor[CurResize] = drw_cur_create(drw, XC_sizing);
1886 cursor[CurMove] = drw_cur_create(drw, XC_fleur);
1887 /* init appearance */
1888 scheme = ecalloc(LENGTH(colors), sizeof(Clr *));
1889 for (i = 0; i < LENGTH(colors); i++)
1890 scheme[i] = drw_scm_create(drw, colors[i], 3);
1891 /* init system tray */
1892 updatesystray();
1893 /* init bars */
1894 updatebars();
1895 updatestatus();
1896 /* supporting window for NetWMCheck */
1897 wmcheckwin = XCreateSimpleWindow(dpy, root, 0, 0, 1, 1, 0, 0, 0);
1898 XChangeProperty(dpy, wmcheckwin, netatom[NetWMCheck], XA_WINDOW, 32,
1899 PropModeReplace, (unsigned char *)&wmcheckwin, 1);
1900 XChangeProperty(dpy, wmcheckwin, netatom[NetWMName], utf8string, 8,
1901 PropModeReplace, (unsigned char *)"dwm", 3);
1902 XChangeProperty(dpy, root, netatom[NetWMCheck], XA_WINDOW, 32,
1903 PropModeReplace, (unsigned char *)&wmcheckwin, 1);
1904 /* EWMH support per view */
1905 XChangeProperty(dpy, root, netatom[NetSupported], XA_ATOM, 32,
1906 PropModeReplace, (unsigned char *)netatom, NetLast);
1907 XDeleteProperty(dpy, root, netatom[NetClientList]);
1908 /* select events */
1909 wa.cursor = cursor[CurNormal]->cursor;
1910 wa.event_mask = SubstructureRedirectMask | SubstructureNotifyMask |
1911 ButtonPressMask | PointerMotionMask | EnterWindowMask |
1912 LeaveWindowMask | StructureNotifyMask | PropertyChangeMask;
1913 XChangeWindowAttributes(dpy, root, CWEventMask | CWCursor, &wa);
1914 XSelectInput(dpy, root, wa.event_mask);
1915 grabkeys();
1916 focus(NULL);
1917}
1918
1919void seturgent(Client *c, int urg) {
1920 XWMHints *wmh;
1921
1922 c->isurgent = urg;
1923 if (!(wmh = XGetWMHints(dpy, c->win)))
1924 return;
1925 wmh->flags = urg ? (wmh->flags | XUrgencyHint) : (wmh->flags & ~XUrgencyHint);
1926 XSetWMHints(dpy, c->win, wmh);
1927 XFree(wmh);
1928}
1929
1930void showhide(Client *c) {
1931 if (!c)
1932 return;
1933 if (ISVISIBLE(c)) {
1934 /* show clients top down */
1935 XMoveWindow(dpy, c->win, c->x, c->y);
1936 if ((!c->mon->lt[c->mon->sellt]->arrange || c->isfloating) &&
1937 !c->isfullscreen)
1938 resize(c, c->x, c->y, c->w, c->h, 0);
1939 showhide(c->snext);
1940 } else {
1941 /* hide clients bottom up */
1942 showhide(c->snext);
1943 XMoveWindow(dpy, c->win, WIDTH(c) * -2, c->y);
1944 }
1945}
1946
1947void spawn(const Arg *arg) {
1948 struct sigaction sa;
1949
1950 if (arg->v == dmenucmd)
1951 dmenumon[0] = '0' + selmon->num;
1952 if (fork() == 0) {
1953 if (dpy)
1954 close(ConnectionNumber(dpy));
1955 setsid();
1956
1957 sigemptyset(&sa.sa_mask);
1958 sa.sa_flags = 0;
1959 sa.sa_handler = SIG_DFL;
1960 sigaction(SIGCHLD, &sa, NULL);
1961
1962 execvp(((char **)arg->v)[0], (char **)arg->v);
1963 die("dwm: execvp '%s' failed:", ((char **)arg->v)[0]);
1964 }
1965}
1966
1967void tag(const Arg *arg) {
1968 if (selmon->sel && arg->ui & TAGMASK) {
1969 selmon->sel->tags = arg->ui & TAGMASK;
1970 focus(NULL);
1971 arrange(selmon);
1972 }
1973}
1974
1975void tagmon(const Arg *arg) {
1976 if (!selmon->sel || !mons->next)
1977 return;
1978 sendmon(selmon->sel, dirtomon(arg->i));
1979}
1980
1981void tile(Monitor *m) {
1982 unsigned int i, n, h, r, oe = enablegaps, ie = enablegaps, mw, my, ty;
1983 Client *c;
1984
1985 for (n = 0, c = nexttiled(m->clients); c; c = nexttiled(c->next), n++)
1986 ;
1987 if (n == 0)
1988 return;
1989 if (smartgaps == n) {
1990 oe = 0; // outer gaps disabled
1991 }
1992
1993 if (n > m->nmaster)
1994 mw = m->nmaster ? (m->ww + m->gappiv * ie) * m->mfact : 0;
1995 else
1996 mw = m->ww - 2 * m->gappov * oe + m->gappiv * ie;
1997 for (i = 0, my = ty = m->gappoh * oe, c = nexttiled(m->clients); c;
1998 c = nexttiled(c->next), i++)
1999 if (i < m->nmaster) {
2000 r = MIN(n, m->nmaster) - i;
2001 h = (m->wh - my - m->gappoh * oe - m->gappih * ie * (r - 1)) / r;
2002 resize(c, m->wx + m->gappov * oe, m->wy + my,
2003 mw - (2 * c->bw) - m->gappiv * ie, h - (2 * c->bw), 0);
2004 my += HEIGHT(c) + m->gappih * ie;
2005
2006 if (my + HEIGHT(c) < m->wh)
2007 my += HEIGHT(c);
2008 } else {
2009 r = n - i;
2010 h = (m->wh - ty - m->gappoh * oe - m->gappih * ie * (r - 1)) / r;
2011 resize(c, m->wx + mw + m->gappov * oe, m->wy + ty,
2012 m->ww - mw - (2 * c->bw) - 2 * m->gappov * oe, h - (2 * c->bw), 0);
2013 ty += HEIGHT(c) + m->gappih * ie;
2014 }
2015}
2016
2017void togglebar(const Arg *arg) {
2018 selmon->showbar = !selmon->showbar;
2019 updatebarpos(selmon);
2020 resizebarwin(selmon);
2021 if (showsystray) {
2022 XWindowChanges wc;
2023 if (!selmon->showbar)
2024 wc.y = -bh;
2025 else if (selmon->showbar) {
2026 wc.y = 0;
2027 if (!selmon->topbar)
2028 wc.y = selmon->mh - bh;
2029 }
2030 XConfigureWindow(dpy, systray->win, CWY, &wc);
2031 }
2032 arrange(selmon);
2033}
2034
2035void togglefloating(const Arg *arg) {
2036 if (!selmon->sel)
2037 return;
2038 if (selmon->sel->isfullscreen) /* no support for fullscreen windows */
2039 return;
2040 selmon->sel->isfloating = !selmon->sel->isfloating || selmon->sel->isfixed;
2041 if (selmon->sel->isfloating)
2042 resize(selmon->sel, selmon->sel->x, selmon->sel->y, selmon->sel->w,
2043 selmon->sel->h, 0);
2044 arrange(selmon);
2045}
2046
2047void togglefullscr(const Arg *arg) {
2048 if (selmon->sel)
2049 setfullscreen(selmon->sel, !selmon->sel->isfullscreen);
2050}
2051
2052void toggletag(const Arg *arg) {
2053 unsigned int newtags;
2054
2055 if (!selmon->sel)
2056 return;
2057 newtags = selmon->sel->tags ^ (arg->ui & TAGMASK);
2058 if (newtags) {
2059 selmon->sel->tags = newtags;
2060 focus(NULL);
2061 arrange(selmon);
2062 }
2063}
2064
2065void toggleview(const Arg *arg) {
2066 unsigned int newtagset =
2067 selmon->tagset[selmon->seltags] ^ (arg->ui & TAGMASK);
2068
2069 if (newtagset) {
2070 selmon->tagset[selmon->seltags] = newtagset;
2071 focus(NULL);
2072 arrange(selmon);
2073 }
2074}
2075
2076void unfocus(Client *c, int setfocus) {
2077 if (!c)
2078 return;
2079 grabbuttons(c, 0);
2080 XSetWindowBorder(dpy, c->win, scheme[SchemeNorm][ColBorder].pixel);
2081 if (setfocus) {
2082 XSetInputFocus(dpy, root, RevertToPointerRoot, CurrentTime);
2083 XDeleteProperty(dpy, root, netatom[NetActiveWindow]);
2084 }
2085}
2086
2087void unmanage(Client *c, int destroyed) {
2088 Monitor *m = c->mon;
2089 XWindowChanges wc;
2090
2091 if (c->swallowing) {
2092 unswallow(c);
2093 return;
2094 }
2095
2096 Client *s = swallowingclient(c->win);
2097 if (s) {
2098 free(s->swallowing);
2099 s->swallowing = NULL;
2100 arrange(m);
2101 focus(NULL);
2102 return;
2103 }
2104
2105 detach(c);
2106 detachstack(c);
2107 if (!destroyed) {
2108 wc.border_width = c->oldbw;
2109 XGrabServer(dpy); /* avoid race conditions */
2110 XSetErrorHandler(xerrordummy);
2111 XSelectInput(dpy, c->win, NoEventMask);
2112 XConfigureWindow(dpy, c->win, CWBorderWidth, &wc); /* restore border */
2113 XUngrabButton(dpy, AnyButton, AnyModifier, c->win);
2114 setclientstate(c, WithdrawnState);
2115 XSync(dpy, False);
2116 XSetErrorHandler(xerror);
2117 XUngrabServer(dpy);
2118 }
2119 free(c);
2120
2121 if (!s) {
2122 arrange(m);
2123 focus(NULL);
2124 updateclientlist();
2125 }
2126}
2127
2128void unmapnotify(XEvent *e) {
2129 Client *c;
2130 XUnmapEvent *ev = &e->xunmap;
2131
2132 if ((c = wintoclient(ev->window))) {
2133 if (ev->send_event)
2134 setclientstate(c, WithdrawnState);
2135 else
2136 unmanage(c, 0);
2137 } else if ((c = wintosystrayicon(ev->window))) {
2138 /* KLUDGE! sometimes icons occasionally unmap their windows, but do
2139 * _not_ destroy them. We map those windows back */
2140 XMapRaised(dpy, c->win);
2141 updatesystray();
2142 }
2143}
2144
2145void updatebars(void) {
2146 unsigned int w;
2147 Monitor *m;
2148 XSetWindowAttributes wa = {.override_redirect = True,
2149 .background_pixmap = ParentRelative,
2150 .event_mask = ButtonPressMask | ExposureMask};
2151 XClassHint ch = {"dwm", "dwm"};
2152 for (m = mons; m; m = m->next) {
2153 if (m->barwin)
2154 continue;
2155 w = m->ww;
2156 if (showsystray && m == systraytomon(m))
2157 w -= getsystraywidth();
2158 m->barwin = XCreateWindow(
2159 dpy, root, m->wx, m->by, w, bh, 0, DefaultDepth(dpy, screen),
2160 CopyFromParent, DefaultVisual(dpy, screen),
2161 CWOverrideRedirect | CWBackPixmap | CWEventMask, &wa);
2162 XDefineCursor(dpy, m->barwin, cursor[CurNormal]->cursor);
2163 if (showsystray && m == systraytomon(m))
2164 XMapRaised(dpy, systray->win);
2165 XMapRaised(dpy, m->barwin);
2166 XSetClassHint(dpy, m->barwin, &ch);
2167 }
2168}
2169
2170void updatebarpos(Monitor *m) {
2171 m->wy = m->my;
2172 m->wh = m->mh;
2173 if (m->showbar) {
2174 m->wh -= bh;
2175 m->by = m->topbar ? m->wy : m->wy + m->wh;
2176 m->wy = m->topbar ? m->wy + bh : m->wy;
2177 } else
2178 m->by = -bh;
2179}
2180
2181void updateclientlist() {
2182 Client *c;
2183 Monitor *m;
2184
2185 XDeleteProperty(dpy, root, netatom[NetClientList]);
2186 for (m = mons; m; m = m->next)
2187 for (c = m->clients; c; c = c->next)
2188 XChangeProperty(dpy, root, netatom[NetClientList], XA_WINDOW, 32,
2189 PropModeAppend, (unsigned char *)&(c->win), 1);
2190}
2191
2192int updategeom(void) {
2193 int dirty = 0;
2194
2195#ifdef XINERAMA
2196 if (XineramaIsActive(dpy)) {
2197 int i, j, n, nn;
2198 Client *c;
2199 Monitor *m;
2200 XineramaScreenInfo *info = XineramaQueryScreens(dpy, &nn);
2201 XineramaScreenInfo *unique = NULL;
2202
2203 for (n = 0, m = mons; m; m = m->next, n++)
2204 ;
2205 /* only consider unique geometries as separate screens */
2206 unique = ecalloc(nn, sizeof(XineramaScreenInfo));
2207 for (i = 0, j = 0; i < nn; i++)
2208 if (isuniquegeom(unique, j, &info[i]))
2209 memcpy(&unique[j++], &info[i], sizeof(XineramaScreenInfo));
2210 XFree(info);
2211 nn = j;
2212
2213 /* new monitors if nn > n */
2214 for (i = n; i < nn; i++) {
2215 for (m = mons; m && m->next; m = m->next)
2216 ;
2217 if (m)
2218 m->next = createmon();
2219 else
2220 mons = createmon();
2221 }
2222 for (i = 0, m = mons; i < nn && m; m = m->next, i++)
2223 if (i >= n || unique[i].x_org != m->mx || unique[i].y_org != m->my ||
2224 unique[i].width != m->mw || unique[i].height != m->mh) {
2225 dirty = 1;
2226 m->num = i;
2227 m->mx = m->wx = unique[i].x_org;
2228 m->my = m->wy = unique[i].y_org;
2229 m->mw = m->ww = unique[i].width;
2230 m->mh = m->wh = unique[i].height;
2231 updatebarpos(m);
2232 }
2233 /* removed monitors if n > nn */
2234 for (i = nn; i < n; i++) {
2235 for (m = mons; m && m->next; m = m->next)
2236 ;
2237 while ((c = m->clients)) {
2238 dirty = 1;
2239 m->clients = c->next;
2240 detachstack(c);
2241 c->mon = mons;
2242 attach(c);
2243 attachstack(c);
2244 }
2245 if (m == selmon)
2246 selmon = mons;
2247 cleanupmon(m);
2248 }
2249 free(unique);
2250 } else
2251#endif /* XINERAMA */
2252 { /* default monitor setup */
2253 if (!mons)
2254 mons = createmon();
2255 if (mons->mw != sw || mons->mh != sh) {
2256 dirty = 1;
2257 mons->mw = mons->ww = sw;
2258 mons->mh = mons->wh = sh;
2259 updatebarpos(mons);
2260 }
2261 }
2262 if (dirty) {
2263 selmon = mons;
2264 selmon = wintomon(root);
2265 }
2266 return dirty;
2267}
2268
2269void updatenumlockmask(void) {
2270 unsigned int i, j;
2271 XModifierKeymap *modmap;
2272
2273 numlockmask = 0;
2274 modmap = XGetModifierMapping(dpy);
2275 for (i = 0; i < 8; i++)
2276 for (j = 0; j < modmap->max_keypermod; j++)
2277 if (modmap->modifiermap[i * modmap->max_keypermod + j] ==
2278 XKeysymToKeycode(dpy, XK_Num_Lock))
2279 numlockmask = (1 << i);
2280 XFreeModifiermap(modmap);
2281}
2282
2283void updatesizehints(Client *c) {
2284 long msize;
2285 XSizeHints size;
2286
2287 if (!XGetWMNormalHints(dpy, c->win, &size, &msize))
2288 /* size is uninitialized, ensure that size.flags aren't used */
2289 size.flags = PSize;
2290 if (size.flags & PBaseSize) {
2291 c->basew = size.base_width;
2292 c->baseh = size.base_height;
2293 } else if (size.flags & PMinSize) {
2294 c->basew = size.min_width;
2295 c->baseh = size.min_height;
2296 } else
2297 c->basew = c->baseh = 0;
2298 if (size.flags & PResizeInc) {
2299 c->incw = size.width_inc;
2300 c->inch = size.height_inc;
2301 } else
2302 c->incw = c->inch = 0;
2303 if (size.flags & PMaxSize) {
2304 c->maxw = size.max_width;
2305 c->maxh = size.max_height;
2306 } else
2307 c->maxw = c->maxh = 0;
2308 if (size.flags & PMinSize) {
2309 c->minw = size.min_width;
2310 c->minh = size.min_height;
2311 } else if (size.flags & PBaseSize) {
2312 c->minw = size.base_width;
2313 c->minh = size.base_height;
2314 } else
2315 c->minw = c->minh = 0;
2316 if (size.flags & PAspect) {
2317 c->mina = (float)size.min_aspect.y / size.min_aspect.x;
2318 c->maxa = (float)size.max_aspect.x / size.max_aspect.y;
2319 } else
2320 c->maxa = c->mina = 0.0;
2321 c->isfixed = (c->maxw && c->maxh && c->maxw == c->minw && c->maxh == c->minh);
2322 c->hintsvalid = 1;
2323}
2324
2325void updatestatus(void) {
2326 if (!gettextprop(root, XA_WM_NAME, stext, sizeof(stext)))
2327 strcpy(stext, "dwm-" VERSION);
2328 drawbar(selmon);
2329 updatesystray();
2330}
2331
2332void updatesystrayicongeom(Client *i, int w, int h) {
2333 if (i) {
2334 i->h = bh;
2335 if (w == h)
2336 i->w = bh;
2337 else if (h == bh)
2338 i->w = w;
2339 else
2340 i->w = (int)((float)bh * ((float)w / (float)h));
2341 applysizehints(i, &(i->x), &(i->y), &(i->w), &(i->h), False);
2342 /* force icons into the systray dimensions if they don't want to */
2343 if (i->h > bh) {
2344 if (i->w == i->h)
2345 i->w = bh;
2346 else
2347 i->w = (int)((float)bh * ((float)i->w / (float)i->h));
2348 i->h = bh;
2349 }
2350 }
2351}
2352
2353void updatesystrayiconstate(Client *i, XPropertyEvent *ev) {
2354 long flags;
2355 int code = 0;
2356
2357 if (!showsystray || !i || ev->atom != xatom[XembedInfo] ||
2358 !(flags = getatomprop(i, xatom[XembedInfo])))
2359 return;
2360
2361 if (flags & XEMBED_MAPPED && !i->tags) {
2362 i->tags = 1;
2363 code = XEMBED_WINDOW_ACTIVATE;
2364 XMapRaised(dpy, i->win);
2365 setclientstate(i, NormalState);
2366 } else if (!(flags & XEMBED_MAPPED) && i->tags) {
2367 i->tags = 0;
2368 code = XEMBED_WINDOW_DEACTIVATE;
2369 XUnmapWindow(dpy, i->win);
2370 setclientstate(i, WithdrawnState);
2371 } else
2372 return;
2373 sendevent(i->win, xatom[Xembed], StructureNotifyMask, CurrentTime, code, 0,
2374 systray->win, XEMBED_EMBEDDED_VERSION);
2375}
2376
2377void updatesystray(void) {
2378 XSetWindowAttributes wa;
2379 XWindowChanges wc;
2380 Client *i;
2381 Monitor *m = systraytomon(NULL);
2382 unsigned int x = m->mx + m->mw;
2383 unsigned int sw = TEXTW(stext) - lrpad + systrayspacing;
2384 unsigned int w = 1;
2385
2386 if (!showsystray)
2387 return;
2388 if (systrayonleft)
2389 x -= sw + lrpad / 2;
2390 if (!systray) {
2391 /* init systray */
2392 if (!(systray = (Systray *)calloc(1, sizeof(Systray))))
2393 die("fatal: could not malloc() %u bytes\n", sizeof(Systray));
2394 systray->win = XCreateSimpleWindow(dpy, root, x, m->by, w, bh, 0, 0,
2395 scheme[SchemeSel][ColBg].pixel);
2396 wa.event_mask = ButtonPressMask | ExposureMask;
2397 wa.override_redirect = True;
2398 wa.background_pixel = scheme[SchemeNorm][ColBg].pixel;
2399 XSelectInput(dpy, systray->win, SubstructureNotifyMask);
2400 XChangeProperty(dpy, systray->win, netatom[NetSystemTrayOrientation],
2401 XA_CARDINAL, 32, PropModeReplace,
2402 (unsigned char *)&netatom[NetSystemTrayOrientationHorz], 1);
2403 XChangeWindowAttributes(
2404 dpy, systray->win, CWEventMask | CWOverrideRedirect | CWBackPixel, &wa);
2405 XMapRaised(dpy, systray->win);
2406 XSetSelectionOwner(dpy, netatom[NetSystemTray], systray->win, CurrentTime);
2407 if (XGetSelectionOwner(dpy, netatom[NetSystemTray]) == systray->win) {
2408 sendevent(root, xatom[Manager], StructureNotifyMask, CurrentTime,
2409 netatom[NetSystemTray], systray->win, 0, 0);
2410 XSync(dpy, False);
2411 } else {
2412 fprintf(stderr, "dwm: unable to obtain system tray.\n");
2413 free(systray);
2414 systray = NULL;
2415 return;
2416 }
2417 }
2418 for (w = 0, i = systray->icons; i; i = i->next) {
2419 /* make sure the background color stays the same */
2420 wa.background_pixel = scheme[SchemeNorm][ColBg].pixel;
2421 XChangeWindowAttributes(dpy, i->win, CWBackPixel, &wa);
2422 XMapRaised(dpy, i->win);
2423 w += systrayspacing;
2424 i->x = w;
2425 XMoveResizeWindow(dpy, i->win, i->x, 0, i->w, i->h);
2426 w += i->w;
2427 if (i->mon != m)
2428 i->mon = m;
2429 }
2430 w = w ? w + systrayspacing : 1;
2431 x -= w;
2432 XMoveResizeWindow(dpy, systray->win, x, m->by, w, bh);
2433 wc.x = x;
2434 wc.y = m->by;
2435 wc.width = w;
2436 wc.height = bh;
2437 wc.stack_mode = Above;
2438 wc.sibling = m->barwin;
2439 XConfigureWindow(dpy, systray->win,
2440 CWX | CWY | CWWidth | CWHeight | CWSibling | CWStackMode,
2441 &wc);
2442 XMapWindow(dpy, systray->win);
2443 XMapSubwindows(dpy, systray->win);
2444 /* redraw background */
2445 XSetForeground(dpy, drw->gc, scheme[SchemeNorm][ColBg].pixel);
2446 XFillRectangle(dpy, systray->win, drw->gc, 0, 0, w, bh);
2447 XSync(dpy, False);
2448}
2449
2450void updatetitle(Client *c) {
2451 if (!gettextprop(c->win, netatom[NetWMName], c->name, sizeof c->name))
2452 gettextprop(c->win, XA_WM_NAME, c->name, sizeof c->name);
2453 if (c->name[0] == '\0') /* hack to mark broken clients */
2454 strcpy(c->name, broken);
2455}
2456
2457void updatewindowtype(Client *c) {
2458 Atom state = getatomprop(c, netatom[NetWMState]);
2459 Atom wtype = getatomprop(c, netatom[NetWMWindowType]);
2460
2461 if (state == netatom[NetWMFullscreen])
2462 setfullscreen(c, 1);
2463 if (wtype == netatom[NetWMWindowTypeDialog])
2464 c->isfloating = 1;
2465}
2466
2467void updatewmhints(Client *c) {
2468 XWMHints *wmh;
2469
2470 if ((wmh = XGetWMHints(dpy, c->win))) {
2471 if (c == selmon->sel && wmh->flags & XUrgencyHint) {
2472 wmh->flags &= ~XUrgencyHint;
2473 XSetWMHints(dpy, c->win, wmh);
2474 } else
2475 c->isurgent = (wmh->flags & XUrgencyHint) ? 1 : 0;
2476 if (wmh->flags & InputHint)
2477 c->neverfocus = !wmh->input;
2478 else
2479 c->neverfocus = 0;
2480 XFree(wmh);
2481 }
2482}
2483
2484void view(const Arg *arg) {
2485 if ((arg->ui & TAGMASK) == selmon->tagset[selmon->seltags])
2486 return;
2487 selmon->seltags ^= 1; /* toggle sel tagset */
2488 if (arg->ui & TAGMASK)
2489 selmon->tagset[selmon->seltags] = arg->ui & TAGMASK;
2490 focus(NULL);
2491 arrange(selmon);
2492}
2493
2494pid_t winpid(Window w) {
2495
2496 pid_t result = 0;
2497
2498#ifdef __linux__
2499 xcb_res_client_id_spec_t spec = {0};
2500 spec.client = w;
2501 spec.mask = XCB_RES_CLIENT_ID_MASK_LOCAL_CLIENT_PID;
2502
2503 xcb_generic_error_t *e = NULL;
2504 xcb_res_query_client_ids_cookie_t c =
2505 xcb_res_query_client_ids(xcon, 1, &spec);
2506 xcb_res_query_client_ids_reply_t *r =
2507 xcb_res_query_client_ids_reply(xcon, c, &e);
2508
2509 if (!r)
2510 return (pid_t)0;
2511
2512 xcb_res_client_id_value_iterator_t i =
2513 xcb_res_query_client_ids_ids_iterator(r);
2514 for (; i.rem; xcb_res_client_id_value_next(&i)) {
2515 spec = i.data->spec;
2516 if (spec.mask & XCB_RES_CLIENT_ID_MASK_LOCAL_CLIENT_PID) {
2517 uint32_t *t = xcb_res_client_id_value_value(i.data);
2518 result = *t;
2519 break;
2520 }
2521 }
2522
2523 free(r);
2524
2525 if (result == (pid_t)-1)
2526 result = 0;
2527
2528#endif /* __linux__ */
2529
2530#ifdef __OpenBSD__
2531 Atom type;
2532 int format;
2533 unsigned long len, bytes;
2534 unsigned char *prop;
2535 pid_t ret;
2536
2537 if (XGetWindowProperty(dpy, w, XInternAtom(dpy, "_NET_WM_PID", 0), 0, 1,
2538 False, AnyPropertyType, &type, &format, &len, &bytes,
2539 &prop) != Success ||
2540 !prop)
2541 return 0;
2542
2543 ret = *(pid_t *)prop;
2544 XFree(prop);
2545 result = ret;
2546
2547#endif /* __OpenBSD__ */
2548 return result;
2549}
2550
2551pid_t getparentprocess(pid_t p) {
2552 unsigned int v = 0;
2553
2554#ifdef __linux__
2555 FILE *f;
2556 char buf[256];
2557 snprintf(buf, sizeof(buf) - 1, "/proc/%u/stat", (unsigned)p);
2558
2559 if (!(f = fopen(buf, "r")))
2560 return 0;
2561
2562 fscanf(f, "%*u %*s %*c %u", &v);
2563 fclose(f);
2564#endif /* __linux__ */
2565
2566#ifdef __OpenBSD__
2567 int n;
2568 kvm_t *kd;
2569 struct kinfo_proc *kp;
2570
2571 kd = kvm_openfiles(NULL, NULL, NULL, KVM_NO_FILES, NULL);
2572 if (!kd)
2573 return 0;
2574
2575 kp = kvm_getprocs(kd, KERN_PROC_PID, p, sizeof(*kp), &n);
2576 v = kp->p_ppid;
2577#endif /* __OpenBSD__ */
2578
2579 return (pid_t)v;
2580}
2581
2582int isdescprocess(pid_t p, pid_t c) {
2583 while (p != c && c != 0)
2584 c = getparentprocess(c);
2585
2586 return (int)c;
2587}
2588
2589Client *termforwin(const Client *w) {
2590 Client *c;
2591 Monitor *m;
2592
2593 if (!w->pid || w->isterminal)
2594 return NULL;
2595
2596 for (m = mons; m; m = m->next) {
2597 for (c = m->clients; c; c = c->next) {
2598 if (c->isterminal && !c->swallowing && c->pid &&
2599 isdescprocess(c->pid, w->pid))
2600 return c;
2601 }
2602 }
2603
2604 return NULL;
2605}
2606
2607Client *swallowingclient(Window w) {
2608 Client *c;
2609 Monitor *m;
2610
2611 for (m = mons; m; m = m->next) {
2612 for (c = m->clients; c; c = c->next) {
2613 if (c->swallowing && c->swallowing->win == w)
2614 return c;
2615 }
2616 }
2617
2618 return NULL;
2619}
2620
2621Client *wintoclient(Window w) {
2622 Client *c;
2623 Monitor *m;
2624
2625 for (m = mons; m; m = m->next)
2626 for (c = m->clients; c; c = c->next)
2627 if (c->win == w)
2628 return c;
2629 return NULL;
2630}
2631
2632Client *wintosystrayicon(Window w) {
2633 Client *i = NULL;
2634
2635 if (!showsystray || !w)
2636 return i;
2637 for (i = systray->icons; i && i->win != w; i = i->next)
2638 ;
2639 return i;
2640}
2641
2642Monitor *wintomon(Window w) {
2643 int x, y;
2644 Client *c;
2645 Monitor *m;
2646
2647 if (w == root && getrootptr(&x, &y))
2648 return recttomon(x, y, 1, 1);
2649 for (m = mons; m; m = m->next)
2650 if (w == m->barwin)
2651 return m;
2652 if ((c = wintoclient(w)))
2653 return c->mon;
2654 return selmon;
2655}
2656
2657/* There's no way to check accesses to destroyed windows, thus those cases are
2658 * ignored (especially on UnmapNotify's). Other types of errors call Xlibs
2659 * default error handler, which may call exit. */
2660int xerror(Display *dpy, XErrorEvent *ee) {
2661 if (ee->error_code == BadWindow ||
2662 (ee->request_code == X_SetInputFocus && ee->error_code == BadMatch) ||
2663 (ee->request_code == X_PolyText8 && ee->error_code == BadDrawable) ||
2664 (ee->request_code == X_PolyFillRectangle &&
2665 ee->error_code == BadDrawable) ||
2666 (ee->request_code == X_PolySegment && ee->error_code == BadDrawable) ||
2667 (ee->request_code == X_ConfigureWindow && ee->error_code == BadMatch) ||
2668 (ee->request_code == X_GrabButton && ee->error_code == BadAccess) ||
2669 (ee->request_code == X_GrabKey && ee->error_code == BadAccess) ||
2670 (ee->request_code == X_CopyArea && ee->error_code == BadDrawable))
2671 return 0;
2672 fprintf(stderr, "dwm: fatal error: request code=%d, error code=%d\n",
2673 ee->request_code, ee->error_code);
2674 return xerrorxlib(dpy, ee); /* may call exit */
2675}
2676
2677int xerrordummy(Display *dpy, XErrorEvent *ee) { return 0; }
2678
2679/* Startup Error handler to check if another window manager
2680 * is already running. */
2681int xerrorstart(Display *dpy, XErrorEvent *ee) {
2682 die("dwm: another window manager is already running");
2683 return -1;
2684}
2685
2686Monitor *systraytomon(Monitor *m) {
2687 Monitor *t;
2688 int i, n;
2689 if (!systraypinning) {
2690 if (!m)
2691 return selmon;
2692 return m == selmon ? m : NULL;
2693 }
2694 for (n = 1, t = mons; t && t->next; n++, t = t->next)
2695 ;
2696 for (i = 1, t = mons; t && t->next && i < systraypinning; i++, t = t->next)
2697 ;
2698 if (systraypinningfailfirst && n < systraypinning)
2699 return mons;
2700 return t;
2701}
2702
2703void zoom(const Arg *arg) {
2704 Client *c = selmon->sel;
2705
2706 if (!selmon->lt[selmon->sellt]->arrange || !c || c->isfloating)
2707 return;
2708 if (c == nexttiled(selmon->clients) && !(c = nexttiled(c->next)))
2709 return;
2710 pop(c);
2711}
2712
2713int main(int argc, char *argv[]) {
2714 if (argc == 2 && !strcmp("-v", argv[1]))
2715 die("dwm-" VERSION);
2716 else if (argc != 1)
2717 die("usage: dwm [-v]");
2718 if (!setlocale(LC_CTYPE, "") || !XSupportsLocale())
2719 fputs("warning: no locale support\n", stderr);
2720 if (!(dpy = XOpenDisplay(NULL)))
2721 die("dwm: cannot open display");
2722 if (!(xcon = XGetXCBConnection(dpy)))
2723 die("dwm: cannot get xcb connection\n");
2724 checkotherwm();
2725 setup();
2726#ifdef __OpenBSD__
2727 if (pledge("stdio rpath proc exec ps", NULL) == -1)
2728 die("pledge");
2729#endif /* __OpenBSD__ */
2730 scan();
2731 run();
2732 cleanup();
2733 XCloseDisplay(dpy);
2734 return EXIT_SUCCESS;
2735}
diff --git a/src/drw/drw.c b/src/drw/drw.c
new file mode 100644
index 0000000..8f53beb
--- /dev/null
+++ b/src/drw/drw.c
@@ -0,0 +1,425 @@
1/* See LICENSE file for copyright and license details. */
2#include <X11/Xft/Xft.h>
3#include <X11/Xlib.h>
4#include <stdio.h>
5#include <stdlib.h>
6#include <string.h>
7
8#include "drw.h"
9#include "util.h"
10
11#define UTF_INVALID 0xFFFD
12#define UTF_SIZ 4
13
14static const unsigned char utfbyte[UTF_SIZ + 1] = {0x80, 0, 0xC0, 0xE0, 0xF0};
15static const unsigned char utfmask[UTF_SIZ + 1] = {0xC0, 0x80, 0xE0, 0xF0,
16 0xF8};
17static const long utfmin[UTF_SIZ + 1] = {0, 0, 0x80, 0x800, 0x10000};
18static const long utfmax[UTF_SIZ + 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF,
19 0x10FFFF};
20
21static long utf8decodebyte(const char c, size_t *i) {
22 for (*i = 0; *i < (UTF_SIZ + 1); ++(*i))
23 if (((unsigned char)c & utfmask[*i]) == utfbyte[*i])
24 return (unsigned char)c & ~utfmask[*i];
25 return 0;
26}
27
28static size_t utf8validate(long *u, size_t i) {
29 if (!BETWEEN(*u, utfmin[i], utfmax[i]) || BETWEEN(*u, 0xD800, 0xDFFF))
30 *u = UTF_INVALID;
31 for (i = 1; *u > utfmax[i]; ++i)
32 ;
33 return i;
34}
35
36static size_t utf8decode(const char *c, long *u, size_t clen) {
37 size_t i, j, len, type;
38 long udecoded;
39
40 *u = UTF_INVALID;
41 if (!clen)
42 return 0;
43 udecoded = utf8decodebyte(c[0], &len);
44 if (!BETWEEN(len, 1, UTF_SIZ))
45 return 1;
46 for (i = 1, j = 1; i < clen && j < len; ++i, ++j) {
47 udecoded = (udecoded << 6) | utf8decodebyte(c[i], &type);
48 if (type)
49 return j;
50 }
51 if (j < len)
52 return 0;
53 *u = udecoded;
54 utf8validate(u, len);
55
56 return len;
57}
58
59Drw *drw_create(Display *dpy, int screen, Window root, unsigned int w,
60 unsigned int h) {
61 Drw *drw = ecalloc(1, sizeof(Drw));
62
63 drw->dpy = dpy;
64 drw->screen = screen;
65 drw->root = root;
66 drw->w = w;
67 drw->h = h;
68 drw->drawable = XCreatePixmap(dpy, root, w, h, DefaultDepth(dpy, screen));
69 drw->gc = XCreateGC(dpy, root, 0, NULL);
70 XSetLineAttributes(dpy, drw->gc, 1, LineSolid, CapButt, JoinMiter);
71
72 return drw;
73}
74
75void drw_resize(Drw *drw, unsigned int w, unsigned int h) {
76 if (!drw)
77 return;
78
79 drw->w = w;
80 drw->h = h;
81 if (drw->drawable)
82 XFreePixmap(drw->dpy, drw->drawable);
83 drw->drawable = XCreatePixmap(drw->dpy, drw->root, w, h,
84 DefaultDepth(drw->dpy, drw->screen));
85}
86
87void drw_free(Drw *drw) {
88 XFreePixmap(drw->dpy, drw->drawable);
89 XFreeGC(drw->dpy, drw->gc);
90 drw_fontset_free(drw->fonts);
91 free(drw);
92}
93
94/* This function is an implementation detail. Library users should use
95 * drw_fontset_create instead.
96 */
97static Fnt *xfont_create(Drw *drw, const char *fontname,
98 FcPattern *fontpattern) {
99 Fnt *font;
100 XftFont *xfont = NULL;
101 FcPattern *pattern = NULL;
102
103 if (fontname) {
104 /* Using the pattern found at font->xfont->pattern does not yield the
105 * same substitution results as using the pattern returned by
106 * FcNameParse; using the latter results in the desired fallback
107 * behaviour whereas the former just results in missing-character
108 * rectangles being drawn, at least with some fonts. */
109 if (!(xfont = XftFontOpenName(drw->dpy, drw->screen, fontname))) {
110 fprintf(stderr, "error, cannot load font from name: '%s'\n", fontname);
111 return NULL;
112 }
113 if (!(pattern = FcNameParse((FcChar8 *)fontname))) {
114 fprintf(stderr, "error, cannot parse font name to pattern: '%s'\n",
115 fontname);
116 XftFontClose(drw->dpy, xfont);
117 return NULL;
118 }
119 } else if (fontpattern) {
120 if (!(xfont = XftFontOpenPattern(drw->dpy, fontpattern))) {
121 fprintf(stderr, "error, cannot load font from pattern.\n");
122 return NULL;
123 }
124 } else {
125 die("no font specified.");
126 }
127
128 font = ecalloc(1, sizeof(Fnt));
129 font->xfont = xfont;
130 font->pattern = pattern;
131 font->h = xfont->ascent + xfont->descent;
132 font->dpy = drw->dpy;
133
134 return font;
135}
136
137static void xfont_free(Fnt *font) {
138 if (!font)
139 return;
140 if (font->pattern)
141 FcPatternDestroy(font->pattern);
142 XftFontClose(font->dpy, font->xfont);
143 free(font);
144}
145
146Fnt *drw_fontset_create(Drw *drw, const char *fonts[], size_t fontcount) {
147 Fnt *cur, *ret = NULL;
148 size_t i;
149
150 if (!drw || !fonts)
151 return NULL;
152
153 for (i = 1; i <= fontcount; i++) {
154 if ((cur = xfont_create(drw, fonts[fontcount - i], NULL))) {
155 cur->next = ret;
156 ret = cur;
157 }
158 }
159 return (drw->fonts = ret);
160}
161
162void drw_fontset_free(Fnt *font) {
163 if (font) {
164 drw_fontset_free(font->next);
165 xfont_free(font);
166 }
167}
168
169void drw_clr_create(Drw *drw, Clr *dest, const char *clrname) {
170 if (!drw || !dest || !clrname)
171 return;
172
173 if (!XftColorAllocName(drw->dpy, DefaultVisual(drw->dpy, drw->screen),
174 DefaultColormap(drw->dpy, drw->screen), clrname, dest))
175 die("error, cannot allocate color '%s'", clrname);
176}
177
178/* Wrapper to create color schemes. The caller has to call free(3) on the
179 * returned color scheme when done using it. */
180Clr *drw_scm_create(Drw *drw, const char *clrnames[], size_t clrcount) {
181 size_t i;
182 Clr *ret;
183
184 /* need at least two colors for a scheme */
185 if (!drw || !clrnames || clrcount < 2 ||
186 !(ret = ecalloc(clrcount, sizeof(XftColor))))
187 return NULL;
188
189 for (i = 0; i < clrcount; i++)
190 drw_clr_create(drw, &ret[i], clrnames[i]);
191 return ret;
192}
193
194void drw_setfontset(Drw *drw, Fnt *set) {
195 if (drw)
196 drw->fonts = set;
197}
198
199void drw_setscheme(Drw *drw, Clr *scm) {
200 if (drw)
201 drw->scheme = scm;
202}
203
204void drw_rect(Drw *drw, int x, int y, unsigned int w, unsigned int h,
205 int filled, int invert) {
206 if (!drw || !drw->scheme)
207 return;
208 XSetForeground(drw->dpy, drw->gc,
209 invert ? drw->scheme[ColBg].pixel : drw->scheme[ColFg].pixel);
210 if (filled)
211 XFillRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w, h);
212 else
213 XDrawRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w - 1, h - 1);
214}
215
216int drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h,
217 unsigned int lpad, const char *text, int invert) {
218 int i, ty, ellipsis_x = 0;
219 unsigned int tmpw, ew, ellipsis_w = 0, ellipsis_len;
220 XftDraw *d = NULL;
221 Fnt *usedfont, *curfont, *nextfont;
222 int utf8strlen, utf8charlen, render = x || y || w || h;
223 long utf8codepoint = 0;
224 const char *utf8str;
225 FcCharSet *fccharset;
226 FcPattern *fcpattern;
227 FcPattern *match;
228 XftResult result;
229 int charexists = 0, overflow = 0;
230 /* keep track of a couple codepoints for which we have no match. */
231 enum { nomatches_len = 64 };
232 static struct {
233 long codepoint[nomatches_len];
234 unsigned int idx;
235 } nomatches;
236 static unsigned int ellipsis_width = 0;
237
238 if (!drw || (render && (!drw->scheme || !w)) || !text || !drw->fonts)
239 return 0;
240
241 if (!render) {
242 w = invert ? invert : ~invert;
243 } else {
244 XSetForeground(drw->dpy, drw->gc,
245 drw->scheme[invert ? ColFg : ColBg].pixel);
246 XFillRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w, h);
247 d = XftDrawCreate(drw->dpy, drw->drawable,
248 DefaultVisual(drw->dpy, drw->screen),
249 DefaultColormap(drw->dpy, drw->screen));
250 x += lpad;
251 w -= lpad;
252 }
253
254 usedfont = drw->fonts;
255 if (!ellipsis_width && render)
256 ellipsis_width = drw_fontset_getwidth(drw, "...");
257 while (1) {
258 ew = ellipsis_len = utf8strlen = 0;
259 utf8str = text;
260 nextfont = NULL;
261 while (*text) {
262 utf8charlen = utf8decode(text, &utf8codepoint, UTF_SIZ);
263 for (curfont = drw->fonts; curfont; curfont = curfont->next) {
264 charexists = charexists ||
265 XftCharExists(drw->dpy, curfont->xfont, utf8codepoint);
266 if (charexists) {
267 drw_font_getexts(curfont, text, utf8charlen, &tmpw, NULL);
268 if (ew + ellipsis_width <= w) {
269 /* keep track where the ellipsis still fits */
270 ellipsis_x = x + ew;
271 ellipsis_w = w - ew;
272 ellipsis_len = utf8strlen;
273 }
274
275 if (ew + tmpw > w) {
276 overflow = 1;
277 /* called from drw_fontset_getwidth_clamp():
278 * it wants the width AFTER the overflow
279 */
280 if (!render)
281 x += tmpw;
282 else
283 utf8strlen = ellipsis_len;
284 } else if (curfont == usedfont) {
285 utf8strlen += utf8charlen;
286 text += utf8charlen;
287 ew += tmpw;
288 } else {
289 nextfont = curfont;
290 }
291 break;
292 }
293 }
294
295 if (overflow || !charexists || nextfont)
296 break;
297 else
298 charexists = 0;
299 }
300
301 if (utf8strlen) {
302 if (render) {
303 ty = y + (h - usedfont->h) / 2 + usedfont->xfont->ascent;
304 XftDrawStringUtf8(d, &drw->scheme[invert ? ColBg : ColFg],
305 usedfont->xfont, x, ty, (XftChar8 *)utf8str,
306 utf8strlen);
307 }
308 x += ew;
309 w -= ew;
310 }
311 if (render && overflow)
312 drw_text(drw, ellipsis_x, y, ellipsis_w, h, 0, "...", invert);
313
314 if (!*text || overflow) {
315 break;
316 } else if (nextfont) {
317 charexists = 0;
318 usedfont = nextfont;
319 } else {
320 /* Regardless of whether or not a fallback font is found, the
321 * character must be drawn. */
322 charexists = 1;
323
324 for (i = 0; i < nomatches_len; ++i) {
325 /* avoid calling XftFontMatch if we know we won't find a match */
326 if (utf8codepoint == nomatches.codepoint[i])
327 goto no_match;
328 }
329
330 fccharset = FcCharSetCreate();
331 FcCharSetAddChar(fccharset, utf8codepoint);
332
333 if (!drw->fonts->pattern) {
334 /* Refer to the comment in xfont_create for more information. */
335 die("the first font in the cache must be loaded from a font string.");
336 }
337
338 fcpattern = FcPatternDuplicate(drw->fonts->pattern);
339 FcPatternAddCharSet(fcpattern, FC_CHARSET, fccharset);
340 FcPatternAddBool(fcpattern, FC_SCALABLE, FcTrue);
341
342 FcConfigSubstitute(NULL, fcpattern, FcMatchPattern);
343 FcDefaultSubstitute(fcpattern);
344 match = XftFontMatch(drw->dpy, drw->screen, fcpattern, &result);
345
346 FcCharSetDestroy(fccharset);
347 FcPatternDestroy(fcpattern);
348
349 if (match) {
350 usedfont = xfont_create(drw, NULL, match);
351 if (usedfont &&
352 XftCharExists(drw->dpy, usedfont->xfont, utf8codepoint)) {
353 for (curfont = drw->fonts; curfont->next; curfont = curfont->next)
354 ; /* NOP */
355 curfont->next = usedfont;
356 } else {
357 xfont_free(usedfont);
358 nomatches.codepoint[++nomatches.idx % nomatches_len] = utf8codepoint;
359 no_match:
360 usedfont = drw->fonts;
361 }
362 }
363 }
364 }
365 if (d)
366 XftDrawDestroy(d);
367
368 return x + (render ? w : 0);
369}
370
371void drw_map(Drw *drw, Window win, int x, int y, unsigned int w,
372 unsigned int h) {
373 if (!drw)
374 return;
375
376 XCopyArea(drw->dpy, drw->drawable, win, drw->gc, x, y, w, h, x, y);
377 XSync(drw->dpy, False);
378}
379
380unsigned int drw_fontset_getwidth(Drw *drw, const char *text) {
381 if (!drw || !drw->fonts || !text)
382 return 0;
383 return drw_text(drw, 0, 0, 0, 0, 0, text, 0);
384}
385
386unsigned int drw_fontset_getwidth_clamp(Drw *drw, const char *text,
387 unsigned int n) {
388 unsigned int tmp = 0;
389 if (drw && drw->fonts && text && n)
390 tmp = drw_text(drw, 0, 0, 0, 0, 0, text, n);
391 return MIN(n, tmp);
392}
393
394void drw_font_getexts(Fnt *font, const char *text, unsigned int len,
395 unsigned int *w, unsigned int *h) {
396 XGlyphInfo ext;
397
398 if (!font || !text)
399 return;
400
401 XftTextExtentsUtf8(font->dpy, font->xfont, (XftChar8 *)text, len, &ext);
402 if (w)
403 *w = ext.xOff;
404 if (h)
405 *h = font->h;
406}
407
408Cur *drw_cur_create(Drw *drw, int shape) {
409 Cur *cur;
410
411 if (!drw || !(cur = ecalloc(1, sizeof(Cur))))
412 return NULL;
413
414 cur->cursor = XCreateFontCursor(drw->dpy, shape);
415
416 return cur;
417}
418
419void drw_cur_free(Drw *drw, Cur *cursor) {
420 if (!cursor)
421 return;
422
423 XFreeCursor(drw->dpy, cursor->cursor);
424 free(cursor);
425}
diff --git a/src/drw/drw.h b/src/drw/drw.h
new file mode 100644
index 0000000..e645d43
--- /dev/null
+++ b/src/drw/drw.h
@@ -0,0 +1,64 @@
1/* See LICENSE file for copyright and license details. */
2
3typedef struct {
4 Cursor cursor;
5} Cur;
6
7typedef struct Fnt {
8 Display *dpy;
9 unsigned int h;
10 XftFont *xfont;
11 FcPattern *pattern;
12 struct Fnt *next;
13} Fnt;
14
15enum { ColFg, ColBg, ColBorder }; /* Clr scheme index */
16typedef XftColor Clr;
17
18typedef struct {
19 unsigned int w, h;
20 Display *dpy;
21 int screen;
22 Window root;
23 Drawable drawable;
24 GC gc;
25 Clr *scheme;
26 Fnt *fonts;
27} Drw;
28
29/* Drawable abstraction */
30Drw *drw_create(Display *dpy, int screen, Window win, unsigned int w,
31 unsigned int h);
32void drw_resize(Drw *drw, unsigned int w, unsigned int h);
33void drw_free(Drw *drw);
34
35/* Fnt abstraction */
36Fnt *drw_fontset_create(Drw *drw, const char *fonts[], size_t fontcount);
37void drw_fontset_free(Fnt *set);
38unsigned int drw_fontset_getwidth(Drw *drw, const char *text);
39unsigned int drw_fontset_getwidth_clamp(Drw *drw, const char *text,
40 unsigned int n);
41void drw_font_getexts(Fnt *font, const char *text, unsigned int len,
42 unsigned int *w, unsigned int *h);
43
44/* Colorscheme abstraction */
45void drw_clr_create(Drw *drw, Clr *dest, const char *clrname);
46Clr *drw_scm_create(Drw *drw, const char *clrnames[], size_t clrcount);
47
48/* Cursor abstraction */
49Cur *drw_cur_create(Drw *drw, int shape);
50void drw_cur_free(Drw *drw, Cur *cursor);
51
52/* Drawing context manipulation */
53void drw_setfontset(Drw *drw, Fnt *set);
54void drw_setscheme(Drw *drw, Clr *scm);
55
56/* Drawing functions */
57void drw_rect(Drw *drw, int x, int y, unsigned int w, unsigned int h,
58 int filled, int invert);
59int drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h,
60 unsigned int lpad, const char *text, int invert);
61
62/* Map functions */
63void drw_map(Drw *drw, Window win, int x, int y, unsigned int w,
64 unsigned int h);
diff --git a/src/transient/transient.c b/src/transient/transient.c
new file mode 100644
index 0000000..ed6539f
--- /dev/null
+++ b/src/transient/transient.c
@@ -0,0 +1,87 @@
1#include <X11/Xlib.h>
2#include <X11/Xutil.h>
3#include <stdlib.h>
4#include <unistd.h>
5
6#define FLOATING_WINDOW_WIDTH 400
7#define FLOATING_WINDOW_HEIGHT 400
8#define FLOATING_WINDOW_X_POS 100
9#define FLOATING_WINDOW_Y_POS 100
10
11#define TRANSIENT_WINDOW_WIDTH 100
12#define TRANSIENT_WINDOW_HEIGHT 100
13#define TRANSIENT_WINDOW_X_POS 50
14#define TRANSIENT_WINDOW_Y_POS 50
15
16#define SLEEP_TIME 5
17
18#define WINDOW_BORDER_WIDTH 0
19#define WINDOW_BORDER_COLOR 0
20#define WINDOW_BACKGROUND_COLOR 0
21
22/**
23 * @brief Function that creates and manages floating and transient window.
24 *
25 * This program opens an X display, creates a floating window with specific
26 * size and position, and then creates a transient (child) window after a
27 * specified delay. The transient window is always associated with the
28 * floating window, meaning it will behave as a child of the floating window.
29 * The program uses Xlib to interact with the X server, handle events, and
30 * manage window properties such as size, title, and position.
31 *
32 * The key steps are:
33 * 1. Create the floating window and set its size constraints.
34 * 2. After a delay (5 seconds), create the transient window and associate
35 * it with the floating window.
36 * 3. Display both windows on the screen.
37 * 4. Wait for and process X events in an infinite loop. This allows the
38 * windows to remain open and interact with the X server.
39 *
40 * @return int Exit status (always 0).
41 */
42int main(void) {
43 Display *display;
44 Window rootWindow, floatingWindow, transientWindow = None;
45 XSizeHints sizeHints;
46 XEvent event;
47
48 display = XOpenDisplay(NULL);
49 if (!display)
50 exit(1);
51
52 rootWindow = DefaultRootWindow(display);
53
54 floatingWindow = XCreateSimpleWindow(
55 display, rootWindow, FLOATING_WINDOW_X_POS, FLOATING_WINDOW_Y_POS,
56 FLOATING_WINDOW_WIDTH, FLOATING_WINDOW_HEIGHT, WINDOW_BORDER_WIDTH,
57 WINDOW_BORDER_COLOR, WINDOW_BACKGROUND_COLOR);
58
59 sizeHints.min_width = sizeHints.max_width = sizeHints.min_height =
60 sizeHints.max_height = FLOATING_WINDOW_WIDTH;
61 sizeHints.flags = PMinSize | PMaxSize;
62 XSetWMNormalHints(display, floatingWindow, &sizeHints);
63 XStoreName(display, floatingWindow, "floating");
64 XMapWindow(display, floatingWindow);
65 XSelectInput(display, floatingWindow, ExposureMask);
66
67 while (1) {
68 XNextEvent(display, &event);
69
70 if (transientWindow == None) {
71 sleep(SLEEP_TIME);
72
73 transientWindow = XCreateSimpleWindow(
74 display, rootWindow, TRANSIENT_WINDOW_X_POS, TRANSIENT_WINDOW_Y_POS,
75 TRANSIENT_WINDOW_WIDTH, TRANSIENT_WINDOW_HEIGHT, WINDOW_BORDER_WIDTH,
76 WINDOW_BORDER_COLOR, WINDOW_BACKGROUND_COLOR);
77
78 XSetTransientForHint(display, transientWindow, floatingWindow);
79 XStoreName(display, transientWindow, "transient");
80 XMapWindow(display, transientWindow);
81 XSelectInput(display, transientWindow, ExposureMask);
82 }
83 }
84
85 XCloseDisplay(display);
86 exit(0);
87}
diff --git a/src/util/util.c b/src/util/util.c
new file mode 100644
index 0000000..7419d0c
--- /dev/null
+++ b/src/util/util.c
@@ -0,0 +1,62 @@
1/* See LICENSE file for copyright and license details. */
2
3#include <stdarg.h>
4#include <stdio.h>
5#include <stdlib.h>
6#include <string.h>
7
8#include "util.h"
9
10/*
11 * die()
12 * Function to print an error message and terminate the program.
13 * Takes a formatted string (`fmt`) and additional arguments (as in `printf`).
14 * This function uses variable arguments and will print the formatted error
15 * message to `stderr` and then exit the program with a non-zero status
16 * (typically 1).
17 *
18 * Usage:
19 * die("Error occurred in function %s", __func__);
20 */
21void die(const char *format_string, ...) {
22 va_list argument_list;
23 va_start(argument_list, format_string);
24 vfprintf(stderr, format_string, argument_list);
25 va_end(argument_list);
26
27 if (format_string[0] && format_string[strlen(format_string) - 1] == ':') {
28 fputc(' ', stderr);
29 perror(NULL);
30 } else
31 fputc('\n', stderr);
32
33 exit(1);
34}
35
36/*
37 * ecalloc()
38 * Function to allocate memory for an array of elements, initializing the memory
39 * to zero. This function works like `calloc()`, but it adds error handling. If
40 * memory allocation fails, it will call the `die()` function to print an error
41 * message and terminate the program.
42 *
43 * Parameters:
44 * - `num_elements`: The number of elements to allocate memory for.
45 * - `element_size`: The size of each element in bytes.
46 *
47 * Returns:
48 * - A pointer to the allocated memory (of type `void *`).
49 * - If the allocation fails, the program will terminate via `die()`.
50 *
51 * Usage:
52 * int *array = ecalloc(10, sizeof(int)); // Allocate memory for an array of
53 * 10 integers.
54 */
55void *ecalloc(size_t num_elements, size_t element_size) {
56 void *allocated_memory;
57
58 if (!(allocated_memory = calloc(num_elements, element_size)))
59 die("calloc:");
60
61 return allocated_memory;
62}
diff --git a/src/util/util.h b/src/util/util.h
new file mode 100644
index 0000000..6ef3c86
--- /dev/null
+++ b/src/util/util.h
@@ -0,0 +1,59 @@
1/* See LICENSE file for copyright and license details. */
2
3/*
4 * MAX(A, B)
5 * Macro to compute the maximum of two values.
6 * It returns the greater of the two values, A or B.
7 * Usage: MAX(3, 5) -> 5
8 */
9#define MAX(A, B) ((A) > (B) ? (A) : (B))
10
11/*
12 * MIN(A, B)
13 * Macro to compute the minimum of two values.
14 * It returns the lesser of the two values, A or B.
15 * Usage: MIN(3, 5) -> 3
16 */
17#define MIN(A, B) ((A) < (B) ? (A) : (B))
18
19/*
20 * BETWEEN(X, A, B)
21 * Macro to check if a value X is between two values A and B (inclusive).
22 * It returns true (1) if X is between A and B, and false (0) otherwise.
23 * Usage: BETWEEN(4, 1, 5) -> 1 (true), BETWEEN(6, 1, 5) -> 0 (false)
24 */
25#define BETWEEN(X, A, B) ((A) <= (X) && (X) <= (B))
26
27/*
28 * die()
29 * Function to print an error message and terminate the program.
30 * Takes a formatted string (`fmt`) and additional arguments (as in `printf`).
31 * This function uses variable arguments and will print the formatted error
32 * message to `stderr` and then exit the program with a non-zero status
33 * (typically 1).
34 *
35 * Usage:
36 * die("Error occurred in function %s", __func__);
37 */
38void die(const char *fmt, ...);
39
40/*
41 * ecalloc()
42 * Function to allocate memory for an array of elements, initializing the memory
43 * to zero. This function works like `calloc()`, but it adds error handling. If
44 * memory allocation fails, it will call the `die()` function to print an error
45 * message and terminate the program.
46 *
47 * Parameters:
48 * - `nmemb`: The number of elements to allocate memory for.
49 * - `size`: The size of each element in bytes.
50 *
51 * Returns:
52 * - A pointer to the allocated memory (of type `void *`).
53 * - If the allocation fails, the program will terminate via `die()`.
54 *
55 * Usage:
56 * int *arr = ecalloc(10, sizeof(int)); // Allocate memory for an array of 10
57 * integers.
58 */
59void *ecalloc(size_t nmemb, size_t size);