dotMorten / WinUIEx

WinUI Extensions
https://dotmorten.github.io/WinUIEx
MIT License
582 stars 37 forks source link

does not restore window size and pos after OS restart. #98

Closed torum closed 1 year ago

torum commented 1 year ago

Hi, thank you for the very usuful tool.

I've been trying to save & restore window size and position on app start up. Using WinUIEx, it works like a charm. However, once I restart my PC, the size and position are gone back to initial size and position.

I've tried the sample app, but it also show the same behaviour. Is this something designed? or...

Sorry about the English. It's not my native language.

WinUIEx v2.1 WinUI3 (both packaged/unpackaged). Windows App SDK v1.2.2 Visual Studio v17.4.4 Windows 11

dotMorten commented 1 year ago

To clarify the app restores fine between runs without restarting but then doesn't after a PC restart?

torum commented 1 year ago

Correct!

dotMorten commented 1 year ago

If would be awesome if you could run through the sample app and step through this code and observe what happens : https://github.com/dotMorten/WinUIEx/blob/09371070131dda986e1b692848fa9ed57b5bcba7/src/WinUIEx/WindowManager.cs#L341

torum commented 1 year ago

I'll try, but it may take some time especially you have to restart PC to observe what is going on... ^^

torum commented 1 year ago

Strainge... after a couple of test using the sample project, I realized that my app is working as expected... I need to test more with another fresh PC and get some more information. Meanwhile, I'll close the issue. Thank you for your time. I'll report it if I find anything. Thank you again.

dotMorten commented 1 year ago

Could it be the app got reinstalled? That would clear old stored settings. Also changes to monitor layout will also ignore saved settings (so window doesn't end up at a location outside where a monitor is)

torum commented 1 year ago

I don't think it is re-installed because my app is unpackaged. (I've tested with packaged version as well) By the way, the symptom is back. When I restart my PC just now, the sample app and my app are back to initial position. Very strange.

dotMorten commented 1 year ago

If it's unpacked you're responsible for setting up the storage. Did you do that?

torum commented 1 year ago

Yes. I mean I just copied and pasted the sample you posted somewhere. ^^

WinUIEx.WindowManager.PersistenceStorage = new FilePersistence(Path.Combine(AppDataFolder, "WinUIExPersistence.json")); and private class FilePersistence : IDictionary<string, object> ...

torum commented 1 year ago

OK, so far, what I found is that it happens infrequently and I don't know what exactly causes it. But it does happen and it seems not only PC restart but also sleep and waking up from it causes this as well.

So, I decided to add bunch of debug codes to the sample project and see what is going on. And finaly, I get this message "Monitor layout changed."!

The thing is I did nothing with monitor.

                    for (int i = 0; i < monitorCount; i++)
                    {
                        var pMonitor = monitors[i];
                        if (pMonitor.Name != br.ReadString() ||
                            pMonitor.RectMonitor.Left != br.ReadDouble() ||
                            pMonitor.RectMonitor.Top != br.ReadDouble() ||
                            pMonitor.RectMonitor.Right != br.ReadDouble() ||
                            pMonitor.RectMonitor.Bottom != br.ReadDouble()) {

                            System.Diagnostics.Debug.WriteLine("Monitor layout changed.");
                            return; // Don't restore - Monitor layout changed
                        }
                    }
dotMorten commented 1 year ago

Thanks for doing this! That was my suspected culprit. Did you happen to notice which bit was changed?

torum commented 1 year ago

I've been trying but I can't reproduce the problem.. yet. ^^

I changed the code like below. So next time it happens, I'll know.

                    var pMonitor = monitors[i];

                    var pMonitorName = br.ReadString();
                    var pMonitorLeft = br.ReadDouble();
                    var pMonitorTop = br.ReadDouble();
                    var pMonitorRight = br.ReadDouble();
                    var pMonitorBottom = br.ReadDouble();

                    if (pMonitor.Name != pMonitorName ||
                        pMonitor.RectMonitor.Left != pMonitorLeft ||
                        pMonitor.RectMonitor.Top != pMonitorTop ||
                        pMonitor.RectMonitor.Right != pMonitorRight ||
                        pMonitor.RectMonitor.Bottom != pMonitorBottom) {

                        System.Diagnostics.Debug.WriteLine("Monitor layout changed.");
                        System.Diagnostics.Debug.WriteLine($"Current values are: {pMonitor.Name}, {pMonitor.RectMonitor.Left}, {pMonitor.RectMonitor.Top}, {pMonitor.RectMonitor.Right}, {pMonitor.RectMonitor.Bottom}");
                        System.Diagnostics.Debug.WriteLine($"Saved values are: {pMonitorName}, {pMonitorLeft}, {pMonitorTop}, {pMonitorRight}, {pMonitorBottom}");

                        return; // Don't restore - Monitor layout changed
                    }
torum commented 1 year ago

Got it. Current values are: \.\DISPLAY2, 0, 0, 3840, 2160 Saved values are: \.\DISPLAY1, 0, 0, 3840, 2160

