bakkeby / dwm-flexipatch

A dwm build with preprocessor directives to decide which patches to include during build time

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Stickyed clients always be first focused after switching view tags.

ysl2 opened this issue · comments

I think a reasonable situation is, when a client is stickyed, and I switch view tags, the stickyed client should not be focused (instead, the master client should.) beacuse I'm doing other things in another clients.

For example, when I'm playing video and sticky it, and also I'm writing code in other clients. This clients might be in different tags. When I switch view tags, the stickyed client still be there without focused, except I use MOD+j or MOD+k to force focus it. Instead, the master client should be focused first.

I think the problem can be abstracted to: The last visited client in specific tags not saved when switching view tags. Pertag has a new patch include pertag select which might fix this.

https://dwm.suckless.org/patches/pertag/dwm-pertag_with_sel-20231003-9f88553.diff

I'm not sure, but it might be. I'm checking code now.

The pertag_with_sel patch is just a horrible idea, mind you. It also has some gaping holes in it. For example you can end up with references to clients that no longer exist and crashing the window manager.

As for the main issue at hand; that the sticky window tends to get focus - yes this can be quite an annoyance. It is trivial enough to fix though.

When navigating to a new tag focus(NULL); will be called to give focus to the first visible client in the stack.

When the client is NULL then the next client is chosen based on this for-loop.

