Improving the Android Studio experience in dwm - Phelipe Teles

Improving the Android Studio experience in dwm

6 min.
View source code

In this post, I want to share how I started to change dwm source code in order to improve my experience with Android Emulator.

First, let me briefly explain the problem. When you launch Android Emulator, a window with the Android device and a window with buttons to control it appear (maybe a modal window saying “Loading state…” too).

The device window is usually the one I’m mostly interested in. I don’t recall ever wanting to focus the other ones (except by interacting with the mouse) and I don’t want them to show up in the taskbar (since I use the awesomebar patch).

At first, I went in and modified the source code to ignore these windows by their WM_NAME properties, which is easy to find out with xprop. But these windows are very badly named and their names are not very distinct from the device window.

Later I learned that these other windows are transient windows in X11:

A window is said to be transient for another window if it belongs to that other window and may not outlast it: a dialog box, such as an alert message, is a common example.

These windows have a property called WM_TRANSIENT_FOR, whose value we can get with an Xlib function named XGetTransientForHint.

Here’s an example on how to use this function, taken from the dwm source code:

c
voidmanage(Window w, XWindowAttributes *wa){	Client *c, *t = NULL;	Window trans = None; 	// ... 	if (XGetTransientForHint(dpy, w, &trans) && (t = wintoclient(trans))) {		c->mon = t->mon;		c->tags = t->tags;	} else {		// ...	}}

As you can see, it’s pretty simple, XGetTransientForHint assigns a Window to the trans variable (if it succeeds), and then we get the respective Client with the wintoclient function.

This example provided me with enough knowledge to solve most of my problems:

  • I don’t want them in the taskbar.
  • I don’t want them in the monocle layout count.
  • I never want to focus them with the keyboard.
  • When I apply the nth tag to the leader window, the same tag should be applied to its transient windows.
  • On floating layout, if I focus on a leader window, I want its transient windows to be raised into view as well.

Skipping transient windows in the taskbar

The taskbar is drawn by the drawbar function.

I just need to check if a window is transient and, if so, skip:

c
voiddrawbar(Monitor *m){	// ...	Client *c, *t = NULL;	Window trans; 	// ...	for (c = m->clients; c; c = c->next) {		if (ISVISIBLE(c) && !XGetTransientForHint(dpy, c->win, &trans))			n++;		// ...	} 	// ... 	if ((w = m->ww - sw - stw - x) > bh) {		if (n > 0) {			// ...			for (c = m->clients; c; c = c->next) {				if (!ISVISIBLE(c) || XGetTransientForHint(dpy, c->win, &trans))					continue;				if (m->sel == c)					scm = SchemeSel;				else if (HIDDEN(c))					scm = SchemeHid;				else					scm = SchemeNorm;				drw_setscheme(drw, scheme[scm]);				// ...			}		// ...		}	}	// ...}

We need to be careful here and also change the buttonpress function, which handles clicks in the taskbar:

diff
diff --git a/dwm.c b/dwm.c--- a/dwm.c+++ b/dwm.c@@ -518,6 +518,7 @@ buttonpress(XEvent *e)	Client *c;	Monitor *m;	XButtonPressedEvent *ev = &e->xbutton;+	Window trans = None; 	click = ClkRootWin;	/* focus monitor if necessary */@@ -546,7 +547,7 @@ buttonpress(XEvent *e) 			if (c) {				do {-					if (!ISVISIBLE(c))+					if (!ISVISIBLE(c) || XGetTransientForHint(dpy, c->win, &trans))						continue;					else						x += (1.0 / (double)m->bt) * m->btw;

Later, by investigating xprop output, I found that some windows give a hint called _NET_WM_STATE_SKIP_TASKBAR so window managers shouldn’t include them in the taskbar, this seemed like a better idea too so I implemented it as well.

Skipping transient windows in monocle layout count

This is simple, we just need to not increment a counter when the window is transient, in the monocle function:

c
voidmonocle(Monitor *m){	unsigned int n = 0;	Client *c;	Window trans; 	for (c = m->clients; c; c = c->next)		if (ISVISIBLE(c) && !XGetTransientForHint(dpy, c->win, &trans))			n++;	if (n > 0) /* override layout symbol */		snprintf(m->ltsymbol, sizeof m->ltsymbol, "[%d]", n);	// ...}

Do not focus transient windows with mod + j and mod + k

I started by checking which function is bound to these keys in config.h: the focusstack function.

Then it’s quick to realize I just need to add another condition to the various if statements (careful not to mix up the i and c variables):

diff
diff --git a/suckless/dwm/dwm.c b/suckless/dwm/dwm.cindex 86e4a41..852666b 100644--- a/suckless/dwm/dwm.c+++ b/suckless/dwm/dwm.c@@ -1053,6 +1053,7 @@ void focusstack(int inc, int hid) {	Client *c = NULL, *i;+	Window trans = None; 	if (!selmon->sel && !hid)		return;@@ -1062,22 +1063,22 @@ focusstack(int inc, int hid)	if (inc > 0) {		if (selmon->sel)			for (c = selmon->sel->next;-					 c && (!ISVISIBLE(c) || (!hid && HIDDEN(c)));+					 c && (!ISVISIBLE(c) || (!hid && HIDDEN(c)) || XGetTransientForHint(dpy, c->win, &trans));					 c = c->next);		if (!c)			for (c = selmon->clients;-					 c && (!ISVISIBLE(c) || (!hid && HIDDEN(c)));+					 c && (!ISVISIBLE(c) || (!hid && HIDDEN(c)) || XGetTransientForHint(dpy, c->win, &trans));					 c = c->next);	} else {		if (selmon->sel) {			for (i = selmon->clients; i != selmon->sel; i = i->next)-				if (ISVISIBLE(i) && !(!hid && HIDDEN(i)))+				if (ISVISIBLE(i) && !(!hid && HIDDEN(i)) && !XGetTransientForHint(dpy, i->win, &trans))					c = i;		} else			c = selmon->clients;		if (!c)			for (; i; i = i->next)-				if (ISVISIBLE(i) && !(!hid && HIDDEN(i)))+				if (ISVISIBLE(i) && !(!hid && HIDDEN(i)) && !XGetTransientForHint(dpy, i->win, &trans))					c = i;	}

Applying nth tag to a leader window’s transient windows

To achieve this, I also started by checking which function is bound to Mod + Shift + n in config.h, which is the tag function.

Then I modified it to apply the same tag to all transient windows whose window leader is the selected client:

c
voidtag(const Arg *arg){	Client *c, *t = NULL;	Window trans = None; 	if (selmon->sel && arg->ui & TAGMASK) {		selmon->sel->tags = arg->ui & TAGMASK; 		for (c = selmon->stack; c; c = c->snext) {			if (XGetTransientForHint(dpy, c->win, &trans) && (t = wintoclient(trans))) {				if (selmon->sel == t) {					c->tags = arg->ui & TAGMASK;				}			}		} 		focus(NULL);		arrange(selmon);	}}

Raising all transient windows when the leader window gets focused

Since the transient windows do not appear in the taskbar and we also ignore them in the focusstack function, there is no way to focus on them if they’re not visible on the screen.

So in floating layout there’s an usability problem: transient windows are never visible.

The fix is to raise all transient windows when a window gets focused. Also, if a transient window gets focused in a non-floating layout, we should raise it as well since transient windows are always floating in dwm). Here’s the code:

diff
diff --git a/suckless/dwm/dwm.c b/suckless/dwm/dwm.cindex 389c61d..70a6dc1 100644--- a/suckless/dwm/dwm.c+++ b/suckless/dwm/dwm.c@@ -1001,6 +1001,9 @@ expose(XEvent *e) void focus(Client *c) {+	Client *i, *t;+	Window trans = None;+ 	if (!c || !ISVISIBLE(c)) 		for (c = selmon->stack; c && (!ISVISIBLE(c) || HIDDEN(c)); c = c->snext); 	if (selmon->sel && selmon->sel != c) {@@ -1023,6 +1026,12 @@ focus(Client *c) 		grabbuttons(c, 1); 		XSetWindowBorder(dpy, c->win, scheme[SchemeSel][ColBorder].pixel); 		setfocus(c); +		if (XGetTransientForHint(dpy, c->win, &trans)) {+			XRaiseWindow(dpy, c->win);+		}++		for (i = selmon->stack; i; i = i->snext) {+			if (ISVISIBLE(i) && (XGetTransientForHint(dpy, i->win, &trans) && (t = wintoclient(trans)) && (t == c))) {+				XRaiseWindow(dpy, i->win);+			}+		} 	} else { 		XSetInputFocus(dpy, root, RevertToPointerRoot, CurrentTime); 		XDeleteProperty(dpy, root, netatom[NetActiveWindow]);

This create another problem though… When the leader window gets unfocused, their transient windows would always reamin on top of everything else!

Thanks to a comment by /u/bakkeby, I found out that I could use XLowerWindow in the unfocus function to fix this:

c
voidunfocus(Client *c, int setfocus){	Client *i, *t;	Window trans = None; 	if (!c)		return;	grabbuttons(c, 0);	XSetWindowBorder(dpy, c->win, scheme[SchemeNorm][ColBorder].pixel); 	if (XGetTransientForHint(dpy, c->win, &trans))		XLowerWindow(dpy, c->win); 	for (i = c->mon->stack; i; i = i->snext) {		if (ISVISIBLE(i) && (XGetTransientForHint(dpy, i->win, &trans) && (t = wintoclient(trans)) && (t == c))) {			XLowerWindow(dpy, i->win);		}	} 	if (setfocus) {		XSetInputFocus(dpy, root, RevertToPointerRoot, CurrentTime);		XDeleteProperty(dpy, root, netatom[NetActiveWindow]);	}}

Final result

Here’s a demonstration of the final result:

Demo of Android Studio running in dwm