-
Notifications
You must be signed in to change notification settings - Fork 103
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add focus stealing prevention to MinimalWindowManager
#3693
base: main
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is not what I expected. I expected a miral API to "opt in" to "focus stealing prevention" [FSP].
This demonstrates that there's already a way to "opt in" to FSP by writing a customise window management policy, but isn't what I expected (a new constructor to MinimalWindowManager
)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's more like what I was expecting. One question and several nits.
On a API design note: bool
parameters tend to make code hard to read:
// unreadable
foo(..., false, ...);
Consider using an enum class
:
// readable
foo(..., FocusStealing::allow, ...);
That is written assuming something like
enum class FocusStealing
{
allow,
prevent
};
FloatingWindowManagerPolicy
MinimalWindowManager
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The API looks good. But I'm not sure the implementation is quite there
src/miral/minimal_window_manager.cpp
Outdated
// If focus stealing prevention is on, swap the old focused window (now in | ||
// the back) with the new window in the front. | ||
// If it's a legitimate window, it'll be focused and raised via | ||
// xdg-activation. | ||
if ((self->focus_stealing == FocusStealing::prevent) && tools.active_window()) | ||
tools.swap_tree_order(tools.active_window(), window_info.window()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not convinced that this is exactly right.
- Consider there being a fullscreen window with focus when a notification window is created. I don't expect the notification to be activated (as it shouldn't get focus), but it should be above the fullscreen window. Trying to push it "below" seems wrong (even if it ultimately fails because of an implementation detail: the windows are not in the same layer);
- Consider an application that creates a new window, e.g. a menu. This should go on top without needing activation. Here the call fails with
log_error("Unable to swap tree order due to overlap");
because BasicWindowManager checks for an overlap between the trees. Spamming the log with errors is a bad idea; - In addition to the
SurfaceStack
,application_selector
is maintaining a stack of windows. I don't see this swap being notified (it will still have the new window on top). Having inconsistencies between the two stacks will surely lead to problems.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On 1., what is "a notification window"? How would we distinguish it from any other surface?
My original bug report about stealing did mention notifications, because that's how it affected me. But notifications should never be toplevels, and we can't be expected to treat them differently to any other toplevel.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we can't be expected to treat them differently to any other toplevel
This code is handling ALL windows, not just toplevels
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For 1., looking at window types:
mir_window_type_normal, /**< AKA "regular" */
mir_window_type_utility, /**< AKA "floating" */
mir_window_type_dialog,
mir_window_type_gloss,
mir_window_type_freestyle,
mir_window_type_menu,
mir_window_type_inputmethod, /**< AKA "OSK" or handwriting etc. */
mir_window_type_satellite, /**< AKA "toolbox"/"toolbar" */
mir_window_type_tip, /**< AKA "tooltip" */
mir_window_type_decoration,
Maybe we can only swap mir_window_type_{normal,utility}
? This would also address point 2 since menus won't get swapped automatically then.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For 3., the first thing that comes to mind is overloading advise_new_window
to sync the two stacks together
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I proposes that the windows that should be swapped are mir_window_type_normal
, in the "application" layer, and parentless; anything else can be left on top
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems like some wayland applications use mir_window_type_freestyle
as well.
Edit: A lot of wayland applications. Including firefox, gnome-terminal, weston-terminal, qterminal, vlc
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK: in the "application" layer, and parentless
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I relaxed the condition and tried around with different applications and things seem to work as expected.
@Saviq @AlanGriffiths Window offsetting might be a bit more involved? The simplest thing I have in mind is making sure that when a window is placed, it shouldn't be completely occluding the windows beneath it in the surface stack. In the case of too many windows underneath, or just a really big window being opened, we can give up and occlude them. This would require some changes to What do you think? Should we spin it off into its own thing? |
Yes. There's something that dawned on me… does e.g. Firefox activate its own new windows on e.g. Ctrl+N, or do we need an exception here for new windows of the currently active client? Which would also address any children opening? |
Add constructor to `FloatingWindowManagerPolicy` and `MinimalWindowManager` to control focus stealing prevention. For properly behaving applications, xdg-activation-v1 should take care of focusing and raising any subsequent children.
unfocused windows
Only applies to windows in the application layer that don't have a parent.
2f48609
to
9002657
Compare
Nope. Only the first (mother?) instance activates. Ctrl+N opens a new window, but in the background. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks reasonable. Not done much experimenting with it as I'm not entirely sure what the expected behaviour is. Will leave merging to @Saviq
That makes sense. And means the stealing prevention logic should only act when the new surface is from a client different than the active one. |
+1 |
active application.
Some bugs I found while trying to fix alt+tab/grave:
As for the alt+tab hackaround, this was just to validate my hypothesis of what caused the bug (FSP'd surfaces not being pushed onto |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
qterminal (or Mir) seems to have a bug where Window::application is not the same between two different instances? This isn't the case with other terminals/applications. So it's likely to be a qterminal bug.
I don't think it is a bug: if you start two qterminal instances, you get two programs running each of which independently connects to the server, so the server sees two different applications.
If you start two gnome-terminal
instances you get one /usr/bin/gnome-terminal.real
program running which connect to the server once and opens two windows. So the server sees one application. (cat $(which gnome-terminal)
for details)
src/miral/application_selector.cpp
Outdated
@@ -204,6 +213,7 @@ auto ApplicationSelector::advance(bool reverse, bool within_app) -> Window | |||
auto it = find(selected); | |||
|
|||
std::optional<Window> next_window = std::nullopt; | |||
auto should_continue = true; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pretty much the same logic, but I think this is a bit clearer. Not sure if I should revert or keep these changes..
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree the logic could be clearer, but I don't think having to understand both next_window.operator bool()
and should_continue
is an improvement.
Also, is there an intentional change to the logic? Or should it be the same? ("Pretty much the same logic" doesn't make that clear)
src/miral/basic_window_manager.cpp
Outdated
|
||
// Need to push onto the MRU list for alt+tab to work correctly. | ||
// | ||
// Originally, applications were pushed only when selected, but due to | ||
// focus stealing prevention, they were not activated and thus weren't | ||
// selected nor added to mru_active_windows, breaking alt+tab | ||
auto window = info_for(surface).window(); | ||
auto const top = mru_active_windows.top(); | ||
if(top && top != window) | ||
mru_active_windows.push_unfocused(window); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pretty sure this is a tad too hacky. Suggestions welcome :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not entirely sure what the expected behaviour is.
miral-shell --focus-stealing-prevention 1
- run gnome-terminal
gedit &
mate-terminal
- go back to the terminal
gedit
Because gedit supports activation, it should be activated each time (as described by #3639). But because mate-terminal
doesn't, it doesn't get activated.
So +1 from me on the behaviour.
Just a nit on the command line option.
fb7c7fa
to
40c93fe
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The TODO list doesn't look "ready", does it need updating?
src/miral/application_selector.cpp
Outdated
@@ -204,6 +213,7 @@ auto ApplicationSelector::advance(bool reverse, bool within_app) -> Window | |||
auto it = find(selected); | |||
|
|||
std::optional<Window> next_window = std::nullopt; | |||
auto should_continue = true; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree the logic could be clearer, but I don't think having to understand both next_window.operator bool()
and should_continue
is an improvement.
Also, is there an intentional change to the logic? Or should it be the same? ("Pretty much the same logic" doesn't make that clear)
src/miral/minimal_window_manager.cpp
Outdated
auto in_background = self->prevent_focus_stealing(window_info); | ||
|
||
self->application_selector.advise_new_window(window_info, in_background); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- It looks odd to call a function on
self
to get a value passed to a function onself
(shouldn't all this logic be inself
?); and, in_background
isn't the clearest name (as evidenced by the long comment explaining it) and could beconst
bool miral::MinimalWindowManager::Impl::prevent_focus_stealing(miral::WindowInfo const& info) | ||
{ | ||
auto const normal_app_without_parent = (info.depth_layer() == mir_depth_layer_application && !info.parent()); | ||
auto const decoration_surface = info.type() == mir_window_type_decoration; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why are we checking for decorations? Won't that mean that decorations will be raised and focussed regardless of parents and layer? (Which seems wrong)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So far, I only see decorations attached to parent windows, so we don't need to check for that. But your comment about the layer is true.
@tarek-y-ismail don't add the |
aef6758
to
10ef673
Compare
Mainly, reduced the calls to `Impl::prevent_focus_stealing` and `Impl::ApplicationSelector::advise_new_window` to one call. And renamed things to be easier to understand.
10ef673
to
6d7de20
Compare
Closes #2586.
Adds a new constructor to
FloatingWindowManagerPolicy
to control focus stealing prevention. This in a nutshell stops new windows from being focused and raised. When used with xdg-activation-v1, this improves security as external actors can't just steal focus by opening a new window (in addition to the niceties xdg-activation-v1 adds to usability).TODO:
Fix window offsetting. This is built off the position of the focused window, so all windows after the second one open in the same (x,y) position.Split off to Window offsetting shouldn't assume new windows are automatically focused #3695
Fix windows closing in the order of opening instead of front to back. see Add focus stealing prevention toMinimalWindowManager
#3693 (comment)Moved to Refocus on application close is broken with focus stealing prevention #3694
Make sure Xwayland applications work properly. My focus has been on Wayland applications so far.Xwayland doesn't seem to support xdg-activation at the moment?
Have to focus other applications before they work with alt + tab
Need some way to focus applications launched via Mir (Ctrl-Alt+t/T for example)Will be in its own PR (Pass XDG_ACTIVATION_TOKEN to apps launched via
launch_app_env
#3703)