Closed samholmes closed 2 years ago
Beautifully said and I think you're absolutely right that this is the ideal behavior. It's how it works in Linux too! (They call it focus-follows-mouse + auto-raise, where the focus-follows-mouse part is always instant and the auto-raise delay is configurable, including the option to not auto-raise at all.)
Unfortunately @sbmpost has looked into it and concluded that Apple just doesn't expose the ability to focus without auto-raising.
If anyone thinks of some clever way to somehow make it happen, that would be amazing.
PS: Dup of #7
@dreeves thanks for covering this one :-) And indeed it is true this cannot be done ATM.
@dreeves @samholmes I suggest we leave this issue open, so others can take note of this as well. Thoughts?
Sounds smart! I'd even advocate for adding notes here about why exactly it's impossible and to solicit ideas on conceivable ways to work around Apple's limitations. I'm guessing it's baked deep into the OS and is truly hopeless but who knows!
This describes the desired functionality very succicntly:
focus-follows-mouse part is always instant and the auto-raise delay is configurable, including the option to not auto-raise at all
Perhaphs a work around could be to instantly auto-foucs, but put on a large custom delay on the auto-raise. The delay could be so large that infact it operates similar to having no auto-raise at all. For example, a Delay setting of a few hours or days that only affects the auto-focus part and not the focus-follows-mouse part.
Is that possible?
I tried changing the Delay setting in the config file to a large number (i.e. 100000000000) to test this theory, but it had no effect.
Is the current Delay setting supposed to affect both the focus-follows-mouse and auto-raise at the same time? Because if you could seperate out those Delays (one delay setting for focus-follows-mouse, and one delay setting for auto-raise) then this workaround might suffice.
@BenJackGill The problem is that OSX needs to be instructed to raise the window in order to focus it. As such the two always come together. There is no other way to focus a window.
Ugh, this is exactly the behaviour I'd like to see, super bummed that it seems not possible right now!
@samholmes @fredngo @dreeves @BenJackGill
I experimented more and used some code from another project. It would seem that with a private Apple API, this can be implemented. But note that this is very low level and may break easily in future OSX versions. I am therefore keeping this in an experimental branch. It may never make it to master, but I figured you might be interested. It can be built like so:
g++ -O2 -Wall -fobjc-arc -o AutoRaise AutoRaise.mm -framework AppKit -F /System/Library/PrivateFrameworks -framework SkyLight
Ooh, exciting! I switched to branch 7-47-focus-without-raise-experimental and built it (confirming that it's v2.8 running) and that works to have focus-follows-mouse without auto-raise! But what I'm really pining for is immediate focus-follows-mouse with 500ms-delayed auto-raise.
So tantalizingly close now! <3
@dreeves ok! Thanks for the feedback. Out of curiosity: what is the difference between raising a window after 500ms or focusing it first while the mouse is hovering over it? I cannot imagine someone to move the mouse while simulatiously using the keyboard to access that window.
@sbmpost I think I see what he wants: with 500ms autoraise, you wouldn't have any problems with windows raising while moving the mouse ect, and you also (with immediate focus follows mouse) not have any issues of your first keypresses being registered in the wrong window.
I think that's a good idea, although I'll try focus instantly follows mouse(without raise), it may be enough for my usecase.
@Laaarge
and you also (with immediate focus follows mouse) not have any issues of your first keypresses being registered in the wrong window.
I see, yes that could be it. Thanks.
Exactly right. The default 40ms delay kinda drives me crazy when I'm a bit too slow with moving the mouse across my various displays. But any longer and it would be too annoying waiting for the focus to change. Immediate focus change with delayed autoraise elegantly gets the best of both worlds!
PS: See also #7
So I've been using this and it is great, it fits my needs much better than traditional AutoRaise. However I had 2 problems:
But:
for better or for worse, I have the Noobest solutions of all:
I removed lines 138-154 (because verbose logged >>>>>>>>psn equals<<<<<<<<<
)
And that has fixed both of my problems… Lol
I do have a remaining problem:
In both of those situations, verbose still logs in a loop Activate
and focus without raise, $window info$
,
which I don't think is the intended behavior
PS PS: I'm not pushing this since it's such an ugly fix, but if someone wants to take a bite at it, I think this is a great starting place (and I'm going to look into doing this properly)
PS PS PS: I notice that AutoRaise's %CPU (in Activity Monitor) is much higher when moving (fast) around in 1 window, then switching focus very frequently (and even higher than when flickering popup menu) (haven't tested it on main branch though)
Thanks a lot for this App!
@Laaarge Thank you for your feedback, always appreciated :-)
I pushed a new version. It logs only if the verbose flag is enabled. I also made some changes in the way it finds the window under the mouse.
I removed lines 138-154 (because verbose logged >>>>>>>>psn equals<<<<<<<<<)
These lines handle the situation where you have 1 single app with multiple windows and you move the mouse between those windows. I think with my latest changes, at least the popup flickering should be gone (leaving lines 138-154 intact). The price however is that any window that is smaller than the window below it, will keep the focus. I am not sure if this is better/worse than the previous situation.
When trying to access the small popup menus of brave extensions, they would disappear as soon as I move the mouse after clicking (to get the menu open), it would close the menu… not Ideal
Not sure about the brave extensions. For these it would be interesting to see what the window title of the popup is:
if (verbose) { NSLog(@"focus without raise, %@", _windowTitle); }
Of course you now have to enable verbose logging for that ;-)
I notice that AutoRaise's %CPU (in Activity Monitor) is much higher when moving (fast) around in 1 window, then switching focus very frequently (and even higher than when flickering popup menu) (haven't tested it on main branch though)
The main branch has the same behaviour. Because for each mouse move we determine the window below the mouse and see if it needs to be raised or not. If you merely switch focus, then these actions are executed just once. To reduce CPU, the polling interval can be increased (see the top section of the AutoRaise.mm file), but that will reduce responsiveness.
@sbmpost 'twas nothing, thanks for the quick reaction (and work!) : )
These lines handle the situation where you have 1 single app with multiple windows and you move the mouse between those windows.
Oh ok (strangely, it didn't run into an issue with that, but testing it now, yeah it didn't switch windows within 1 app)
at least the popup flickering should be gone
Yup confirmed
The price however is that any window that is smaller than the window below it, will keep the focus. I am not sure if this is better/worse than the previous situation.
I'll keep an eye on that, but it's something that definitely won't be frequent, if it even happens to me: I can't think of cases where that would cause a problem… but that situation definitely exists :'(
Not sure about the brave extensions. For these it would be interesting to see what the window title of the popup is
it's a bit weird, it doesn't give anything else than the tab name, and I saw a few different behaviors for this, but it seems now that it works without problems the latest commit (popups only disappearing if the browser loses & regains focus). verbose only logged me this when I was having issues… but I don't think it's worth looking into, as it seems fixed (though if you want to, do ask me, as there is more info)
… 18:04:29.518 AutoRaise[…] focus without raise, New Tab - Brave
… 18:04:29.518 AutoRaise[…] >>>>>>>>psn equals<<<<<<<<<
@dreeves @Laaarge @samholmes @BenJackGill @fredngo So it would seem I have implemented the holy grail then ;-) Check out the latest version of this branch and run this:
make clean && make && ./AutoRaise -delay 25
(don't forget to close already running AutoRaise instances) Of course you can play with the delay to suit your needs. And to disable the raise, simply set -delay 0
This is amazing! Nice work! It's working beautifully for me. Now I'm wondering if there's any possible use case for it to ever not work this way...
@dreeves @Laaarge @samholmes @BenJackGill @fredngo I have done a few more fixes and tweaks so you might want to update again. If nothing else too serious pops up, I will now leave this branch alone for a while.
I suggest to keep this issue open for future reference and additional feedback. Thanks everyone :-)
I'm on the latest as of now and it's smooth as butter. I'm really, really happy with this!
I'm also really curious to hear if there's some argument or use case for the focus-follows-mouse part to ever be non-immediate.
(Maybe one reason is that you don't trust it to keep working in future macOS versions? That would be a tragedy.)
PS: Note that this GitHub issue is a slight misnomer now (#7 characterizes it more fully) but that's fine to keep the discussion here.
PPS: We know there are some people like @Laaarge and @samholmes and @fredngo who want focus-follows-mouse without autoraise at all. Maybe it would make sense to overload the -delay option like so:
I.e., focus-follows-mouse could always be instant and the delay for autoraise, including whether it happens at all, could be specified by the -delay parameter.
PPPS: Just realized I'm proposing something pretty similar to what @BenJackGill proposed earlier in this thread but with "-1" as the stand-in for "infinity" rather than "100000000" or whatever. (Also I'm suggesting that it never makes sense to have a delay on the focus-follows-mouse part, only the actual autoraise.)
@dreeves
I'm also really curious to hear if there's some argument or use case for the focus-follows-mouse part to ever be non-immediate. (Maybe one reason is that you don't trust it to keep working in future macOS versions? That would be a tragedy.)
Exactly that :-)
Maybe it would make sense to overload the -delay option
Currently in this particular branch it works like this:
So very close to what you are proposing actually ;-)
However I see your point that if this would go to master (unlikely, because of aforementioned reason), then the delay should probably work similar to what you described. Note that currently in master a delay equal to 1 means instant raise.
I'm in the -delay 0
club because I think if I want to raise a window, I will click it. How do I bundle this command AutoRaise -delay 0
into an app bundle?
@samholmes
How do I bundle this command AutoRaise -delay 0 into an app bundle?
You probably don't ;-). To pass arguments to the app bundle version you can use a configuration file (see the README).
I have built the branch and run with -delay 0. I do not ever want auto-raise. Just focus.
I am observing that the menu bar at the top of the screen is changing apps as the mouse pointer is still in motion and crosses other open apps. This is no good. How is one supposed to access the bar when there are other windows in the way? The answer is Ctrl-F2, but most users probably don't know that. Even so, accessing the menus only by keyboard is then a sub-optimal experience.
The original requirement in this thread specified the mouse needs to come to a stop before triggering focus. That made sense, and should enable menus access. But that does not seem to be happening, and that makes the branch difficult to use for the original use case: Just focus, no raise.
Recommendation is to either
Accessing the menu bar is the major use case for the focus delay.
- delay = -1 (aka infinity) means no autoraise, only focus-follows-mouse (which is instant)
I'm in the delay = -1
club for sure. I don't want it to ever auto-raise (despite the name of this app, lol). I just want it to focus-follow-mouse immediately. I guess this is the behavior I just got used to working on old Unix machines. 😆
- delay = 0: no autoraise, only focus-follows-mouse (which is instant)
Oops, just saw this -- I guess I'm in the delay = 0
club instead then.
I am observing that the menu bar at the top of the screen is changing apps as the mouse pointer is still in motion and crosses other open apps. This is no good. How is one supposed to access the bar when there are other windows in the way
Yup, finally got around to building and running the experimental setup. Works well except for this one problem!
By the way, I went ahead and created a homebrew formula for the experimental branch which makes it way easier to compile/install/etc for most folks!
https://github.com/fredngo/homebrew-autoraise-experimental
The HOWTO is in the README, but here's a synopsis.
Don't forget to uninstall the original homebrew formula if you already had it installed before:
brew uninstall autoraise
Update your ~/.config/AutoRaise/config
file to disable auto raise:
delay=0
Then:
brew tap fredngo/autoraise-experimental
brew install --HEAD fredngo/autoraise-experimental/autoraise
brew services start autoraise
@fredngo
By the way, I went ahead and created a homebrew formula for the experimental branch which makes it way easier to compile/install/etc for most folks!
That is cool 👍
@git-rz
detect when the pointer stops moving before focus
The patch below will accomplish this (pull first to make sure you have the latest code). However I am not sure if I like the behaviour of it. Let me know if you do ;-)
- if (raiseTimes) { raiseTimes--; }
+// if (raiseTimes) { raiseTimes--; }
+if (raiseTimes && !mouseMoved) { raiseTimes--; }
else { raiseTimes = 3; }
if (focusFirst) {
+if (!mouseMoved) {
OSStatus error = GetProcessForPID(mouseWindow_pid, &mouseWindow_psn);
if (!error) {
window_manager_focus_window_without_raise(&mouseWindow_psn,
mouseWindow_id, _focusedWindow_psn, focusedWindow_id);
if (_lastFocusedWindow) { CFRelease(_lastFocusedWindow); }
_lastFocusedWindow = _mouseWindow;
lastFocusedWindow_pid = mouseWindow_pid;
if (delayCount) { [workspaceWatcher windowFocused: _lastFocusedWindow]; }
}
+}
}
@sbmpost , At long last!! This is perfect!! What is not to like?
Admittedly I haven't poked around on menus and other places where autoraise/autofocus can go wrong for apps that were not built with it in mind. But this implementation proves once and for all that autofocus and the Mac Menu Bar can co-exist, no problem. I can only assume that the Menu bar is the reason why apple made this so difficult to implement. But here we are - a brilliant implementation that finally works.
@git-rz
What is not to like?
Guess I got used to raised windows by now, but your enthusiasm encourages me to try this out for a while :-)
I can only assume that the Menu bar is the reason why apple made this so difficult to implement.
Around 900 lines of code, yeah I guess this is/was quite a challenge to get right.
a brilliant implementation that finally works.
Thank you!
@dreeves @fredngo @samholmes @BenJackGill @Laaarge Would be great if you can tell me what for you guys the preferred way of working is. With or without the above patch? So I know whether or not to permanently include it in this branch.
@sbmpost @dreeves @fredngo @samholmes @BenJackGill
Would be great if you can tell me what for you guys the preferred way of working is. With or without the above patch?
I'm a bit torn… on one hand, the menu bar is an issue with the default behavior, but I see 2 downsides (they're actually pretty minor, but hey we're way past necessary here :)
I think we're in a not so simple situation where the right way is going to depend on people's use cases… Personally, I don't quite know what I prefer… I'm going to try the patch, as I have had issues where I needed to drag a window to the menu bar, and I think I'm going to get used to the "delay". But it is a win some, lose some situation. I'm going to let the other weigh in.
@dreeves @fredngo @samholmes @BenJackGill @Laaarge Would be great if you can tell me what for you guys the preferred way of working is. With or without the above patch? So I know whether or not to permanently include it in this branch.
Since the branch is experimental anyway; if you go ahead and put it into the branch I'd be happy to try it out. No sense holding anything back from the experimental branch.
@fredngo done
I fell in love with focus follows mouse with no autoraise in the 1990s on Linux and Solaris. Then was forced to use Windows as well, where I used Xmouse and other solutions. In almost all of those cases the following behaviors were configurable, where each option enabled the availability of the next:
I'm now primarily on Mac, and have been very frustrated with the lack of using just the first bullet point.
For me, the use case where no-autoraise is most helpful is when I have overlapping windows and need to briefly type in the half-covered window while still looking at the top-level one. But, it also solves some major problems.
The major problems, especially for OSes that do not natively support autoraise are:
This patch solves both these issues. Now it just needs to make everything configurable, and you'll have the best of all worlds.
@git-rz Thank you for your feedback. After my lastest push to this branch it goes like this:
1 Focus follow mouse ? -> delay = 0 (disables raise)
2 Autoraise ? -> delay = 1 (1 means no delay at all)
3 Autoraise delay [ms] -> delay > 1
In all these cases, the mouse should first come to a full stop before the delay is considered. I am not sure if that is the correct behaviour. Perhaps for cases 2 and 3, a mouse stop should not be required? Or perhaps this should be a configurable setting for all cases? If the delay has become obsolete because of the mouse stop solution, we might also get rid of it completely.
@Laaarge Thanks for your feedback. I especially agree with the responsiveness part. I was considering the following:
If raise enabled, don't wait for a mouse stop. If raise is disabled (focus only), demand a mouse stop
After some more thought I have come to the conclusion it is probably best to make the "mouse stop solution" a configurable setting in this branch. Let me not make the same mistake as Apple and pretend to know what is best for people ;-)
Full configurability sounds like the way to go, just because there are so many oddball applications out there, where the interactions cannot be predicted. If you wish to keep using only the one -delay flag:
-delay 0 no auto-raise but focus follows mouse after mouse stop
-delay -x raise x ms after mouse stop
-delay x raise after x ms
But perhaps not overloading the one input would be better:
[-f --focus] use only focus-follows mouse
[-m --mouse] mouse stop logic: delay applies only after mouse stop
[-d --delay x] focus or raise after delay of x ms
However, the more I use the mouse stop solution, the more I am seeing that it solves all the major annoyances I think we have all grown used to (moving the window to the top just to access the menu bar.. cmd-tabbing to get the pop-up window back.., menus closing on us, autocomplete suggestion dialogs in IDEs disappearing, and on and on).
All those issues go away completely with the mouse stop solution. It should work in the auto-raise case too: you will get a grace period to focus that popup or IDE suggestion with the mouse still moving, but still be able to auto-raise, with no delay (after the mouse stops, anyway).
It may feel less responsive, but is it really? There need not be a delay after the mouse stops, which is an improvement over the previous implementation (I configured a substantial delay to allow for menu bar access).
The only use case I can think of where the mouse is in motion, crossing window boundaries, and simultaneous keyboard input is required, would be multi-tasking while playing an Osu type game. It just seems exceedingly unlikely.
Something @samholmes requested in the original issue, is two delays actually.
I'm still of the mind that delays were a work-around due to lack of mouse-stop logic, and it may be possible to do-away with them totally, but if you are going for full configurability, and especially for his originally requested use case, where he wants focus first, then raise if he hovers too long:
[--mouse] use mouse stop logic [default: true]
[--delay x] auto-raise after x ms [default: 1]
[--focus-delay x] auto-focus after x ms [default: 1]
[--no-raise] focus follows mouse only [default: false]
Raise implies focus, so logically the focus delay should only ever be less than the raise delay.
configurable setting [...] pretend to know what is best for people
I'm not sure yet but I actually think you should default to pretending to know what's best for people and at least wait for a specific use case before making something a setting. The maintenance burden can explode combinatorially as settings proliferate! (I've made the case for that philosophy at https://blog.beeminder.com/choices.) @git-rz's argument for always using the mouse-stop criterion sounds persuasive to me (but I haven't tried it yet). I'm still loving instant focus-follows-mouse with 500ms-delayed autoraise.
delays were a work-around due to lack of mouse-stop logic
Fascinating theory! Now I'm curious to try a version with no delays, just focus+autoraise the moment the mouse stops. If that works then maybe only a single binary setting is needed: when the mouse stops should only the focus change or should autoraise also happen?
Fascinating theory!
But my guess falls apart for the people who want immediate focus but delayed raise. It just isn't my preference, so I forgot about it.
Looking at Windows Xmouse, as well as some Linux implementations, it seems like the focus-delay is not configurable (and always immediate), whereas the raise-delay is an option. If looking to cut down on the configurability here, the --focus-delay
should be the first to go. -delay x
is there to stay. --mouse
could be replaced with --activation-style=mouse-stop | legacy | sloppy | ...
, so that other styles can be used without having to resort to branches.
Even after only one day of use, I can conclusively say that the mouse-stop implementation is more reliable than Xmouse on Windows. It is probably more reliable than many sloppy-focus implementations of different window managers, too.
@git-rz A lot of things to think about :) You provide good arguments to make mouse stop the default. Other things to consider are simplicity (too many options might become confusing) and what people got used to. Plus there is the complication that Apple may change the way to do "focus only" as they please as it is completely undocumented (one of the reasons I am keeping this in an experimental branch).
I am going to need some time figuring out what is best. Until then, I hope you guys can tweak things in a way that is working for you.
Recently I've been using a tiling window manager (Amethyst), so no windows overlap anyway :) But the mouse-stop logic will help even in the normal auto-raise case. I am hoping others can test, to prove this. You may be on the cutting edge of window activation strategy here. There are a very large number of bugs related to bad interactions of sloppy focus with new UI elements, or subsequent to keyboard navigation, an errant window being activated (or deactivated). This strategy, if not totally new, is certainly working for me, and seems to eliminate the potential for many of those types of defects.
Thanks for this great utility :D
Because focus-follows-mouse is instant and the app menu changes based on what is focused, I feel like having a delay after mouse is still of say 100 ms would be useful to avoid accidentally focusing on windows while you're mouse is moving towards the menu. We need two delays:
I purpose milliseconds over whatever other unit was used.
@samholmes , please try the branch with -delay 0
. You might be surprised that the delay isn't needed at all anymore, not even for menu bar access when crossing apps.
But I agree with you: there still is a use case for a separate option for focusDelay. It would be there for people who may have trouble moving the mouse smoothly. Trackball users, amputees and the elderly come to mind. Or wireless mice suffering interference or low batteries. The delay would provide forgiveness in those scenarios.
Regarding the unit: it is currently 20 ms.
@dreeves @samholmes @fredngo @BenJackGill @git-rz @Laaarge
Fascinating theory! Now I'm curious to try a version with no delays, just focus+autoraise the moment the mouse stops. If that works then maybe only a single binary setting is needed: when the mouse stops should only the focus change or should autoraise also happen?
I have pushed another change to this branch that allows you to easily test the best solution by editing these flags on top of the AutoRaise.mm file:
focusFirst = true/false
mouseStop = true/false
I think together with -delay (0=no-raise, 1=no-delay, 2=20ms, 3=40ms) this covers all preferences one could have. I am considering to make the mouseStop flag a command line option in the master branch. In this way the experimental branch will only be about adding the "focus-first" functionality and we get an extra feature in master.
I'm testing the new code now. Only feedback so far is that the default value for the mouseStop
is false
, and that is opposite the value needed if we need more people testing the new behavior on the experimental branch. ;)
I'm testing the new code now. Only feedback so far is that the default value for the
mouseStop
isfalse
, and that is opposite the value needed if we need more people testing the new behavior on the experimental branch. ;)
Ya I was just about to comment that after recompiling and trying the latest it doesn't seem to wait for the mouse to stop, I guess that explains it!
It's very odd, I edited the file to have mouseStop = true
and recompiled, but when I run it it says:
% autoraise -delay 0
v2.9 by sbmpost(c) 2022, usage:
AutoRaise -delay <0=no-raise, 1=no-delay, 2=20ms, 3=40ms, ...> [-warpX <0.5> -warpY <0.5> -scale <2.0> [-verbose <true|false>]]
Started with warp and focus only (no-raise), warpX: 0.5, warpY: 0.5, scale: 2.0
Supposed to be "no-raise", but oddly, it DOES raise. :-/
Similar to ctrl+opt+click, I'd like to config this tool to focus a window without bringing it to the front of all other windows. The primary use-case for this tool for me is to focus on a window for quick access to keyboard shortcuts for that window. The focus without raise feature could be instant or customized to a specific delay.
If I want to bring the window to the front, then it would be nice to make this tool bring the window to the front after 500ms of no cursor motion. This way I can move my cursor anywhere without raising windows but focusing instantly on windows. Only after stopping my cursor on one window will the window be raised. This is the ideal UX I'd like to achieve. Is this doable?