""".. versionadded:: 0.0.1
Control the macOS Terminal application using JXA-like syntax.
"""
import subprocess
from typing import Dict, Union
import AppKit
from PyXA import XABase
from PyXA import XABaseScriptable
from ..XAProtocols import XACanOpenPath, XAClipboardCodable
[docs]
class XATerminalApplication(XABaseScriptable.XASBApplication, XACanOpenPath):
"""A class for managing and interacting with Messages.app
.. seealso:: :class:`XATerminalWindow`, :class:`XATerminalTab`, :class:`XATerminalSettingsSet`
.. versionadded:: 0.0.1
"""
def __init__(self, properties):
super().__init__(properties)
self.xa_wcls = XATerminalWindow
@property
def name(self) -> str:
"""The name of the application."""
return self.xa_scel.name()
@property
def frontmost(self) -> bool:
"""Whether Terminal is the active application."""
return self.xa_scel.frontmost()
@property
def version(self) -> str:
"""The version of Terminal.app."""
return self.xa_scel.version()
@property
def default_settings(self) -> "XATerminalSettingsSet":
"""The settings set used for new windows."""
return self._new_element(self.xa_scel.defaultSettings(), XATerminalSettingsSet)
@default_settings.setter
def default_settings(self, default_settings: "XATerminalSettingsSet"):
self.set_property("defaultSettings", default_settings.xa_elem)
@property
def startup_settings(self) -> "XATerminalSettingsSet":
"""The settings set used for the window created on application startup."""
return self._new_element(self.xa_scel.startupSettings(), XATerminalSettingsSet)
@startup_settings.setter
def startup_settings(self, startup_settings: "XATerminalSettingsSet"):
self.set_property("startupSettings", startup_settings.xa_elem)
@property
def current_tab(self) -> "XATerminalTab":
"""The currently active Terminal tab."""
return self.front_window.selected_tab
@current_tab.setter
def current_tab(self, current_tab: "XATerminalTab"):
self.front_window.selected_tab = current_tab
[docs]
def do_script(
self,
script: str,
window_tab: Union["XATerminalWindow", "XATerminalTab"] = None,
return_result: bool = False,
) -> Union["XATerminalApplication", Dict[str, str]]:
"""Executes a Terminal script in the specified window or tab.
If no window or tab is provided, the script will run in a new tab of the frontmost window. If return_result is True, the script will be run in a new tab no regardless of the value of window_tab.
:param script: The script to execute.
:type script: str
:param window_tab: The window or tab to execute the script in, defaults to None
:type window_tab: Union[XATerminalWindow, XATerminalTab], optional
:param return_result: Whether to return the result of script execution, defaults to False
:type return_result: bool, optional
:return: A reference to the Terminal application object, or the result of script execution.
:rtype: Union[XATerminalApplication, Dict[str, str]]
.. versionchanged:: 0.0.9
Now optionally returns the script execution result.
.. versionadded:: 0.0.1
"""
if window_tab is None:
window_tab = self.front_window
if return_result:
value = subprocess.Popen(
[script], stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True
)
return {
"stdout": value.stdout.read().decode(),
"stderr": value.stderr.read().decode(),
}
else:
self.xa_scel.doScript_in_(script, window_tab.xa_elem)
return self
[docs]
def settings_sets(
self, filter: dict = None
) -> Union["XATerminalSettingsSetList", None]:
"""Returns a list of settings sets, as PyXA-wrapped objects, matching the given filter.
:param filter: A dictionary specifying property-value pairs that all returned settings sets will have, or None
:type filter: Union[dict, None]
:return: The list of settings sets
:rtype: XATerminalSettingsSetList
.. versionadded:: 0.0.7
"""
return self._new_element(
self.xa_scel.settingsSets(), XATerminalSettingsSetList, filter
)
[docs]
class XATerminalWindow(
XABaseScriptable.XASBWindow, XABaseScriptable.XASBPrintable, XABase.XAObject
):
"""A class for managing and interacting with windows in Terminal.app.
.. versionadded:: 0.0.1
"""
def __init__(self, properties):
super().__init__(properties)
@property
def frontmost(self) -> bool:
"""Whether the window is currently the frontmost Terminal window."""
return self.xa_elem.frontmost()
@frontmost.setter
def frontmost(self, frontmost: bool):
self.set_property("frontmost", frontmost)
@property
def selected_tab(self) -> "XATerminalTab":
"""The Terminal tab currently displayed in the window."""
return self._new_element(self.xa_elem.selectedTab(), XATerminalTab)
@selected_tab.setter
def selected_tab(self, selected_tab: "XATerminalTab"):
self.set_property("selectedTab", selected_tab.xa_elem)
@property
def position(self) -> tuple[int, int]:
return tuple(self.xa_elem.position())
@position.setter
def position(self, position: tuple[int, int]):
"""The position of the top-left corner of the window."""
self.set_property("position", position)
[docs]
def tabs(self, filter: dict = None) -> Union["XATerminalTabList", None]:
"""Returns a list of tabs, as PyXA-wrapped objects, matching the given filter.
:param filter: A dictionary specifying property-value pairs that all returned tabs will have, or None
:type filter: Union[dict, None]
:return: The list of tabs
:rtype: XATerminalTabList
.. versionadded:: 0.0.7
"""
return self._new_element(self.xa_elem.tabs(), XATerminalTabList, filter)
[docs]
class XATerminalTabList(XABase.XAList, XAClipboardCodable):
"""A wrapper around lists of Terminal tabs that employs fast enumeration techniques.
All properties of tabs can be called as methods on the wrapped list, returning a list containing each tab's value for the property.
.. versionadded:: 0.0.7
"""
def __init__(self, properties: dict, filter: Union[dict, None] = None):
super().__init__(properties, XATerminalTab, filter)
[docs]
def number_of_rows(self) -> list[int]:
return list(self.xa_elem.arrayByApplyingSelector_("numberOfRows") or [])
[docs]
def number_of_columns(self) -> list[int]:
return list(self.xa_elem.arrayByApplyingSelector_("numberOfColumns") or [])
[docs]
def contents(self) -> list[str]:
return list(self.xa_elem.arrayByApplyingSelector_("contents") or [])
[docs]
def history(self) -> list[str]:
return list(self.xa_elem.arrayByApplyingSelector_("history") or [])
[docs]
def busy(self) -> list[bool]:
return list(self.xa_elem.arrayByApplyingSelector_("busy") or [])
[docs]
def processes(self) -> list[list[str]]:
return [
list(x) for x in self.xa_elem.arrayByApplyingSelector_("processes") or []
]
[docs]
def selected(self) -> list[bool]:
return list(self.xa_elem.arrayByApplyingSelector_("selected") or [])
[docs]
def title_displays_custom_title(self) -> list[bool]:
return list(
self.xa_elem.arrayByApplyingSelector_("titleDisplaysCustomTitle") or []
)
[docs]
def custom_title(self) -> list[str]:
return list(self.xa_elem.arrayByApplyingSelector_("customTitle") or [])
[docs]
def tty(self) -> list[str]:
return list(self.xa_elem.arrayByApplyingSelector_("tty") or [])
[docs]
def current_settings(self) -> "XATerminalSettingsSetList":
ls = self.xa_elem.arrayByApplyingSelector_("currentSettings") or []
return self._new_element(ls, XATerminalSettingsSetList)
[docs]
def by_number_of_rows(self, number_of_rows: int) -> Union["XATerminalTab", None]:
return self.by_property("numberOfRows", number_of_rows)
[docs]
def by_number_of_columns(
self, number_of_columns: int
) -> Union["XATerminalTab", None]:
return self.by_property("numberOfColumns", number_of_columns)
[docs]
def by_contents(self, contents: str) -> Union["XATerminalTab", None]:
return self.by_property("contents", contents)
[docs]
def by_history(self, history: str) -> Union["XATerminalTab", None]:
return self.by_property("history", history)
[docs]
def by_busy(self, busy: bool) -> Union["XATerminalTab", None]:
return self.by_property("busy", busy)
[docs]
def by_processes(self, processes: list[str]) -> Union["XATerminalTab", None]:
return self.by_property("processes", processes)
[docs]
def by_selected(self, selected: bool) -> Union["XATerminalTab", None]:
return self.by_property("selected", selected)
[docs]
def by_title_displays_custom_title(
self, title_displays_custom_title: bool
) -> Union["XATerminalTab", None]:
return self.by_property("titleDisplaysCustomTitle", title_displays_custom_title)
[docs]
def by_custom_title(self, custom_title: str) -> Union["XATerminalTab", None]:
return self.by_property("customTitle", custom_title)
[docs]
def by_tty(self, tty: str) -> Union["XATerminalTab", None]:
return self.by_property("tty", tty)
[docs]
def by_current_settings(
self, current_settings: "XATerminalSettingsSet"
) -> Union["XATerminalTab", None]:
return self.by_property("currentSettings", current_settings.xa_elem)
[docs]
def get_clipboard_representation(self) -> list[str]:
"""Gets a clipboard-codable representation of each tab in the list.
When the clipboard content is set to a list of Terminal tabs, each tab's custom title and history are added to the clipboard.
:return: The list of each tab's custom title and history
:rtype: list[str]
.. versionadded:: 0.0.8
"""
items = []
titles = self.custom_title()
histories = self.history()
for index, title in enumerate(titles):
items.append(title)
items.append(histories[index])
return items
def __repr__(self):
return "<" + str(type(self)) + str(self.custom_title()) + ">"
[docs]
class XATerminalTab(XABase.XAObject, XAClipboardCodable):
"""A class for managing and interacting with tabs in Terminal.app.
.. versionadded:: 0.0.1
"""
def __init__(self, properties):
super().__init__(properties)
@property
def number_of_rows(self) -> int:
"""The number of rows displayed in the tab."""
return self.xa_elem.numberOfRows()
@number_of_rows.setter
def number_of_rows(self, number_of_rows: int):
self.set_property("numberOfRows", number_of_rows)
@property
def number_of_columns(self) -> int:
"""The number of columns displayed in the tab."""
return self.xa_elem.numberOfColumns()
@number_of_columns.setter
def number_of_columns(self, number_of_columns: int):
self.set_property("numberOfColumns", number_of_columns)
@property
def contents(self) -> str:
"""The currently visible contents of the tab."""
return self.xa_elem.contents()
@property
def history(self) -> str:
"""The contents of the entire scrolling buffer of the tab."""
return self.xa_elem.history()
@property
def busy(self) -> bool:
"""Whether the tab is currently busy running a process."""
return self.xa_elem.busy()
@property
def processes(self) -> list[str]:
"""The processes currently running in the tab."""
return list(self.xa_elem.processes())
@property
def selected(self) -> bool:
"""Whether the tab is currently selected."""
return self.xa_elem.selected()
@selected.setter
def selected(self, selected: bool):
self.set_property("selected", selected)
@property
def title_displays_custom_title(self) -> bool:
"""Whether the tab's title contains a custom title."""
return self.xa_elem.titleDisplaysCustomTitle()
@title_displays_custom_title.setter
def title_displays_custom_title(self, title_displays_custom_title: bool):
self.set_property("titleDisplaysCustomTitle", title_displays_custom_title)
@property
def custom_title(self) -> str:
"""The tab's custom title."""
return self.xa_elem.customTitle()
@custom_title.setter
def custom_title(self, custom_title: str):
self.set_property("customTitle", custom_title)
@property
def tty(self) -> str:
"""The tab's TTY device."""
return self.xa_elem.tty()
@property
def current_settings(self) -> "XATerminalSettingsSet":
"""The set of settings which control the tab's behavior and appearance."""
return self._new_element(self.xa_elem.currentSettings(), XATerminalSettingsSet)
@current_settings.setter
def current_settings(self, current_settings: "XATerminalSettingsSet"):
self.set_property("currentSettings", current_settings.xa_elem)
[docs]
def get_clipboard_representation(self) -> list[str]:
"""Gets a clipboard-codable representation of the tab.
When the clipboard content is set to a Terminal tab, the tab's custom title and its history are added to the clipboard.
:return: The tab's custom title and history
:rtype: list[str]
.. versionadded:: 0.0.8
"""
return [self.custom_title, self.history]
def __repr__(self):
return "<" + str(type(self)) + self.custom_title + ">"
[docs]
class XATerminalSettingsSetList(XABase.XAList, XAClipboardCodable):
"""A wrapper around lists of Terminal settings sets that employs fast enumeration techniques.
All properties of settings sets can be called as methods on the wrapped list, returning a list containing each settings set's value for the property.
.. versionadded:: 0.0.7
"""
def __init__(self, properties: dict, filter: Union[dict, None] = None):
super().__init__(properties, XATerminalSettingsSet, filter)
[docs]
def id(self) -> list[int]:
return list(self.xa_elem.arrayByApplyingSelector_("id") or [])
[docs]
def name(self) -> list[str]:
return list(self.xa_elem.arrayByApplyingSelector_("name") or [])
[docs]
def number_of_rows(self) -> list[int]:
return list(self.xa_elem.arrayByApplyingSelector_("numberOfRows") or [])
[docs]
def number_of_columns(self) -> list[int]:
return list(self.xa_elem.arrayByApplyingSelector_("numberOfColumns") or [])
[docs]
def cursor_color(self) -> list[XABase.XAColor]:
ls = self.xa_elem.arrayByApplyingSelector_("cursorColor") or []
return [XABase.XAColor(x) for x in ls]
[docs]
def background_color(self) -> list[XABase.XAColor]:
ls = self.xa_elem.arrayByApplyingSelector_("backgroundColor") or []
return [XABase.XAColor(x) for x in ls]
[docs]
def normal_text_color(self) -> list[XABase.XAColor]:
ls = self.xa_elem.arrayByApplyingSelector_("normalTextColor") or []
return [XABase.XAColor(x) for x in ls]
[docs]
def bold_text_color(self) -> list[XABase.XAColor]:
ls = self.xa_elem.arrayByApplyingSelector_("boldTextColor") or []
return [XABase.XAColor(x) for x in ls]
[docs]
def font_name(self) -> list[str]:
return list(self.xa_elem.arrayByApplyingSelector_("fontName") or [])
[docs]
def font_size(self) -> list[int]:
return list(self.xa_elem.arrayByApplyingSelector_("fontSize") or [])
[docs]
def font_antialiasing(self) -> list[bool]:
return list(self.xa_elem.arrayByApplyingSelector_("fontAntialiasing") or [])
[docs]
def clean_commands(self) -> list[list[str]]:
return [
list(x)
for x in self.xa_elem.arrayByApplyingSelector_("cleanCommands") or []
]
[docs]
def title_displays_device_name(self) -> list[bool]:
return list(
self.xa_elem.arrayByApplyingSelector_("titleDisplaysDeviceName") or []
)
[docs]
def title_displays_shell_path(self) -> list[bool]:
return list(
self.xa_elem.arrayByApplyingSelector_("titleDisplaysShellPath") or []
)
[docs]
def title_displays_window_size(self) -> list[bool]:
return list(
self.xa_elem.arrayByApplyingSelector_("titleDisplaysWindowSize") or []
)
[docs]
def title_displays_settings_name(self) -> list[bool]:
return list(
self.xa_elem.arrayByApplyingSelector_("titleDisplaysSettingsName") or []
)
[docs]
def title_displays_custom_title(self) -> list[bool]:
return list(
self.xa_elem.arrayByApplyingSelector_("titleDisplaysCustomTitle") or []
)
[docs]
def custom_title(self) -> list[str]:
return list(self.xa_elem.arrayByApplyingSelector_("customTitle") or [])
[docs]
def by_id(self, id: int) -> Union["XATerminalSettingsSet", None]:
return self.by_property("id", id)
[docs]
def by_name(self, name: str) -> Union["XATerminalSettingsSet", None]:
return self.by_property("name", name)
[docs]
def by_number_of_rows(
self, number_of_rows: int
) -> Union["XATerminalSettingsSet", None]:
return self.by_property("numberOfRows", number_of_rows)
[docs]
def by_number_of_columns(
self, number_of_columns: int
) -> Union["XATerminalSettingsSet", None]:
return self.by_property("numberOfColumns", number_of_columns)
[docs]
def by_cursor_color(
self, cursor_color: XABase.XAColor
) -> Union["XATerminalSettingsSet", None]:
for settings_set in self.xa_elem:
if settings_set.cursorColor() == cursor_color.xa_elem:
return self._new_element(settings_set, XATerminalSettingsSet)
[docs]
def by_background_color(
self, background_color: XABase.XAColor
) -> Union["XATerminalSettingsSet", None]:
for settings_set in self.xa_elem:
if settings_set.backgroundColor() == background_color.xa_elem:
return self._new_element(settings_set, XATerminalSettingsSet)
[docs]
def by_normal_text_color(
self, normal_text_color: XABase.XAColor
) -> Union["XATerminalSettingsSet", None]:
for settings_set in self.xa_elem:
if settings_set.normalTextColor() == normal_text_color.xa_elem:
return self._new_element(settings_set, XATerminalSettingsSet)
[docs]
def by_bold_text_color(
self, bold_text_color: XABase.XAColor
) -> Union["XATerminalSettingsSet", None]:
for settings_set in self.xa_elem:
if settings_set.boldTextColor() == bold_text_color.xa_elem:
return self._new_element(settings_set, XATerminalSettingsSet)
[docs]
def by_font_name(self, font_name: str) -> Union["XATerminalSettingsSet", None]:
return self.by_property("fontName", font_name)
[docs]
def by_font_size(self, font_size: int) -> Union["XATerminalSettingsSet", None]:
return self.by_property("fontSize", font_size)
[docs]
def by_font_antialiasing(
self, font_antialiasing: bool
) -> Union["XATerminalSettingsSet", None]:
return self.by_property("fontAntialiasing", font_antialiasing)
[docs]
def by_clean_commands(
self, clean_commands: list[str]
) -> Union["XATerminalSettingsSet", None]:
return self.by_property("cleanCommands", clean_commands)
[docs]
def by_title_displays_device_name(
self, title_displays_device_name: bool
) -> Union["XATerminalSettingsSet", None]:
return self.by_property("titleDisplaysDeviceName", title_displays_device_name)
[docs]
def by_title_displays_shell_path(
self, title_displays_shell_path: bool
) -> Union["XATerminalSettingsSet", None]:
return self.by_property("titleDisplaysShellPath", title_displays_shell_path)
[docs]
def by_title_displays_window_size(
self, title_displays_window_size: bool
) -> Union["XATerminalSettingsSet", None]:
return self.by_property("titleDisplaysWindowSize", title_displays_window_size)
[docs]
def by_title_displays_settings_name(
self, title_displays_settings_name: bool
) -> Union["XATerminalSettingsSet", None]:
return self.by_property(
"titleDisplaysSettingsName", title_displays_settings_name
)
[docs]
def by_title_displays_custom_title(
self, title_displays_custom_title: bool
) -> Union["XATerminalSettingsSet", None]:
return self.by_property("titleDisplaysCustomTitle", title_displays_custom_title)
[docs]
def by_custom_title(
self, custom_title: str
) -> Union["XATerminalSettingsSet", None]:
return self.by_property("customTitle", custom_title)
[docs]
def get_clipboard_representation(self) -> list[str]:
"""Gets a clipboard-codable representation of each settings set in the list.
When the clipboard content is set to a list of settings sets, each setting set's name is added to the clipboard.
:return: The list of setting set names
:rtype: list[str]
.. versionadded:: 0.0.8
"""
return self.name()
def __repr__(self):
return "<" + str(type(self)) + str(self.name()) + ">"
[docs]
class XATerminalSettingsSet(XABase.XAObject, XAClipboardCodable):
"""A class for managing and interacting with settings sets in Terminal.app.
.. versionadded:: 0.0.1
"""
def __init__(self, properties):
super().__init__(properties)
@property
def id(self) -> int:
"""The unique identifier of the settings set."""
return self.xa_elem.id()
@property
def name(self) -> str:
"""The name of the settings set."""
return self.xa_elem.name()
@name.setter
def name(self, name: str):
self.set_property("name", name)
@property
def number_of_rows(self) -> int:
"""The number of rows displayed in the tab."""
return self.xa_elem.numberOfRows()
@number_of_rows.setter
def number_of_rows(self, number_of_rows: int):
self.set_property("numberOfRows", number_of_rows)
@property
def number_of_columns(self) -> int:
"""The number of columns displayed in the tab."""
return self.xa_elem.numberOfColumns()
@number_of_columns.setter
def number_of_columns(self, number_of_columns: int):
self.set_property("numberOfColumns", number_of_columns)
@property
def cursor_color(self) -> XABase.XAColor:
"""The cursor color for the tab."""
return XABase.XAColor(self.xa_elem.cursorColor())
@cursor_color.setter
def cursor_color(self, cursor_color: XABase.XAColor):
self.set_property("cursorColor", cursor_color.xa_elem)
@property
def background_color(self) -> XABase.XAColor:
"""The background color for the tab."""
return XABase.XAColor(self.xa_elem.backgroundColor())
@background_color.setter
def background_color(self, background_color: XABase.XAColor):
self.set_property("backgroundColor", background_color.xa_elem)
@property
def normal_text_color(self) -> XABase.XAColor:
"""The normal text color for the tab."""
return XABase.XAColor(self.xa_elem.normalTextColor())
@normal_text_color.setter
def normal_text_color(self, normal_text_color: XABase.XAColor):
self.set_property("normalTextColor", normal_text_color.xa_elem)
@property
def bold_text_color(self) -> XABase.XAColor:
"""The bold text color for the tab."""
return XABase.XAColor(self.xa_elem.boldTextColor())
@bold_text_color.setter
def bold_text_color(self, bold_text_color: XABase.XAColor):
self.set_property("boldTextColor", bold_text_color.xa_elem)
@property
def font_name(self) -> str:
"""The name of the font used to display the tab's contents."""
return self.xa_elem.fontName()
@font_name.setter
def font_name(self, font_name: str):
self.set_property("fontName", font_name)
@property
def font_size(self) -> int:
"""The size of the font used to display the tab's contents."""
return self.xa_elem.fontSize()
@font_size.setter
def font_size(self, font_size: int):
self.set_property("fontSize", font_size)
@property
def font_antialiasing(self) -> bool:
"""Whether the font used to display the tab's contents is antialiased."""
return self.xa_elem.fontAntialiasing()
@font_antialiasing.setter
def font_antialiasing(self, font_antialiasing: bool):
self.set_property("fontAntialiasing", font_antialiasing)
@property
def clean_commands(self) -> list[str]:
"""The processes which will be ignored when checking whether a tab can be closed without showing a prompt."""
return list(self.xa_elem.cleanCommands())
@clean_commands.setter
def clean_commands(self, clean_commands: list[str]):
self.set_property("cleanCommands", clean_commands)
@property
def title_displays_device_name(self) -> bool:
"""Whether the title contains the device name."""
return self.xa_elem.titleDisplaysDeviceName()
@title_displays_device_name.setter
def title_displays_device_name(self, title_displays_device_name: bool):
self.set_property("titleDisplaysDeviceName", title_displays_device_name)
@property
def title_displays_shell_path(self) -> bool:
"""Whether the title contains the shell path."""
return self.xa_elem.titleDisplaysShellPath()
@title_displays_shell_path.setter
def title_displays_shell_path(self, title_displays_shell_path: bool):
self.set_property("titleDisplaysShellPath", title_displays_shell_path)
@property
def title_displays_window_size(self) -> bool:
"""Whether the title contains the tab's size, in rows and columns."""
return self.xa_elem.titleDisplaysWindowSize()
@title_displays_window_size.setter
def title_displays_window_size(self, title_displays_window_size: bool):
self.set_property("titleDisplaysWindowSize", title_displays_window_size)
@property
def title_displays_settings_name(self) -> bool:
"""Whether the title contains the settings set name."""
return self.xa_elem.titleDisplaysSettingsName()
@title_displays_settings_name.setter
def title_displays_settings_name(self, title_displays_settings_name: bool):
self.set_property("titleDisplaysSettingsName", title_displays_settings_name)
@property
def title_displays_custom_title(self) -> bool:
"""Whether the title contains a custom title."""
return self.xa_elem.titleDisplaysCustomTitle()
@title_displays_custom_title.setter
def title_displays_custom_title(self, title_displays_custom_title: bool):
self.set_property("titleDisplaysCustomTitle", title_displays_custom_title)
@property
def custom_title(self) -> str:
"""The tab's custom title."""
return self.xa_elem.customTitle()
@custom_title.setter
def custom_title(self, custom_title: str):
self.set_property("customTitle", custom_title)
[docs]
def get_clipboard_representation(self) -> str:
"""Gets a clipboard-codable representation of the settings set.
When the clipboard content is set to a settings set, the setting set's name is added to the clipboard.
:return: The setting set's name
:rtype: str
.. versionadded:: 0.0.8
"""
return self.name
def __repr__(self):
return "<" + str(type(self)) + self.name + ">"