import ctypes
import dataclasses as dc
import enum
import struct
from typing import Iterator, List, Optional, Tuple, Union
from . import colors
from . import sending
from .exceptions import Error
from .flow import ahk_call, global_ahk_lock, _wait_for
from .hotkey_context import HotkeyContext
from .settings import get_settings, optional_ms
from .unset import UNSET, UnsetType
__all__ = [
"Control",
"ExWindowStyle",
"Window",
"Windows",
"WindowStyle",
"all_windows",
"visible_windows",
"windows",
]
TITLE_MATCH_MODES = {"startswith", "contains", "exact", "regex"}
TEXT_MATCH_MODES = {"fast", "slow"}
[docs]@dc.dataclass(frozen=True)
class Windows:
"""Windows()
The immutable object containing the criteria that is used to match the
windows, iterate over them, do bulk actions, or to pick one window.
"""
title: Union[str, UnsetType] = UNSET
class_name: Union[str, UnsetType] = UNSET
id: Union[int, UnsetType] = UNSET
pid: Union[int, UnsetType] = UNSET
exe: Union[str, UnsetType] = UNSET
text: Union[str, UnsetType] = UNSET
exclude_title: Union[str, UnsetType] = UNSET
exclude_text: Union[str, UnsetType] = UNSET
hidden_windows: bool = False
hidden_text: bool = True
title_mode: str = "startswith"
text_mode: str = "fast"
[docs] def filter(self, title=UNSET, *, class_name=UNSET, id=UNSET, pid=UNSET, exe=UNSET, text=UNSET, match=None):
"""filter(title: str = UNSET, **criteria)
Match specific windows using the given criteria. All strings are
case-sensitive.
:param str title: the window title to match. The matching behavior is
specified in the *match* argument and defaults to ``"startswith"``.
:param str class_name: the window class to match.
:param int id: the unique ID of a window or a control; also known as
HWND.
:param int pid: the process identifier that is used to match the windows
belonging to this process.
:param str exe: the filename of the process that is used to match the
windows belonging to this process. If *match* is ``"regex"``, the
*exe* argument accepts a regular expression that matches the full
path of the process.
:param str text: the substring from a single text element of the target
window. The matching behavior is affected by the
:meth:`include_hidden_text` and :meth:`match_text_slow` methods.
:param str match: sets the matching behavior. Takes one of the following
values:
- ``"startswith"`` – a window's title must start with the given
*title*.
- ``"contains"`` – a window's title must contain the given *title*.
- ``"exact"`` – a window's title must exactly match the given
*title*.
- ``"regex"`` – changes *title*, *class_name*, *exe*, and *text*
arguments to accept `PCRE regular expressions
<https://www.autohotkey.com/docs/misc/RegEx-QuickRef.htm>`_.
Defaults to ``"startswith"``.
"""
return self._filter(title, class_name, id, pid, exe, text, match)
def _filter(self, title, class_name, id, pid, exe, text, match) -> 'Windows':
if (
title is UNSET and class_name is UNSET and id is UNSET and pid is UNSET and exe is UNSET and
text is UNSET and match is None
):
return self
if match is not None and match not in TITLE_MATCH_MODES:
raise ValueError(f"{match!r} is not a valid title match mode")
return dc.replace(
self,
title=title if title is not UNSET else self.title,
class_name=class_name if class_name is not UNSET else self.class_name,
id=id if id is not UNSET else self.id,
pid=pid if pid is not UNSET else self.pid,
exe=exe if exe is not UNSET else self.exe,
text=text if text is not UNSET else self.text,
title_mode=match if match is not None else self.title_mode,
)
[docs] def exclude(self, title=UNSET, *, text=UNSET, match=None):
"""exclude(title: str = UNSET, **exclude_criteria)
Exclude windows from the match using the given criteria. All strings are
case-sensitive.
:param str title: exclude windows with the given title.
:param str text: exclude windows with the given text. The matching
behavior is affected by the :meth:`match_text_slow` method only. It's
not affected by :meth:`include_hidden_text`.
:param str match: sets the matching behavior. For more information refer
to :meth:`filter`.
"""
# TODO: Consider implementing class_name, id, pid, and exe exclusion in
# Python.
if title is UNSET and text is UNSET and match is None:
return self
if match is not None and match not in TITLE_MATCH_MODES:
raise ValueError(f"{match!r} is not a valid title match mode")
return dc.replace(
self,
exclude_title=title if title is not UNSET else self.exclude_title,
exclude_text=text if text is not UNSET else self.exclude_text,
title_mode=match if match is not None else self.title_mode,
)
[docs] def include_hidden_windows(self, include=True):
"""Change the window-matching behavior based on window visibility.
If *include* is true, includes the hidden windows in the search.
Default behavior is matching only visible windows while ignoring the
hidden ones.
"""
return dc.replace(self, hidden_windows=include)
[docs] def exclude_hidden_windows(self):
"""Exclude hidden windows from the search.
A shorthand for ``windows.include_hidden_windows(False)``.
"""
return dc.replace(self, hidden_windows=False)
[docs] def include_hidden_text(self, include=True):
"""Change whether to ignore invisible controls when matching windows
using the *text* criterion.
If *include* is false, ignores the invisible controls when searching for
text in the windows.
Default behavior is searching for visible and hidden text.
"""
return dc.replace(self, hidden_text=include)
[docs] def exclude_hidden_text(self):
"""Exclude hidden text from the search.
A shorthand for ``windows.include_hidden_text(False)``.
"""
return dc.replace(self, hidden_text=False)
[docs] def match_text_slow(self, is_slow=True):
"""Change how windows are matched when using the *text* criterion.
If *is_slow* is true, the matching can be much slower, but works with
all controls which respond to the `WM_GETTEXT
<https://msdn.microsoft.com/en-us/library/ms632627>`_ message.
Default behavior is to use the fast mode. However, certain types of
controls are not detected. For instance, text is typically detected
within Static and Button controls, but not Edit controls, unless they
are owned by the script.
"""
# Not including the parameter in filter() because it's used very rarely.
if is_slow:
return dc.replace(self, text_mode="slow")
else:
return dc.replace(self, text_mode="fast")
[docs] def first(self, title=UNSET, *, class_name=UNSET, id=UNSET, pid=UNSET, exe=UNSET, text=UNSET, match=None):
"""first(title: str = UNSET, **criteria) -> ahkpy.Window
Return the window handle of the first (top) matching window.
If there are no matching windows, returns ``Window(None)``.
For arguments refer to :meth:`filter`.
:aliases: :meth:`exist`, :meth:`top`
:command: `WinExist
<https://www.autohotkey.com/docs/commands/WinExist.htm>`_
"""
self = self._filter(title, class_name, id, pid, exe, text, match)
win_id = self._call("WinExist", *self._query())
if not win_id:
return Window(None)
return Window(win_id)
exist = first
top = first
[docs] def last(self, title=UNSET, *, class_name=UNSET, id=UNSET, pid=UNSET, exe=UNSET, text=UNSET, match=None):
"""last(title: str = UNSET, **criteria) -> ahkpy.Window
Return the window handle of the last (bottom) matching window.
If there are no matching windows, returns ``Window(None)``.
For arguments refer to :meth:`filter`.
:alias: :meth:`bottom`
:command: `WinGet, $, IDLast
<https://www.autohotkey.com/docs/commands/WinGet.htm#IDLast>`_
"""
self = self._filter(title, class_name, id, pid, exe, text, match)
win_id = self._call("WinGet", "IDLast", *self._query())
if not win_id:
return Window(None)
return Window(win_id)
bottom = last
[docs] def get_active(self, title=UNSET, *, class_name=UNSET, id=UNSET, pid=UNSET, exe=UNSET, text=UNSET, match=None):
"""get_active(title: str = UNSET, **criteria) -> ahkpy.Window
Return the window instance of the active matching window.
If no criteria are given, returns the current active window. If no
window is active, returns ``Window(None)``.
For arguments refer to :meth:`filter`.
:command: `WinActive
<https://www.autohotkey.com/docs/commands/WinActive.htm>`_
"""
self = self._filter(title, class_name, id, pid, exe, text, match)
query = self._query()
if query == ("", "", "", ""):
query = ("A", "", "", "")
win_id = self._call("WinActive", *query)
if not win_id:
return Window(None)
return Window(win_id)
[docs] def wait(self, title=UNSET, *, class_name=UNSET, id=UNSET, pid=UNSET, exe=UNSET, text=UNSET, match=None,
timeout=None):
"""wait(title: str = UNSET, *, timeout=None, **criteria) -> ahkpy.Window
Wait until the specified window exists and return it.
Returns the first matched window. If there are no matching windows after
*timeout* seconds, then ``Window(None)`` will be returned. If *timeout*
is not specified or ``None``, there is no limit to the wait time.
For other arguments refer to :meth:`filter`.
:command: `WinWait
<https://www.autohotkey.com/docs/commands/WinWait.htm>`_
"""
self = self._filter(title, class_name, id, pid, exe, text, match)
return _wait_for(timeout, self.exist) or Window(None)
[docs] def wait_active(self, title=UNSET, *, class_name=UNSET, id=UNSET, pid=UNSET, exe=UNSET, text=UNSET, match=None,
timeout=None):
"""wait_active(title: str = UNSET, *, timeout=None, **criteria) -> ahkpy.Window
Wait until the window is active and return it.
For more information refer to :meth:`wait`.
:command: `WinWaitActive
<https://www.autohotkey.com/docs/commands/WinWaitActive.htm>`_
"""
self = self._filter(title, class_name, id, pid, exe, text, match)
query = self._query()
if query == ("", "", "", ""):
self = dc.replace(self, title="A")
return _wait_for(timeout, self.get_active) or Window(None)
[docs] def wait_inactive(self, title=UNSET, *, class_name=UNSET, id=UNSET, pid=UNSET, exe=UNSET, text=UNSET, match=None,
timeout=None) -> bool:
"""wait_inactive(title: str = UNSET, *, timeout=None, **criteria) -> bool
Wait until there are no matching active windows.
Returns ``True`` if there are no matching active windows. If there are
still matching windows after *timeout* seconds, then ``False`` will be
returned. If *timeout* is not specified or ``None``, there is no limit
to the wait time.
For other arguments refer to :meth:`filter`.
:command: `WinWaitNotActive
<https://www.autohotkey.com/docs/commands/WinWaitActive.htm>`_
"""
self = self._filter(title, class_name, id, pid, exe, text, match)
return _wait_for(timeout, lambda: not self.get_active()) or False
[docs] def wait_close(self, title=UNSET, *, class_name=UNSET, id=UNSET, pid=UNSET, exe=UNSET, text=UNSET, match=None,
timeout=None):
"""wait_close(title: str = UNSET, *, timeout=None, **criteria) -> bool
Wait until there are no matching windows.
Returns ``True`` if there are no matching windows. If there are still
matching windows after *timeout* seconds, then ``False`` will be
returned. If *timeout* is not specified or ``None``, there is no limit
to the wait time.
For other arguments refer to :meth:`filter`.
:command: `WinWaitClose
<https://www.autohotkey.com/docs/commands/WinWaitClose.htm>`_
"""
self = self._filter(title, class_name, id, pid, exe, text, match)
# WinWaitClose doesn't set Last Found Window, return False if the wait
# was timed out.
return _wait_for(timeout, lambda: not self.exist()) or False
[docs] def close_all(self, title=UNSET, *, class_name=UNSET, id=UNSET, pid=UNSET, exe=UNSET, text=UNSET, match=None,
timeout=None):
"""close_all(title: str = UNSET, *, timeout=None, **criteria) -> bool
Close all matching windows.
Returns ``True`` if all matching windows were closed. If there are still
matching windows after *timeout* seconds, then ``False`` will be
returned. If *timeout* is not specified or ``None``, the function
returns immediately.
For arguments refer to :meth:`filter`.
:command: `WinClose
<https://www.autohotkey.com/docs/commands/WinClose.htm>`_
"""
self = self._filter(title, class_name, id, pid, exe, text, match)
return self._group_action("WinClose", timeout)
[docs] def hide_all(self, title=UNSET, *, class_name=UNSET, id=UNSET, pid=UNSET, exe=UNSET, text=UNSET, match=None):
"""hide_all(title: str = UNSET, **criteria)
Hide all matching windows.
For arguments refer to :meth:`filter`.
:command: `WinHide
<https://www.autohotkey.com/docs/commands/WinHide.htm>`_
"""
self = self._filter(title, class_name, id, pid, exe, text, match)
return self._group_action("WinHide")
[docs] def kill_all(self, title=UNSET, *, class_name=UNSET, id=UNSET, pid=UNSET, exe=UNSET, text=UNSET, match=None,
timeout=None):
"""kill_all(title: str = UNSET, *, timeout=None, **criteria) -> bool
Forces all matching windows to close.
Returns ``True`` if all matching windows were closed. If there are still
matching windows after *timeout* seconds, then ``False`` will be
returned. If *timeout* is not specified or ``None``, the function
returns immediately.
For arguments refer to :meth:`filter`.
:command: `WinKill
<https://www.autohotkey.com/docs/commands/WinKill.htm>`_
"""
self = self._filter(title, class_name, id, pid, exe, text, match)
return self._group_action("WinKill", timeout)
[docs] def maximize_all(self, title=UNSET, *, class_name=UNSET, id=UNSET, pid=UNSET, exe=UNSET, text=UNSET, match=None):
"""maximize_all(title: str = UNSET, **criteria)
Maximize all matching windows.
For arguments refer to :meth:`filter`.
:command: `WinMaximize
<https://www.autohotkey.com/docs/commands/WinMaximize.htm>`_
"""
self = self._filter(title, class_name, id, pid, exe, text, match)
return self._group_action("WinMaximize")
[docs] def minimize_all(self, title=UNSET, *, class_name=UNSET, id=UNSET, pid=UNSET, exe=UNSET, text=UNSET, match=None):
"""minimize_all(title: str = UNSET, **criteria)
Minimize all matching windows.
For arguments refer to :meth:`filter`.
:command: `WinMinimize
<https://www.autohotkey.com/docs/commands/WinMinimize.htm>`_
"""
self = self._filter(title, class_name, id, pid, exe, text, match)
return self._group_action("WinMinimize")
# TODO: Implement WinMinimizeAllUndo.
[docs] def restore_all(self, title=UNSET, *, class_name=UNSET, id=UNSET, pid=UNSET, exe=UNSET, text=UNSET, match=None):
"""restore_all(title: str = UNSET, **criteria)
Restore all matching windows.
For arguments refer to :meth:`filter`.
:command: `WinRestore
<https://www.autohotkey.com/docs/commands/WinRestore.htm>`_
"""
self = self._filter(title, class_name, id, pid, exe, text, match)
self._group_action("WinRestore")
[docs] def show_all(self, title=UNSET, *, class_name=UNSET, id=UNSET, pid=UNSET, exe=UNSET, text=UNSET, match=None):
"""show_all(title: str = UNSET, **criteria)
Show all matching windows.
For arguments refer to :meth:`filter`.
:command: `WinShow
<https://www.autohotkey.com/docs/commands/WinShow.htm>`_
"""
self = self._filter(title, class_name, id, pid, exe, text, match)
self._group_action("WinShow")
def _group_action(self, cmd, timeout=UNSET):
if self == Windows() and cmd == "WinMinimize":
# If the filter matches all the windows, minimize everything except
# the desktop window.
self._call("WinMinimizeAll", set_delay=True)
return
query_hash = hash(self)
query_hash_str = str(query_hash).replace("-", "m") # AHK doesn't allow "-" in group names
label = ""
self._call("GroupAdd", query_hash_str, *self._include(), label, *self._exclude())
self._call(cmd, f"ahk_group {query_hash_str}", "", "", set_delay=True)
if timeout is not UNSET:
return self.wait_close(timeout=timeout)
[docs] def window_context(self, title=UNSET, *, class_name=UNSET, id=UNSET, pid=UNSET, exe=UNSET, text=UNSET, match=None):
"""window_context(title: str = UNSET, **criteria) -> ahkpy.HotkeyContext
Create a context for hotkeys that will work only when the matching
windows exist.
For arguments refer to :meth:`filter`.
:command: `Hotkey, IfWinExist
<https://www.autohotkey.com/docs/commands/Hotkey.htm>`_, `#IfWinExist
<https://www.autohotkey.com/docs/commands/_IfWinActive.htm>`_.
"""
# Not using Hotkey, IfWinActive/Exist because:
#
# 1. It doesn't support excluding windows.
# 2. It doesn't set DetectHiddenWindows from the given query before
# enumerating the windows.
self = self._filter(title, class_name, id, pid, exe, text, match)
return HotkeyContext(lambda: self.exist())
[docs] def nonexistent_window_context(
self, title=UNSET, *, class_name=UNSET, id=UNSET, pid=UNSET, exe=UNSET, text=UNSET, match=None):
"""nonexistent_window_context(title: str = UNSET, **criteria) -> ahkpy.HotkeyContext
Create a context for hotkeys that will work only when there are no
matching windows.
For arguments refer to :meth:`filter`.
:command: `Hotkey, IfWinNotExist
<https://www.autohotkey.com/docs/commands/Hotkey.htm>`_,
`#IfWinNotExist
<https://www.autohotkey.com/docs/commands/_IfWinActive.htm>`_.
"""
self = self._filter(title, class_name, id, pid, exe, text, match)
return HotkeyContext(lambda: not self.exist())
[docs] def active_window_context(
self, title=UNSET, *, class_name=UNSET, id=UNSET, pid=UNSET, exe=UNSET, text=UNSET, match=None):
"""active_window_context(title: str = UNSET, **criteria) -> ahkpy.HotkeyContext
Create a context for hotkeys that will work only when the matching
windows are active.
For arguments refer to :meth:`filter`.
:command: `Hotkey, IfWinActive
<https://www.autohotkey.com/docs/commands/Hotkey.htm>`_,
`#IfWinActive
<https://www.autohotkey.com/docs/commands/_IfWinActive.htm>`_.
"""
self = self._filter(title, class_name, id, pid, exe, text, match)
return HotkeyContext(lambda: self.get_active())
[docs] def inactive_window_context(
self, title=UNSET, *, class_name=UNSET, id=UNSET, pid=UNSET, exe=UNSET, text=UNSET, match=None):
"""inactive_window_context(title: str = UNSET, **criteria) -> ahkpy.HotkeyContext
Create a context for hotkeys that will work only when the matching
windows are not active.
For arguments refer to :meth:`filter`.
:command: `Hotkey, IfWinNotActive
<https://www.autohotkey.com/docs/commands/Hotkey.htm>`_,
`#IfWinNotActive
<https://www.autohotkey.com/docs/commands/_IfWinActive.htm>`_.
"""
self = self._filter(title, class_name, id, pid, exe, text, match)
return HotkeyContext(lambda: not self.get_active())
[docs] def __iter__(self) -> Iterator['Window']:
"""__iter__() -> typing.Iterator[ahkpy.Window]
Return matching windows ordered from top to bottom.
:command: `WinGet, $, List
<https://www.autohotkey.com/docs/commands/WinGet.htm#List>`_
"""
win_ids = self._call("WinGetList", *self._query())
if win_ids is None:
return
for win_id in win_ids.values():
if win_id > 0:
yield Window(win_id)
[docs] def __len__(self):
"""Return the number of matching windows.
:command: `WinGet, $, Count
<https://www.autohotkey.com/docs/commands/WinGet.htm#Count>`_
"""
return self._call("WinGet", "Count", *self._query()) or 0
def __repr__(self):
field_strs = []
for field in dc.fields(self):
value = getattr(self, field.name)
if value is not UNSET:
field_strs.append(f"{field.name}={value!r}")
return self.__class__.__qualname__ + f"({', '.join(field_strs)})"
def _call(self, cmd, *args, set_delay=False):
if (
self.title is None or self.class_name is None or self.id is None or self.pid is None or self.exe is None or
self.text is None
):
# Querying a non-existent window's attributes.
return
with global_ahk_lock:
if self.hidden_windows:
ahk_call("DetectHiddenWindows", "On")
else:
ahk_call("DetectHiddenWindows", "Off")
if self.text is not UNSET or self.exclude_text is not UNSET:
if self.hidden_text:
ahk_call("DetectHiddenText", "On")
else:
ahk_call("DetectHiddenText", "Off")
_set_title_match_mode(self.title_mode)
if self.text_mode == "fast":
ahk_call("SetTitleMatchMode", "fast")
elif self.text_mode == "slow":
ahk_call("SetTitleMatchMode", "slow")
else:
raise ValueError(f"{self.text_mode!r} is not a valid text match mode")
if set_delay:
ahk_call("SetWinDelay", optional_ms(get_settings().win_delay))
return ahk_call(cmd, *args)
def _query(self):
return (*self._include(), *self._exclude())
def _include(self):
parts = []
if self.title is not UNSET:
parts.append(str(self.title))
if self.class_name is not UNSET:
parts.append(f"ahk_class {self.class_name}")
if self.id is not UNSET:
parts.append(f"ahk_id {self.id}")
if self.pid is not UNSET:
parts.append(f"ahk_pid {self.pid}")
if self.exe is not UNSET:
parts.append(f"ahk_exe {self.exe}")
return (
" ".join(parts),
str(self.text) if self.text is not UNSET else "",
)
def _exclude(self):
return (
str(self.exclude_title) if self.exclude_title is not UNSET else "",
str(self.exclude_text) if self.exclude_text is not UNSET else "",
)
windows = visible_windows = Windows()
all_windows = windows.include_hidden_windows()
[docs]@dc.dataclass(frozen=True)
class WindowHandle:
"""The immutable object that contains the *id* (HWND) of a window/control.
"""
# I'd like the Window and Control classes to be hashable, and making the
# dataclass frozen also makes it hashable. However, frozen dataclasses
# cannot have setter properties unless it's a subclass.
id: Optional[int]
__slots__ = ("id",)
[docs] def __bool__(self):
"""Check if the window/control exists.
Returns ``True`` if the window/control exists. Returns ``False`` if the
window/control doesn't exist or it's a ``Window(None)`` or
``Control(None)`` instance.
"""
return bool(self.id) and self.exists
@property
def exists(self) -> bool:
"""The window/control existence (read-only).
Returns ``True`` if the window/control exists. Returns ``False`` if the
window/control doesn't exist or it's a ``Window(None)`` or
``Control(None)`` instance.
:command: `WinExist
<https://www.autohotkey.com/docs/commands/WinExist.htm>`_
"""
return bool(self._call("WinExist", *self._include()))
def _call(self, cmd, *args, hidden_windows=True, title_mode=None, set_delay=False):
with global_ahk_lock:
# TODO: Setting DetectHiddenWindows should not be necessary for
# controls.
# > Control's HWND can be used directly as an ahk_id WinTitle (this
# > also works on hidden controls even when DetectHiddenWindows is
# > Off).
if hidden_windows:
ahk_call("DetectHiddenWindows", "On")
else:
ahk_call("DetectHiddenWindows", "Off")
if title_mode is not None:
_set_title_match_mode(title_mode)
if set_delay:
self._set_delay()
return ahk_call(cmd, *args)
def _set_delay(self):
raise NotImplementedError
def _include(self):
win_text = ""
return f"ahk_id {self.id or 0}", win_text
[docs]class BaseWindow(WindowHandle):
"""Base window class that is inherited by :class:`~ahkpy.Window` and
:class:`~ahkpy.Control` classes.
To get a window instance, use :meth:`~ahkpy.Windows.first`,
:meth:`~ahkpy.Windows.last`, :meth:`~ahkpy.Windows.get_active` methods, or
iterate the windows with :meth:`~ahkpy.Windows.__iter__`.
To get a control instance, use the :meth:`~ahkpy.Window.get_control`,
:meth:`~ahkpy.Window.get_focused_control`, or get a list of window controls
with :attr:`~ahkpy.Window.controls`.
"""
__slots__ = ("id",)
@property
def style(self) -> Optional['WindowStyle']:
"""The styles of the window/control.
Returns ``None`` unless the window exists.
.. TODO: Remove :types: once
https://github.com/sphinx-doc/sphinx/issues/7383 is resolved and
released.
:type: WindowStyle
:command: `WinGet, $, Style
<https://www.autohotkey.com/docs/commands/WinGet.htm#Style>`_,
`WinSet, $, Style
<https://www.autohotkey.com/docs/commands/WinSet.htm#Style>`_,
`ControlGet, $, Style
<https://www.autohotkey.com/docs/commands/ControlGet.htm#Style>`_,
`Control, $, Style
<https://www.autohotkey.com/docs/commands/Control.htm#Style>`_
"""
style = self._get("Style")
if style is None:
return None
return WindowStyle(style)
@style.setter
def style(self, value):
self._set("Style", int(value))
@property
def ex_style(self) -> Optional['ExWindowStyle']:
"""The extended styles of the window/control.
Returns ``None`` unless the window exists.
:type: ExWindowStyle
:command: `WinGet, $, ExStyle
<https://www.autohotkey.com/docs/commands/WinGet.htm#ExStyle>`_,
`WinSet, $, ExStyle
<https://www.autohotkey.com/docs/commands/WinSet.htm#ExStyle>`_,
`ControlGet, $, ExStyle
<https://www.autohotkey.com/docs/commands/ControlGet.htm#ExStyle>`_,
`Control, $, ExStyle
<https://www.autohotkey.com/docs/commands/Control.htm#ExStyle>`_
"""
ex_style = self._get("ExStyle")
if ex_style is None:
return None
return ExWindowStyle(ex_style)
@ex_style.setter
def ex_style(self, value):
self._set("ExStyle", int(value))
@property
def class_name(self) -> Optional[str]:
"""The window/control class name (read-only).
Returns ``None`` unless the window exists.
:type: str
:command: `WinGetClass
<https://www.autohotkey.com/docs/commands/WinGetClass.htm>`_
"""
class_name = self._call("WinGetClass", *self._include())
if class_name == "":
# Windows API doesn't allow the class name to be an empty string. If
# the window doesn't exist or there was a problem getting the class
# name, AHK returns an empty string.
return None
return class_name
@property
def pid(self) -> Optional[int]:
"""The identifier of the process that created the window/control
(read-only).
Returns ``None`` unless the window exists.
:type: int
:command: `WinGet, PID
<https://www.autohotkey.com/docs/commands/WinGet.htm#PID>`_
"""
return Window._get(self, "PID")
@property
def process_name(self) -> Optional[str]:
"""The name of the executable that created the window/control
(read-only).
Returns ``None`` unless the window exists.
:type: str
:alias: :attr:`exe`
:command: `WinGet, ProcessName
<https://www.autohotkey.com/docs/commands/WinGet.htm#ProcessName>`_
"""
return Window._get(self, "ProcessName")
exe = process_name
@property
def process_path(self) -> Optional[str]:
"""The full path to the executable that created the window/control
(read-only).
Returns ``None`` unless the window exists.
:type: str
:command: `WinGet, ProcessPath
<https://www.autohotkey.com/docs/commands/WinGet.htm#ProcessPath>`_
"""
return Window._get(self, "ProcessPath")
@property
def rect(self) -> Optional[Tuple[int, int, int, int]]:
"""The position and size of the window/control as a ``(x, y, width,
height)`` tuple.
Returns ``None`` unless the window exists.
:type: Tuple[int, int, int, int]
:command: `WinGetPos
<https://www.autohotkey.com/docs/commands/WinGetPos.htm>`_,
`ControlGetPos
<https://www.autohotkey.com/docs/commands/ControlGetPos.htm>`_,
`WinMove <https://www.autohotkey.com/docs/commands/WinMove.htm>`_,
`ControlMove
<https://www.autohotkey.com/docs/commands/ControlMove.htm>`_
"""
result = self._get_pos()
if result is None:
return None
x, y, width, height = result["X"], result["Y"], result["Width"], result["Height"]
return (
x if x != "" else None,
y if y != "" else None,
width if width != "" else None,
height if height != "" else None,
)
@rect.setter
def rect(self, new_rect):
x, y, width, height = new_rect
self.move(x, y, width, height)
@property
def position(self):
"""The window/control position as a ``(x, y)`` tuple.
Returns ``None`` unless the window exists.
:type: Tuple[int, int]
:command: `WinGetPos
<https://www.autohotkey.com/docs/commands/WinGetPos.htm>`_,
`ControlGetPos
<https://www.autohotkey.com/docs/commands/ControlGetPos.htm>`_,
`WinMove <https://www.autohotkey.com/docs/commands/WinMove.htm>`_,
`ControlMove
<https://www.autohotkey.com/docs/commands/ControlMove.htm>`_
"""
rect = self.rect
if rect is None:
return None
x, y, _, _ = rect
return x, y
@position.setter
def position(self, new_position):
x, y = new_position
self.move(x, y)
@property
def x(self):
"""The *x* coordinate of the window/control.
Returns ``None`` unless the window exists.
:type: int
:command: `WinGetPos
<https://www.autohotkey.com/docs/commands/WinGetPos.htm>`_,
`ControlGetPos
<https://www.autohotkey.com/docs/commands/ControlGetPos.htm>`_,
`WinMove <https://www.autohotkey.com/docs/commands/WinMove.htm>`_,
`ControlMove
<https://www.autohotkey.com/docs/commands/ControlMove.htm>`_
"""
rect = self.rect
if rect is None:
return None
x, _, _, _ = rect
return x
@x.setter
def x(self, new_x):
self.move(x=new_x)
@property
def y(self):
"""The *y* coordinate of the window/control.
Returns ``None`` unless the window exists.
:type: int
:command: `WinGetPos
<https://www.autohotkey.com/docs/commands/WinGetPos.htm>`_,
`ControlGetPos
<https://www.autohotkey.com/docs/commands/ControlGetPos.htm>`_,
`WinMove <https://www.autohotkey.com/docs/commands/WinMove.htm>`_,
`ControlMove
<https://www.autohotkey.com/docs/commands/ControlMove.htm>`_
"""
rect = self.rect
if rect is None:
return None
_, y, _, _ = rect
return y
@y.setter
def y(self, new_y):
self.move(y=new_y)
@property
def size(self):
"""The window/control size.
Returns a ``(width, height)`` tuple. If the window doesn't exist,
returns ``None``.
:type: Tuple[int, int]
:command: `WinGetPos
<https://www.autohotkey.com/docs/commands/WinGetPos.htm>`_,
`ControlGetPos
<https://www.autohotkey.com/docs/commands/ControlGetPos.htm>`_,
`WinMove <https://www.autohotkey.com/docs/commands/WinMove.htm>`_,
`ControlMove
<https://www.autohotkey.com/docs/commands/ControlMove.htm>`_
"""
rect = self.rect
if rect is None:
return None
_, _, width, height = rect
return width, height
@size.setter
def size(self, new_size):
width, height = new_size
self.move(width, height)
@property
def width(self):
"""The window/control width.
Returns ``None`` unless the window exists.
:type: int
:command: `WinGetPos
<https://www.autohotkey.com/docs/commands/WinGetPos.htm>`_,
`ControlGetPos
<https://www.autohotkey.com/docs/commands/ControlGetPos.htm>`_,
`WinMove <https://www.autohotkey.com/docs/commands/WinMove.htm>`_,
`ControlMove
<https://www.autohotkey.com/docs/commands/ControlMove.htm>`_
"""
rect = self.rect
if rect is None:
return None
_, _, width, _ = rect
return width
@width.setter
def width(self, new_width):
self.move(width=new_width)
@property
def height(self):
"""The window/control height.
Returns ``None`` unless the window exists.
:type: int
:command: `WinGetPos
<https://www.autohotkey.com/docs/commands/WinGetPos.htm>`_,
`ControlGetPos
<https://www.autohotkey.com/docs/commands/ControlGetPos.htm>`_,
`WinMove <https://www.autohotkey.com/docs/commands/WinMove.htm>`_,
`ControlMove
<https://www.autohotkey.com/docs/commands/ControlMove.htm>`_
"""
rect = self.rect
if rect is None:
return None
_, _, _, height = rect
return height
@height.setter
def height(self, new_height):
self.move(height=new_height)
[docs] def move(self, x=None, y=None, width=None, height=None):
"""Move and/or resize the window/control.
Does nothing unless the window/control exists.
:command: `WinMove
<https://www.autohotkey.com/docs/commands/WinMove.htm>`_,
`ControlMove
<https://www.autohotkey.com/docs/commands/ControlMove.htm>`_
"""
self._move(
int(x) if x is not None else "",
int(y) if y is not None else "",
int(width) if width is not None else "",
int(height) if height is not None else "",
)
@property
def is_enabled(self) -> Optional[bool]:
"""The enabled state of the window/control.
Returns ``None`` unless the window exists.
:type: bool
:command: `WinSet, Enable
<https://www.autohotkey.com/docs/commands/WinSet.htm#Enable>`_,
`ControlGet, $, Enabled
<https://www.autohotkey.com/docs/commands/ControlGet.htm#Enabled>`_,
`Control, Enable
<https://www.autohotkey.com/docs/commands/Control.htm#Enable>`_
"""
style = self.style
if style is None:
return None
return WindowStyle.DISABLED not in style
@is_enabled.setter
def is_enabled(self, value):
if value:
self.enable()
else:
self.disable()
[docs] def enable(self):
"""Enable the window/control.
Does nothing unless the window/control exists.
:command: `WinSet, Enable
<https://www.autohotkey.com/docs/commands/WinSet.htm#Enable>`_,
`Control, Enable
<https://www.autohotkey.com/docs/commands/Control.htm#Enable>`_
"""
raise NotImplementedError
[docs] def disable(self):
"""Disable the window/control.
Does nothing unless the window/control exists.
:command: `WinSet, Disable
<https://www.autohotkey.com/docs/commands/WinSet.htm#Disable>`_,
`Control, Disable
<https://www.autohotkey.com/docs/commands/Control.htm#Disable>`_
"""
raise NotImplementedError
@property
def is_visible(self):
"""The visibility of the window/control.
Returns ``False`` unless the window/control exists.
:type: bool
:command: `WinShow
<https://www.autohotkey.com/docs/commands/WinShow.htm>`_,
`ControlGet, $, Visible
<https://www.autohotkey.com/docs/commands/ControlGet.htm#Visible>`_,
`Control, Show
<https://www.autohotkey.com/docs/commands/Control.htm#Show>`_
"""
style = self.style
if style is None:
return False
return WindowStyle.VISIBLE in style
@is_visible.setter
def is_visible(self, value):
if value:
self.show()
else:
self.hide()
[docs] def show(self):
"""Show the window/control.
Does nothing unless the window/control exists.
:command: `WinShow
<https://www.autohotkey.com/docs/commands/WinShow.htm>`_, `Control,
Show <https://www.autohotkey.com/docs/commands/Control.htm#Show>`_
"""
raise NotImplementedError
[docs] def hide(self):
"""Hide the window/control.
Does nothing unless the window/control exists.
:command: `WinHide
<https://www.autohotkey.com/docs/commands/WinHide.htm>`_, `Control,
Hide <https://www.autohotkey.com/docs/commands/Control.htm#Hide>`_
"""
raise NotImplementedError
[docs] def send(self, keys):
"""Send simulated keystrokes to the window/control.
Unlike the :func:`~ahkpy.send` function, mouse clicks cannot be sent.
Does nothing unless the window/control exists.
:command: `ControlSend
<https://www.autohotkey.com/docs/commands/ControlSend.htm>`_
"""
# TODO: Add level, key_delay, and key_duration arguments.
with global_ahk_lock:
# Unlike the Send command, mouse clicks cannot be sent by
# ControlSend. Thus, no need to set mouse_delay.
sending._set_delay(mouse_delay=UNSET)
control = ""
try:
self._call("ControlSend", control, str(keys), *self._include())
except Error as err:
if err.message == 1:
# Control doesn't exist.
return
raise
# TODO: Implement ControlClick.
[docs] def send_message(self, msg: int, w_param: int = 0, l_param: int = 0,
signed_int=False, timeout=5) -> Optional[int]:
"""Send a message to the window/control and get a response.
The *msg* argument is the message number to send. See the `message list
<https://www.autohotkey.com/docs/misc/SendMessageList.htm>`_ for common
numbers. The *w_param* and *l_param* arguments are the first and second
components of the message.
If there is no response after *timeout* seconds, then an :exc:`Error`
will be raised. If *timeout* is not specified or ``None``, there is no
limit to the wait time.
The range of possible response values depends on the target window and
the version of AutoHotkey that is running. When using the 32-bit version
of AutoHotkey, or if the target window is 32-bit, the result is a 32-bit
integer. When using the 64-bit version of AutoHotkey with a 64-bit
window, the result is a 64-bit integer.
If *signed_int* argument is true, the response is interpreted as a
signed integer. Defaults to ``False``.
Returns ``None`` if the window/control doesn't exist.
:command: `SendMessage
<https://www.autohotkey.com/docs/commands/PostMessage.htm>`_
"""
# TODO: SendMessage is not interruptable.
if not self.id:
return None
SMTO_ABORTIFHUNG = 0x0002
result = ctypes.c_void_p()
send_result = ctypes.windll.user32.SendMessageTimeoutW(
self.id,
int(msg),
int(w_param),
int(l_param),
SMTO_ABORTIFHUNG,
int(timeout * 1000),
ctypes.byref(result),
)
if not send_result:
if not self.exists:
return None
message = "there was a problem sending message or response timed out"
raise Error(message)
result = result.value or 0
if not signed_int:
return ctypes.c_uint64(result).value
elif self._is_win32():
return ctypes.c_int32(result).value
return ctypes.c_int64(result).value
def _is_win32(self):
if struct.calcsize("P") == 4:
# Using a 32-bit version of AutoHotkey, result is a 32-bit int.
return True
kernel32 = ctypes.windll.kernel32
PROCESS_QUERY_LIMITED_INFORMATION = 0x1000
proc_handle = kernel32.OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, True, self.pid)
if not proc_handle:
# Couldn't get the process handle.
return False
try:
from ctypes import wintypes
process_machine = wintypes.USHORT()
native_machine = wintypes.USHORT()
ok = kernel32.IsWow64Process2(proc_handle, ctypes.byref(process_machine), ctypes.byref(native_machine))
if not ok:
# IsWow64Process2 failed.
return False
IMAGE_FILE_MACHINE_I386 = 0x014c
if native_machine.value == IMAGE_FILE_MACHINE_I386:
# OS is 32-bit.
return True
elif process_machine.value == IMAGE_FILE_MACHINE_I386:
# Target window is 32-bit.
return True
return False
finally:
kernel32.CloseHandle(proc_handle)
[docs] def post_message(self, msg: int, w_param: int = 0, l_param: int = 0) -> Optional[bool]:
"""Post a message to the window/control.
The *msg* argument is the message number to send. See the `message list
<https://www.autohotkey.com/docs/misc/SendMessageList.htm>`_ for common
numbers. The *w_param* and *l_param* arguments are the first and second
components of the message.
Returns ``True`` if the message was received. Returns ``False`` if there
was a problem. Returns ``None`` if the window/control doesn't exist.
:command: `PostMessage
<https://www.autohotkey.com/docs/commands/PostMessage.htm>`_
"""
control = ""
try:
err = self._call("PostMessage", int(msg), int(w_param), int(l_param), control, *self._include())
if err is None:
return None
return not err
except Error as err:
if err.message == 1:
if not self.exists:
return None
err.message = "there was a problem posting message"
raise
def _get_pos(self):
raise NotImplementedError
def _move(self, x, y, width, height):
raise NotImplementedError
def _get(self, subcmd):
raise NotImplementedError
def _set(self, subcmd, value=""):
# Used mainly by Window. Also used by Control for style and ex_style
# properties. It's OK to use 'WinSet' for controls because control delay
# has no effect on the 'Control, Style' command and they essentially do
# the same.
try:
super()._call("WinSet", subcmd, value, *self._include())
except Error as err:
if err.message == 1 and not super().exists:
return
raise
[docs]class Window(BaseWindow):
"""The object representing a window."""
__slots__ = ("id",)
@property
def is_active(self) -> bool:
"""Whether the window is active (read-only).
Returns ``None`` unless the window exists.
:type: bool
:command: `WinActive
<https://www.autohotkey.com/docs/commands/WinActive.htm>`_
"""
return bool(self._call("WinActive", *self._include()))
@property
def text(self) -> Optional[str]:
"""The text that was retrieved from the window (read-only).
Each text element ends with ``"\\r\\n"``. If the window doesn't exist,
returns ``None``. Raises an :exc:`Error` if there was a problem
retrieving the window text.
:type: str
:command: `WinGetText
<https://www.autohotkey.com/docs/commands/WinGetText.htm>`_
"""
try:
# TODO: Consider adding an argument that will call VarSetCapacity.
text = self._call("WinGetText", *self._include())
return str(text)
except Error as err:
if err.message == 1:
if not self.exists:
return None
err.message = "there was a problem getting the window text"
raise
@property
def title(self) -> Optional[str]:
"""The window title.
Returns ``None`` unless the window exists.
:type: str
:command: `WinGetTitle
<https://www.autohotkey.com/docs/commands/WinGetTitle.htm>`_,
`WinSetTitle
<https://www.autohotkey.com/docs/commands/WinSetTitle.htm>`_
"""
title = self._call("WinGetTitle", *self._include())
# If the window doesn't exist, AHK returns an empty string. Check that
# the window exists.
if not self.exists:
return None
return str(title)
@title.setter
def title(self, new_title):
return self._call("WinSetTitle", *self._include(), str(new_title))
@property
def is_minimized(self) -> Optional[bool]:
"""Whether the window is minimized.
Returns ``None`` unless the window exists.
Setting ``True`` minimizes the window, setting ``False`` restores the
window if it's minimized.
:type: bool
:command: `WinGet, MinMax
<https://www.autohotkey.com/docs/commands/WinGet.htm#MinMax>`_,
`WinMinimize
<https://www.autohotkey.com/docs/commands/WinMinimize.htm>`_,
`WinRestore
<https://www.autohotkey.com/docs/commands/WinRestore.htm>`_
"""
min_max = self._get("MinMax")
if min_max is None:
return None
return min_max == -1
@is_minimized.setter
def is_minimized(self, value):
if value:
self.minimize()
elif self.is_minimized:
self.restore()
[docs] def toggle_minimized(self):
"""Toggle the minimized state of the window.
If window is minimized, restores it. Otherwise, minimizes it. Does
nothing unless the window exists.
:command: `WinMinimize
<https://www.autohotkey.com/docs/commands/WinMinimize.htm>`_,
`WinRestore
<https://www.autohotkey.com/docs/commands/WinRestore.htm>`_
"""
is_minimized = self.is_minimized
if is_minimized is None:
return
if is_minimized:
self.restore()
else:
self.minimize()
[docs] def minimize(self):
"""Minimize the window.
Does nothing unless the window exists.
:command: `WinMinimize
<https://www.autohotkey.com/docs/commands/WinMinimize.htm>`_
"""
self._call("WinMinimize", *self._include(), set_delay=True)
@property
def is_restored(self) -> Optional[bool]:
"""Whether the window is not minimized nor maximized (read-only).
Returns ``None`` unless the window exists.
:type: bool
:command: `WinGet, MinMax
<https://www.autohotkey.com/docs/commands/WinGet.htm#MinMax>`_
"""
min_max = self._get("MinMax")
if min_max is None:
return None
return min_max == 0
[docs] def restore(self):
"""Restore the window.
Does nothing unless the window exists.
:command: `WinRestore
<https://www.autohotkey.com/docs/commands/WinRestore.htm>`_
"""
self._call("WinRestore", *self._include(), set_delay=True)
@property
def is_maximized(self) -> Optional[bool]:
"""Whether the window is maximized.
Returns ``None`` unless the window exists.
Setting ``True`` maximizes the window, setting ``False`` restores the
window if it's maximized.
:type: bool
:command: `WinGet, MinMax
<https://www.autohotkey.com/docs/commands/WinGet.htm#MinMax>`_,
`WinMaximize
<https://www.autohotkey.com/docs/commands/WinMaximize.htm>`_,
`WinRestore
<https://www.autohotkey.com/docs/commands/WinRestore.htm>`_
"""
min_max = self._get("MinMax")
if min_max is None:
return None
return min_max == 1
@is_maximized.setter
def is_maximized(self, value):
if value:
self.maximize()
elif self.is_maximized:
self.restore()
[docs] def toggle_maximized(self):
"""Toggle the maximized state of the window.
If window is maximized, restores it. Otherwise, maximizes it. Does
nothing unless the window exists.
:command: `WinMaximize
<https://www.autohotkey.com/docs/commands/WinMaximize.htm>`_,
`WinRestore
<https://www.autohotkey.com/docs/commands/WinRestore.htm>`_
"""
is_maximized = self.is_maximized
if is_maximized is None:
return
if is_maximized:
self.restore()
else:
self.maximize()
[docs] def maximize(self):
"""Maximize the window.
Does nothing unless the window exists.
:command: `WinMaximize
<https://www.autohotkey.com/docs/commands/WinMaximize.htm>`_
"""
self._call("WinMaximize", *self._include(), set_delay=True)
@property
def control_classes(self) -> Optional[List[str]]:
"""The list of class names of the window controls (read-only).
Returns ``None`` unless the window exists.
:type: List[str]
:command: `WinGet, ControlList
<https://www.autohotkey.com/docs/commands/WinGet.htm#ControlList>`_
"""
names = self._get("ControlList")
if names is None:
return None
return names.splitlines()
@property
def controls(self) -> Optional[List['Control']]:
"""The list of window controls (read-only).
Returns ``None`` unless the window exists.
:type: List[Control]
:command: `WinGet, ControlListHwnd
<https://www.autohotkey.com/docs/commands/WinGet.htm#ControlListHwnd>`_
"""
handles = self._get("ControlListHwnd")
if handles is None:
return None
hwnds = handles.splitlines()
return [
Control(int(hwnd, base=16))
for hwnd in hwnds
]
[docs] def get_control(self, class_or_text, match="startswith") -> 'Control':
"""get_control(class_or_text, match="startswith") -> ahkpy.Control
Find the window control by its class name or text.
In the case of searching the control by its text, the optional *match*
argument specifies the matching behavior and defaults to
``"startswith"``. For other modes refer to :meth:`Windows.filter`.
Returns ``Control(None)`` if either window or control doesn't exist.
:command: `ControlGet
<https://www.autohotkey.com/docs/commands/ControlGet.htm>`_
"""
try:
control_id = self._call("ControlGet", "Hwnd", "", class_or_text, *self._include(), title_mode=match)
return Control(control_id)
except Error as err:
if err.message == 1:
# Control doesn't exist.
return Control(None)
raise
[docs] def get_focused_control(self) -> 'Control':
"""get_focused_control() -> ahkpy.Control
Get the currently focused control.
Returns ``Control(None)`` if the window doesn't exist or no control is
focused.
:command: `ControlGet
<https://www.autohotkey.com/docs/commands/ControlGet.htm>`_
"""
try:
class_name = self._call("ControlGetFocus", *self._include())
except Error as err:
if err.message == 1:
# None of window's controls have input focus.
return Control(None)
raise
return self.get_control(class_name)
# TODO: Implement WinMenuSelectItem.
@property
def always_on_top(self) -> Optional[bool]:
"""Whether the window is always on top.
Returns ``None`` unless the window exists.
:type: bool
:command: `WinSet, AlwaysOnTop
<https://www.autohotkey.com/docs/commands/WinSet.htm#AlwaysOnTop>`_
"""
ex_style = self.ex_style
if ex_style is None:
return None
return ExWindowStyle.TOPMOST in ex_style
@always_on_top.setter
def always_on_top(self, value):
if value:
self.pin_to_top()
else:
self.unpin_from_top()
[docs] def pin_to_top(self):
"""Make the window be always on top.
Does nothing unless the window exists.
:command: `WinSet, AlwaysOnTop, On
<https://www.autohotkey.com/docs/commands/WinSet.htm#AlwaysOnTop>`_
"""
self._set("AlwaysOnTop", "On")
[docs] def unpin_from_top(self):
"""Unpin the window from the top.
Does nothing unless the window exists.
:command: `WinSet, AlwaysOnTop, Off
<https://www.autohotkey.com/docs/commands/WinSet.htm#AlwaysOnTop>`_
"""
self._set("AlwaysOnTop", "Off")
[docs] def toggle_always_on_top(self):
"""Toggle the topmost state of the window.
Does nothing unless the window exists.
:command: `WinSet, AlwaysOnTop, Toggle
<https://www.autohotkey.com/docs/commands/WinSet.htm#AlwaysOnTop>`_
"""
self._set("AlwaysOnTop", "Toggle")
[docs] def send_to_bottom(self):
"""Send the window to the bottom; that is, beneath all other windows.
Does nothing unless the window exists.
:command: `WinSet, Bottom
<https://www.autohotkey.com/docs/commands/WinSet.htm#Bottom>`_
"""
self._set("Bottom")
[docs] def bring_to_top(self):
"""Bring the window to the top without explicitly activating it.
However, the system default settings will probably cause it to activate
in most cases. In addition, this method may have no effect due to the
operating system's protection against applications that try to steal
focus from the user (it may depend on factors such as what type of
window is currently active and what the user is currently doing). One
possible work-around is to call :meth:`toggle_always_on_top` twice in a
row.
Does nothing unless the window exists.
:command: `WinSet, Top
<https://www.autohotkey.com/docs/commands/WinSet.htm#Top>`_
"""
self._set("Top")
def disable(self):
"""Disable the window.
When a window is disabled, the user cannot move it or interact with its
controls. In addition, disabled windows are omitted from the Alt+Tab
list.
Does nothing unless the window exists.
:command: `WinSet, Disable
<https://www.autohotkey.com/docs/commands/WinSet.htm#Disable>`_
"""
self._set("Disable")
def enable(self):
"""Enable the window.
Does nothing unless the window exists.
:command: `WinSet, Enable
<https://www.autohotkey.com/docs/commands/WinSet.htm#Enable>`_
"""
self._set("Enable")
[docs] def redraw(self):
"""Redraw the window.
This method attempts to update the appearance/contents of a window by
informing the OS that the window's rectangle needs to be redrawn. If
this approach does not work for a particular window, try
:meth:`~ahkpy.window.BaseWindow.move`. If that does not work, try
:meth:`~ahkpy.window.BaseWindow.hide` in combination with
:meth:`~ahkpy.window.BaseWindow.show`.
Does nothing unless the window exists.
:command: `WinSet, Redraw
<https://www.autohotkey.com/docs/commands/WinSet.htm#Redraw>`_
"""
self._set("Redraw")
[docs] def set_region(self, options: str):
"""Change the shape of a window to rectangle, ellipse, or polygon.
Does nothing unless the window exists.
:command: `WinSet, Region
<https://www.autohotkey.com/docs/commands/WinSet.htm#Region>`_
"""
# TODO: Implement better options.
self._set("Region", options)
[docs] def reset_region(self):
"""Restore the window to its original/default display area.
Does nothing unless the window exists.
:command: `WinSet, Region
<https://www.autohotkey.com/docs/commands/WinSet.htm#Region>`_
"""
self._set("Region", "")
@property
def opacity(self) -> Optional[int]:
"""The window opacity level.
Returns a integer between 0 and 255, where 0 indicates an invisible
window and 255 indicates an opaque window.
Returns ``None`` under the following circumstances:
- the window doesn't exist
- the window has no transparency level
- the OS is older than Windows XP
- other conditions (caused by OS behavior) such as the window having
been minimized, restored, and/or resized since it was made
transparent
Setting ``None`` makes the window opaque. Does nothing unless the window
exists.
:type: int
:command: `WinGet, Transparent
<https://www.autohotkey.com/docs/commands/WinGet.htm#Transparent>`_,
`WinSet, Transparent
<https://www.autohotkey.com/docs/commands/WinSet.htm#Transparent>`_
"""
return self._get("Transparent")
@opacity.setter
def opacity(self, value):
if value is None:
ahk_value = "Off"
elif not 0 <= value <= 255:
raise ValueError("opacity value must be between 0 and 255")
else:
ahk_value = int(value)
self._set("Transparent", ahk_value)
@property
def transparent_color(self) -> Optional[Tuple[int, int, int]]:
"""The color that is marked transparent in the window.
Returns a ``(red, green, blue)`` tuple. The color components are
integers between 0 and 255.
Returns ``None`` under the following circumstances:
- the window doesn't exist
- the window has no transparent color
- the OS is older than Windows XP
- other conditions (caused by OS behavior) such as the window having
been minimized, restored, and/or resized since it was made
transparent
Setting ``None`` makes the window opaque. Does nothing unless the window
exists.
:type: Tuple[int, int, int]
:command: `WinGet, TransColor
<https://www.autohotkey.com/docs/commands/WinGet.htm#TransColor>`_,
`WinSet, TransColor
<https://www.autohotkey.com/docs/commands/WinSet.htm#TransColor>`_
"""
result = self._get("TransColor")
if result is None:
return None
hex_color = hex(result)[2:]
return colors.to_tuple(hex_color)
@transparent_color.setter
def transparent_color(self, value):
if value is None:
ahk_value = "Off"
else:
r, g, b = value
ahk_value = colors.to_hex(r, g, b)
self._set("TransColor", ahk_value)
def hide(self):
self._call("WinHide", *self._include(), set_delay=True)
def show(self):
self._call("WinShow", *self._include(), set_delay=True)
[docs] def activate(self, timeout=None) -> bool:
"""Activate the window.
Returns ``True`` if the window was activated. If the window is not
active after *timeout* seconds, then ``False`` will be returned. If
*timeout* is not specified or ``None``, the function returns
immediately.
Does nothing unless the window exists.
:command: `WinActivate
<https://www.autohotkey.com/docs/commands/WinActivate.htm>`_
"""
self._call("WinActivate", *self._include())
if timeout is None:
return self.is_active
return self.wait_active(timeout=timeout)
[docs] def close(self, timeout=None) -> bool:
"""Close the window.
Returns ``True`` if the window was closed. If the window still exists
after *timeout* seconds, then ``False`` will be returned. If *timeout*
is not specified or ``None``, the function returns immediately.
:command: `WinClose
<https://www.autohotkey.com/docs/commands/WinClose.htm>`_
"""
self._call("WinClose", *self._include(), set_delay=True)
if timeout is not None:
return all_windows.wait_close(id=self.id)
return not self.exists
[docs] def kill(self, timeout=None) -> bool:
"""Forces the window to close.
Returns ``True`` if the window was closed. If the window still exists
after *timeout* seconds, then ``False`` will be returned. If *timeout*
is not specified or ``None``, the function returns immediately.
:command: `WinKill
<https://www.autohotkey.com/docs/commands/WinKill.htm>`_
"""
self._call("WinKill", *self._include(), set_delay=True)
if timeout is not None:
return all_windows.wait_close(id=self.id)
return not self.exists
[docs] def wait_active(self, timeout=None) -> bool:
"""Wait until the window is active.
Returns ``True`` if the window is active. If it's still not active after
*timeout* seconds, then ``False`` will be returned. If *timeout* is not
specified or ``None``, there is no limit to the wait time.
:command: `WinWaitActive
<https://www.autohotkey.com/docs/commands/WinWaitActive.htm>`_
"""
return bool(all_windows.wait_active(id=self.id, timeout=timeout))
[docs] def wait_inactive(self, timeout=None) -> bool:
"""Wait until the window is inactive.
Returns ``True`` if the window is inactive or doesn't exist. If it's
still active after *timeout* seconds, then ``False`` will be returned.
If *timeout* is not specified or ``None``, there is no limit to the wait
time.
:command: `WinWaitNotActive
<https://www.autohotkey.com/docs/commands/WinWaitActive.htm>`_
"""
return bool(all_windows.wait_inactive(id=self.id, timeout=timeout))
[docs] def wait_hidden(self, timeout=None) -> bool:
"""Wait until the window is hidden.
Returns ``True`` if the window is hidden or doesn't exist. If it's still
visible after *timeout* seconds, then ``False`` will be returned. If
*timeout* is not specified or ``None``, there is no limit to the wait
time.
:command: `WinWaitClose
<https://www.autohotkey.com/docs/commands/WinWaitClose.htm>`_
"""
return bool(windows.wait_close(id=self.id, timeout=timeout))
[docs] def wait_close(self, timeout=None) -> bool:
"""Wait until the window is closed.
Returns ``True`` if the window doesn't exist. If it still exists after
*timeout* seconds, then ``False`` will be returned. If *timeout* is not
specified or ``None``, there is no limit to the wait time.
:command: `WinWaitClose
<https://www.autohotkey.com/docs/commands/WinWaitClose.htm>`_
"""
return bool(all_windows.wait_close(id=self.id, timeout=timeout))
[docs] def get_status_bar_text(self, part=0) -> Optional[str]:
"""Get the status bar text from the window.
The *part* argument specifies which part of the bar to retrieve.
Defaults to 0, which is usually the part that contains the text of
interest.
Returns ``None`` if the window doesn't exist or there's no status bar.
Raises an :exc:`Error` if there was a problem accessing the status bar.
:command: `StatusBarGetText
<https://www.autohotkey.com/docs/commands/StatusBarGetText.htm>`_
"""
try:
text = self._call("StatusBarGetText", int(part) + 1, *self._include())
return str(text)
except Error as err:
if err.message == 1:
if not self._status_bar_exists():
return None
err.message = "status bar cannot be accessed"
raise
[docs] def wait_status_bar(self, bar_text="", *,
timeout=None, part=0, interval=0.05, match="startswith") -> Optional[bool]:
"""Wait until the window's status bar contains the specified *bar_text*
string.
If the status bar doesn't contain the specified string after *timeout*
seconds, then ``False`` will be returned. If *timeout* is not specified
or ``None``, there is no limit to the wait time.
The *part* argument specifies which part of the bar to retrieve.
Defaults to 0, which is usually the part that contains the text of
interest.
The *interval* argument specifies how often the status bar should be
checked. Defaults to 0.05 seconds.
The *match* argument specifies how *bar_text* is matched. Defaults to
``"startswith"``. For other modes refer to :meth:`Windows.filter`.
Returns ``None`` if the window doesn't exist or there's no status bar.
Raises an :exc:`Error` if there was a problem accessing the status bar.
:command: `StatusBarWait
<https://www.autohotkey.com/docs/commands/StatusBarWait.htm>`_
"""
# TODO: StatusBarWait is blocking and is not interruptable. However, it
# is usually more efficient to use StatusBarWait rather than calling
# StatusBarGetText in a loop.
try:
ok = self._call(
"StatusBarWait",
bar_text,
timeout,
part + 1,
*self._include(),
interval * 1000,
title_mode=match,
)
return bool(ok)
except Error as err:
if err.message == 2:
if not self._status_bar_exists():
return None
err.message = "status bar cannot be accessed"
raise
def _status_bar_exists(self):
status_bar = self.get_control("msctls_statusbar321")
return bool(status_bar)
def _move(self, x, y, width, height):
self._call("WinMove", *self._include(), x, y, width, height, set_delay=True)
def _get_pos(self):
return self._call("WinGetPos", *self._include())
def _get(self, subcmd):
result = self._call("WinGet", subcmd, *self._include())
if result == "":
return None
return result
def _set_delay(self):
ahk_call("SetWinDelay", optional_ms(get_settings().win_delay))
[docs]class Control(BaseWindow):
"""The object representing a control: button, edit, checkbox, radio button,
list box, combobox, list view.
"""
__slots__ = ("id",)
@property
def is_checked(self) -> Optional[bool]:
"""Whether the checkbox or radio button is checked.
Returns ``None`` if the control doesn't exist.
:type: bool
:command: `ControlGet, $, Checked
<https://www.autohotkey.com/docs/commands/ControlGet.htm#Checked>`_,
`Control, Check
<https://www.autohotkey.com/docs/commands/Control.htm#Check>`_
"""
checked = self._get("Checked")
if checked is None:
return None
return bool(checked)
@is_checked.setter
def is_checked(self, value):
if value:
self.check()
else:
self.uncheck()
[docs] def check(self):
"""Check the checkbox or radio button.
Does nothing unless the control exists.
To improve reliability, a :attr:`Settings.control_delay` is done
automatically after calling this method.
:command: `Control, Check
<https://www.autohotkey.com/docs/commands/Control.htm#Check>`_
"""
return self._call("Control", "Check", "", "", *self._include(), set_delay=True)
[docs] def uncheck(self):
"""Uncheck the checkbox or radio button.
Does nothing unless the control exists.
To improve reliability, a :attr:`Settings.control_delay` is done
automatically after calling this method.
:command: `Control, Uncheck
<https://www.autohotkey.com/docs/commands/Control.htm#Uncheck>`_
"""
return self._call("Control", "Uncheck", "", "", *self._include(), set_delay=True)
def enable(self):
"""Enable the control.
Does nothing unless the control exists.
To improve reliability, a :attr:`Settings.control_delay` is done
automatically after calling this method.
:command: `Control, Enable
<https://www.autohotkey.com/docs/commands/Control.htm#Enable>`_
"""
return self._call("Control", "Enable", "", "", *self._include(), set_delay=True)
def disable(self):
"""Disable the control.
Does nothing unless the control exists.
To improve reliability, a :attr:`Settings.control_delay` is done
automatically after calling this method.
:command: `Control, Disable
<https://www.autohotkey.com/docs/commands/Control.htm#Disable>`_
"""
return self._call("Control", "Disable", "", "", *self._include(), set_delay=True)
def hide(self):
"""Hide the control.
Does nothing unless the control exists.
To improve reliability, a :attr:`Settings.control_delay` is done
automatically after calling this method.
:command: `Control, Hide
<https://www.autohotkey.com/docs/commands/Control.htm#Hide>`_
"""
return self._call("Control", "Hide", "", "", *self._include(), set_delay=True)
def show(self):
"""Show the control.
Does nothing unless the control exists.
To improve reliability, a :attr:`Settings.control_delay` is done
automatically after calling this method.
:command: `Control, Show
<https://www.autohotkey.com/docs/commands/Control.htm#Show>`_
"""
return self._call("Control", "Show", "", "", *self._include(), set_delay=True)
@property
def text(self) -> Optional[str]:
"""The text of the control.
Each text element ends with ``"\\r\\n"``. If the window doesn't exist,
returns ``None``.
To improve reliability, a :attr:`Settings.control_delay` is done
automatically after setting the text.
:type: str
:command: `ControlGetText
<https://www.autohotkey.com/docs/commands/ControlGetText.htm>`_,
`ControlSetText
<https://www.autohotkey.com/docs/commands/ControlSetText.htm>`_
"""
text = self._call("ControlGetText", "", *self._include())
if text is None:
return None
return str(text)
@text.setter
def text(self, value):
return self._call("ControlSetText", "", str(value), *self._include(), set_delay=True)
@property
def is_focused(self) -> bool:
"""Whether the control is focused (read-only).
Returns ``False`` if the control doesn't exist.
:type: bool
"""
if not self:
return False
focused_control = windows.get_active().get_focused_control()
if not focused_control:
return False
return self == focused_control
[docs] def focus(self):
"""Focus the control.
Does nothing unless the control exists.
To improve reliability, a :attr:`Settings.control_delay` is done
automatically after calling this method.
:command: `ControlFocus
<https://www.autohotkey.com/docs/commands/ControlFocus.htm>`_
"""
return self._call("ControlFocus", "", *self._include(), set_delay=True)
[docs] def paste(self, text):
"""Paste *text* at the caret/insert position in an Edit control.
Does not affect the contents of the clipboard. Does nothing unless the
control exists.
To improve reliability, a :attr:`Settings.control_delay` is done
automatically after calling this method.
:command: `Control, EditPaste
<https://www.autohotkey.com/docs/commands/Control.htm#EditPaste>`_
"""
self._call("Control", "EditPaste", str(text), "", *self._include(), set_delay=True)
@property
def line_count(self) -> Optional[int]:
"""The number of lines in an Edit control (read-only).
All Edit controls have at least 1 line, even if the control is empty.
Returns ``None`` if the control doesn't exist.
:type: int
:command: `ControlGet, $, LineCount
<https://www.autohotkey.com/docs/commands/ControlGet.htm#LineCount>`_
"""
return self._get("LineCount")
@property
def current_line_number(self) -> Optional[int]:
"""The line number in an Edit control where the caret resides
(read-only).
The first line is 0. If there is text selected in the control, the
result is set to the line number where the selection begins. Returns
``None`` if the control doesn't exist.
:type: int
:command: `ControlGet, $, CurrentLine
<https://www.autohotkey.com/docs/commands/ControlGet.htm#CurrentLine>`_
"""
result = self._get("CurrentLine")
if result is None:
return None
return result - 1
@property
def current_column(self) -> Optional[int]:
"""The column number in an Edit control where the caret resides
(read-only).
The first column is 0. If there is text selected in the control, the
result is set to the column number where the selection begins. Returns
``None`` if the control doesn't exist.
:type: int
:command: `ControlGet, $, CurrentCol
<https://www.autohotkey.com/docs/commands/ControlGet.htm#CurrentCol>`_
"""
result = self._get("CurrentCol")
if result is None:
return None
return result - 1
[docs] def get_line(self, lineno: int) -> Optional[str]:
"""Retrieve the text of line *lineno* in an Edit control.
Line 0 is the first line. If the specified line number is out of range,
an :exc:`Error` is raised. Returns ``None`` if the control doesn't
exist.
If the *lineno* argument is negative, the number is relative to the end.
:command: `ControlGet, $, Line
<https://www.autohotkey.com/docs/commands/ControlGet.htm#Line>`_
"""
line_count = self.line_count
if line_count is None:
return None
if lineno < 0:
lineno = line_count + lineno
out_of_range_err = "line number out of range"
if not 0 <= lineno < line_count:
raise Error(out_of_range_err)
try:
result = self._get("Line", lineno + 1)
if result is None:
return None
return str(result)
except Error as err:
if err.message == 1:
if lineno < self.line_count:
return ""
err.message = out_of_range_err
raise
@property
def current_line(self) -> Optional[str]:
"""The text of the line in an Edit control where the caret resides
(read-only).
If there is text selected in the control, the result is set to the line
number where the selection begins. Returns ``None`` if the control
doesn't exist.
"""
lineno = self.current_line_number
if lineno is None:
return None
return self.get_line(lineno)
@property
def selected_text(self) -> Optional[str]:
"""The selected text in an Edit control (read-only).
If no text is selected, the result is an empty string. Certain types of
controls, such as RichEdit20A, might not produce the correct text in
some cases (e.g. Metapad). Returns ``None`` if the control doesn't
exist.
:command: `ControlGet, $, Selected
<https://www.autohotkey.com/docs/commands/ControlGet.htm#Selected>`_
"""
result = self._get("Selected")
if result is None:
return None
return str(result)
@property
def list_choice(self) -> Optional[str]:
"""The name of the currently selected entry in a ListBox or
ComboBox (read-only).
Returns ``None`` if the control doesn't exist or no item is selected.
:type: str
:command: `ControlGet, $, Choice
<https://www.autohotkey.com/docs/commands/ControlGet.htm#Choice>`_
"""
try:
choice = self._get("Choice")
if choice is None:
return None
return str(choice)
except Error as err:
if err.message == 1 and self.list_choice_index == -1:
return None
raise
@property
def list_choice_index(self) -> Optional[int]:
"""The index of the currently selected entry in a ListBox or ComboBox.
The first item is 0. Returns ``None`` if the control doesn't exist.
Returns -1 if no item is selected.
:type: int
"""
class_name = self.class_name
if class_name is None:
return None
class_name_lower = class_name.lower()
if "combo" in class_name_lower:
getcursel = CB_GETCURSEL
elif "list" in class_name_lower:
getcursel = LB_GETCURSEL
else:
return None
result = self.send_message(getcursel, signed_int=True, timeout=5)
if result is None:
return None
return result
@list_choice_index.setter
def list_choice_index(self, index):
self.choose_item_index(index)
[docs] def choose_item_index(self, index):
"""Set the selection in a ListBox or ComboBox to be the *index* entry.
The first item is 0. If the *index* argument is negative, it's relative
to the end of the list.
Does nothing unless the control exists. Raises an :exc:`Error` if
the index is out of range.
To improve reliability, a :attr:`Settings.control_delay` is done
automatically after calling this method.
:command: `Control, Choose
<https://www.autohotkey.com/docs/commands/Control.htm#Choose>`_
"""
index = int(index)
if index < 0:
index = self.list_item_count + index
try:
self._call("Control", "Choose", index + 1, set_delay=True)
except Error as err:
if err.message == 1:
if self.list_item_count < index + 1:
raise
raise
[docs] def choose_item(self, value):
"""Set the selection (choice) in a ListBox or ComboBox to be the first
entry whose leading part matches *value*.
Does nothing unless the control exists. Raises an :exc:`Error` if no
item matches the *value*.
To improve reliability, a :attr:`Settings.control_delay` is done
automatically after calling this method.
:command: `Control, ChooseString
<https://www.autohotkey.com/docs/commands/Control.htm#ChooseString>`_
"""
value = str(value)
try:
self._call("Control", "ChooseString", value, set_delay=True)
except Error as err:
if err.message == 1 and self.list_item_index(value) == -1:
# TODO: list_item_index does the exact match while choose_item
# matches the leading part.
err.message = f"list item {value!r} doesn't exist"
raise
[docs] def list_item_index(self, value) -> Optional[int]:
"""Retrieve the entry number of a ListBox or ComboBox that is a case
insensitive match for *value*.
Returns ``None`` if the control doesn't exist. Returns -1 if no item
matches the *value*.
:command: `ControlGet, $, FindString
<https://www.autohotkey.com/docs/commands/ControlGet.htm#FindString>`_
"""
# Let's implement this in Python because in AHK there's no difference
# between "an error trying to find the string" and "no such string
# found".
class_name = self.class_name
if class_name is None:
return None
class_name_lower = class_name.lower()
if "combo" in class_name_lower:
find_string_exact = CB_FINDSTRINGEXACT
elif "list" in class_name_lower:
find_string_exact = LB_FINDSTRINGEXACT
else:
return None
value_buffer = ctypes.create_unicode_buffer(value)
result = self.send_message(
msg=find_string_exact,
w_param=-1,
l_param=ctypes.addressof(value_buffer),
signed_int=True,
timeout=5,
)
if result is None:
return None
return result
@property
def list_items(self) -> Optional[list]:
"""The list of items from a ListView, ListBox, ComboBox, or DropDownList
(read-only).
If the control is a ListBox, ComboBox, or DropDownList, returns a list
of strings. If the control is a ListView, returns a list of lists: rows
and columns.
Returns ``None`` if the control doesn't exist. Raises an :exc:`Error` if
there was a problem getting list items.
:command: `ControlGet, $, List
<https://www.autohotkey.com/docs/commands/ControlGet.htm#List>`_
"""
try:
items = self._get("List")
except Error as err:
if err.message == 1:
err.message = "there was a problem getting list items"
raise
if items is None:
return None
# AHK separates list items with a '\n' character. Also, in the case of
# ListViews, the items may have multiple columns that are separated by a
# '\t' character. Unfortunately, AHK does not escape the '\n' and '\t'
# characters in the list items and columns, so there's no guaranteed way
# to split and get the correct values. However, the '\n' and '\t'
# characters are rare in these controls, so the convenience of working
# on a list of strings is more valuable than potential corner cases.
class_name = self.class_name
if class_name is None:
return None
if "syslistview32" in class_name.lower():
return self._split_list_items(items)
return items.split("\n")
@property
def selected_list_items(self) -> Optional[List[List[str]]]:
"""The selected (highlighted) rows in a ListView control (read-only).
Returns ``[]`` if no item is selected. Returns ``None`` if the control
doesn't exist or isn't a ListView. Raises an :exc:`Error` if there was a
problem getting list items.
:type: List[List[str]]
:command: `ControlGet, $, List, Selected
<https://www.autohotkey.com/docs/commands/ControlGet.htm#List>`_
"""
return self.get_list_items(selected=True)
@property
def focused_list_item(self) -> Optional[List[str]]:
"""The focused row in a ListView control (read-only).
Returns ``None`` if the control doesn't exist, isn't a ListView, or no
item is focused. Raises an :exc:`Error` if there was a problem getting
list items.
:type: List[str]
:command: `ControlGet, $, List, Focused
<https://www.autohotkey.com/docs/commands/ControlGet.htm#List>`_
"""
items = self.get_list_items(focused=True)
if items is None or len(items) == 0:
return None
return items[0]
[docs] def get_list_items(self, *, selected=False, focused=False, column: int = None) -> Optional[list]:
"""Retrieve items from a ListView control.
Returns ``None`` if the control doesn't exist or isn't a ListView.
Raises an :exc:`Error` if *column* is out of range or there was a
problem getting list items.
:param selected: if true, returns only selected rows.
:param focused: if true, returns only the focused row.
:param int column: return rows with only the given column, indexed by
its number. Supports negative indexing.
:command: `ControlGet, $, List
<https://www.autohotkey.com/docs/commands/ControlGet.htm#List>`_
"""
options = []
if selected:
options.append("Selected")
if focused:
options.append("Focused")
column_count = self.list_view_column_count
if column_count is None:
return None
if column is not None:
if column < 0:
column = column_count + column
options.append(f"Col{column + 1}")
str_options = " ".join(options)
try:
items = self._get("List", str_options)
if items is None:
return None
if column is not None:
return items.split('\n')
return self._split_list_items(items)
except Error as err:
if err.message == 1:
class_name = self.class_name
if class_name is None:
return None
if "syslistview32" not in class_name.lower():
return None
if column is not None and column_count < column + 1:
err.message = "column index out of range"
else:
err.message = "there was a problem getting list items"
raise err
def _split_list_items(self, string):
if string == "":
return []
return [item.split("\t") for item in string.split("\n")]
@property
def list_item_count(self) -> Optional[int]:
"""The total number of rows in a ListBox, ComboBox, or ListView control
(read-only).
Returns ``None`` if the control doesn't exist.
:type: int
"""
class_name = self.class_name
if class_name is None:
return None
class_name_lower = class_name.lower()
if "syslistview32" in class_name_lower:
return self._count_list_items()
if "combo" in class_name_lower:
get_count = CB_GETCOUNT
elif "list" in class_name_lower:
get_count = LB_GETCOUNT
else:
return None
result = self.send_message(get_count, timeout=5)
if result is None:
return None
return result
@property
def selected_list_item_count(self) -> Optional[int]:
"""The number of selected (highlighted) rows in a ListView
control (read-only).
Returns ``None`` if the control doesn't exist or isn't a ListView.
Raises an :exc:`Error` if there was a problem getting list items.
:type: int
:command: `ControlGet, $, List, Count Selected
<https://www.autohotkey.com/docs/commands/ControlGet.htm#List>`_
"""
return self._count_list_items("Selected")
@property
def focused_list_item_index(self) -> Optional[int]:
"""The row number of the focused row in a ListView control
(read-only).
The first item is 0. Returns -1 if no item is focused. Returns ``None``
if the control doesn't exist or isn't a ListView. Raises an :exc:`Error`
if there was a problem getting list items.
:type: int
:command: `ControlGet, $, List, Count Focused
<https://www.autohotkey.com/docs/commands/ControlGet.htm#List>`_
"""
count = self._count_list_items("Focused")
if count is None:
return None
return count - 1
@property
def list_view_column_count(self) -> Optional[int]:
"""The number of columns in a ListView control (read-only).
Returns -1 if the count cannot be determined. Returns ``None`` if the
control doesn't exist or isn't a ListView. Raises an :exc:`Error` if
there was a problem getting list items.
:type: int
:command: `ControlGet, $, List, Count Col
<https://www.autohotkey.com/docs/commands/ControlGet.htm#List>`_
"""
return self._count_list_items("Col")
def _count_list_items(self, option="") -> Optional[int]:
try:
return self._get("List", f"Count {option}")
except Error as err:
if err.message == 1:
class_name = self.class_name
if class_name is None:
return None
if "syslistview32" not in class_name.lower():
return None
err.message = "there was a problem getting list items"
raise err
def _get_pos(self):
return self._call("ControlGetPos", "", *self._include())
def _move(self, x, y, width, height):
self._call("ControlMove", "", x, y, width, height, *self._include(), set_delay=True)
def _get(self, subcmd, value=""):
return self._call("ControlGet", subcmd, value, "", *self._include())
def _set_delay(self):
ahk_call("SetControlDelay", optional_ms(get_settings().control_delay))
def _call(self, cmd, *args, hidden_windows=True, title_mode=None, set_delay=False):
try:
return super()._call(cmd, *args, hidden_windows=hidden_windows, title_mode=title_mode, set_delay=set_delay)
except Error as err:
if err.message == 1 and not super().exists:
return None
raise
def _set_title_match_mode(title_mode):
if title_mode == "startswith":
ahk_call("SetTitleMatchMode", 1)
elif title_mode == "contains":
ahk_call("SetTitleMatchMode", 2)
elif title_mode == "exact":
ahk_call("SetTitleMatchMode", 3)
elif title_mode == "regex":
ahk_call("SetTitleMatchMode", "regex")
else:
raise ValueError(f"{title_mode!r} is not a valid title match mode")
[docs]class WindowStyle(enum.IntFlag):
"""The object that holds the window styles.
For more information on styles refer to `Window Styles
<https://docs.microsoft.com/en-us/windows/win32/winmsg/window-styles>`_ on
Microsoft Docs.
"""
BORDER = 0x00800000
CAPTION = 0x00C00000
CHILD = 0x40000000
CHILDWINDOW = 0x40000000
CLIPCHILDREN = 0x02000000
CLIPSIBLINGS = 0x04000000
DISABLED = 0x08000000
DLGFRAME = 0x00400000
GROUP = 0x00020000
HSCROLL = 0x00100000
ICONIC = 0x20000000
MAXIMIZE = 0x01000000
MAXIMIZEBOX = 0x00010000
MINIMIZE = 0x20000000
MINIMIZEBOX = 0x00020000
OVERLAPPED = 0x00000000
POPUP = 0x80000000
SIZEBOX = 0x00040000
SYSMENU = 0x00080000
TABSTOP = 0x00010000
THICKFRAME = 0x00040000
TILED = 0x00000000
TOOLWINDOW = 0x00000080
VISIBLE = 0x10000000
VSCROLL = 0x00200000
OVERLAPPEDWINDOW = (OVERLAPPED | CAPTION | SYSMENU | THICKFRAME | MINIMIZEBOX | MAXIMIZEBOX)
POPUPWINDOW = (POPUP | BORDER | SYSMENU)
TILEDWINDOW = (OVERLAPPED | CAPTION | SYSMENU | THICKFRAME | MINIMIZEBOX | MAXIMIZEBOX)
[docs]class ExWindowStyle(enum.IntFlag):
"""The object that holds the extended window styles.
For more information on styles refer to `Extended Window Styles
<https://docs.microsoft.com/en-us/windows/win32/winmsg/extended-window-styles>`_
on Microsoft Docs.
"""
ACCEPTFILES = 0x00000010
APPWINDOW = 0x00040000
CLIENTEDGE = 0x00000200
COMPOSITED = 0x02000000
CONTEXTHELP = 0x00000400
CONTROLPARENT = 0x00010000
DLGMODALFRAME = 0x00000001
LAYERED = 0x00080000
LAYOUTRTL = 0x00400000
LEFT = 0x00000000
LEFTSCROLLBAR = 0x00004000
LTRREADING = 0x00000000
MDICHILD = 0x00000040
NOACTIVATE = 0x08000000
NOINHERITLAYOUT = 0x00100000
NOPARENTNOTIFY = 0x00000004
NOREDIRECTIONBITMAP = 0x00200000
RIGHT = 0x00001000
RIGHTSCROLLBAR = 0x00000000
RTLREADING = 0x00002000
STATICEDGE = 0x00020000
TOOLWINDOW = 0x00000080
TOPMOST = 0x00000008
TRANSPARENT = 0x00000020
WINDOWEDGE = 0x00000100
OVERLAPPEDWINDOW = (WINDOWEDGE | CLIENTEDGE)
PALETTEWINDOW = (WINDOWEDGE | TOOLWINDOW | TOPMOST)
CB_FINDSTRINGEXACT = 0x158
CB_GETCOUNT = 0x146
CB_GETCURSEL = 0x147
LB_FINDSTRINGEXACT = 0x1A2
LB_GETCOUNT = 0x18B
LB_GETCURSEL = 0x188