Skip to content
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

Fix X11 window invisible after WindowState has changed to Maximized or FullScreen #16922

Open
wants to merge 4 commits into
base: master
Choose a base branch
from

Conversation

walterlv
Copy link
Contributor

@walterlv walterlv commented Sep 4, 2024

⚠️IMPORTANT EDIT⚠️:

See comments below to see the PR udpates:


What does the pull request do?

If we have such code in the MainWindow:

public MainWindow()
{
    InitializeComponent();
}

private async void TestButton_Click(object sender, RoutedEventArgs e)
{
    WindowState = WindowState.Minimized;
    await Task.Delay(1000);
    WindowState = WindowState.FullScreen;
}

Run it on a linux system, click the button, the window will be minimized. After 1 second, the window will still be invisible, but the property WindowState has been changed to FullScreen.

Why does this happen? Because the XIconifyWindow function minimizes the window but there is no oppsite function to restore it. What we should do is to send a _NET_ACTIVE_WINDOW message to activate the window. Remove the _NET_WM_STATE_HIDDEN atom does nothing actually.

Please see my changes and you can find that if the previous state is WindowState.Normal, the behavior is correct because the message is sent there. This PR adds the same message sending to the WindowState.Maximized and WindowState.FullScreen cases.

What is the current behavior?

The current behavior is that if a window is maximized or fullscreen, it will be invisible after it has been minimized and then restored.

What is the updated/expected behavior with this PR?

The window should be visible after it has been restored.

How was the solution implemented (if it's not obvious)?

Checklist

Breaking changes

Obsoletions / Deprecations

Fixed issues

@avaloniaui-bot
Copy link

You can test this PR using the following package version. 11.2.999-cibuild0051656-alpha. (feed url: https://nuget-feed-all.avaloniaui.net/v3/index.json) [PRBUILDID]

@SeWZC
Copy link

SeWZC commented Sep 5, 2024

In the EWMH _NET_WM_STATE, it is stated that:

Implementation note: if an Application asks to toggle _NET_WM_STATE_HIDDEN the Window Manager should probably just ignore the request, since _NET_WM_STATE_HIDDEN is a function of some other aspect of the window such as minimization, rather than an independent state.

Thus, we shouldn't toggle this state manually.

@walterlv
Copy link
Contributor Author

walterlv commented Sep 10, 2024

UPDATE 1

Reason

Let's see the official document:

4.1.4. Changing Window State

Normal -> Iconic - The client should send a ClientMessage event as described later in this section.
Iconic -> Normal - The client should map the window. The contents of WM_HINTS.initial_state are irrelevant in this case.

Once a client's window has left the Withdrawn state, the window will be mapped if it is in the Normal state and the window will be unmapped if it is in the Iconic state.

This means:

  1. (For clients) If we want a X11 window to be minimized, we should send a ClientMessage event to the root window. Fortunately, the Xlib provides a XIconifyWindow function to do this so we can use it directly.
  2. (For clients) If we want a X11 window to be restored, we should map the window. Unfortunately, the Xlib provides a XMapWindow function for us but it doesn't work for all circumstances.
  3. (For window manager) If a window is in the normal state, the window should be mapped. If a window is in the iconic state, the window should be unmapped.

Let's see the window.c code of xorg-server:

int
MapWindow(WindowPtr pWin, ClientPtr client)
{
    ScreenPtr pScreen;

    WindowPtr pParent;
    WindowPtr pLayerWin;

    if (pWin->mapped)
        return Success;

    // ...
}

If a X11 window is mapping, the window manager will check if the window is mapped or not. If the window is mapped, the window manager will return Success directly. But what is the state of our iconic window? For the window manager, the iconic window shoud be unmapped. However, some window manager (e.g. kwin, the default window manager of KDE) provides a configuration to change this behavior.

; /etc/xdg/kwinrc
[Compositing]
HiddenPreviews=6

If the HiddenPreviews is set to 6, the window manager will not unmap the iconic window. So if the window calls XMapWindow to restore an iconic window, it does nothing. The window will still be invisible.


Solution

Thanks to my friend @kkwpsv. After several days of research, we know the reason why the state changing failed and how to fix it. The best way to restore a window is to map a window without calling the XMapWindow function. So we should use the XMapRequestEvent to map it, which is the code after the pWin->mapped check.

In conclusion, we get the final code to fix this issue.

new XEvent
{
    MapRequestEvent = new XMapRequestEvent
    {
        type = XEventName.MapRequest,
        serial = 0,
        display = _x11.Display,
        send_event = 1,
        parent = _x11.RootWindow,
        window = _handle,
    },
};

Please review the full code in this PR, thanks.

P.S.

Why we remove the _NET_ACTIVE_WINDOW calling?

  1. The official document says that we should map the window to restore it.

  2. We have another situation that the _NET_ACTIVE_WINDOW calling will not work. See the example code below:

    private async void TestButton_Click(object sender, RoutedEventArgs e)
    {
        WindowState = WindowState.Minimized;
        await Task.Delay(1000);
        new Window
        {
            Width = 100,
            Height = 100,
            Content = new Border
            {
                Background = Brushes.Red,
                Child = new TextBlock
                {
                    Text = "I'm a window!",
                },
            },
        }.Show();
        await Task.Delay(1000);
        WindowState = WindowState.FullScreen;
    }

    Comparing to the first example code above, we show a new window during the iconic state which makes the _NET_ACTIVE_WINDOW calling failed. We don't know the reason but the official XMapRequestEvent method fixes it.

@avaloniaui-bot
Copy link

You can test this PR using the following package version. 11.2.999-cibuild0051732-alpha. (feed url: https://nuget-feed-all.avaloniaui.net/v3/index.json) [PRBUILDID]

…contents that not been layouted or rendered will be shown.
@walterlv
Copy link
Contributor Author

UPDATE 2

After some testing, I found that there are some issues in the previous change: if MapWindow is used before X11Window is displayed for the first time, it will be displayed in advance. At this moment, the Avalonia window has not even started layout and rendering, which will cause the content inside the window to be very messy after the window is displayed. This is probably the reason why _NET_ACTIVE_WINDOW was used before.

Now, I decide which method to use to display the window based on whether the window has once called MapWindow, so that both issues can be solved at the same time.

@avaloniaui-bot
Copy link

You can test this PR using the following package version. 11.2.999-cibuild0051853-alpha. (feed url: https://nuget-feed-all.avaloniaui.net/v3/index.json) [PRBUILDID]

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants