BurntSushi / xgbutil

A utility library to make use of the X Go Binding easier. (Implements EWMH and ICCCM specs, key binding support, etc.)
Do What The F*ck You Want To Public License
194 stars 46 forks source link

Resize issues and Geometry values #53

Closed aacebedo closed 4 years ago

aacebedo commented 4 years ago

Hi !

I am trying to resize and move window using xgbutil and facing some issues about it. I am running i3wm and use pkg.go.dev/go.i3wm.org/i3/ to obtain the focused window ID. Then I resize the window using one of the several methods provided by xgbutil.

My issue is : I have a window of random size, I want to move it and set it to a specific size, let's say 640x360. When I run one time my program, the window is moved in the correct place however the size is not correct. Geometry functions report a bogus size. If I run a second time the program immediately after, I can see the window changing a liittle bit its size again to finally get the correct size. Geometry functions returns the same value as before but this time it is correct. Subsquent run does not change the size anymore.

I have printed the geometry of the window before and after the call to the resize function and can see the second time that the size was incorrect (geometry reports the incorrect size). I don't understand why the geometry after the call in the first run is incorrect though.

Let's wrap-up: I create two sets of objects to be sure I am not reusing bufferized values. My code is

xgbUtil, _ := xgbutil.NewConn()
xWin := xwindow.New(xgbUtil, windID)
geom, _ := xWin.Geometry()
fmt.Println("Before ", uint(geom.Width()))
ewmh.MoveresizeWindow(xgbUtil, windID, 0, 0, 640,360)
xgbUtil2, _ := xgbutil.NewConn()
xWin2 := xwindow.New(xgbUtil2, windID)
geom2, _ := xWin2.Geometry()
fmt.Println("After  ", uint(geom2.Width()))

First run I got:

Before 789 <= Random initial width of the window
After 640 <= This is incorrect, the window is smaller

I do not touch anything and a rerun the program, I got:

Before 637 <= This shall have been in the "After" value on the first run
After 640 <= Now this is correct !

I am a little bit confused in front of the multiple function to resize a window. I found: ewmh.MoveresizeWindow ewmh.MoveresizeWindowExtra ewmh.WmMoveresizeWindow ewmh.WmMoveresizeWindowExtra ewmh.ResizeWindow xwindow.Resize xwindow.MoveResize xwindow.WMResize xwindow.WMMoveResize

I understand some of the differences (such as between Move and MoveResize) but I do not understand the difference between Resize and WMResize for example.

BurntSushi commented 4 years ago

The ewmh package is just a dumb convenience layer for interacting with a window manager using the ewmh spec. In order to understand the package, you need to understand the spec. The spec explains the difference between moveresize and wmmoveresize for example.

As for your problem, you'll need to investigate the wm. Xgbutil is just sending a message. The wm is what implements the moveresize itself.

You should consider that some windows (most commonly terminals) are only allowed to be certain widths or heights based on an interval of fixed increments chosen by the client, usually related to font size. You haven't really given enough details to tell whether that's the case here. In any case, the WM enforces that too IIRC.

In the end, moveresize is just a request. The wm decides what to do. So that's what you need to investigate next. You could try other tools, like wmctrl, to make sure there is nothing funny happening in xgbutil or if there is some better technique. But I doubt it.

BurntSushi commented 4 years ago

You might also consider using: https://godoc.org/github.com/BurntSushi/xgbutil/xwindow#Window.WMMoveResize

Again, see package docs for the difference between moveresize and wmmoveresize.

BurntSushi commented 4 years ago

See also: https://godoc.org/github.com/BurntSushi/xgbutil/xwindow#Window.DecorGeometry

Apparently there is some additional complexity here with respect to window decorations. This is what xwindow.WMMoveResize tries to account for I think.

aacebedo commented 4 years ago

Thank you for this fast reponse !

I am quite a newbie with everything about X development. I'll take a closer look to the links you've provided. Yes I am manipulating a terminal window. I struggle to understand exactly the relation between (X and the WM (I guess i3 for me ?)).

I modified my code to use directly xgb instead of xgbutil and use the ConfigureWindowChecked as I thought it was closer to the X server. I noticed that if I poll the window geometry (with RawGeometry) it changes itself automatically after 3 ou 4 ms (from the wrong to the correct reported value). It still doesn't solve my problem but it explains the change of the geometry between two consecutive runs.

aacebedo commented 4 years ago

wmctrl has the same behaviour. First run incorrect size, second run correct. The width increment (8) is a divider of the requested width (640).