void
focus(Client *c)
{
    if (!c || !ISVISIBLE(c))
        for (c = selmon->stack; c && !ISVISIBLE(c); c = c->snext);
...

all you need to do here is to also skip sticky windows.

 void
 focus(Client *c)
 {
     if (!c || !ISVISIBLE(c))
-        for (c = selmon->stack; c && !ISVISIBLE(c); c = c->snext);
+        for (c = selmon->stack; c && !ISVISIBLE(c) && !c->issticky; c = c->snext);
 ...

Now, is this something to include in dwm-flexipatch? Generally I would say no because it would be providing a different experience than you would get if you were to take a bare dwm and apply the sticky patch, and as such in principle it goes against the main goals of this project.

That said this is pretty minor and I think the benefits / practicality of the change outweighs that principle.

Thank you for kind advise! But I
still have some questions. I would appreciate if you could help me, thank you!

For example you can end up with references to clients that no longer exist and crashing the window manager.

I wonder when this might happen? The pertag_with_sel author uploaded a fix commit for preventing invalid memory access in https://git.suckless.org/sites/commit/2b1ff4f11e8e6e31cf2d59e207db9febe162e47c.html
He released all c->mon->pertag->sel[i] pointers in unmanage() function when a client is closed.

Also, I'm not very clear about the definition of ISVISIBLE(c), when will the clinent be not ISVISIBLE? Is there some transparent (or ghost I mean) client might be in dwm?

all you need to do here is to also skip sticky windows.

This is really an elegent way to achieve the goal of this issue, however if do this, the sticky client also never be focused by default, which might not I want. I want to keep last focusing client, sometimes might be the other client, but sometimes I'm focusing the sticky one. I don't know if there has a better way to achieve the goal? Thanks!

This is really an elegant way to achieve the goal of this issue, however if do this, the sticky client also never be focused by default, which might not I want. I want to keep last focusing client, sometimes might be the other client, but sometimes I'm focusing the sticky one.

Personal preference I guess. I'd think that it would make more sense from a user perspective that sticky windows are never focused by default, but to be honest this is not something that I personally use so I can't form an informed opinion.

In either case you just have to do the search twice if no non-sticky clients are found.

 void
 focus(Client *c)
 {
+    if (!c || !ISVISIBLE(c))
+        for (c = selmon->stack; c && !ISVISIBLE(c) && !c->issticky; c = c->snext);
     if (!c || !ISVISIBLE(c))
         for (c = selmon->stack; c && !ISVISIBLE(c); c = c->snext);
 ...

^ so you search first for a non-sticky visible client, and if none is found you revert to the first visible client (if any)

I'm not very clear about the definition of ISVISIBLE(c), when will the client be not ISVISIBLE?

In a bare dwm ISVISIBLE is a macro and is defined like this:

#define ISVISIBLE(C)            ((C->tags & C->mon->tagset[C->mon->seltags]))

What this means is that if the client's tags are on any of the visible (viewed) tags of the monitor, then that client is visible. This has to do with tags and how a client is visible on one tag and not another, that's all there is to it.

The sticky patch changes this to:

#define ISVISIBLE(C)            ((C->tags & C->mon->tagset[C->mon->seltags]) || C->issticky)

What the change means is that a sticky client is always visible, regardless of what tag(s) the client is assigned to.

If you exploring to learn more about how dwm works internally then you may want to have a look at dwm-commented as well which aims to explain most of the code base of a bare dwm.

I wonder when this might happen? The pertag_with_sel author uploaded a fix commit for preventing invalid memory access in 2b1ff4f
He released all c->mon->pertag->sel[i] pointers in unmanage() function when a client is closed.

The author is most likely a single monitor user.

Let's say that you have a single client on one monitor. Now if you were to move that client from one monitor to another then the pertag selected client would remain as-is.

If you then close the client then, with the fix that was provided, the pertag selected client will be cleared for the client's monitor. If you then move to another tag and back on the previous monitor then it would refer to a client that does not exist. I haven't tested this to confirm but there were similar issues with the zoomswap which keeps a reference to the previously zoomed client.

A fix would be to loop through the pertag pointers for all monitors, but you may still have oddities such as changing tags on one monitor and the focus is given to a client on another monitor (that may or may not be visible).

I wonder what the incentive was for creating the pertag with sel variant because by default in a bare dwm the focus will always revert to the most recently focused client on any tag you move to. I suspect that the user did not realise that the mouse cursor could be causing enternotify events thus affecting what client ends up receiving focus.

I didn't realize this is so difficult... You are really the expert of dwm!

May I ask an irrelevant question? How do you debug dwm? Write log? Or use print? Or gdb something else?

I noticed there is a DEBUG(...) macro in utils.h, however, it seems only print to stderr, and I haven't figured out how to use it. Beacuse the X will complain that I'm already in a display instance, so I can't open a new one for debug use.

I also searched some way to debug on internet, for example, https://dompoer.io/linux,/debugging/2023/01/08/hack_wm.html. But I just want to know an expert's way like yours :-)

In fact, I also write some small patches before, but without debugger or log or print, they are directly checked with my brain. To be honest, this is a little painful, especially for the bit operations in mind, and especially without your dwm-commented at that time, I always struggle in the code. If something error in my code, I have to quit dwm and login again and again.... to change something and to contrast the different behaviour in dwm before and after. So sad :-/

By the way, I have starred and forked your dwm-commented a few days ago for learning it. Thank you!

How do you debug dwm? Write log? Or use print? Or gdb something else?

Perhaps the general recommendation is to use gdb for debugging; it is supposed to make everything easier. Personally I have only used fprintf statements in relation to dwm.

Because the X will complain that I'm already in a display instance, so I can't open a new one for debug use.

You could just do Ctrl+Alt+F6 for example to go to a different tty and start a new session there, but I'd much rather recommend that you install Xephyr and start your dwm in that to test your changes. It is kind of annoying if you break dwm so badly that it crashes immediately and you have to use some other window manager to fix it.

Example commands to get you started:

$ Xephyr +extension RANDR -screen 1280x760 -ac :3

^ starting Xephyr, you can add more than one screen if necessary

$ DISPLAY=:3 dwm

^ starting dwm on the display that Xephyr is using

Any output will be shown in the terminal where you started dwm from.

That's awesome, thank you!

For anyone who want the sticky window don't auto gain focus, use this:

LukeSmithxyz/dwm#181

Funny how that is that was exactly the same solution.

The commit 63bab1a doesn't work, I tested just now.

Open a terminal client in tile layout and set it to float, then sticky it. Then switch to other tags and open some clients in them. Change focus to non-sticky and swich between tags. The sticky clients still be focused.

Ah yes, because !ISVISIBLE(c) will be negated if the client is sticky. Rookie mistake.

Thank you! It works now. But it still a little difference between your commit and the reference of lukesmith's.

If I use your commit, no matter what if I'm focusing the sticky one, when switching to another tag, the sticky one will never be focused.

If I use the reference, when I'm not focusing the sticky one, and switch to another tag, the sticky one will not be focused. But when I'm focusing the sticky one, and switch to another tag, the sticky one still be focused. I think if this may be more reasonable?


This is what I did in myself's fork:

Frist, I added the #if ... #endif statement to compatitble with current code.

	if (!c || !ISVISIBLE(c)) {
+		#if STICKY_PATCH
+		for (c = selmon->stack; c && (!ISVISIBLE(c) || (c->issticky && !selmon->sel->issticky)); c = c->snext);
+		if (!c) /* No windows found; check for available stickies */
+		#endif // STICKY_PATCH
			for (c = selmon->stack; c && !ISVISIBLE(c); c = c->snext);
	}

Second, I removed these two lines in patch/shift.c

 	for (selmon = mons; selmon; selmon = selmon->next)
 	#endif // TAGSYNC_PATCH
 	for (c = selmon->clients; c && clients; c = c->next) {
-		if (c == selmon->sel)
-			continue;
 		#if SCRATCHPADS_PATCH && !RENAMED_SCRATCHPADS_PATCH
 		if (!(c->tags & SPTAGMASK))
 			tagmask |= c->tags;

If we don't do this, when all other tags are focusing the sticky client, and the sticky client's tag is only one sticky client, using shiftviewclients to swich tags will skip the tag which the sticky client lies in.

I have checked the original shift-tools patch, it didn't have this statement. But this will omit the sticky client beacuse it doen't belong to current tag when we are in another tag. So this will be a bug. Hence I decided to delete the two lines directly instead of wrapping them in #if ... #endif condition statement.

I'll check the shift logic in relation to sticky.

As for the reference patch that sounds like it is not that much different from plain dwm.

Let's say that you have a sticky client x, and you have multiple non-sticky clients on tags 1 and 5 with tag 1 being the selected one.

With the reference fix, if I understand it correctly, when you move from tag 1 to tag 8, which does not have any clients, then the sticky client will get focus. When you then move to tag 5 the sticky client will still have focus.

That is the primary annoyance that we are trying to address here. If you use shiftview for example to scroll through tags then the sticky client will generally end up having focus provided that there are empty tags.

If you use shiftview for example to scroll through tags then the sticky client will generally end up having focus provided that there are empty tags.

Yes, if I use the reference fix, this is the behaviour. For my solution is, open a non-sticky client (for example, a terminal) in the tag which contains the sticky client, then move the focus to the non-sticky client. This solution is not relative to any code. After do this, when switching tags with shiftviewclient, the sticky client will never be automatically focused, except I just manually focus it. But another problem is the shiftview, when running into a empty tag, the sticky client will be automatically focused again. However, for my self preference, I use shiftviewclient in most cases, so I can avoid this happen in most situation.

You have to define your wanted behaviour when you either click on the tag icons in the bar or you press the MOD+num keybindings to change tags.

That you have some odd behaviour when using shiftview or shiftviewclients is totally unrelated and shouldn't be relevant to that primary behaviour.

 	for (selmon = mons; selmon; selmon = selmon->next)
 	#endif // TAGSYNC_PATCH
 	for (c = selmon->clients; c && clients; c = c->next) {
-		if (c == selmon->sel)
-			continue;
 		#if SCRATCHPADS_PATCH && !RENAMED_SCRATCHPADS_PATCH
 		if (!(c->tags & SPTAGMASK))
 			tagmask |= c->tags;

Your explanation for removing these two lines in your build does not make much sense to me.

The reason why this code exists is simply that let's say that your workflow involves assigning multiple tags to windows to have different views - rather than having a single tag assigned to each window.

You have three windows open:

client A - on tag 2, 5 and 7
client B - on tag 8
client C - on tag 6

Now let's say that you are on tag 2 and client A is the selected one. You do a shiftviewclient to the right - the intention being to move to the first tag the the right of tag 2 that is occupied by clients.

Rather than going to tag 6 where client C resides, it will move to tag 5 because that tag is occupied by the selected client. In practice you want to move to the next tag that has other clients, hence that line to skip the selected client is in that for loop.

To me it makes more sense to skip sticky clients because these will have been assigned one or more tags, and these tags are irrelevant in this context because sticky windows are always shown.

Come to think of it there is nothing preventing sticky clients from being included in the calculations that work out which tags are occupied by clients for the tags drawing in the bar either.

Thank you for your reply :-)

I think this is relative to the people's personal preferences. And this is made of multiple questions. We need to split them:

1. The behaviour of focus() for switching tags when there are some sticky clients exist

You have to define your wanted behaviour when you either click on the tag icons in the bar or you press the MOD+num keybindings to change tags.

For myself, No matter when using MOD+number or click, or using shiftview or shiftviewclients, I want to keep the focus. More explains below:

  • If we are just focusing the sticky one, keep the focus to the sticky one after switching tags by any method.
  • If we are focusing the non-sticky one, keep the focus to non-sticky one after swtiching tags by any method.

However, I found the behaviour (before I opened the issue) is:

  • If we are just focusing the sticky one, the focus is the sticky one after switching tags, that's what I expected.
  • But If we are focusing the non-sticky one, after switching tags, the focus will be forced to the sticky one. For me, that's a problem.

That's why I opened this issue.

As for the tag which only have the sticky client, I think if we switch to it, the focus will be automatically changed to the sticky client, that's normal. Because even if the client is not sticky, the focus still be automatically changed to the non-sticky one. That's reasonable.

2. The behaviour of shiftviewclients()

To me it makes more sense to skip sticky clients because these will have been assigned one or more tags, and these tags are irrelevant in this context because sticky windows are always shown.

First, I would say the if (c == selmon->sel) or if (c->issticky) in the shiftviewclients might not reasonable for most people. Please let me explain why.

The difference of the shiftviewclients and other moving method (like shiftview or MOD+numbers) is, the shiftviewclients only count those tags which contains one or more client(s). I understand that you considered the tags which only have duplicate clients should not be swiched. But in my humble opinion, this might be a little overcorrect. Because when we use the shiftviewclients, we just want to traverse the tags that contains client. If we really care if the tag is a "duplicate" one, we still can use MOD+numbers to more precisely go to specific tags.

The shiftview and shiftviewclients, they are both designed for fuzzy and fast traverse tags. In intuition, when using shiftviewclients, we just eyes on the tags column and see the jumps between the tags which have the dot indicator. If it suddenly skips any dotted tag, we will be some curious about that and manually go to the tag MOD+numbers to see what's happening there, and say "Ah, this is only a duplicated tag, I forgot it!" Then we switch to another tag.

You might think this is too personalized. But the shift-tool's patch author didn't put the if (c == selmon->sel) into his personal dwm repo and official dwm site, even if after you pointed out this and he confirmed.

So I think, if we can traverse the "duplicated" tag, this is a feature, not a bug.

The explanation above also suits for if (c->issticky).

3. The solution for me

For the question 1, If I use your commit, the sticky client's focus will never be kept. So I added this into dwm.c -> focus() to achieve:

if (!c || !ISVISIBLE(c)) {
+  #if STICKY_PATCH
+  for (c = selmon->stack; c && (!ISVISIBLE(c) || (c->issticky && !selmon->sel->issticky)); c = c->snext);
+  if (!c) /* No windows found; check for available stickies */
+  #endif // STICKY_PATCH
    for (c = selmon->stack; c && !ISVISIBLE(c); c = c->snext);
}

For question 2, If add any if (c == selmon->sel) or if (c->issticky), will cause some tag cannot be traversed by shiftviewclients. So I still decided to remove them.

for (c = selmon->clients; c && clients; c = c->next) {
-  if (c == selmon->sel)
-    continue;
-  #if STICKY_PATCH
-  if (c->issticky)
-    continue;
-  #endif // STICKY_PATCH
  #if SCRATCHPADS_PATCH && !RENAMED_SCRATCHPADS_PATCH
  if (!(c->tags & SPTAGMASK))
    tagmask |= c->tags;

I think I will still keep this modifications in my fork version.

Finally, thank you for your excellent work! :-)

After some experences, I think you are right:

	#endif // FOCUSFOLLOWMOUSE_PATCH
+	#if STICKY_PATCH
+	if (!c || !ISVISIBLE(c))
+		for (c = selmon->stack; c && (!ISVISIBLE(c) || c->issticky); c = c->snext);
+	#endif // STICKY_PATCH
	if (!c || !ISVISIBLE(c))
		for (c = selmon->stack; c && !ISVISIBLE(c); c = c->snext);
for (c = selmon->clients; c && clients; c = c->next) {
+  if (c == selmon->sel)
+	continue;
+  #if STICKY_PATCH
+  if (c->issticky)
+    continue;
+  #endif // STICKY_PATCH
  #if SCRATCHPADS_PATCH && !RENAMED_SCRATCHPADS_PATCH
  if (!(c->tags & SPTAGMASK))
    tagmask |= c->tags;

But we need to ignore the sticky client in HIDEVACENTTAGS_PATCH. I created PR #396 to solve this.

Close this beacuse this has been solved.