PyWinCtl

repository·master·Indexed 42 days ago

https://github.com/kalmat/pywinctl

A cross-platform Python library for discovering, controlling, and monitoring desktop windows across Windows, macOS, and Linux using a unified API. It provides functionality to find windows by title or PID, move, resize, and activate windows, and monitor state changes in real time via a watchdog mechanism. Version 0.4.2 supports native backends including Win32 API, Apple Script, and EWMH/Xlib.

Tokens
7.6K
Snippets
17
Records
36
Agent score
53%

What's inside PyWinCtl

  1. Overview of PyWinCtl

    master

    PyWinCtl is a cross-platform Python library for discovering, controlling, and monitoring desktop windows on Windows, macOS, and Linux. It provides a unified API that uses native backends: Win32 API on Windows, Apple Script on macOS, and EWMH/Xlib on Linux.

    Common use cases include:

    • Finding windows by title or PID.
    • Moving, resizing, or snapping windows.
    • Activating or bringing windows to the front.
    • Monitoring window state changes (closing, moving, title changes) in real time.
    • Automating GUI workflows and multi-window test harnesses.
    • Getting exact window coordinates for screen capture tools (e.g., with mss or OpenCV).
  2. PyWinCtl API Layers

    master

    PyWinCtl exposes its functionality through three distinct layers:

    1. Module-level functions: Called directly on the pywinctl module to interact with the desktop without needing a specific window object (e.g., pwc.getActiveWindow(), pwc.getAllWindows(), pwc.getWindowsWithTitle()).
    2. Window methods: Actions performed on a specific Window object instance (e.g., win.close(), win.minimize(), win.resizeTo(), win.moveTo()).
    3. Window properties: Attributes of a Window object that can be read or written (e.g., win.title, win.isMaximized, win.position, win.size).
  3. Monitor window changes with the watchdog

    master

    PyWinCtl provides a watchdog (accessible via window.watchdog) which is a background thread that monitors a window and executes callbacks when its state changes. The watchdog stops automatically when the window closes or the program exits.

    Available Callbacks

    CallbackTriggerParameter(s)
    isAliveCBWindow is no longer aliveFalse
    isActiveCBWindow gains/loses focusTrue / False
    isVisibleCBWindow is shown/hiddenTrue / False
    isMinimizedCBWindow is minimized/restoredTrue / False
    isMaximizedCBWindow is maximized/restoredTrue / False
    resizedCBWindow is resized(width, height)
    movedCBWindow is moved(x, y)
    changedTitleCBWindow title changesnew title (str)
    changedDisplayCBWindow moves to different displaynew display name (str)

    Important Usage Notes

    • Callback Signatures: Must match the parameter types: bool, str, or (int, int).
    • Asynchronous Nature: Notifications are not immediate. Use updateInterval() to adjust polling frequency.
    • Updating Callbacks: When using updateCallbacks(), you must pass all desired callbacks. Any omitted callback (passed as None) will be deactivated.
    • macOS Note: On macOS (AppleScript backend), if the title changes, the watchdog stops unless setTryToFind(True) is used to attempt a similarity-based re-location.
    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)
    
    # Example loop to demonstrate updating callbacks and intervals
    i = 0
    while True:
        try:
            if i == 50:
                # Replaces current callbacks with a new set
                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()
  4. Use WatchDog to monitor window state changes

    master

    The _WatchDog class allows you to set up a separate thread that monitors a window and executes callbacks when specific states change.

    Important Notes:

    • Asynchronous: Notifications are not immediate; you can adjust the interval to control polling frequency.
    • macOS Performance: The Apple Script version on macOS can be extremely slow.
    • Callback Signature: Your callback functions MUST match the expected return type of the state they monitor (e.g., a resizedCB must accept and return a (width, height) tuple).
    • Initial State: Changes are notified based on the window status at the exact moment start() is executed.
    # Example concept of starting a watchdog
    watchdog.start(
        isActiveCB=lambda active: print(f"Active status: {active}"),
        movedCB=lambda pos: print(f"Moved to: {pos}"),
        interval=0.5
    )
  5. Install PyWinCtl

    master

    Install PyWinCtl using pip or uv. The library requires Python ≥ 3.9. Platform-specific dependencies (pywin32 for Windows, python-xlib + ewmhlib for Linux, pyobjc for macOS) are installed automatically.

    Via pip:

    python -m pip install pywinctl

    Via uv:

    uv add pywinctl

    From a wheel file:

    python -m pip install PyWinCtl-x.xx-py3-none-any.whl
    python -m pip install pywinctl
  6. Enable Wayland unsafe_mode

    master

    On Linux Wayland, PyWinCtl functions like getActiveWindow and getAllWindows will not work unless unsafe_mode is activated via the Looking Glass (lg) tool:

    1. Press Alt + F2.
    2. Type lg and press Enter.
    3. In the command entry box at the bottom, type global.context.unsafe_mode = true and press Enter.
    4. To exit lg, click an option in the upper right corner, then press Escape.

    To disable it, repeat the steps using global.context.unsafe_mode = false.

  7. Troubleshooting common PyWinCtl issues

    master

    Known Gotchas

    • Minimized Windows: getWindowsWithTitle() and getAllWindows() may not return minimized windows on some platforms. To find them, use getAllWindows() and filter manually.
    • Search Matching: getWindowsWithTitle() is a substring search by default.
      • Use condition=pwc.Re.IS for an exact match.
      • Use condition=pwc.Re.CONTAINS with flags=pwc.Re.IGNORECASE for case-insensitive partial matching.
    • macOS Performance: Apple Script calls (like getActiveWindowTitle()) can be slow (400–500 ms). For high-frequency tasks, use the watchdog with a tuned interval instead of polling in a loop.
    • Wayland Support: Most window enumeration functions may fail or return empty results on Wayland. Use X11/XWayland mode or instantiate Window directly with a known XID.
    • WSL2: Not supported. PyWinCtl requires a native X server environment.
    • Linux sendBehind(): Only works reliably on Mint/Cinnamon and Ubuntu 22.04+.
  8. Capture a window's client frame for screenshots

    master

    To capture only the content of a window (excluding borders and title bars), use the getClientFrame() method. This returns a frame object containing left, top, right, and bottom coordinates, which can be used to define a bounding box for libraries like mss.

    import pywinctl as pwc
    import mss
    
    win = pwc.getWindowsWithTitle("My App")[0]
    frame = win.getClientFrame()
    box = {"left": frame.left, "top": frame.top, "width": frame.right - frame.left, "height": frame.bottom - frame.top}
    with mss.mss() as sct:
        win.activate()
        screenshot = sct.grab(box)
  9. Monitor window state changes with watchdog

    master

    PyWinCtl provides a watchdog mechanism to react to window events. You can use win.watchdog.start(isAliveCB=callback_function) to trigger a callback when the window's isAlive status changes (e.g., when the window is closed).

    import pywinctl as pwc
    
    def on_closed(is_alive):
        print("Window gone!")
    
    win = pwc.getActiveWindow()
    win.watchdog.start(isAliveCB=on_closed)
  10. Wait for a launched application window to appear

    master

    When launching an application via subprocess, you can poll for its window using a loop and pwc.getWindowsWithTitle() to ensure the window is ready for interaction.

    import subprocess, time, pywinctl as pwc
    
    subprocess.Popen("notepad")
    win = None
    while not win:
        time.sleep(0.2)
        windows = pwc.getWindowsWithTitle("Notepad")
        if windows:
            win = windows[0]
    win.activate()
    win.resizeTo(800, 600)
  11. Find and control a window with PyWinCtl

    master

    You can use pwc.getWindowsWithTitle(title) to retrieve a list of window objects matching a specific title. Once you have a window object, you can perform actions like activate(), resizeTo(width, height), moveTo(x, y), and alwaysOnTop(True). You can also read live properties such as title, size, isMaximized, and isAlive.

    import pywinctl as pwc
    
    # Find a window and take full control
    win = pwc.getWindowsWithTitle("Notepad")[0]
    
    win.activate()            # bring to front
    win.resizeTo(1280, 720)   # set exact size
    win.moveTo(0, 0)          # snap to top-left corner
    win.alwaysOnTop(True)     # pin it above everything else
    
    # Read live properties
    print(win.title, win.size, win.isMaximized, win.isAlive)
  12. Manage window hierarchy and relationships

    master

    Work with parent-child relationships between windows:

    • getParent(): Returns the handle of the window's parent (can be another window or an application).
    • getChildren(): Returns a list of handles for the window's children.
    • setParent(parent: int): Sets the current window as a child of the provided parent handle. Warning: Not implemented for foreign windows on macOS via AppleScript.
    • isParent(child: int) / isParentOf(child: int): Returns True if the current window is the parent of the provided child handle.
    • isChild(parent: int) / isChildOf(parent: int): Returns True if the current window is a child of the provided parent handle.