Skip to main content

Cross-Platform toolkit to get info on and control windows on screen

Project description

PyWinCtl

Type Checking PyPI version

Cross-Platform module to get info on and control windows on screen.

With PyWinCtl you can retrieve info or control windows from other open applications, as well as use it as a cross-platform toolkit to manipulate your own application windows.

This module is a Python 3 evolution from asweigart's PyGetWindow module, which adds Linux/X11 and macOS support to the MS Windows-only original module, experimental multi-monitor support, and many additional features; in the hope others can use it, test it or contribute.

My most sincere thanks and acknowledgement to MestreLion, super-ibby, Avasam, macdeport and holychowders for their help and moral boost.

  1. Window Features
    1. Important macOS notice
    2. Important Linux notice
  2. Window Change Notifications
    1. Important comments
    2. Important macOS Apple Script version notice
  3. Menu Features
  4. Install
  5. Support
  6. Using this code
  7. Test

Window Features

You need a Window object to manipulate/control the target window on screen. It's possible to get a Window object by using any of the general methods (e.g. getActiveWidow() or getWindowsWithTitle()). You can also use windows id, as returned by PyQt's self.winId() or tkinter's root.frame(), which is very handy to get the Window object for your own application.

These functions are available at the moment, in all three platforms (Windows, Linux and macOS):

General, independent functions: Window class methods: Window class properties:
getActiveWindow close title
getActiveWindowTitle minimize updatedTitle (MacOSWindow only)
getAllWindows maximize isMaximized
getAllTitles restore isMinimized
getWindowsWithTitle hide isActive
getAllAppsNames show isVisible
getAppsWithName activate isAlive
getAllAppsWindowsTitles resize / resizeRel
getWindowsAt resizeTo
getTopWindowAt move / moveRel
displayWindowsUnderMouse moveTo
version raiseWindow
checkPermissions (macOS only) lowerWindow
alwaysOnTop
alwaysOnBottom
sendBehind
acceptInput
getAppName
getHandle
getParent
setParent
getChildren
isParent
isChild
getDisplay
getExtraFrame
getClientFrame

Important macOS notice

macOS doesn't "like" controlling windows from other apps, so there are two separate classes you can use:

  • To control your own application's windows: MacOSNSWindow() is based on NSWindow Objects (you have to pass the NSApp() Object reference).
  • To control other applications' windows: MacOSWindow() is based on Apple Script, so it is non-standard, slower and, in some cases, tricky (uses window name as reference, which may change or be duplicate), but it's working fine in most cases. You will likely need to grant permissions on Settings -> Security&Privacy -> Accessibility. Notice some applications will have limited Apple Script support or no support at all, so some or even all methods may fail!

Important Linux notice

The enormous variety of Linux distributions, Desktop Environments, Window Managers, and their combinations, make it impossible to test in all scenarios.

This module has been tested OK in some X11 setups: Ubuntu/Gnome, Ubuntu/KDE, Ubuntu/Unity, Mint/Cinnamon and Raspbian/LXDE. Except for Mint/Cinnamon and Ubuntu 22.04+, sendBehind() method doesn't properly work!

In Wayland (the new GNOME compositor for Ubuntu 22.04+), it is not possible to retrieve the active window nor the list of open windows, so getActiveWindow() and getAllWindows() will not work even though unsafe-mode is
enabled (built-in and "official" applications do not populate their XID nor their X-Window object, so it may work for other applications like Chrome or your own application windows)

In case you find problems in other configs, please open an issue. Furthermore, if you have knowledge in these other configs, do not hesitate to contribute!

Window Change Notifications

Window watchdog sub-class, running in a separate Thread, will allow you to define hooks and its callbacks to be notified when some window states change. Accessible through 'watchdog' submodule.

The watchdog will automatically stop when window doesn't exist anymore or main program quits.

isAliveCB:      callback to invoke when window is not alive anymore. Set to None to not to watch this
                Passes the new alive status value (False)

isActiveCB:     callback to invoke if window changes its active status. Set to None to not to watch this
                Passes the new active status value (True/False)

isVisibleCB:    callback to invoke if window changes its visible status. Set to None to not to watch this
                Passes the new visible status value (True/False)

isMinimizedCB:  callback to invoke if window changes its minimized status. Set to None to not to watch this
                Passes the new minimized status value (True/False)

isMaximizedCB:  callback to invoke if window changes its maximized status. Set to None to not to watch this
                Passes the new maximized status value (True/False)

resizedCB:      callback to invoke if window changes its size. Set to None to not to watch this
                Passes the new size (width, height)

movedCB:        callback to invoke if window changes its position. Set to None to not to watch this
                Passes the new position (x, y)

changedTitleCB: callback to invoke if window changes its title. Set to None to not to watch this
                Passes the new title (as string)
                IMPORTANT: In MacOS AppScript version, if title changes, watchdog will stop unless using setTryToFind(True)

changedDisplayCB: callback to invoke if window changes display. Set to None to not to watch this
                  Passes the new display name (as string)
watchdog sub-module methods:
start
updateCallbacks
updateInterval
setTryToFind
isAlive
stop

Example:

import pywinctl as pwc
import time

