I have a python console app which runs on WIndows. It also has a web GUI which runs in the browser. In my GUI I have buttons which when clicked should open a folder in the Windows file explorer. So, when the button is clicked, I send a command from the browser to the backend (the python console app) which in turn should open the needed file.
The problem is that the explorer’s window appears under the browser’s window (though in front of the console’s window):
I’ve found out that you can bring the window to front using win32 API. Though, you need to know the HWND - the window handle (ID).
I’m opening the file explorer using subprocess, which provides the PID of the spawned process. Knowing the PID we can find all the windows of the process like so:
import win32gui
import win32process
import subprocess
import time
def enumHandler(hwnd, lParam):
thread_id, process_id = win32process.GetWindowThreadProcessId(hwnd)
if process_id == opened_pid:
print("Found a window:")
print(hwnd, win32gui.GetWindowText(hwnd))
with subprocess.Popen("notepad.exe") as proc:
opened_pid = proc.pid
time.sleep(2)
win32gui.EnumWindows(enumHandler, None)
The problem with my approach
This works well if the command is "notepad.exe", for example. However, it won’t find any windows if I try the command that I want, which is r"explorer path\to\folder".
I suspect that with a command like this I’m getting the wrong PID from proc.pid. Therefore, my script cannot find any windows associated with it.
Running explorer "path\to\folder" will reuse the session’s main Explorer process, unless you enable the option to “launch folder windows in a separate process”. Even with the latter option, Explorer won’t use an arbitrarily spawned instance. It uses the DCOM launcher service to respawn itself, and the first instance exits immediately.
I’d require the session to have a desktop shell (not headless) and require this shell to be “explorer.exe”. This includes the vast majority of Windows installations, but it’s trivial to check, so it can’t hurt. The function GetShellWindow() returns the handle of the shell window, if there is one.
For the enumeration of top-level windows, if you know it’s a window that’s owned by Explorer, it should have at least the base folder name in the window title.
Thank you for this creative solution! Unfortunately, as of now it fails for my app with the following error: error: (5, 'OpenProcess', 'Access is denied.')
I googled a little and some people suggest using PROCESS_QUERY_LIMITED_INFORMATION, which your solution already does. I will try to investigate further why this happens.
There’s a window in your desktop session that’s owned by a process that restricts access. You can modify the function to continue to the next window. For example:
import pywintypes
def enum_func(hwnd, param):
if param['hwnd'] is None:
try:
path = os.path.normcase(get_window_process_path(hwnd))
except pywintypes.error:
return True
if path == param['path']:
text = win32gui.GetWindowText(hwnd)
if os.path.normcase(os.path.basename(text)) == param['text']:
param['hwnd'] = hwnd
return True
You were right - skipping the windows that caused the error helped. However, for some reason after your code completes the param['hwnd'] is still None. I didn’t investigate further and ended up using a workaround: before opening the explorer I bring the console window to front and activate it. This way the explorer’s window appears on top (though, the console window is now in between the explorer and the web browser). Thank you for your effort.
Explorer should have at least the base name of the opened path, whatever it is, in the title bar text. It works like a charm for me when I open an Explorer window for “C:\Temp”. Of course, one does have to wait for Explorer to create the window. If you enumerate the top-level windows immediately after executing Explorer, it probably won’t find anything.
You might not be able to depend on manipulating the console window. The most common case that’s a concern is when a pseudoconsole (i.e. ConPTY) is used, such as Windows Terminal. In a pseudoconsole session, GetConsoleWindow() returns a handle for a fake console window that’s invisible and empty. In earlier versions of the pseudoconsole implementation, basic ShowWindow() operations on this fake window had no effect. In April of last year, Michael Niksa implemented a backend to relay some window operations back to the ConPTY terminal. He wrote up the design details in the document “Show Hide operations on GetConsoleWindow via PTY”. You should verify that your approach works when running under Windows Terminal. This matters because Windows Terminal can be set as the default for all console sessions in Windows 11, and it’s increasingly popular.
There are a couple other noteworthy factors regarding GetConsoleWindow(). The first is that the console session may have been created with the flag CREATE_NO_WINDOW, in which case GetConsoleWindow() returns NULL. The second is that maybe your script doesn’t have a console session, which is the case if it’s run via “pythonw.exe” or created with the flag DETACHED_PROCESS. The second case can be addressed, if you so wish, by calling AllocConsole().
Thanks for the additional information. As far as I understand, I should not face such issues for two reasons:
I can control the way my app is being launched. I even ship my app along with the console which should be used (currently this is ConEmu).
My software is for non-programmers, so they have no interest to experiment with different consoles and ways to start the app.
This allows me to adjust the code for the specific console (even for the specific version of it). Namely, what I had to do was get the current process ID and then move up the hierarchy a couple of times to find the needed parent process which owns the visible console window.