i3 icon indicating copy to clipboard operation
i3 copied to clipboard

`focus output <direction>` (and `focus <direction>` across outputs) considers only one axis

Open CyberShadow opened this issue 1 year ago • 19 comments

I'm submitting a…

[ ] Bug
[x] Feature Request
[ ] Documentation Request
[ ] Other (Please describe in detail)

Current Behavior

Consider the following XRandR layout:

image

When focus is on output 1, focus output right moves focus to 2.

This seems to be because i3 considers only one axis when choosing the best output to focus.

Desired Behavior

I think moving the focus to 3 would be less surprising.

Impact

[ ] This feature requires new configuration and/or commands

Environment

Output of i3 --moreversion 2>&-:

Binary i3 version:  4.20.1-78-ga661d82c © 2009 Michael Stapelberg and contributors
Running i3 version: 4.20.1-78-ga661d82c (pid 2570446)
Loaded i3 config:
  /home/vladimir/.i3/config (main) (last modified: 2023-05-27T09:33:08 UTC, 0 seconds ago)

The i3 binary you just called: /usr/bin/i3
The i3 binary you are running: /usr/bin/i3
- Linux Distribution & Version: Arch Linux
- Are you using a compositor (e.g., xcompmgr or compton): picom

CyberShadow avatar May 27 '23 09:05 CyberShadow

Please note that new features which require additional configuration will usually not be considered. We are happy with the feature set of i3 and want to focus in fixing bugs instead. We do accept feature requests, however, and will evaluate whether the added benefit (clearly) outweighs the complexity it adds to i3.

Keep in mind that i3 provides a powerful way to interact with it through its IPC interface: https://i3wm.org/docs/ipc.html.

i3bot avatar May 27 '23 09:05 i3bot

This patch works for me:

diff --git a/src/randr.c b/src/randr.c
index fb733205..7c5d46da 100644
--- a/src/randr.c
+++ b/src/randr.c
@@ -299,6 +299,26 @@ Output *get_output_next(direction_t direction, Output *current, output_close_far
                 continue;
             }
         }
+
+        /* If this output is as close on our direction's axis as the best output,
+         * check the other axis. */
+        if ((direction == D_RIGHT || direction == D_LEFT) && other->x == best->rect.x) {
+            uint32_t cur_center = cur->y + cur->height / 2;
+            int32_t best_distance = abs((best->rect.y + best->rect.height / 2) - cur_center);
+            int32_t other_distance = abs((other->y + other->height / 2) - cur_center);
+            if (other_distance < best_distance) {
+                best = output;
+                continue;
+            }
+        } else if ((direction == D_DOWN || direction == D_UP) && other->y == best->rect.y) {
+            uint32_t cur_center = cur->x + cur->width / 2;
+            int32_t best_distance = abs((best->rect.x + best->rect.width / 2) - cur_center);
+            int32_t other_distance = abs((other->x + other->width / 2) - cur_center);
+            if (other_distance < best_distance) {
+                best = output;
+                continue;
+            }
+        }
     }
 
     DLOG("current = %s, best = %s\n", output_primary_name(current), (best ? output_primary_name(best) : "NULL"));

CyberShadow avatar May 27 '23 10:05 CyberShadow

I'll accept this patch with minor adjustments if you submit a PR but it would also need tests:

  1. include a new test case using fake_outputs and a setup similar to yours
  2. correct behavior when wrapping

Let me know if you'd prefer somebody else tackles that.

orestisfl avatar May 27 '23 10:05 orestisfl

I wouldn't mind doing so, except for one obstacle: the last time I contributed to i3, I think it took me half a day to get the test suite working. If the situation is unchanged, unfortunately I can't commit to that right now.

CyberShadow avatar May 27 '23 11:05 CyberShadow

The situation hasn't changed. Tbf, it's quite easy to set everything up once you know what is needed but I agree it's not the most elegant setup for a first time. Happy to write the tests for you if you prefer it though.

orestisfl avatar May 28 '23 09:05 orestisfl

Yes, please!

CyberShadow avatar Jun 16 '23 08:06 CyberShadow

