3

In Python 3.10.5 on Windows 11 using the win32gui module, I want to create a simple window switching application. First, I make a list of hwnds of all the running windows and then elsewhere in the code I switch to one of those windows using the given hwnd. The relevant and simplified parts of the code look like this:

def winEnumHandler(hwnd, ctx):
  if win32gui.IsWindowVisible(hwnd):
    windows.append(hwnd)

def switchToWindow(hwnd):
  win32gui.SetForegroundWindow(hwnd)

def main():
  windows = []
  win32gui.EnumWindows(winEnumHandler, None)
  
main()

The code works without an issue if my Python application built using the wxPython module is in the foreground and when I want to switch to a window other than my Python application, but whenever, having another application in the foreground, I want to switch back to the main frame of my application or to the Windows command line from which I am running the Python application, I get the following error:

Exception in thread Thread-2 (showSwitcher):
Traceback (most recent call last):
  File "C:\Users\asamec\AppData\Local\Programs\Python\Python310-32\lib\threading.py", line 1016, in _bootstrap_inner
    self.run()
  File "C:\Users\asamec\AppData\Local\Programs\Python\Python310-32\lib\threading.py", line 953, in run
    self._target(*self._args, **self._kwargs)
  File "c:\Users\asamec\Dropbox\DIY\Python\WinSwitcher\WinSwitcher\src\WinSwitcher.py", line 207, in showSwitcher
    self.switchToApp(app)
  File "c:\Users\asamec\Dropbox\DIY\Python\WinSwitcher\WinSwitcher\src\WinSwitcher.py", line 165, in switchToApp
    self.switchToWindow(hwnd)
  File "c:\Users\asamec\Dropbox\DIY\Python\WinSwitcher\WinSwitcher\src\WinSwitcher.py", line 172, in switchToWindow
    win32gui.SetForegroundWindow(hwnd)
pywintypes.error: (5, 'SetForegroundWindow', 'Access is denied.')   t

So the questions are: how can I bypass the "Access is denied" exception, and why is it thrown in the case of switching only from another window to my Python application window running in the background, not when switching from my application window in the foreground to other windows?

Update

Based on the first anser provided for this post, it looks like due to the Windows system restrictions the needed window switching is not possible using the win32gui module and its APIs. Then the question is: is there another way how to switch to a specific window even when the Python app is not running in the foreground? I am asking because such switching is actually possible using the pywinauto Python module if the process PID is known, with the following code:

from pywinauto import Application

app = Application().connect(process=pid)
app.top_window().set_focus()

My original reason for not wanting to use the above code employing the pywinauto module was that I thought it is performing slowly, however, I have found out that the performance issue was in some other part of the code. Therefore I am marking the first ansewr to this question as the accepted one, because it also answers my follow-up question, which was: why was it that the pywinauto method was not affected by the Windows system restrictions?

The accepted anser provides a solution which does not require the PID of the desired application and related pywinauto methods for switching to that application. Instead, it provides a solution for switching to the desired window rather than aplication via the window's hwnd, which was actually the original intent of the question. The answer also explains that the workaround for bypassing the Windows system window switching restrictions lies in the usage of the tricck of moving the mouse pointer out of the screen before running the win32gui.SetForegroundWindow(hwnd) method.

Adam
  • 1,926
  • 23
  • 21

1 Answers1

3

This is a limitation of the operating system. Microsoft's documentation explains in detail when it prevents the foreground window from being set with SetForegroundWindow:

The system restricts which processes can set the foreground window. A process can set the foreground window only if one of the following conditions is true:

  • The process is the foreground process.
  • The process was started by the foreground process.
  • The process received the last input event.
  • There is no foreground process.
  • The process is being debugged.
  • The foreground process is not a Modern Application or the Start Screen.
  • The foreground is not locked (see LockSetForegroundWindow).
  • The foreground lock time-out has expired (see SPI_GETFOREGROUNDLOCKTIMEOUT in SystemParametersInfo).
  • No menus are active.

An application cannot force a window to the foreground while the user is working with another window. Instead, Windows flashes the taskbar button of the window to notify the user. A process that can set the foreground window can enable another process to set the foreground window by calling the AllowSetForegroundWindow function. The process specified by dwProcessId loses the ability to set the foreground window the next time the user generates input, unless the input is directed at that process, or the next time a process calls AllowSetForegroundWindow, unless that process is specified.

Additional Details

pywinauto is subject to the same restrictions when calling SetForegroundWindow. However, they get around this with a trick by setting the mouse position to a point outside the screen. Then SetForegroundWindow can be called again successfully. Since pywinauto is open source, you can also take a look at the corresponding code snippet.

Test

The following program is executed when a Wordpad instance and a Task Manager window are open at the same time. This works fine with selecting the second window because the mouse is moved out of the screen. If the mouse movement is commented out the Access is denied. error appears.

Of course, this requires pywinauto import mouse, but it allows to easily demonstrate the functionality in this way.

import win32gui
import win32con
from pywinauto import mouse
from time import sleep


def foreground(window_title):
    hwnd = win32gui.FindWindow(None, window_title)
    win32gui.ShowWindow(hwnd, win32con.SW_SHOW)
    win32gui.SetForegroundWindow(hwnd)


if __name__ == "__main__":
    foreground("Document - Wordpad")
    sleep(1)
    #if you comment the next line you will get a 'Access is denied.' error
    mouse.move(coords=(-10000, 500))
    foreground("Task Manager")
Stephan Schlecht
  • 26,556
  • 1
  • 33
  • 47
  • Thanks for the explanation. Then some other questions arise, which I added to the end of my original question. Could you please address those? – Adam Aug 07 '22 at 16:16
  • I have added additional details regarding the updated pywinauto question to the answer, including a link to the relevant source code location and a simple test program for demo purposes. – Stephan Schlecht Aug 07 '22 at 22:01
  • 1
    Accepting as it explains the mouse pointer movement workaround, which is the actual answer to my problem. – Adam Aug 08 '22 at 11:12