Source code for echofilter.win.manager

"""
Window management for Windows.
"""

# This file is part of Echofilter.
#
# Copyright (C) 2020-2022  Scott C. Lowe and Offshore Energy Research Association (OERA)
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, version 3.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program.  If not, see <https://www.gnu.org/licenses/>.

from contextlib import contextmanager
import re

from .. import ui

try:
    import pywintypes
    import win32com.client
    import win32con
    import win32gui
except ImportError:
    from ..path import check_if_windows

    if check_if_windows():
        raise

    import warnings

    msg = "The Windows management module is only for Windows operating" " systems."
    with ui.style.warning_message(msg) as msg:
        warnings.warn(msg, category=RuntimeWarning)


__all__ = ["opencom", "WindowManager"]


[docs]class WindowManager: """ Encapsulates calls to window management using the Windows api. Notes ----- Based on: https://stackoverflow.com/a/2091530 and https://stackoverflow.com/a/4440622 """ def __init__(self, title=None, class_name=None, title_pattern=None): self.handle = None if title is not None: self.find_window(class_name=class_name, title=title) elif title_pattern is not None: self.find_window_regex(title_pattern)
[docs] def find_window(self, class_name=None, title=None): """Find a window by its exact title""" handle = win32gui.FindWindow(class_name, title) if handle == 0: raise EnvironmentError( "Couldn't find window with class={}, title={}.".format( class_name, title ) ) else: self.handle = handle
def _window_enum_callback(self, hwnd, pattern): """Pass to win32gui.EnumWindows() to check all the opened windows""" if re.match(pattern, str(win32gui.GetWindowText(hwnd))) is not None: self.handle = hwnd
[docs] def find_window_regex(self, pattern): """Find a window whose title matches a regular expression.""" self.handle = None win32gui.EnumWindows(self._window_enum_callback, pattern) if self.handle is None: raise EnvironmentError( "Couldn't find a window with title matching pattern {}.".format(pattern) )
[docs] def set_foreground(self): """Bring the window to the foreground.""" win32gui.SetForegroundWindow(self.handle)
[docs] def hide(self): """Hide the window.""" win32gui.ShowWindow(self.handle, win32con.SW_HIDE)
[docs] def show(self): """Show the window.""" win32gui.ShowWindow(self.handle, win32con.SW_SHOW)
[docs]@contextmanager def opencom( com_name, can_make_anew=False, title=None, title_pattern=None, minimize=False, hide="never", ): """ Open a connection to an application with a COM object. The application may or may not be open before this context begins. If it was not already open, the application is closed when leaving the context. Parameters ---------- com_name : str Name of COM object to dispatch. can_make_anew : bool, optional Whether arbitrarily many sessions of the COM object can be created, and if so whether they should be. Default is `False`, in which case the context manager will check to see if the application is already running before connecting to it. If it was already running, it will not be closed when this context closes. title : str, optional Exact title of window. If the title can not be determined exactly, use `title_pattern` instead. title_pattern : str, optional Regular expression for the window title. minimize : bool, optional If `True`, the application will be minimized while the code runs. Default is `False`. hide : {"never", "new", "always"}, optional Whether to hide the application window entirely. Default is `"never"`. If this is enabled, at least one of `title` and `title_pattern` must be specified. If `hide="new"`, the application is only hidden if it was created by this context, and not if it was already running. If `hide="always"`, the application is hidden even if it was already running. In the latter case, the window will be revealed again when leaving this context. Yields ------ win32com.gen_py Interface to COM object. """ HIDE_OPTIONS = {"never", "new", "always"} if hide not in HIDE_OPTIONS: raise ValueError( "Unsupported hide value: {}. Must be one of {}".format(hide, HIDE_OPTIONS) ) make_anew = can_make_anew if not make_anew: # Try to fetch existing session try: app = win32com.client.GetActiveObject(com_name) existing_session = True except pywintypes.com_error as err: # No existing session, make a new session make_anew = True if make_anew: # Create a new session existing_session = False app = win32com.client.Dispatch(com_name) was_minimized = False if minimize: # Tell the app to minimize itself try: app.Minimize() was_minimized = True except: print(ui.style.warning_fmt("Could not minimize {} window".format(com_name))) was_hidden = False if hide == "always" or (hide == "new" and not existing_session): # Find handle to the app based on the window title, and hide it winman = WindowManager() if title is None and title_pattern is None: raise ValueError( "One of the arguments title or title_pattern must be set in" " order to hide the window." ) if title is not None: try: winman.find_window(title=title) except: pass if winman.handle is None and title_pattern is not None: try: winman.find_window_regex(title_pattern) except: pass if winman.handle is None: print( ui.style.warning_fmt( "Could not hide {} window with title {}".format( com_name, title if title_pattern is None else title_pattern ) ) ) else: winman.hide() was_hidden = True try: yield app finally: # As we leave the context, fix the state of the app to how it was # before we started command = "" try: if not existing_session: # If we opened it, tell the application to quit command = "exit" app.Quit() else: if was_minimized: # Restore the window from being minimised command = "restore" app.Restore() if was_hidden: # Show the window again command = "unhide" winman.show() except: # We'll get an error if the application was already closed, etc print( ui.style.warning_fmt( "Could not {} the {} window with handle {}.".format( command, com_name, app ) ) )