Avalonia icon indicating copy to clipboard operation
Avalonia copied to clipboard

On Linux WindowStartupLocation="CenterScreen" not expected behavior

Open KorotynskiyA opened this issue 2 years ago • 22 comments

Describe the bug On Linux OS window and MessageBox, etc. called appear in a random position, although the WindowStartupLocation="CenterScreen" property is set. On Windows OS window positions is correct.

Expected behavior When WindowStartupLocation="CenterScreen" window called appear in Center of screen independent of platform.

Desktop (please complete the following information):

  • OS: Raspbian, Linux Ubuntu 18.04
  • Version 0.10.6, 0.10.7

Additional context But in version 0.10.3 it works.

KorotynskiyA avatar Aug 17 '21 17:08 KorotynskiyA

Exactly!! I have the same issue. Devs should fix it.

SamuelGameKiller avatar Aug 23 '21 10:08 SamuelGameKiller

I think this is by design. The underlying windowing system might not support requesting a specific window position in a synchronous way. A window might be shown at a random initial position.

Gillibald avatar Aug 23 '21 13:08 Gillibald

I think this is by design. The underlying windowing system might not support requesting a specific window position in a synchronous way. A window might be shown at a random initial position.

No, man, this is not design. 1) For random initial position you can use WindowStartupLocation="Manual". 2) On Windows window called appear in Center of screen with "CenterScreen" property.

KorotynskiyA avatar Aug 23 '21 14:08 KorotynskiyA

  1. On Windows window called appear in Center of screen with "CenterScreen" property.

You shouldn't always compare other platforms with Windows, since their desktop manager capabilities might be different. From what I know, for instance, Wayland does not support manual positioning, while X11 and Windows do.

But in this particular case it looks more like a bug, especially when it did worked on the same OS as it was mentioned.

Devs should fix it.

@SamuelGameKiller since it's a OSS project where developers work mostly in their free time, they are free to decide on priorities in most of the cases. And if users want any bug to be fixed as soon as possible, they should consider to help in any way possible. Which also means, pull requests are welcomed.

maxkatz6 avatar Aug 23 '21 15:08 maxkatz6

With Avalonia 0.10.5 the dialogs are displayed centered on a Raspberry Pi using WindowStartupLocation="CenterOwner". The feature/behaviour broke with changes (0.10.5 -> 0.10.6) in the method ShowDialog in Windows.cs - the order of the call to SetWindowStartupLocation(owner.PlatformImpl) changed.

I reverted the order of the call to SetWindowStartupLocation(owner.PlatformImpl), like it was in 0.10.5, and compiled 0.10.7 with this change. On my Raspberry Pi the dialogs are now displayed centered again.

mork2020 avatar Aug 28 '21 07:08 mork2020

I'm also having this issue with 0.10.7 running on PopOS 21.04 with a 4K monitor.

Neither CenterScreen or CenterOwner work, but the window is always displayed top-left.

kuiperzone avatar Sep 08 '21 18:09 kuiperzone

I'm using Avalonia 0.10.7 on Ubuntu MATE 20.04 LTS.

WindowStartupLocation="CenterScreen" works on Marco, but not on Compiz. On Marco, the window is properly placed where it should; but on Compiz its placed to the top-left corner directly.

It seems like this issue might be caused by some window managers.

xavizardKnight avatar Oct 31 '21 13:10 xavizardKnight

Thanks @mork2020, it looks like this one is the culprit: https://github.com/AvaloniaUI/Avalonia/commit/ad9519102e7b8af0ff146ab7a4c94ff0af053307

Basically SetWindowStartupLocation() has to be called after the window is shown. On Linux with X11 and KDE that seems to solve the problem.

iq2luc avatar Nov 17 '21 14:11 iq2luc

I'm having this as well, on manjaro X11 KDE, 0.10.6 to 0.10.10 all do it

edit: the reports that i've seen are for arm, and i'd like to add that it also happens on x86_64

headassbtw avatar Nov 20 '21 00:11 headassbtw

I'm having the same issue. As a workaround I center my windows with this code


private bool m_Done = false;

private void InitializeComponent()
{
    AvaloniaXamlLoader.Load(this);

    var iv = this.GetObservable(Window.IsVisibleProperty);
    iv.Subscribe(value =>
    {
        if (value && !m_Done) {
            m_Done = true;
            CenterWindow();
        }
    });
}

