Source code for PyXA.apps.Terminal

""".. 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 + ">"