I only have one display setup. I checked Windows setting and it says I have "Display 1" and says nothing about "Display 2".

As I dig depper, I found EnumDisplayMonitors() seems to be very tricky thing...

torum commented 1 year ago

Since a display monitor name is irrelevant here, could we omit the "pMonitor.Name != br.ReadString()" check?

"Most applications have no use for a display monitor name, and so can save some bytes by using a MONITORINFO structure." https://learn.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-monitorinfoexw

dotMorten commented 1 year ago

@torum awesome! Thank you for tracking this down. I'll have a think about the best way to handle this. Odd the name changes but 🤷‍♂️

torum commented 1 year ago

Odd indeed. A quick google search did not turn out to be fruitful..., except "From my experience there's no rhyme or reason to the numbering scheme for the monitor device names" https://stackoverflow.com/questions/9829083/how-does-windows-assign-display-device-names-eg-display1-and-determine-di

Only thing that might be related is that my display is connected to a different PC (Ubuntu) which is off with a different display port. But that shouldn't affect at all, right?

torum commented 1 year ago

I think WinUIEx is very important project since WinUI3 lacks very basic functionalities such as ability to set width and height of a window. About a year and half ago, I evaluated WinUI3 as an alternative to WPF and found out about the lack of this very basic functionality of WinUI3 within a 5 minutes and decided that WinUI3 is not ready at all. When I gave a second try a few weeks ago, I found WinUIEx through Template Studio and I was delighted. WinUI3 needs projects such as WinUIEx to be widely used and become popular. (WinUI3 does not even allow us to change mouse cousor to wait! Could WinUIEx implement that?) So, I'm glad to do whatever I can to help.

dotMorten commented 1 year ago

❤️

dotMorten commented 10 months ago

Now available in v2.3 release

rocksdanister commented 10 months ago

You can use DISPLAY_DEVICE DeviceID which is what I am using for my application Lively Wallpaper.

DeviceID is unique and does not change like the name because its based on EDID, so persistence will work regardless of rect values. In the event DeviceID does not exist you can switch back to rect checks.

Sample Code:

private static DISPLAY_DEVICE GetDisplayDevice(string deviceName)
{
    var result = new DISPLAY_DEVICE();

    var displayDevice = new DISPLAY_DEVICE();
    displayDevice.cb = Marshal.SizeOf(displayDevice);
    try
    {
        for (uint id = 0; EnumDisplayDevices(deviceName, id, ref displayDevice, EDD_GET_DEVICE_INTERFACE_NAME); id++)
        {
            if (displayDevice.StateFlags.HasFlag(DisplayDeviceStateFlags.AttachedToDesktop)
                && !displayDevice.StateFlags.HasFlag(DisplayDeviceStateFlags.MirroringDriver))
            {
                result = displayDevice;
                break;
            }

            displayDevice.cb = Marshal.SizeOf(displayDevice);
        }
    }
    catch { }

    if (string.IsNullOrEmpty(result.DeviceID)
        || string.IsNullOrWhiteSpace(result.DeviceID))
    {
        result.DeviceID = GetDefaultDisplayDeviceId();
    }

    return result;
}

private static string GetDefaultDisplayDeviceId() => GetSystemMetrics(SM_REMOTESESSION) != 0 ?
    "\\\\?\\DISPLAY#REMOTEDISPLAY#" : "\\\\?\\DISPLAY#LOCALDISPLAY#";

[DllImport("user32.dll", ExactSpelling = true, CharSet = CharSet.Auto)]
public static extern int GetSystemMetrics(int nIndex);

const int EDD_GET_DEVICE_INTERFACE_NAME = 0x00000001;
const int SM_REMOTESESSION = 0x1000;

[Flags]
public enum DisplayDeviceStateFlags : int
{
    /// <summary>The device is part of the desktop.</summary>
    AttachedToDesktop = 0x1,

    MultiDriver = 0x2,

    /// <summary>The device is part of the desktop.</summary>
    PrimaryDevice = 0x4,

    /// <summary>
    /// Represents a pseudo device used to mirror application drawing for remoting or other purposes.
    /// </summary>
    MirroringDriver = 0x8,

    /// <summary>The device is VGA compatible.</summary>
    VGACompatible = 0x10,

    /// <summary>
    /// The device is removable; it cannot be the primary display.
    /// </summary>
    Removable = 0x20,

    /// <summary>
    /// The device has more display modes than its output devices support.
    /// </summary>
    ModesPruned = 0x8000000,

    Remote = 0x4000000,
    Disconnect = 0x2000000
}

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct DISPLAY_DEVICE
{
    [MarshalAs(UnmanagedType.U4)]
    public int cb;

    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
    public string DeviceName;

    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
    public string DeviceString;

    [MarshalAs(UnmanagedType.U4)]
    public DisplayDeviceStateFlags StateFlags;

    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
    public string DeviceID;

    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
    public string DeviceKey;
}

const int EDD_GET_DEVICE_INTERFACE_NAME = 0x00000001;