private async void CenterWindow()
{
    if (this.WindowStartupLocation == WindowStartupLocation.Manual)
        return;

    Screen screen = null;
    while (screen == null) {
        await Task.Delay(1);
        screen = this.Screens.ScreenFromVisual(this);
    }

    if (this.WindowStartupLocation == WindowStartupLocation.CenterScreen) {
        var x = (int)Math.Floor(screen.Bounds.Width / 2 - this.Bounds.Width / 2);
        var y = (int)Math.Floor(screen.Bounds.Height / 2 - (this.Bounds.Height + 30) / 2);

        this.Position = new PixelPoint(x, y);
    } else if (this.WindowStartupLocation == WindowStartupLocation.CenterOwner) {
        var pw = this.Owner as Window;
        if (pw != null) {
            var x = (int)Math.Floor(pw.Bounds.Width / 2 - this.Bounds.Width / 2 + pw.Position.X);
            var y = (int)Math.Floor(pw.Bounds.Height / 2 - (this.Bounds.Height + 30) / 2 + pw.Position.Y);

            this.Position = new PixelPoint(x, y);
        }
    }
}

sakya avatar Dec 18 '21 19:12 sakya

Hi @sakya, to avoid using hard-coded values to compensate for decorations, and also consider desktop scaling, you could do something similar to what Avalonia does internally:

private void SetWindowStartupLocationWorkaround() {
      if(OperatingSystem.IsWindows()) { // Not needed for Windows
         return;
      }

      double scale = PlatformImpl?.DesktopScaling ?? 1.0;
      IWindowBaseImpl powner = Owner?.PlatformImpl;
      if(powner != null) {
         scale = powner.DesktopScaling;
      }
      PixelRect rect = new PixelRect(PixelPoint.Origin,
         PixelSize.FromSize(ClientSize, scale));
      if(WindowStartupLocation == WindowStartupLocation.CenterScreen) {
         Screen screen = Screens.ScreenFromPoint(powner?.Position ?? Position);
         if(screen == null) {
            return;
         }
         Position = screen.WorkingArea.CenterRect(rect).Position;
      }
      else {
         if(powner == null ||
            WindowStartupLocation != WindowStartupLocation.CenterOwner) {
            return;
         }
         Position = new PixelRect(powner.Position,
            PixelSize.FromSize(powner.ClientSize, scale)).CenterRect(rect).Position;
      }
   }

iq2luc avatar Dec 19 '21 10:12 iq2luc

Many thanks. My solution worked with desktop scaling (at least using Wayland). Your solution doesn't work if called when IsVisible is first set to true. To make it work I need to put at the beginning await Task.Delay(1);

sakya avatar Dec 19 '21 11:12 sakya

Indeed, I forgot to mention the function has to be called after the window is showed. Basically you could derive from the Window class and hijack its Show() / ShowDialog() / IsVisble, so there is no need to subscribe to events. In your example the delay is needed so the window gets the chance to be actually shown, in my opinion it is better to just call the workaround function when we know for sure it is already shown (and so avoiding subscribing and delays).

Or better yet, a proper fix :-) revert https://github.com/AvaloniaUI/Avalonia/commit/ad9519102e7b8af0ff146ab7a4c94ff0af053307 or adjust the code for X11 (and Wayland?).

iq2luc avatar Dec 19 '21 11:12 iq2luc

This does not work

public override void Show()
{
    base.Show();
    SetWindowStartupLocationWorkaround();
}

Furthermore, when IsVisible is set, Owner is still null! I tried running it when Owner is set, but it still fails to set the correct position. Using Garuda Linux (Arch) KDE.

mysteryx93 avatar Dec 27 '21 02:12 mysteryx93

Hi @mysteryx93, please see ShowCore(Window parent) function which the Show() and Show(Window parent) functions actually calls. The Owner is set in that function, so in your example if you set WindowStartupLocation="CenterOwner" in the XAML file but you call it without the parent parameter (no Owner) it works as expected (i.e. no parent centering). Either specify WindowStartupLocation="CenterScreen" in the XAML file (if that is what you actually want), or use the Show(Window parent) function instead. I confirm the workaround I proposed works (at least on Arch with X11 and KDE).

