Yet another question about saving window geometry

Hello!
It’s my almost first time trying to use wxpython and have something really strange and can’t find any reason for that. I save and restore window geometry using wx.lib.agw.persist.PersistenceManager and each next run makes window’s height more than was saved to config file. I noticed that as I set window size using SetSize, window recieves EVT_SIZE with actual size bigger that was set. But if I manually save and restore window size using SetClientSize instead of PersistanceManager and SetSize everything goes fine. Does anyone have an idea how to fix this issue? Is there any workaround? Problem happends using 4.2.0 gtk3 (phoenix) wxWidgets 3.2.2 under Ubuntu 23.04 with Gnome.
My minimal code which reproduces this behavior:

import os
import sys

import wx
import wx.propgrid
import wx.lib.agw.persist as pm

dirName = os.path.dirname(os.path.abspath(sys.argv[0]))


class Window(wx.Frame):
    def __init__(self):
        super().__init__(None)
        self.SetName('wnd')
        self.persist = pm.PersistenceManager.Get()
        self.persist_file = os.path.join(dirName, "Persist.conf")
        self.persist.SetPersistenceFile(self.persist_file)

        self.Bind(wx.EVT_CLOSE, self.OnClose)
        wx.CallAfter(self.register_persist)

    def register_persist(self):
        self.Freeze()

        if not self.persist.RegisterAndRestore(self):
            self.SetSize(300, 200)

        self.Thaw()

    def OnClose(self, event):
        print(f'window size: {self.GetSize()}')
        self.persist.SaveAndUnregister()
        event.Skip()


if __name__ == '__main__':
    app = wx.App()
    window = Window()
    window.Show()
    app.MainLoop()

I would be grateful for any hint.

Hi and welcome,

When I run your code using Python 3.10.6 + wxPython 4.2.1 gtk3 (phoenix) wxWidgets 3.2.2.1 on Linux Mint 21.2, it outputs the following:

window size: (300, 200)

The Persist.conf file contains:

[Persistence_Options]
[Persistence_Options/Window]
[Persistence_Options/Window/wnd]
x=('int', '0')
y=('int', '0')
w=('int', '300')
h=('int', '200')
Maximized=('bool', 'False')
Iconized=('bool', 'False')

Thank you!
I’d like to get the same result. But when I clear Persist.conf and run this code I get the following result. Note that I don’t change window size manually. I just run it and close window.

sergey@Master:~/dev/winsize$ python main.py
window size: (300, 237)
sergey@Master:~/dev/winsize$ python main.py
window size: (300, 274)
sergey@Master:~/dev/winsize$ python main.py
window size: (300, 311)
sergey@Master:~/dev/winsize$ python main.py
window size: (300, 348)
sergey@Master:~/dev/winsize$ python main.py
window size: (300, 385)
sergey@Master:~/dev/winsize$ python main.py
window size: (300, 422)
sergey@Master:~/dev/winsize$ python main.py
window size: (300, 459)
sergey@Master:~/dev/winsize$ python main.py
window size: (300, 496)
sergey@Master:~/dev/winsize$ python main.py
window size: (300, 533)
sergey@Master:~/dev/winsize$ cat Persist.conf 
[Persistence_Options]
[Persistence_Options/Window]
[Persistence_Options/Window/wnd]
x=('int', '879')
y=('int', '213')
w=('int', '300')
h=('int', '533')
Maximized=('bool', 'False')
Iconized=('bool', 'False')

May be it is about changes in GTK or Gnome’s window manager?

Are you running under Wayland? Ubuntu uses Wayland by default, whereas Linux Mint uses Xorg.

No, it’s X11 session.

sergey@Master:~/dev/winsize$ echo $DISPLAY
:1
loginctl show-session 2
Id=2
User=1000
Name=sergey
Timestamp=Wed 2023-07-26 23:54:28 MSK
TimestampMonotonic=42766843
VTNr=2
Seat=seat0
TTY=tty2
Remote=no
Service=gdm-password
Scope=session-2.scope
Leader=2199
Audit=2
Type=x11
Class=user
Active=yes
State=active
IdleHint=no
IdleSinceHint=1690614546551000
IdleSinceHintMonotonic=209720438953
LockedHint=no

