Show/Hide attached surfaces when entering/exiting from fullscreen#4664
Show/Hide attached surfaces when entering/exiting from fullscreen#4664tarek-y-ismail wants to merge 41 commits intomainfrom
Conversation
e76e32d to
4a67dd1
Compare
src/miral/basic_window_manager.cpp
Outdated
| bool const was_fullscreen{info.state() == mir_window_state_fullscreen}; | ||
| if (was_fullscreen) |
There was a problem hiding this comment.
No need for a variable (especially with scope beyond the if statement).
But... does this code belong here anyway? I mean, if the focus shifts to another fullscreen window we need to undo this work.
Instead, when a window (or no window) gets focus we should be checking the state of the attached windows and acting accordingly.
There was a problem hiding this comment.
I looked into it. This is the latest we can do this check. It was added specifically for the case where you have a fullscreen window, and you close it without exiting fullscreen first.
In that case, a few lines later, the info for this window will be erased before we reach the focus checking code, so we won't be able to move this code there.
states They are attached! The previous state is constant for all of them!
Mir saw that the buffer is not mapped, but the client submitted a buffer with a size, so it sets its status to restored. `place_and_size_for_state` then sees that the attached client is not overlapping with the application zone. Which is correct because application zones shrink for attached surfaces with exclusion zones. It then tries to center the surface in the application zone, causing clients like waybar, ironbar, and gbar to appear in the middle of the screen when they try submitting a buffer while hidden under a fullscreen app.
…p to a fullscreen app
|
Something I've just realized is that I haven't taken into account cases where focus changes (or a surface is closed) with the closed surface and the newly focused surface on different outputs/display areas. So far I've assumed both are on the same display area. Another thing I should mention is that focus handling feels quite messy at the moment. For example, when we have two apps open. Let's call them A and B. If we close A, Context: mir/src/miral/basic_window_manager.cpp Lines 327 to 349 in 38d8e69 mir/src/miral/basic_window_manager.cpp Lines 578 to 581 in 38d8e69 We also call mir/src/miral/basic_window_manager.cpp Lines 385 to 402 in 38d8e69 Then that calls mir/src/miral/basic_window_manager.cpp Line 1703 in 38d8e69 Then we execute the rest of Edit: I just realized that I've worked on this alone for 120% of my time today. So there might be a solution to the mini-rant above that my neurons are just too fried to see right now. |
Should appease GCC
When there would be a layer shell surface with and a fullscreen surface on one output, and we click on empty space in the other output, this would cause the hidden layer shell surface to erroneously be restored.
its own private method
…al to its own private method
AlanGriffiths
left a comment
There was a problem hiding this comment.
This seems messy.
I don't have a clear solution to offer, but trying to model a window state of "attached and hidden" as "hidden and in a list" introduces a lot of corner cases that might be avoided with another approach. (A separate "hidden" property or a new "attached and hidden" state.)
| // If a layer shell surface (waybar) tries to submit a buffer while | ||
| // its hidden (in favor of another fullscreen surface), don't try | ||
| // to modify its placement. | ||
| if (display_area_for(info)->is_hidden_attached(info)) | ||
| mods.state().consume(); |
There was a problem hiding this comment.
This seems overbroad: it bans any transitions. E.g. a panel that decides to transition from "attached" to "restored" (and, maybe, change layer too).
There was a problem hiding this comment.
The problem is that some bars (waybar, ironbar, etc...) update themselves even after being set to hidden. This thread on matrix has all the details.
There was a problem hiding this comment.
I don't see much detail:
tarek-y-ismail
I also may have discovered a Waybar bug...
tarek-y-ismail
Doesn't happen with Yambar, happens with ironbar
tarek-y-ismail
Also breaks with gBar
What are these components actually doing?
There was a problem hiding this comment.
Oops, I may have supplied the wrong link. Please try this
There was a problem hiding this comment.
It may be the trouble I have following these links
There was a problem hiding this comment.
Ok I think I figured out what's happening.
When we enter fullscreen, we set the window's state to hidden, which works fine.
The bug occurs when the bar submits a frame. This piece of code here sees that the surface is not mapped, but it submitted a buffer, so it should be mapped. It then sets its state to restored: https://github.com/canonical/mir/blob/7e7e41f259a0cba81e870467d488d1dda37913a2/src/server/frontend_xwayland/xwayland_surface_role.cpp#L136-L141
Execution reaches this piece of code, where it sees the state as restored and sees that the restore rect does not overlap the application zone, because layer shell zones may have an exclusion zone, which shrinks the application zone: https://github.com/canonical/mir/blob/7e7e41f259a0cba81e870467d488d1dda37913a2/src/miral/basic_window_manager.cpp#L1445-L1452
Then clamp_top actually does not clamp, it centers the surface vertically: https://github.com/canonical/mir/blob/7e7e41f259a0cba81e870467d488d1dda37913a2/src/miral/basic_window_manager.cpp#L48-L51
Which leads to the bars (see next thread) appearing in the middle of the screen.
There was a problem hiding this comment.
OK so there's a mess: this logic is clearly simplistic...
bool const is_mapped = surface.value()->visible();
bool const should_be_mapped = static_cast<bool>(wl_surface->buffer_size());
if (!is_mapped && should_be_mapped && surface.value()->state() == mir_window_state_hidden)
{
spec.state = mir_window_state_restored;
}
else if (is_mapped && !should_be_mapped)
{
spec.state = mir_window_state_hidden;
}...not only does it ignore the existence of attached windows, it ignores fullscreen, maximized, vertmaximized,, etc.
For this approach to work we should maintain the correct state to "unhide" to. (The example FloatingWindowManagerPolicy does something along these lines with PolicyData::old_state.)
But that isn't the end of the story.
In BasicWindowManager::modify_surface() we should recognise that the client is trying to show a window we want hidden and attached and prevent that. (I.e. we should not get to BasicWindowManager::update_attached_and_fullscreen_sets() in this scenario.)
There was a problem hiding this comment.
Summarising conversation from #4694:
The code quoted above does not distinguish between the window manager hiding the window and the client hiding the window. The current behaviour is correct for the client hiding the window, but wrong for the window manager hiding the window in the case of shell surfaces hidden when an app fullscreens.
SurfaceStateTracker has logic covering the current case but cannot be applied for everything: we shouldn't be breaking the behaviour client expects. (In particular, losing the "attached" status as described above.)
tl;dr: this still needs fixing
There was a problem hiding this comment.
Before I forget all about this.
set_state is called in three locations: in modify_window, and when hiding/reattaching windows.
Right now what we do should work, as attached windows are returned to attached when visible once again. This covers set_state calls from hiding/reattaching code. If we really want to fix this properly for all other cases, we'll have to distinguish modify_window calls from the client and internally from the compositor. And once that's done, we should be able to apply a solution similar to the on in #4694
In any case, this PR should move forward, and the proper fix should be in its own PR since this issue is pre-existing.
Added `BasicWindowManager::hide_attached_windows_for_fullscreen` as a helper methods as well. Co-authored-by: Alan Griffiths <[email protected]>
`BasicWindowManager`
Just some cleanups that I found while working on #4664 and don't quite fit there.
AlanGriffiths
left a comment
There was a problem hiding this comment.
I think there's a cascade of problems caused by conflating "not visible" with mir_window_state_hidden.
In the general case, "hidden" is an aspect of window state orthogonal to "fullscreen", "maximized", "attached", etc. (Yes, it is implied by mir_window_state_hidden but can co-exist with the others.)
Maybe things would be simpler if we were only be setting BasicSurface::State::hidden without changing the state from attached.
| // If a layer shell surface (waybar) tries to submit a buffer while | ||
| // its hidden (in favor of another fullscreen surface), don't try | ||
| // to modify its placement. | ||
| if (display_area_for(info)->is_hidden_attached(info)) | ||
| mods.state().consume(); |
There was a problem hiding this comment.
OK so there's a mess: this logic is clearly simplistic...
bool const is_mapped = surface.value()->visible();
bool const should_be_mapped = static_cast<bool>(wl_surface->buffer_size());
if (!is_mapped && should_be_mapped && surface.value()->state() == mir_window_state_hidden)
{
spec.state = mir_window_state_restored;
}
else if (is_mapped && !should_be_mapped)
{
spec.state = mir_window_state_hidden;
}...not only does it ignore the existence of attached windows, it ignores fullscreen, maximized, vertmaximized,, etc.
For this approach to work we should maintain the correct state to "unhide" to. (The example FloatingWindowManagerPolicy does something along these lines with PolicyData::old_state.)
But that isn't the end of the story.
In BasicWindowManager::modify_surface() we should recognise that the client is trying to show a window we want hidden and attached and prevent that. (I.e. we should not get to BasicWindowManager::update_attached_and_fullscreen_sets() in this scenario.)
I just came across |
Introduced here: #2015 |
|
@AlanGriffiths spawned a few PRs off of your feedback as it didn't seem directly applicable to this PR. The commit history needs some cleaning up here. Will do that once you give it the thumbs up. |
fullscreening and unfullscreening a normal surface.
| // If a layer shell surface (waybar) tries to submit a buffer while | ||
| // its hidden (in favor of another fullscreen surface), don't try | ||
| // to modify its placement. | ||
| if (display_area_for(info)->is_hidden_attached(info)) | ||
| mods.state().consume(); |
There was a problem hiding this comment.
Summarising conversation from #4694:
The code quoted above does not distinguish between the window manager hiding the window and the client hiding the window. The current behaviour is correct for the client hiding the window, but wrong for the window manager hiding the window in the case of shell surfaces hidden when an app fullscreens.
SurfaceStateTracker has logic covering the current case but cannot be applied for everything: we shouldn't be breaking the behaviour client expects. (In particular, losing the "attached" status as described above.)
tl;dr: this still needs fixing
| policy->advise_focus_gained(info_for_hint); | ||
|
|
||
| if(prev_window != hint) | ||
| handle_attached_surfaces_for_focus_change(prev_window, hint); |
There was a problem hiding this comment.
There's an apparent mismatch between the supplied "prev_window, hint" and the parameter names "prev, current". But I guess that's pre-existing.
Co-authored-by: Alan Griffiths <[email protected]>
TICS Quality Gate✔️ Passedmir
|
Closes #3274
What's new?
BasicWindowManagerto hide/restore attached surfaces in the "above" layer when a surface is fullscreened, unfullscreened, and removed.How to test
Scenario 1: waybar on the above layer + fullscreen app
~/.config/waybar/config):Scenario 2: Multiple outputs
Scenario 3: Fullscreen app and OSK
ubuntu-frame-oskkate, open any text file. OSK should pop up.Preferably, we'd like the OSK to be visible when an app is fullscreened, but since it's placed in the "above" layer, and we have no way in the layer shell protocol to identify it, it will be broken for now.
Scenario 4: Waybar + Fullscreen app + non-fullscreen app
This follows scenario 1, with the following extra steps
Scenario 5: Waybar + Fullscreen app exits
Repeat scenario 1. Then close the app via a shortcut. If using a terminal, CTRL + D. Waybar should pop into view once more.
Scenario 5: Waybar + Fullscreen app with child surfaces
Media > Open Fileor Ctrl + o,Scenario 6:
Same as scenario 4, but instead of switching focus between the two apps, you exit the non-fullscreen app first.
Focus should switch to the fullscreen app, and waybar should remain hidden.
Checklist