iq2luc avatar Dec 27 '21 09:12 iq2luc

Alright this base class is working, almost. Overriding Show works for the main window, but for a sub-dialog, it won't work unless I call await Task.Delay(1)

using System;
using System.Threading.Tasks;
using Avalonia;
using Avalonia.Controls;

namespace HanumanInstitute.Common.Avalonia;

public abstract class CommonWindow<T> : Window
{
    protected CommonWindow()
    {
        // ReSharper disable once VirtualMemberCallInConstructor
        Initialize();
#if DEBUG
        this.AttachDevTools();
#endif
    }

    public override async void Show()
    {
        base.Show();
        await Task.Delay(1);
        SetWindowStartupLocationWorkaround();
    }
    
    protected abstract void Initialize();

    public T ViewModel => (T)DataContext!;

    /// <summary>
    /// Fix center start position not working on Linux. 
    /// </summary>
    private void SetWindowStartupLocationWorkaround()
    {
        if (OperatingSystem.IsWindows())
        {
            // Not needed for Windows
            return;
        }

        var scale = PlatformImpl?.DesktopScaling ?? 1.0;
        var pOwner = Owner?.PlatformImpl;
        if (pOwner != null)
        {
            scale = pOwner.DesktopScaling;
        }
        var rect = new PixelRect(PixelPoint.Origin,
            PixelSize.FromSize(ClientSize, scale));
        if (WindowStartupLocation == WindowStartupLocation.CenterScreen)
        {
            var screen = Screens.ScreenFromPoint(pOwner?.Position ?? Position);
            if (screen == null)
            {
                return;
            }
            Position = screen.WorkingArea.CenterRect(rect).Position;
        }
        else
        {
            if (pOwner == null ||
                WindowStartupLocation != WindowStartupLocation.CenterOwner)
            {
                return;
            }
            Position = new PixelRect(pOwner.Position,
                PixelSize.FromSize(pOwner.ClientSize, scale)).CenterRect(rect).Position;
        }
    }
}

Btw is there a way to make this line work from the base class without having to repeat it in derived classes? (if I can put that in the base class, all View classes would be empty. I love empty classes. Easy to maintain.)

protected override void Initialize() => AvaloniaXamlLoader.Load(this);  

CenterOwner isn't quite accurate though. Here's a screenshot.

Screenshot-2.png

mysteryx93 avatar Dec 27 '21 21:12 mysteryx93

I create a behavior out of this code that works fine and doesn't need to have the Delay(1). Just cannot make it work with the last version of the code but with the first.

aboimpinto avatar Dec 29 '21 05:12 aboimpinto

Any idea when there's going to be a fix for this? Although I can patch it in my own code, MessageBox.Avalonia still displays message boxes in the wrong locations, and they're waiting for Avalonia to fix it.

mysteryx93 avatar Feb 15 '22 16:02 mysteryx93

I'm having the same issue. Especially on a cross platform application it's more than annoying if such basic feature is extremely inconsistent. More over all of these fixes and solutions are working but the moment you update the nuget packages they can be broken again.

Symbai avatar Mar 17 '22 13:03 Symbai

Any news on this bug?

gusmanb avatar Jul 30 '22 21:07 gusmanb

BTW, for the workaround, if instead of overriding Show() you override OnOpened(EventArgs e) it works for all Show* functions even with ones that specify a parent.

gusmanb avatar Jul 30 '22 21:07 gusmanb

If anyone has a fix for it, send a PR to Avalonia. If you're not sure if your idea is a good one, submit it anyway. From my experience you will get a good feedback and advice then.

Happy coding Tim

timunie avatar Jul 31 '22 07:07 timunie

On 0.10.19 still not working

malex81 avatar May 02 '23 13:05 malex81

On 0.10.19 still not working

It seems to be fixed (at least) from A11P6 onward. So the fix is coming.

kuiperzone avatar May 02 '23 17:05 kuiperzone

This does not appear to be working as of 11.0.6, in Linux Mint 21.3 (Virginia) using X11. See the attached image.

I'm going through the Avalonia.MusicStore tutorial on the website and decided to add the WindowStartupLocation to MainWindow.axaml to ensure the initial window started up in the center of the screen. It doesn't work.

image

JeromyWalsh avatar Mar 02 '24 00:03 JeromyWalsh