And moreover whe I run the following code:

import wx


class Window(wx.Frame):
    def __init__(self):
        super().__init__(None)
        self.Bind(wx.EVT_SIZE, self.OnSize)

        self.SetSize(300, 200)

    def OnSize(self, _event):
        print(f'window size: {self.GetSize()}')


if __name__ == '__main__':
    app = wx.App()
    window = Window()
    window.Show()
    app.MainLoop()

Result is:

/usr/bin/python3.11 /home/sergey/dev/winsize/size.py 
window size: (300, 200)
window size: (300, 200)
window size: (300, 237)

Note the last line. +37 pixels in window height. Is window manager kidding me?

Weird, I only get 2 lines from that code:

window size: (300, 200)
window size: (300, 200)

However, what’s even weirder, is that when I step through the code in PyCharm’s debugger, it does output three lines, with a similar incorrect size values!

/usr/bin/python3.10 /usr/lib/pycharm-community/plugins/python-ce/helpers/pydev/pydevd.py --multiprocess --qt-support=auto --client 127.0.0.1 --port 37253 --file /home/richardt/Development/python/exp/hci/wxpython/frame/frame_size/persistence_manager/exp_1.py 
Connected to pydev debugger (build 232.8660.197)
window size: (300, 200)
window size: (300, 200)
window size: (308, 228)

I have no idea why that’s happening…

switched to ubuntu-wayland and everything goes fine with my first code example. Persistance works as it shood. But the second example gives my the following (argh!) output:

sergey@Master:~/dev/winsize$ python size.py 
window size: (300, 200)
window size: (300, 200)
window size: (352, 289)
window size: (300, 200)

Some good wizard from a fairy tale came and fixed this bad joke at line 3. I don’t understand what’s happening.

If I modify the OnSize() method in your second example to call event.Skip() as shown below, then I no longer get the third call with the wrong sizes, even when running in debug.

    def OnSize(self, event):
        print(f'window size: {self.GetSize()}')
        event.Skip()

I added call to event.Skip() and still have the same result.

sergey@Master:~/dev/winsize$ cat size.py
import wx


class Window(wx.Frame):
    def __init__(self):
        super().__init__(None)
        self.Bind(wx.EVT_SIZE, self.OnSize)

        self.SetSize(300, 200)

    def OnSize(self, event):
        print(f'window size: {self.GetSize()}')
        event.Skip()


if __name__ == '__main__':
    app = wx.App()
    window = Window()
    window.Show()
    app.MainLoop()

sergey@Master:~/dev/winsize$ python size.py
window size: (300, 200)
window size: (300, 200)
window size: (300, 237)
sergey@Master:~/dev/winsize$ 

Moreover, I created a simple C++ application with wxWidgets 3.2.2-Linux-Unicode build and it has the same problem. The Qt script doesn’t have it. I hope today or tomorrow I will have some time to try this with pygobject or gtkmm (I’m not familiar with it so it requires more time).
Thank you for your time!

pygobject with gtk-3 and gtk-4 produced only one event with correct size.
But… Here we are!
size
Am I right that this is even more weird now?
Window is created with height 200. Then window manager adds title and it is included into window height.
When window is closed it’s height is 237 and this value is saved to config. When application starts again it read 237 from config, window manager add title… (Groundhog Day).
But why?

I added print(f'client size: {self.GetClientSize()}') to OnSize and now output is:

/usr/bin/python3.11 /home/sergey/dev/winsize/size.py 
window size: (300, 200)
client size: (300, 200)
window size: (300, 200)
client size: (300, 200)
window size: (300, 237)
client size: (300, 200)

Am I missing something obvious?

Could you please show output of the folowing program:

import os
import sys

import wx
import wx.lib.agw.persist as pm

