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

Adjust window box for GTK HeaderBar #2

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

Conversation

roym899
Copy link

@roym899 roym899 commented Oct 6, 2023

Goes hand-in-hand with Kalmat/PyWinCtl#79

This is another attempt at fixing Kalmat/PyWinCtl#66, which still seems to be off for me.

The idea is to get the right box including the title bar / header bar in PyWinBox.

To get the correct client area (i.e., without title bar), the opposite has to be done for HeaderBar-free applications. This is done in a separate PR in PyWinCtl.

So to summarize the state after these two PRs:
For applications without GTK HeaderBar

  • PyWinBox gives us the tight bounding box now (excluding shadows, including title bar)
  • getClientFrame in PyWinCtl will remove the title bar from PyWinBox's bounding box

For applications with GTK HeaderBar

  • PyWinBox gives us the tight bounding box now (excluding shadows, including title bar)
  • getClientFrame in PyWinCtl will not modify PyWinBox's bounding box

This would handle 3 of 4 cases perfectly, only getClientFrame with GTK HeaderBar still doesn't work. But it would still give a reasonable result in that case.

The test might need some more work (failed for me on master as well though). Let me know if you want me to take a stab at it. Otherwise feel free to just add to this PR.

Copy link
Owner

@Kalmat Kalmat left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you! This is really helpful!!!

Since in Linux there are a tone of Desktop / Window managers and GUI platforms and so on, I would suggest to also check if _getGtkFrameExtents() is not empty nor None and has a length of at least 4, then returning the values retrieved by get_geometry() / translate_coords() otherwise... Do you agree?

Thank you again!

@roym899
Copy link
Author

roym899 commented Oct 6, 2023

Yes makes sense, I'll make the change tomorrow.

@roym899 roym899 requested a review from Kalmat October 7, 2023 07:43
Kalmat
Kalmat previously approved these changes Oct 7, 2023
@Kalmat
Copy link
Owner

Kalmat commented Oct 8, 2023