Hmm, I am having second thoughts about which output in your example is the most natural to focus. I feel focusing 2 is more natural and consistent, the user can always expect output focus to always move left to right, top to bottom without doing math in their heads.

Important to note that right now it depends on the randr output order, not actually y coordinates.

@stapelberg @Airblader thoughts?

orestisfl avatar Sep 09 '23 08:09 orestisfl

I feel focusing 2 is more natural and consistent

I can assure you that it is not, especially when you have a window focused at the bottom of 1.

This has been an ongoing annoyance in my setup, until I configured my dotfiles to build/run a fixed i3 version (using Nix).

the user can always expect output focus to always move left to right, top to bottom without doing math in their heads.

I strongly disagree. "left to right, top to bottom" sounds a lot more like "doing math in my head" than "focus the output to the right", which I think most reasonable people would say is 3.

CyberShadow avatar Sep 09 '23 08:09 CyberShadow

I got bit by this yesterday with a new small extra monitor (drawing tablet) apparently there is no way to define the 'directional' mapping between screens in i3 at all (or even to just excempt a screen from move / focus directional commands (requiring an explicit workspace 'jump' to move focus there).

I understand the extreme reticence to add configurations ... but this seems like a head-scratcher it isn't acknowledged as a must-allow [the configuration of]?

m10d avatar Sep 20 '23 01:09 m10d

Hmm, I am having second thoughts about which output in your example is the most natural to focus. I feel focusing 2 is more natural and consistent, the user can always expect output focus to always move left to right, top to bottom without doing math in their heads.

Important to note that right now it depends on the randr output order, not actually y coordinates.

@stapelberg @Airblader thoughts?

The way I think about this behavior is through the lens of consistency: The existing focus <direction> command doesn’t try to do a “match the direction most closely to the current focus” as suggested here.

Consider this layout with multiple splitv containers:

2023-09-23-i3

When I focus right, focus moves to the top right container (highlighted in gray).

IIUC, the focus output <direction> behavior with multiple monitors is identical, so I don’t think we should change it at this point.


For people who feel strongly about the movement commands not doing what you think they should, I would recommend using i3’s IPC interface to implement your desired behavior. Changing focus is conceptually relatively simple — you fetch the layout tree from i3, find the node that is "focused": true to understand what the current state of the world is, then send a focus command with the id of the node you want to focus instead.

stapelberg avatar Sep 23 '23 09:09 stapelberg

The existing focus <direction> command doesn’t try to do a “match the direction most closely to the current focus” as suggested here.

I don't understand how that is at all related to the current discussion. focus for tiled containers has to work with completely different things. Outputs aren't tiled and subdivided rectangles, they are rectangles with arbitrary coordinates and sizes (and are possibly-non-adjacent and possibly-overlapping).

There is no other situation in i3 that it would make sense to compare this to. If i3 did spatial navigation for floating containers, then that would be something to consider, but it does not do that. (Perhaps it should, though!)

Consider this layout with multiple splitv containers:

I don't understand how this is relevant. The outcome there is only so because the top-right window is focused within the right container. If the bottom-right window was focused instead, the outcome would have been different.

In fact, I can use the exact same arguments as yours to show the opposite conclusion.

I'm guessing that you were misled by that the outputs in my illustration have the same width, but of course this is not necessarily true. Unlike your example, outputs 2 and 3 are not in a vertical container (or any "container" that would also exclude 1).

IIUC, the focus output <direction> behavior with multiple monitors is identical, so I don’t think we should change it at this point.

No, it is not. It uses a completely different code path, which of course it has to because the representation, geometry, and underlying concepts are completely different.


Instead, I invite you to consider the outcome of using repeated focus left / focus right commands in this situation and variations thereof:

image

Is the behavior really something that most users would find "natural and consistent"?

I hope you will agree that the current behavior is a violation of the principle of the least surprise. As I hope we've established, the current behavior is not really consistent with anything. Consider the outcome of a hypothetical poll that asks users what they expect to happen after focus right in the situation illustrated in the original post.

For people who feel strongly about the movement commands not doing what you think they should, I would recommend using i3’s IPC interface to implement your desired behavior. Changing focus is conceptually relatively simple — you fetch the layout tree from i3, find the node that is "focused": true to understand what the current state of the world is, then send a focus command with the id of the node you want to focus instead.

As an aside - yeah, that sounds nice in theory. I already use the IPC for many things. In practice, the IPC is generally quite underpowered and it does not allow many types of tree manipulations that are possible with normal controls (here is one example). It is also very easy to accidentally crash i3 via the IPC; I doubt it has ever been fuzzed. So, the advice to just do it via the IPC comes across a bit sour, sorry.

CyberShadow avatar Sep 24 '23 07:09 CyberShadow

I don't understand how that is at all related to the current discussion. focus for tiled containers has to work with completely different things. Outputs aren't tiled and subdivided rectangles, they are rectangles with arbitrary coordinates and sizes (and are possibly-non-adjacent and possibly-overlapping).

The way I think about it, outputs are just another kind of container.

I would recommend configuring your setup such that outputs are in fact adjacent and aligned. You will likely find other application software (or toolkits, for example) that makes assumptions about outputs.

While outputs can technically be overlapping, that’s not supported in i3.

No, it is not. It uses a completely different code path, which of course it has to because the representation, geometry, and underlying concepts are completely different.

I suggest thinking about this problem independent of code paths and other implementation details. Look at it from the perspective of the user guide instead.

Instead, I invite you to consider the outcome of using repeated focus left / focus right commands in this situation and variations thereof:

Can you spell out the behavior? I’m not in a position to test it right now.

As an aside - yeah, that sounds nice in theory. I already use the IPC for many things. In practice, the IPC is generally quite underpowered and it does not allow many types of tree manipulations that are possible with normal controls (here is one example). It is also very easy to accidentally crash i3 via the IPC; I doubt it has ever been fuzzed. So, the advice to just do it via the IPC comes across a bit sour, sorry.

We tried making everything possible via the IPC interface that seemed achievable.

If you have specific problems with the IPC interface, please file individual issues for those. You’re correct that we never fuzzed the IPC interface (or any other part of i3, really — fuzzing became popular years after i3 was started).


In general, I found your reply needlessly aggressive, and not at all convincing :(. It makes me not want to spend much more time on this issue. Can I ask you to take a deep breath, sleep over it, and then come back focusing on your core argument? Thanks.

stapelberg avatar Sep 24 '23 09:09 stapelberg

In practice, the IPC is generally quite underpowered and it does not allow many types of tree manipulations that are possible with normal controls (here is https://github.com/i3/i3/issues/3564). It is also very easy to accidentally crash i3 via the IPC

Is there any specific bug or crash that blocks you from implementing this in the IPC? Otherwise, I don't see how what you say is relevant. We generally prioritize fixing bugs, especially crashes. There are only a couple of non-trivial-to-fix bugs that affect the IPC but otherwise the statement that "it's very easy to accidentally crash i3 via the IPC" is not generally true.

Consider the outcome of a hypothetical poll that asks users what they expect to happen after focus right in the situation illustrated in the original post.

I've asked people and their response is not what you want it to be ;) My point is not that I know what the outcome of a hypothetical poll would be, but so do you. Even I personally expressed my personal preference for the current behavior:

Hmm, I am having second thoughts about which output in your example is the most natural to focus. I feel focusing 2 is more natural and consistent, the user can always expect output focus to always move left to right, top to bottom without doing math in their heads.

That you later dismissed:

I can assure you that it is not, especially when you have a window focused at the bottom of 1.

All in all, I see that you have a strong fixation in how you think something needs to be done and you become disappointed when people disagree with you. So, I want to join @stapelberg in requesting to re-consider your approach in this.

That is not to say that your usecase is invalid, I do see the merit in what you are proposing but we need to figure out if there's a default behavior that is an overall improvement, if there is any alternative using the IPC or if a new configuration make sense and if the investment in a new option is worth it.

orestisfl avatar Sep 24 '23 09:09 orestisfl

The way I think about it, outputs are just another kind of container.

Do you mean that you would suggest looking at identically-sized adjacent outputs as if they were in a container?

I would recommend configuring your setup such that outputs are in fact adjacent and aligned.

Well, even if 2 and 3 in the original example were actually adjacent, that doesn't change the problem.

Computer monitors can differ in resolution, so I'm not sure if it's reasonable to ask that all i3 users have outputs that are perfectly aligned.

Look at it from the perspective of the user guide instead.

I don't know of anything in the user guide that seems to indicate that the current behavior makes sense. What have I missed?

Can you spell out the behavior? I’m not in a position to test it right now.

Focus will climb up to the topmost two outputs and get "stuck" there.

In general, I found your reply needlessly aggressive, and not at all convincing :(. It makes me not want to spend much more time on this issue. Can I ask you to take a deep breath, sleep over it, and then come back focusing on your core argument? Thanks.

I apologize for the tone. In truth I am frustrated because, from my point of view, the project maintainers of a project dear to me were refusing to acknowledge that a certain nonsensical behavior is nonsensical, and their arguments about why the current behavior makes sense did not make sense to me at all.

Is there any specific bug or crash that blocks you from implementing this in the IPC? Otherwise, I don't see how what you say is relevant.

I admit it wasn't relevant, sorry.

I will do this via the IPC, so I am going to close this issue, but if you feel like it, we can continue the discussion.

but otherwise the statement that "it's very easy to accidentally crash i3 via the IPC" is not generally true.

OK, it must just be my personal experience, which I admit may be atypical. I have crashed i3 many times while developing my own IPC helper.

I've asked people and their response is not what you want it to be ;)

That is unfair. As a program's developer, I am burdened with and biased by the knowledge of the program's inner workings, and can't trust myself to make good decisions that affect the user experience.

That you later dismissed:

I have taken some liberty here because I have first-hand experience with trying to use such a setup. I feel that perhaps first-hand experience may be qualitatively different than theoretical hypotheses, but I suppose we can agree to disagree.

All in all, I see that you have a strong fixation in how you think something needs to be done and you become disappointed when people disagree with you. So, I want to join @stapelberg in requesting to re-consider your approach in this.

I feel that this was unnecessarily judgemental on your part.

I am generally happy to disagree and collaborate in finding the best solution, but I feel like this isn't what happened here. I still don't see why the current behavior makes more sense (especially considering that it currently actually depends on internal output order instead of their geometry), and all the presented arguments attempting to justify it so far seem erroneous to me. But, I'm fine with dropping it in the interests of everyone's time.

CyberShadow avatar Sep 24 '23 09:09 CyberShadow

Having slept on this, I think I know of a way how to make this work closer to what I think Michael has envisioned it.

The reason why focus right in Michael's example does what it does is because Michael had focused the top-right window within the right container before focusing the left container. If the bottom-right window had been focused instead, focus had been moved to it.

If we want to treat outputs as close as possible as containers, I think we can do the same thing here: when breaking a tie between outputs with the same coordinate along the axis we're moving focus in, we should pick the output that had been most recently focused. Then, it will work like in Michael's example, and I believe it will work well in all of the cases discussed above, and should more consistent with how i3 behaves in general.

To sum it up:

  • Currently, the tie-breaker is the output index, which is a bit arbitrary.
  • I had originally proposed making the tie-breaker the distance of the output from the current output along the other axis.
  • Orestis suggested using the raw coordinate as the tie-breaker (top-most or left-most wins).
  • I think a better solution would like to use the last focus time as the tie-breaker (most recently focused wins).

CyberShadow avatar Sep 25 '23 06:09 CyberShadow

Using the last-focused output as a tie-breaker sounds like a good suggestion to me. @orestisfl, what do you think?

stapelberg avatar Sep 25 '23 06:09 stapelberg

Most recently focused is consistent with how container focus works, great suggestion :+1:

orestisfl avatar Sep 25 '23 08:09 orestisfl

@CyberShadow I've worked a lot with focus order previously so I can submit a PR with tests a bit later in the next couple days.

orestisfl avatar Sep 25 '23 08:09 orestisfl

what's the best summary of the focus rules you might link me to?

Unlike containers which are dynamic, physical displays are not. In my case I believe using the heuristic above will be worse than it currently will, because it will require keeping a mental stack of what screens I've used to successfully jump.

m10d avatar Sep 25 '23 16:09 m10d