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

getActiveWindow() retrieving a window that isn't retrieved by getAllWindows (specifically, with windows search bar) #88

Closed
RealmX1 opened this issue Apr 23, 2024 · 8 comments

Comments

@RealmX1
Copy link

RealmX1 commented Apr 23, 2024

to replicate this problem, in Win11, write loop that runs both getactivewindow and getallwindows and check for existance of return of getactivewindow in getallwindonw, like the following piece

        active_window = pywinctl.getActiveWindow()
        curr_windows = pywinctl.getAllWindows()

        error_message = f"Active window not in current windows: {active_window_title}"
        assert active_window in curr_windows, print(error_message)

then press windows key to open windows search bar; assertion will fail.

@Kalmat
Copy link
Owner

Kalmat commented Apr 23, 2024

Hi!

getActiveWindow() and getAllWindows() return Window class objects, so they won't match. You need to use the titles. Please, try this and let me know if it works:

import pywinctl

active_window = pywinctl.getActiveWindow()
active_window_title = pywinctl.getActiveWindowTitle()
curr_windows_titles = pywinctl.getAllTitles()

print("ACTIVE WINDOW", active_window_title)
print("ALL WINDOWS", curr_windows_titles)

error_message = f"Active window not in current windows: {active_window_title}"
assert active_window_title in curr_windows_titles, print(error_message)
assert active_window.title in curr_windows_titles, print(error_message)

@RealmX1
Copy link
Author

RealmX1 commented Apr 23, 2024

Hi!

getActiveWindow() and getAllWindows() return Window class objects, so they won't match. You need to use the titles. Please, try this and let me know if it works:

import pywinctl

active_window = pywinctl.getActiveWindow()
active_window_title = pywinctl.getActiveWindowTitle()
curr_windows_titles = pywinctl.getAllTitles()

print("ACTIVE WINDOW", active_window_title)
print("ALL WINDOWS", curr_windows_titles)

error_message = f"Active window not in current windows: {active_window_title}"
assert active_window_title in curr_windows_titles, print(error_message)
assert active_window.title in curr_windows_titles, print(error_message)

when trying to press windows key to enter windows start menu, provide the following error; indicating that active_widnow is None. Is it intentional that this can return none?

image
assert active_window.title in curr_windows_titles, print(error_message)
^^^^^^^^^^^^^^^^^^^
AttributeError: 'NoneType' object has no attribute 'title'

I do recall that win32gui never return none when calling GetForgroundWindow (I'm not sure though), so it is probably Win32Window(hWnd) returning None for a not-none handle.
(in your source code for windows)

def getActiveWindow() -> Optional[Win32Window]:
    """
    Get the currently active (focused) Window

    :return: Window object or None
    """
    hWnd = win32gui.GetForegroundWindow()
    if hWnd:
        return Win32Window(hWnd)
    else:
        return None

@Kalmat
Copy link
Owner

Kalmat commented Apr 24, 2024

Hi again!

getActiveWindow() intentionally returns None in case the active window handle couldn't be found/retrieved, so it is something to take into account when invoking it and working with its result.

As per the documentation, GetForegroundWindow can return NULL. Check this.

Besides, there is no way that Win32Window class returns None when trying to instantiate it. In case the handle passed to the class is not valid, it will crash when trying to perform any query or action, but it is not returning None.

I made a very simple snippet to try to reproduce your case:

import time

import win32gui
from pywinctl._pywinctl_win import Win32Window

def getActiveWindow():
    hWnd = win32gui.GetForegroundWindow()
    print("HANDLE", hWnd)
    if hWnd:
        return Win32Window(hWnd)
    else:
        return None

while True:
    try:
        print("ACTIVE", getActiveWindow().title)
        time.sleep(1)
    except KeyboardInterrupt:
        break

The output was:

Right after pressing "Run" in PyCharm:

HANDLE 6293316
TITLE PyWinCtl – test.py
HANDLE 6293316
TITLE PyWinCtl – test.py
HANDLE 6293316
TITLE PyWinCtl – test.py

After pressing Windows key:

HANDLE 66048
TITLE Búsqueda  # Búsqueda means "Search" in Spanish 
HANDLE 66048
TITLE Búsqueda

Clicking in the desktop background (no user active window). Notice that some windows (especially system windows) will return an empty title:

HANDLE 263846
TITLE 
HANDLE 263846
TITLE 
HANDLE 263846
TITLE 

Rapidly clicking between Start icon and the Search box in the taskbar:

HANDLE 65690
TITLE 
HANDLE 66048
TITLE Búsqueda
HANDLE 66048
TITLE Búsqueda
HANDLE 66048
TITLE Búsqueda
HANDLE 2491590
TITLE 
HANDLE 66048
TITLE Búsqueda
HANDLE 0  # <-- HERE!!!
Traceback (most recent call last):
  File "D:\Users\alesc\Documents\Proyectos\PycharmProjects\PyWinCtl\tests\test.py", line 22, in <module>
    print("TITLE", getActiveWindow().title)
                   ^^^^^^^^^^^^^^^^^^^^^^^
AttributeError: 'NoneType' object has no attribute 'title'

FOUND!!! As you can see, the handle returned by GetForegroundWindow is 0 (I guess it's the python translation of the NULL value that pywin32 wrapper is making). Most likely it is the case described in the docs: "The foreground window can be NULL in certain circumstances, such as when a window is losing activation."

In short, None is a valid return that must be handled accordingly.

@RealmX1
Copy link
Author

RealmX1 commented May 7, 2024

Got it. Thanks.

@RealmX1 RealmX1 closed this as completed May 7, 2024
@RealmX1
Copy link
Author

RealmX1 commented May 7, 2024

By the way I resorted to refrain from PyWinCtl, and using win32gui from pywin32 instead as wrapper used by PyWinCtl is way too inefficient when used on windows.

@Kalmat
Copy link
Owner

Kalmat commented May 8, 2024

Well, PyWinCtl is actually using win32gui and win32api... can you please explain why PyWinCtl is inefficient? This will help me a lot to improve the module.

Thx.

@Kalmat Kalmat reopened this May 8, 2024
@RealmX1
Copy link
Author

RealmX1 commented May 8, 2024

Well, PyWinCtl is actually using win32gui and win32api... can you please explain why PyWinCtl is inefficient? This will help me a lot to improve the module.

Thx.

Sure. the main issue comes with the wrapper for win32gui doesn't ever store any information (about the window itself, other than the core ones) after an instance of it is created. Instead it tries to call win32gui each time an attribute is used by the user.
With "getAppName" being the most inefficient.

I do understand that it might be an intentional design decision for skae of not having to keep active sync for the window information, but it becomes quite laggy. I sometimes have to wait 5+ seconds before each call for my function that iterates over the entire list of foreground/background windows (where as my implmentation using win32gui directly result in almost instantanious output, as everything can be done without having to iterate over all windows (for search/access) multiple times.)

A quick fix that I came up rather rashly for you is to enable a toggle between manual and auto sync mode;
with the manual sync mode where data of the window isn't updated unless the user uses a refresh/update function call on the window item;
and the auto sync representing how it is implemented now other than the fact that it would store all information of window but change evertime when attribute is called)

@Kalmat
Copy link
Owner

Kalmat commented May 9, 2024

Thank you!!! This kind of discussions really help me a lot.

Sure. the main issue comes with the wrapper for win32gui doesn't ever store any information (about the window itself, other than the core ones) after an instance of it is created. Instead it tries to call win32gui each time an attribute is used by the user. With "getAppName" being the most inefficient.

I do understand that it might be an intentional design decision for skae of not having to keep active sync for the window information, but it becomes quite laggy. I sometimes have to wait 5+ seconds before each call for my function that iterates over the entire list of foreground/background windows (where as my implmentation using win32gui directly result in almost instantanious output, as everything can be done without having to iterate over all windows (for search/access) multiple times.)

It is actually intentional since all info about / attributes of the window can change at any moment, so it is necessary to query again or it may become totally obsolete and, therefore, useless in many use cases.
Perhaps, as you are pointing out, some info/attributes are not so usual or even impossible to change over the time (app name, maybe, but not many more). I will try to find a way to optimize in this sense but, by the moment, invoking it just once I think that solves the issue... or am I missing something?
I have not detected that enormous lag you mention in my own tests (not in Windows, at least). Can you please provide me with your code or a snippet that I can test?

A quick fix that I came up rather rashly for you is to enable a toggle between manual and auto sync mode; with the manual sync mode where data of the window isn't updated unless the user uses a refresh/update function call on the window item; and the auto sync representing how it is implemented now other than the fact that it would store all information of window but change evertime when attribute is called)

Just to be sure I understand your proposal, you suggest to add a new input parameter which allows to decide whether to return a preivously stored value (under the risk of being obsolete, but it's up to the user), or query for the updated one. Do you think this should be done for all values? I think that attributes like isActive, size or position are not as permanent over the time like others (as app name)... Should this behavior be limited to just some attributes? And, if so, which ones?

Regarding this, there is a watchdog (use window.watchdog.start()) that continuously queries the window info. You can pass callbacks to be invoked when one of these values change. It runs in a separate thread in order to avoid harming main thread performance. Not sure if this fulfills your requirements (to be honest, I wasn't sure how to implement this because I had no real case to use as a base. Yours is the first till now). I can adapt it in order to continuously keep updated a set of values/attributes (to be defined) that could be immediately accessed and returned by Window class without the need of querying again. In short: keep info synced, in parallel using a separate thread that the user can activate/deactivate. If active, the info returned will be taken from the watchdog. If deactivated, the info will be queried using win32gui/win32api.
Back to the lag problem, when testing with this watchdog using a 0.3 sleep loop, it was almost immediate detecting all changes (e.g. active / not active, window moved or resized, etc.)... this is why it is very strange to me and will really appreciate if you can provide that example code.

@Kalmat Kalmat closed this as completed Sep 23, 2024
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

No branches or pull requests

2 participants