BurntSushi commented 4 years ago

Yeah then this isn't a problem with xgbutil. If you're new to X, then you'll want to read and understand icccm and ewmh. Otherwise, ask the i3 folks for help. But try xwindow.wmmoveresize.

Otherwise, it has been many many years since I've done anything X related. The history of this project should be informative. Sorry I can't be of more help.

aacebedo commented 4 years ago

:( Thank I'll continue to search. i3 folks told me to go and check xcb... :) That's the reason I was here. I have updated the issue on their side to continue my research

BurntSushi commented 4 years ago

Aye. I think the fact that both xgbutil and wmctrl behave the same means it's probably on the WM side. Remember, all xgbutil is doing here is sending a client message. xwindow.WmMoveresize is a little bit smarter in that it tries to take window decorations into account. But that's it.

I'd also like to say that i3 may well be behaving as expected here. Window geometry and resizing in the X world is a strange beast. I remember being confounded by it several times. I recall being stymied by it when I wrote pytyle, which was a tiling tool that sat on top of window managers. It was often the case that pytyle simply could not resize windows to a precise geometry, and you just had to be okay with small gaps.

Also, using wmctrl to show your problem may have better results than using xgbutil. xgbutil is fairly obscure and not widely used AFAIK. wmctrl has been around longer, is more mature, more people know about it and it will likely be easier to explain your case in terms of wmctrl. The hope is that wmctrl is a proxy for your actual issue, in that if you figure out how to solve it with wmctrl, then it should be somewhat straight-forward to convert that solution to xgbutil.

aacebedo commented 4 years ago

Thanks.

I have managed to have the same behaviour between i3 and xgbutil by modifying the HINTS with xgbutil. I have added a precise usecase and way to reproduce the issue on the i3 bug https://github.com/i3/i3/issues/4213#issuecomment-706128805

I simply hope this is not having a link with X server request handling as It will be very difficult to find a workaround.

BurntSushi commented 4 years ago

You might also consider trying a range of different widths. Try 640, 641, 642, ..., 670 or whatever. Do any of those result in an initially correct geometry after the first moveresize command? If one does, then maybe it turns out you were misinterpreting how the size hint increments were applied.

Also, IIRC, setting the size hint increments to 1 on the client is not something that worked for me. But the last time I tried that was probably 10 years ago and I've since forgotten the details. It could be that the client will fight with you and set the hints back to what it wants once it sees that they have changed. So you might want to at least verify that that isn't happening.

I think that fundamentally, you cannot expect to have a pixel perfect positioning of clients unless you are the WM.

aacebedo commented 4 years ago

I'll try with different size. But tweaking the HINTS to 1 definitely impacts the behaviour of i3. The little PoC I wrote illustrates it .

aacebedo commented 4 years ago

It does seem to apply the increment. Tried 640 to 646 and it really applied 637 and then jumped to 645 (the increment is 8) However 640 is divisible by 8, not 645 so I don't understand why it will stick with an origin different than 0.

FascinatedBox commented 2 years ago

I know that the last comment to this thread was a significant amount of time ago, but I found this thread while searching the internet. It came up because OP's issue is the exact same one I am...was having but with using the xcb api directly instead of using this Go wrapper. However, I think the concept of my fix can be translated over. So I'm adding that in the off-chance that some other person has the same issue I've been having and also stumbles onto here.

What I do before closing a connection is to ask for the currently focused window, then wait for that reply. I know that because that's the last reply I sent out, if I wait for it, all my requests must have been processed. This is what the other program was doing, which I verified using xtrace (great program btw, very useful). The down side is that I can't say this is my idea since it was copied from another program. Oh well.

In C, that looks like this:

    free(xcb_get_input_focus_reply(conn, xcb_get_input_focus(conn), NULL));

I don't know what that translates into in terms of this particular binding, but my C code using ewmh went from periodically running to running every single time.

BurntSushi commented 2 years ago

@FascinatedBox thanks for pitching in. Although, I'm not sure what your comment has to do with window resizing and geometry?

FascinatedBox commented 2 years ago

When I run one time my program, the window is moved in the correct place however the size is not correct. Geometry functions report a bogus size. If I run a second time the program immediately after, I can see the window changing a liittle bit its size again to finally get the correct size.

I read this as OP having the same issue that I was having earlier (changes not being taken because the program exits before they can be taken in). Reading a bit more, I think I jumped the gun. Sorry.