Closed roym899 closed 1 year ago
Also maybe you want to look into using the _NET_FRAME_EXTENTS
property from xprop -id {window_id}
. Instead of the current way of getting the border size. But at least on my system they agree with each other any way.
Hi Leonard! Thank you for your feedback.
This is something that really drove me crazy on GNOME. It doesn't seem to be using _NET_FRAME_EXTENTS as expected, but rather using it as the reserved space AROUND the window.
In my Ubuntu 20.04 installation, these are the results of the various methods I tried (without success, unfortunately):
WINDOW GEOMETRY
X 235 Y 33 WIDTH 786 HEIGHT 533
WINDOW ATTRIBUTES
X 235 Y 33 WIDTH 786 HEIGHT 533 BORDER 0
FRAME EXTENTS
GNOME <GetProperty serial = 20, data = {'sequence_number': 20, 'property_type': 6, 'bytes_after': 0, 'value': (32, array('I', [26, 26, 23, 29]))}, error = None>
OTHERS None
TKINTER
TITLE BAR / BORDERS (37, 0)
As you can see, FRAME_EXTENTS make no sense (as far as I can see, at least).
Apart from this, please let me ask which is your goal in this case. Do you need the exact dimensions of the window minus the title bar and the borders?
Perhaps you can try yourself by running this simple snippet and see what happens.
import pywinctl as pwc
import Xlib.display
import tkinter as tk
from ctypes import cdll, Structure, c_int32, c_ulong, c_uint32, byref, CDLL
from ctypes.util import find_library
display = Xlib.display.Display()
win = pwc.getActiveWindow()
xWin = win._xWin
_xlib = None
_xcomp = None
def _loadX11Library():
global _xlib
if _xlib is None:
lib = -1
try:
libPath = find_library('X11')
if libPath:
lib = cdll.LoadLibrary(libPath)
except:
pass
_xlib = lib
return _xlib
class _XWindowAttributes(Structure):
_fields_ = [('x', c_int32), ('y', c_int32),
('width', c_int32), ('height', c_int32), ('border_width', c_int32),
('depth', c_int32), ('visual', c_ulong), ('root', c_ulong),
('class', c_int32), ('bit_gravity', c_int32),
('win_gravity', c_int32), ('backing_store', c_int32),
('backing_planes', c_ulong), ('backing_pixel', c_ulong),
('save_under', c_int32), ('colourmap', c_ulong),
('mapinstalled', c_uint32), ('map_state', c_uint32),
('all_event_masks', c_ulong), ('your_event_mask', c_ulong),
('do_not_propagate_mask', c_ulong), ('override_redirect', c_int32), ('screen', c_ulong)]
def _XGetAttributes(winId: int, dpyName: str = ""):
resOK: bool = False
attr = _XWindowAttributes()
xlib = _loadX11Library()
if isinstance(xlib, CDLL):
try:
if not dpyName:
dpyName = display.get_display_name()
dpy: int = xlib.XOpenDisplay(dpyName.encode())
xlib.XGetWindowAttributes(dpy, winId, byref(attr))
xlib.XCloseDisplay(dpy)
resOK = True
except:
pass
return resOK, attr
def _getBorderSizes():
# Didn't find a way to get title bar height using Xlib in GNOME
# ret, a = self.XlibAttributes() # -> Should return client area, but it doesn't...
# if res:
# res = Rect(a.x, a.y, a.x + a.width, a.y + a.height)
# else:
# res = self.getWindowRect()
#
# This works in Cinnamon, but not in GNOME
# titleHeight = 0
# extents = self._win.getFrameExtents()
# if extents and len(extents) >= 4:
# titleHeight = extents[2]
# geom = self._xWin.get_geometry()
# borderWidth = geom.border_width
# return titleHeight, borderWidth
class App(tk.Tk):
def __init__(self):
super().__init__()
self.geometry('0x0+200+200')
self.update_idletasks()
pos = self.geometry().split('+')
self.bar_height = self.winfo_rooty() - int(pos[2])
self.border_width = self.winfo_rootx() - int(pos[1])
self.destroy()
def getTitlebarHeight(self):
return self.bar_height
def getBorderWidth(self):
return self.border_width
app = App()
# app.mainloop()
return app.getTitlebarHeight(), app.getBorderWidth()
print("WINDOW GEOMETRY")
print("X", win.position.x, "Y", win.position.y, "WIDTH", win.size.width, "HEIGHT", win.size.height)
print()
print("WINDOW ATTRIBUTES")
res, attr = _XGetAttributes(win.getHandle(), display.get_display_name())
print("X", attr.x, "Y", attr.y, "WIDTH", attr.width, "HEIGHT", attr.height, "BORDER", attr.border_width)
print()
print("FRAME EXTENTS")
print("GNOME", xWin.get_full_property(display.get_atom("_GTK_FRAME_EXTENTS"), Xlib.X.AnyPropertyType, 10))
print("OTHERS", xWin.get_full_property(display.get_atom("_NET_FRAME_EXTENTS"), Xlib.X.AnyPropertyType, 10))
print()
print("TKINTER")
print("TITLE BAR / BORDERS", _getBorderSizes())
Any idea or help is really appreciated!!!
Seems like it depends on the window for me. You can try it on different windows with xprop
and clicking on the window to test.
_NET_FRAME_EXTENTS
seems to give the correct size of the titlebar and borders (border is 0). For example, Chrome is like this.xwininfo
and xprop
is strange and seems to add some margin around the windows.In my case I could workaround the issue like this:
import pywinctl._pywinctl_linux as pwcl
title_height, border_width = pwcl._getBorderSizes()
if include_frame:
x = window.topleft.x - border_width
y = window.topleft.y - title_height
width = window.width + 2 * border_width
height = window.height + title_height + border_width
else:
x, y = window.topleft.x, window.topleft.y
width, height = window.width, window.height
This will only work for the standard titlebars, not for the default terminal though.
I think one approach could be to
_NET_FRAME_EXTENTS
is available_GTK_FRAME_EXTENTS
from geometry given by xwininfo
could work?getClientFrame
to take different headerbar / titlebar into account
Totally right! I erroneously assumed all windows in GNOME were using _GTK_FRAME_EXTENTS, but testing it in some other windows as you suggested (e.g. Firefox), they use _NET_FRAME_EXTENTS instead!!!
So, regarding your approach:
Just to be sure. In your workaround, include_frame identifies if the window is standard (using _NET_FRAME_EXTENTS) or not (using GTK_FRAME_EXTENTS), right?
Thank you so much for you help!!!
Hi! I have prepared a new version that hopefully solves this issue. If you want to give it a try, you can find it here. Anyway, I will upload it to PyPi in a few days, as soon as I finish all required, intensive tests.
Thank you again!
Great, thanks for fixing it. I'll give it a try.
In Gnome (I'm using Gnome 41 with Ubuntu 20.04) it seems like the standard window dimensions (i.e., what is retrieved with
window.topleft
,window.height
, etc.) don't include the border / titlebar.Looking at the code
getClientFrame
subtracts the border / titlebar and hence it cuts off parts of the window as it is right now.