I deleted my previous message (I'm a total nerd!).

The typing checks failed, so we should fix the code before merging it (do you have access to the checks results? Otherwise, just let me know to send them to you).

In addition to that, the PR to PyWinCtl must also be modified in the same way that PyWinBox's one, right?

I will also modify the PyWinCtl's setup.py file in order to sync both versions (PyWinCtl will require this new PyWinBox version in order to properly work)

@roym899
Copy link
Author

roym899 commented Oct 8, 2023

Yes, I can fix the typing issue. I'll get to it later today. I'll comment in the other PR regarding the PyWinCtl change.

@Kalmat
Copy link
Owner

Kalmat commented Oct 8, 2023

Hi again!

I was making some tests (in Ubuntu/GNOME. I will test in other distributions as well) to try to understand / check everything (and put my brain in place, to be honest). Using this as PyWinBox's _getWindowBox():

def _getWindowBox(handle: EwmhWindow) -> Box:
    # https://stackoverflow.com/questions/12775136/get-window-position-and-size-in-python-with-xlib
    # Previous version (check if the new one works OK in all cases)
    win = handle.xWindow
    geom = win.get_geometry()
    x = geom.x
    y = geom.y
    w = geom.width
    h = geom.height
    while True:
        parent = win.query_tree().parent
        if not parent or not isinstance(parent, XWindow):
            break
        pgeom = parent.get_geometry()
        x += pgeom.x
        y += pgeom.y
        if parent.id == 0:
            break
        win = parent
    print("VERSION 0:", x, y, w, h)

    geom = handle.xWindow.get_geometry()
    pos = handle.root.translate_coords(handle.id, 0, 0)
    print("VERSION 1:", pos.x, pos.y, geom.width, geom.height)

    _net_extents = handle._getNetFrameExtents()
    _gtk_extents = handle._getGtkFrameExtents()
    if _net_extents and len(_net_extents) >= 4:
        # this means it has no GTK HeaderBar
        x = pos.x - int(_net_extents[0])
        y = pos.y - int(_net_extents[2])
        w = geom.width + int(_net_extents[0]) + int(_net_extents[1])
        h = geom.height + int(_net_extents[2]) + int(_net_extents[3])
    elif _gtk_extents and len(_gtk_extents) >= 4:
        # this means there is a GTK HeaderBar
        _gtk_extents = handle._getGtkFrameExtents()
        x = pos.x + int(_gtk_extents[0])
        y = pos.y + int(_gtk_extents[2])
        w = geom.width - int(_gtk_extents[0]) - int(_gtk_extents[1])
        h = geom.height - int(_gtk_extents[2]) - int(_gtk_extents[3])
    else:
        # something else: best guess is to trust pos and geom from above
        # NOTE: if you have this case and are not getting the expected result,
        #   please open an issue: https://github.com/Kalmat/PyWinBox/issues/new
        x = pos.x
        y = pos.y
        w = geom.width
        h = geom.height
    print("NEW VERSION:", x, y, w, h)

   # Options to test:
   # return Box(pos.x, pos.y, geom.width, geom.height)  # OLD VERSIONS
   # return Box(x, y, w, h)  # NEW VERSION
    return Box(x, y, geom.width, geom.height)   # OLD AND NEW VERSIONS COMBINED

Some things I found so far:

  • VERSION 0 matches VERSION 1 values, but seems to need additional time to refresh right values (I will definitely discard it!)
  • X and Y coordinates are right in your new version (WTF!!!), but something weird is happening to WIDTH and HEIGHT values.
    • This window should be right next to right and bottom edges of the screen, but it is not (it seems to still be somehow considering extents)
      Captura
    • When returning new version's values, the window is progressively shrinking in every window.box call. This doesn't happen when using old versions' values.
    • Both issues are partially solved when combining both, old and new versions, returning new X, Y coordinates and old WIDTH, HEIGHT values. But next problem is still there.
  • When using _NET_MOVERESIZE_WINDOW (to just move the window, not resizing it), the values returned by the old versions are right (I mean, they match the values passed to MOVERESIZE), but the values returned by the new version, do not match it (perhaps they are more precise regarding the actual position of the window, but the system believes a different thing!).

In short, the whole system seems to take into consideration all those extents values when performing any action or query. To be honest, I don't feel comfortable manipulating the values returned by the Desktop / Window Manager or Xorg themselves. In addtion to that, I am unsure of how other distributions, Desktop/Window Managers, GUI plaforms and so on, will behave in this sense.

I don't know how you are using these functions, sorry, but my proposals (from this ignorance) are:

  1. In PyWinBox: Create a new method called something like "translatedBox" or "normalizedBox" or similar in which we take into account the extents values. This way we encapsulate this manipulation in an innocuous environment.
  2. In PyWinCtl: Modify existing getClientFrame / Create a new getTranslatedClientFrame method to return the "right" values taking into account the required extents (e.g. by using previous "translatedBox" method). Again, this method is very specific, so the impact is isolated.
  3. Any other idea you may have is more than welcome!!!!

I was running this very simple test script, if you can test it yourself to check what happens in your case:

import time

import pywinctl as pwc
import pymonctl as pmc

windows = pwc.getAllWindows()
for window in windows:
    print(window.title)
    print(window.box)
    print()

win = pwc.getActiveWindow()
print("MOVE TO", 200, 200)
win.moveTo(200, 200)
while True:
    try:
        time.sleep(0.1)
    except KeyboardInterrupt:
        break
print("==============================")
print(win.box)
print()

print("MOVE TO", 72, 27)
win.moveTo(72, 27)
while True:
    try:
        time.sleep(0.1)
    except KeyboardInterrupt:
        break
print("==============================")
print(win.box)
print()

mon: pmc.Monitor = pmc.getPrimary()
print("MOVE TO", mon.size.width - win.width, mon.size.height - win.height)
win.moveTo(mon.size.width - win.width, mon.size.height - win.height)
while True:
    try:
        time.sleep(0.1)
    except KeyboardInterrupt:
        break
print("==============================")
print(win.box)

This is the output I get when running it (I opened some random apps to just test):

Nueva pestaña - Google Chrome
VERSION 0: 1951 0 909 540
VERSION 1: 1951 0 909 540
NEW VERSION: 1973 19 865 496
Box(left=1973, top=19, width=909, height=540)

Sin título 1 - LibreOffice Writer
VERSION 0: 95 132 981 759
VERSION 1: 95 132 981 759
NEW VERSION: 95 95 981 796
Box(left=95, top=95, width=981, height=759)

Configuración
VERSION 0: 480 119 1032 869
VERSION 1: 480 119 1032 869
NEW VERSION: 506 142 980 817
Box(left=506, top=142, width=1032, height=869)

Software
VERSION 0: 454 98 1252 852
VERSION 1: 454 98 1252 852
NEW VERSION: 480 121 1200 800
Box(left=480, top=121, width=1252, height=852)

kalma@kalma-ubuntu: ~/Proyectos/PycharmProjects/PyWinBox/tests
VERSION 0: 1134 547 786 533
VERSION 1: 1134 547 786 533
NEW VERSION: 1160 570 734 481
Box(left=1160, top=570, width=786, height=533)

MOVE TO 200 200
VERSION 0: 1134 547 786 533
VERSION 1: 1134 547 786 533
NEW VERSION: 1160 570 734 481
VERSION 0: 1134 547 786 533
VERSION 1: 200 200 786 533
NEW VERSION: 226 223 734 481
^C==============================
VERSION 0: 200 200 786 533
VERSION 1: 200 200 786 533
NEW VERSION: 226 223 734 481
Box(left=226, top=223, width=786, height=533)

MOVE TO 72 27
VERSION 0: 200 200 786 533
VERSION 1: 200 200 786 533
NEW VERSION: 226 223 734 481
VERSION 0: 200 200 786 533
VERSION 1: 72 27 786 533
NEW VERSION: 98 50 734 481
^C==============================
VERSION 0: 72 27 786 533
VERSION 1: 72 27 786 533
NEW VERSION: 98 50 734 481
Box(left=98, top=50, width=786, height=533)

VERSION 0: 72 27 786 533
VERSION 1: 72 27 786 533
NEW VERSION: 98 50 734 481
VERSION 0: 72 27 786 533
VERSION 1: 72 27 786 533
NEW VERSION: 98 50 734 481
MOVE TO 1134 547
VERSION 0: 72 27 786 533
VERSION 1: 72 27 786 533
NEW VERSION: 98 50 734 481
VERSION 0: 72 27 786 533
VERSION 1: 72 27 786 533
NEW VERSION: 98 50 734 481
VERSION 0: 72 27 786 533
VERSION 1: 72 27 786 533
NEW VERSION: 98 50 734 481
VERSION 0: 72 27 786 533
VERSION 1: 1134 547 786 533
NEW VERSION: 1160 570 734 481
^C==============================
VERSION 0: 1134 547 786 533
VERSION 1: 1134 547 786 533
NEW VERSION: 1160 570 734 481
Box(left=1160, top=570, width=786, height=533)

@roym899
Copy link
Author

roym899 commented Oct 8, 2023

I guess the same change should be made in set window size as well? I.e., for NET_EXTENTS remove the extents, for GTK_EXTENTS add the extents. Then the window wouldn't resize anymore?

There's a similar discussion here

_NET_FRAME_EXTENTS tell you how much EXTRA SPACE add to your Window Size calculations.
_GTK_FRAME_EXTENTS tell you how much EXTRA SPACE to REMOVE from your Window Size calculations.

I think this library would be most useful if it would give consistent window size and client frame sizes across platforms and window types. I agree, other window managers and distributions are a concern, but maybe start by getting the most common Linux one right?

@roym899
Copy link
Author

roym899 commented Oct 8, 2023

I pushed two new commits. With these, both the moveTo and resizeTo works correctly for both types of windows.

I agree its a mess, but I think there might be no good solution here. 😕

@Kalmat
Copy link
Owner

Kalmat commented Oct 8, 2023

Hi again!

Really thanks a lot for your help and your patience and these kind of discussions which really help a lot!!!

I remember to read that article when looking for calculating the client area! To be honest, I then thought that GNOME values were complex and useless! HAHAHAHA!

Well, I have made some additional tests in Mint/Cinnamon and Kubuntu. The results using extents in calculations are definitely wrong. Apart from returned values, when visually checking the window position, old versions were right, but not the new version.

Check this in KUbuntu:

MOVE TO 72 27
VERSION 0: 72 27 1040 464
VERSION 1: 72 27 1040 464
NEW VERSION: 72 -2 1040 493   <-- The window was clearly placed right under the upper menu bar (Y=27, I guess)
Box(left=72, top=-2, width=1040, height=464)

And this in Mint/Cinnamon (don't know why the "print" statements inside the module do not show in the console output):

MOVE TO 72 27
Box(left=72, top=54, width=654, height=437) 7  <-- The window was placed at, aprox., Y=27 (clearly not 54) since Mint has no upper bar

In short, it seems that, even though we may agree that we should try to stick to the actual window edges (again, not sure how this is going to affect other users / needs); it has to be done only in GNOME by the moment.

@roym899
Copy link
Author

roym899 commented Oct 8, 2023

Do you still get NET and / or GTK extents on these? I have no setup other than Ubuntu GNOME right now ready to test myself unfortunately.

I might try to install other window managers to check it out myself, but I'm not sure if I have time for that at the moment. Feel free to commit to this PR, if you want to make changes for the other window managers. I hope it's just the window manager affecting this, not the distribution.

@Kalmat
Copy link
Owner

Kalmat commented Oct 8, 2023

Yes, but only _NET_EXTENTS in the cases I tested, which returns [0, 0, 27, 0] for almost all windows, and [0, 0, 24, 0] for the LibreOffice splash tips window. The values returned are the ones you have to substract to window position and size if you want to calculate client area only. I mean, these are the values INSIDE the window (mainly title bar height, borders size and/or status bar height, if any... 27 and 24 stand for title bar height), whilst in GNOME (GTK) you get the values AROUND the window.

In GNOME, almost all windows return _GTK_EXTENTS = [26, 26, 23, 29] (None for _NET_EXTENTS), but in the case of LibreOffice, it returns _NET_EXTENTS = [0, 0, 37, 0] (and None for GTK value), which again matches a title bar height of 37...

It seems that:

  • If you have _NET_EXTENTS, you have to add/subtract nothing in _getWindowBox() and _moveResize(); and you have to add those values in getClientFrame()
  • If you get GTK_EXTENTS, you can add/subtract these values in _getWindowBox() and _moveResize(), and nothing else in getClientFrame(), although I have found no way to calculate the actual client area in this case.

We can use this rule to decide how to make all calculations, but I am not sure how this all will work in other distros or combinations. In my opinion, asking if we are in GNOME and if we have _GTK_EXTENTS values before adding/substracting anything is a safer solution... What do you think?

Finally, do not worry, I can test anything you need.

@Kalmat Kalmat self-requested a review October 9, 2023 11:47
@Kalmat Kalmat dismissed their stale review October 9, 2023 11:48

Check all impacts and alternatives first

@Kalmat
Copy link
Owner

Kalmat commented Oct 18, 2023

Hi! Sorry to bother you. Are you planning to create a new PR including what we discussed? Just checking, no hurries, of course. It's just to know. If you intend to do so, I will wait.

@roym899
Copy link
Author

roym899 commented Oct 18, 2023

I'll be a bit busy until mid of November unfortunately. Then I could have another look at this.

@Kalmat
Copy link
Owner

Kalmat commented Oct 18, 2023

Ok. Thank you and good luck in your "businesses"!

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

Successfully merging this pull request may close these issues.

2 participants