def activeCB(active):
    print("NEW ACTIVE STATUS", active)

def movedCB(pos):
    print("NEW POS", pos)

npw = pwc.getActiveWindow()
npw.watchdog.start(isActiveCB=activeCB)
npw.watchdog.setTryToFind(True)
print("Toggle focus and move active window")
print("Press Ctl-C to Quit")
i = 0
while True:
    try:
        if i == 50:
            npw.watchdog.updateCallbacks(isActiveCB=activeCB, movedCB=movedCB)
        if i == 100:
            npw.watchdog.updateInterval(0.1)
            npw.watchdog.setTryToFind(False)
        time.sleep(0.1)
    except KeyboardInterrupt:
        break
    i += 1
npw.watchdog.stop()

Important comments

  • The callbacks definition MUST MATCH their invocation params (boolean, string or (int, int))
  • The watchdog is asynchronous, so notifications won't be immediate (adjust interval value to your needs). Use window object properties instead (e.g. isAlive)
  • Position and size notifications will trigger several times between initial and final values
  • When updating callbacks, remember to set ALL desired callbacks. Non-present (None) callbacks will be deactivated

Important macOS Apple Script version notice

  • Might be very slow and resource-consuming
  • It uses the title to identify the window, so if it changes, the watchdog will consider it is not available anymore and will stop
  • To avoid this, use ''tryToFind(True)'' method to try to find the new title (not fully guaranteed since it uses a similarity check, so the new title might not be found or correspond to a different window)

Menu Features

Available in: MS-Windows and macOS Apple Script version (Win32Window() and MacOSWindow() classes)

menu sub-class for Menu info and control methods, accessible through 'menu' submodule.

menu sub-module methods:
getMenu
getMenuInfo
getMenuItemCount
getMenuItemInfo
getMenuItemRect
clickMenuItem

MS-Windows example (notice it is language-dependent):

import pywinctl as pwc
import subprocess
# import json

subprocess.Popen('notepad')
windows = pwc.getWindowsWithTitle('notepad', condition=pwc.Re.CONTAINS, flags=pwc.Re.IGNORECASE)
if windows:
    win = windows[0]
    menu = win.menu.getMenu()
    # print(json.dumps(menu, indent=4, ensure_ascii=False))  # Prints menu dict in legible format
    ret = win.menu.clickMenuItem(["File", "Exit"])           # Exit program
    if not ret:
        print("Option not found. Check option path and language")
else:
    print("Window not found. Check application name and language")

Menu dictionary (returned by getMenu() method) will likely contain all you may need to handle application menu:

Key:            item title
Values:
  "parent":     parent sub-menu handle
  "hSubMenu":   item handle (!= 0 for sub-menus only)
  "wID":        item ID (required for other actions, e.g. clickMenuItem())
  "rect":       Rect struct of the menu item. (Windows: It is relative to window position, so it won't likely change if window is moved or resized)
  "item_info":  [Optional] Python dictionary (macOS) / MENUITEMINFO struct (Windows)
  "shortcut":   shortcut to menu item, if any (macOS: only if item_info is included)
  "entries":    sub-items within the sub-menu (or not present otherwise)
                these sub-items will have this very same format, in a nested struct.

Note not all windows/applications will have a menu accessible by these methods.

INSTALL

To install this module on your system, you can use pip:

pip install pywinctl

or

python3 -m pip install pywinctl

Alternatively, you can download the wheel file (.whl) available in the Download page and the dist folder, and run this (don't forget to replace 'x.x.xx' with proper version number):

pip install PyWinCtl-x.x.xx-py3-none-any.whl

You may want to add --force-reinstall option to be sure you are installing the right dependencies version.

Then, you can use it on your own projects just importing it:

import pywinctl as pwc

SUPPORT

In case you have a problem, comments or suggestions, do not hesitate to open issues on the project homepage

USING THIS CODE

If you want to use this code or contribute, you can either:

Be sure you install all dependencies described on "docs/requirements.txt" by using pip

TEST

To test this module on your own system, cd to "tests" folder and run:

pytest -vv test_pywinctl.py

or, in case you get an import error, try this:

python3 -m pytest -vv test_pywinctl.py

MacOSNSWindow class and methods can be tested by running this, also on "tests" folder:

python3 test_MacNSWindow.py

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distributions

No source distribution files available for this release.See tutorial on generating distribution archives.

Built Distribution

PyWinCtl-0.1-py3-none-any.whl (100.4 kB view details)

Uploaded Python 3

File details

Details for the file PyWinCtl-0.1-py3-none-any.whl.

File metadata

  • Download URL: PyWinCtl-0.1-py3-none-any.whl
  • Upload date:
  • Size: 100.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/4.0.2 CPython/3.10.9

File hashes

Hashes for PyWinCtl-0.1-py3-none-any.whl
Algorithm Hash digest
SHA256 2411bbd3a960d622c37aaf7eea67edfd34a732076d520a80c9a3c18845e97630
MD5 b215d0e3434569356f7f9f6cef6e4d2a
BLAKE2b-256 60aed616cd07c17f4a6b651a382b7b0ececa90f7015cda2cc805dd86aaffa8d1

See more details on using hashes here.

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page