dirName = os.path.dirname(os.path.abspath(sys.argv[0]))


class Window(wx.Frame):
    def __init__(self):
        super().__init__(None)
        self.SetName('wnd')
        self.persist = pm.PersistenceManager.Get()
        self.persist_file = os.path.join(dirName, "Persist.conf")
        self.persist.SetPersistenceFile(self.persist_file)

        self.Bind(wx.EVT_CLOSE, self.OnClose)
        self.Bind(wx.EVT_SIZE, self.OnSize)
        self.Bind(wx.EVT_SHOW, self.OnShow)
        self.first_show = True
        # c

    def register_persist(self):
        self.Freeze()
        print('registering persistence')
        if not self.persist.RegisterAndRestore(self):
            self.SetSize(300, 200)

        self.Thaw()

    def OnShow(self, event):
        if self.first_show:
            self.first_show = False
            wx.CallAfter(self.register_persist)
        event.Skip()

    def OnSize(self, event):
        print(f'window size: {self.GetSize()}; client size: {self.GetClientSize()}')
        event.Skip()

    def OnClose(self, event):
        self.persist.SaveAndUnregister()
        event.Skip()


if __name__ == '__main__':
    app = wx.App()
    window = Window()
    window.Show()
    app.MainLoop()

What I get is:

/usr/bin/python3.11 /home/sergey/dev/winsize/main.py 
window size: (400, 250); client size: (400, 250)
registering persistence
window size: (300, 237); client size: (300, 237)
window size: (400, 287); client size: (400, 250)
window size: (300, 274); client size: (300, 237)

Process finished with exit code 0

Looks like window geometry is restored too early and adding title to window after that causes problem. Maybe your window manager produces different event queue and that’s why we get different results.

I get:

/usr/bin/python3.10 /home/richardt/Development/python/exp/hci/wxpython/frame/frame_size/persistence_manager/exp_4.py 
window size: (400, 250); client size: (392, 222)
registering persistence
window size: (300, 200); client size: (292, 172)
window size: (400, 250); client size: (392, 222)
window size: (300, 200); client size: (292, 172)

Process finished with exit code 0

Edit: I am using MATE desktop environment with Marco + Compton window manager.

Thank you. I don’t know why but it looks like in my case window is created without decorations. And it’s geometry should be restored after receiving decorations. Looks like soon it will be popular question :wink: I wonder if there is a way to determine that window has received it’s final size and is ready to be actually shown on screen? As we see in my code the first call of OnShow also happends to early.
Do you think it shoud be reported to wxwidgets.org?

I noticed that a similar issue was raised on wxWidgets for dialogs on GTK a few years ago:

One comment identifies possible changes that could have caused the issue, but there is no indication that anyone is working on fixing it.

Thank you!
GTK_CSD=1 /usr/bin/python3.11 /home/sergey/dev/winsize/main.py
produces

window size: (400, 250); client size: (400, 250)
window size: (452, 339); client size: (400, 250)
registering persistence
window size: (300, 200); client size: (300, 200)

3 size events instead of previous 4 and no size growing. I suppose it is the answer. I’m not sure that window size is supposed to be equal to it’s client size but screenshots of window with toolbar and some panes before and after restoring geometry become identical after setting GTK_CSD=1. Great thanks for your help.
PS: Is GTK_CSD set in your env?

No. I only have 3 GTK related env-vars:

richardt@Pavilion:~ $ env | grep GTK
GTK_MODULES=gail:atk-bridge
GTK_OVERLAY_SCROLLING=0
GTK3_MODULES=xapp-gtk3-module

And everything got explained. Linux Mint with Mate is not using cliend side decorations and everything goes fine. And Ubuntu is using it but doesn’t have GTK_CSD set, so it has issue with window geometry until setting GTK_CSD. Looks like that problem is really fixed in wxWidgets and now it is caused by Ubuntu environment only.
Thank you for your help. Everything looked so weird that I thought I was going crazy.

1 Like