Closed Avasam closed 2 years ago
I think the issue is that I had a window disappear at the same time. The following fixed the issue and properly only listed existing non-problematic windows:
def getAllWindows():
"""
Get the list of Window objects for all visible windows
:return: list of Window objects
"""
windows = EWMH.getClientList()
def remove_bad_windows(windows):
for window in windows:
try:
yield LinuxWindow(window)
except Xlib.error.XResourceError:
pass
return [window for window in remove_bad_windows(windows)]
Edit: added the full getAllWindows
function.
Hi! Thank you so much for your comments and help!
Sorry not to properly understand your issue. Is it solved now? Just curious, what does it mean "a window disappear"? Can you explain a little further or, even better, paste an example on how you create that window/widget? I think I could use your piece of code to solve the problem, right? But, since it's Linux specific, I would need to test/develop in other platforms too!
Thanks again.
Hi! Sorry I'll try to be a bit more clear. Especially now that I have a better understanding of what's happening.
A bit of context:
I ask my users to select a window by clicking on it. The way I achieve that using PyQt is to create a new Widget, place it above all windows fit it to screen size, (it's also transparent gray as a visual overlay indicator) then detect clicks on that widgets (so I know the X/Y position).
Once the user has clicked, I record the click position and close the widget.* (<-- keep this in mind)
I then ask PyWinCtl to tell me which windows intersect that position using getWindowsAt
. But LinuxWindow
(which is called by getWindowsAt
) errors out.
The issue on the code side:
The error happens because LinuxWindow
tries to manipulate an invalid hWnd
. Which in turn happens because EWMH.getClientList()
in getAllWindows
returned some invalid windows. There seem to be a wrong assumption in the code that getAllWindows
will always return valid windows. I think that assumption is fine if returning a single Window (as I could just try-except it), but it's problematic when I expect a list, one of the elements are invalid, and I end up not being able to access the rest of the list.
How I think I'm triggering the issue:
I believe that in my specific case, I get an invalid window because it's picked up by EWMH.getClientList
as my Widget window is closing. I'd need to do a bit more tests to show whether that's actually the trigger here. I'll also try to get you a minimal reproduction. But in any case, it shows that it's possible in certain scenarios for EWMH.getClientList
to return unusable/invalid windows.
Possible fixes:
getAllWindows
before returning the list (this what my snippet of code above does)Once again, I don't think getAllWindows
should raise an error because one item in the list is invalid, when the user may not even have control over this (the offending window may not even be part of the user's application/python script!).
Thanks a lot for your time and this careful and so well explained issue. This kind of discussion really helps me out to improve the module's approach from a real development perspective (not what I somehow "imagine" it should be, I mean).
The general answer is yes, getAllWindows()
and all other methods can return "bad windows" (e.g., as you pointed out, a window closing right after it has been included in the return list). Some cases can be detected within getAllWindows()
, but not all of them. So, de decission was to return everything, and let the developer to decide what to do with it and how to control it (note there is a property named isAlive
, amongst others, which may help to detect the window status before doing anything with it). Anyway this doesn't mean it has to crash, of course!
Another fix that I was debating with myself is to wrap some parts within a try -- except
block. This would avoid crashes like this, but would "hide" other relevant, conceptual errors. Since PyWinCtl module is still very experimental, I preferred to let it crash, so these errors would help to detect and improve "bad assumptions" I could've made.
In this case, I think the best option is to use your piece of code to make a last "filter", returning only "good windows", whilst avoiding to crash. I will apply this solution to all other methods which return Window() objects and all other platforms. Thanks a lot for this.
Besides, if you have any Xlib knowledge... do you know if there is any way or anything I can try to send a window above the wallpaper but behind desktop icons in GNOME??? This problem is turning me crazy (I managed to make it work in sendBehind()
method for Mint/Cinnamon and Raspbian/LXDE, but found no way to do it in GNOME!)
Thanks a lot again!
Given my use case, I wouldn't mind either solutions.
Unfortunately I'm still very new to developing on Linux (and MacOS). I am learning a lot as I am porting https://github.com/Avasam/Auto-Split/tree/linux , which deals with recording specific windows or screens, window positions and sizes, listening to user inputs, and sending keystrokes.
But if I find anything, I'll gladly let you know. I'd like to abstract away the Window system for all 3 platforms in my project and this is exactly what PyWinCtl already does. So any improvements I can bring to fully switch to an existing library instead of re-inventing the wheel, I'll do so.
Wow! Really interesting module! It seems quite complex too, an impressive point to start learning from!
I am out these days, but I hope that in a few days I will upload a new version of PyWinCtl including the improvements you suggested to avoid crashes. I will let you know so you can keep working with it. So happy to hear it's useful!
I really appreciate your comments and any issue, comment or suggestion you may have in the future.
Keep pushing, mate!!!
Here's my stack trace. Simply calling
pywinctl.getWindowsAt(x, y)
results in an error most of the time. Seemingly randomly.Ubuntu 22.04
Possibly caused by the fact I generate a PyQt widget for the screen-point selection. I wouldn't be surprised if it tries to create a LinuxWindow with it and fails.