module Tput::Response

Overview

Terminal query/response.

Where Probe round-trips a batch of detection queries at startup, this module exposes individual, on-demand queries: write a request escape sequence and synchronously read the terminal's reply, parsed into a typed result. Reuses Probe's low-level reply readers (probe_read_byte/probe_read_csi/probe_read_osc, probe_ints, parse_rgb).

Counterpart of Blessed's Program#response family (getCursor, #dsr/deviceStatus, #da/sendDeviceAttributes, getCursorColor, getTextParams, getWindowSize, requestParameters, requestLocatorPosition). Blessed dispatches replies asynchronously through its input EventEmitter; tput.cr has no such emitter, so — like probe! — each query reads its reply synchronously, with a timeout.

NOTE since the read is synchronous and pulls straight from @input, a query must not run concurrently with an active Input#listen loop (they'd race for the same bytes). Issue outside the main input loop, same constraint as probe!.

Included Modules

Direct including types

Defined in:

tput/response.cr

Constant Summary

RESPONSE_TIMEOUT = 2.seconds

Default per-reply timeout. A responsive terminal answers almost immediately; this only bounds the wait when the terminal stays silent.

Instance Method Summary

Instance Method Detail

def da(param = "", timeout : Time::Span = RESPONSE_TIMEOUT) #

A bare * only opens the keyword-only section — there is nothing to forward for it. Everything after it must be passed by name. A bare * only opens the keyword-only section — there is nothing to forward for it. Everything after it must be passed by name. :nodoc:


def da2(timeout : Time::Span = RESPONSE_TIMEOUT) #

A bare * only opens the keyword-only section — there is nothing to forward for it. Everything after it must be passed by name. :nodoc:


def decreqtparm(param = 0, timeout : Time::Span = RESPONSE_TIMEOUT) #

A bare * only opens the keyword-only section — there is nothing to forward for it. Everything after it must be passed by name. A bare * only opens the keyword-only section — there is nothing to forward for it. Everything after it must be passed by name. :nodoc:


def decrqlp(param = "", timeout : Time::Span = RESPONSE_TIMEOUT) #

A bare * only opens the keyword-only section — there is nothing to forward for it. Everything after it must be passed by name. A bare * only opens the keyword-only section — there is nothing to forward for it. Everything after it must be passed by name. :nodoc:


def device_status(param = 0, dec = false, timeout : Time::Span = RESPONSE_TIMEOUT) : Array(Int32) | Nil #

Device Status Report (CSI Ps n, or CSI ? Ps n when dec is true). Returns the numeric parameters of the reply (for param 6 this is the CPR row ; col), or nil on no answer.


[View source]
def dsr(param = 0, dec = false, timeout : Time::Span = RESPONSE_TIMEOUT) #

A bare * only opens the keyword-only section — there is nothing to forward for it. Everything after it must be passed by name. A bare * only opens the keyword-only section — there is nothing to forward for it. Everything after it must be passed by name. A bare * only opens the keyword-only section — there is nothing to forward for it. Everything after it must be passed by name. :nodoc:


def get_cell_size_pixels(timeout : Time::Span = RESPONSE_TIMEOUT) : Tuple(Int32, Int32) | Nil #

Requests the cell size in pixels via XTWINOPS 16 (CSI 16 t) and returns {height, width} in pixels, or nil on no answer. Most direct source for a terminal's true cell aspect ratio. Zero-valued replies (see #get_text_area_size_pixels) are treated as no answer.


[View source]
def get_clipboard(selection : String = "c", timeout : Time::Span = RESPONSE_TIMEOUT) : String | Nil #

OSC 52: reads the terminal clipboard selection ("c", "p", …) and returns its text, or nil if no answer (many terminals allow clipboard writes but disable reads for security).


[View source]
def get_cursor(timeout : Time::Span = RESPONSE_TIMEOUT) #

A bare * only opens the keyword-only section — there is nothing to forward for it. Everything after it must be passed by name. :nodoc:


def get_cursor_color(timeout : Time::Span = RESPONSE_TIMEOUT) : RGB | Nil #

Queries the hardware cursor color (OSC 12) and returns it as an RGB, or nil if the terminal does not report one.


[View source]
def get_text_area_size(timeout : Time::Span = RESPONSE_TIMEOUT) #

A bare * only opens the keyword-only section — there is nothing to forward for it. Everything after it must be passed by name. :nodoc:


def get_text_area_size_pixels(timeout : Time::Span = RESPONSE_TIMEOUT) : Tuple(Int32, Int32) | Nil #

Requests the text-area size in pixels via XTWINOPS 14 (CSI 14 t) and returns {height, width} in pixels, or nil on no answer. Replies whose dimensions are zero (some terminals, and many multiplexers, answer with 0s when they have no real pixel grid) are treated as no answer.


[View source]
def get_text_params(param : Int32, timeout : Time::Span = RESPONSE_TIMEOUT) : String | Nil #

Queries a terminal text parameter (OSC Ps ; ? BEL) and returns its text value (Pt), or nil on no answer.


[View source]
def get_window_size(timeout : Time::Span = RESPONSE_TIMEOUT) : Tuple(Int32, Int32) | Nil #

Requests the text-area size via XTWINOPS 18 (CSI 18 t) and returns {height, width} in character cells, or nil on no answer.


[View source]
def read_clipboard_response(io : IO, timeout : Time::Span) : String | Nil #

Parses an OSC 52 clipboard reply (OSC 52 ; <selection> ; <base64> ST) and returns the decoded text.


[View source]
def read_color_scheme_response(io : IO, timeout : Time::Span) : ColorScheme | Nil #

Parses a color-scheme reply (CSI ? 997 ; Ps n) into a ColorScheme.


[View source]
def read_cursor_color_response(io : IO, timeout : Time::Span) : RGB | Nil #

Parses an OSC 12 cursor-color reply into an RGB.


[View source]
def read_cursor_response(io : IO, timeout : Time::Span) : Point | Nil #

Parses a CPR reply (CSI row ; col R) into a 0-based Point.


[View source]
def read_decrqm_response(io : IO, timeout : Time::Span, mode : Int32) : Bool | Nil #

Parses a DECRQM reply (CSI ? mode ; Ps $ y). Returns true if mode is recognized (Ps 1–4), false if not (Ps 0), nil on a mismatched or absent reply.


[View source]
def read_device_attributes_response(io : IO, timeout : Time::Span) : Array(Int32) | Nil #

Parses a DA1 reply (CSI ? … c).


[View source]
def read_device_status_response(io : IO, timeout : Time::Span) : Array(Int32) | Nil #

Parses a DSR/CPR reply (final n for status, R for cursor position).


[View source]
def read_locator_position_response(io : IO, timeout : Time::Span) : Array(Int32) | Nil #

Parses a DECRQLP locator-position reply (CSI … & w). The & intermediate is stripped before the parameters are parsed.


[View source]
def read_pixel_size_response(io : IO, timeout : Time::Span) : Tuple(Int32, Int32) | Nil #

Parses an XTWINOPS pixel-size reply, accepting only the report whose op code is in codes — op 4 (CSI 4 ; h ; w t) answers CSI 14 t (text-area pixels), op 6 (CSI 6 ; h ; w t) answers CSI 16 t (cell size). The two aren't interchangeable, so each caller filters on its own op code (same disambiguation as #read_window_size_response for op 8). With no codes given, both are accepted.

Rejects zero dimensions: a terminal with no real pixel grid (notably tmux/screen) may answer with 0s, an unusable size.


[View source]
def read_pixel_size_response(io : IO, timeout : Time::Span, *codes : Int32) : Tuple(Int32, Int32) | Nil #

Parses an XTWINOPS pixel-size reply, accepting only the report whose op code is in codes — op 4 (CSI 4 ; h ; w t) answers CSI 14 t (text-area pixels), op 6 (CSI 6 ; h ; w t) answers CSI 16 t (cell size). The two aren't interchangeable, so each caller filters on its own op code (same disambiguation as #read_window_size_response for op 8). With no codes given, both are accepted.

Rejects zero dimensions: a terminal with no real pixel grid (notably tmux/screen) may answer with 0s, an unusable size.


[View source]
def read_request_parameters_response(io : IO, timeout : Time::Span) : Array(Int32) | Nil #

Parses a DECREQTPARM reply (CSI … x).


[View source]
def read_text_params_response(io : IO, timeout : Time::Span, param : Int32) : String | Nil #

Parses an OSC text-parameter reply (OSC param ; Pt) and returns Pt.


[View source]
def read_window_size_response(io : IO, timeout : Time::Span) : Tuple(Int32, Int32) | Nil #

Parses an XTWINOPS text-area-in-characters reply (CSI 8 ; height ; width t).


[View source]
def read_xtgettcap_response(io : IO, timeout : Time::Span, expected : Int32 = 1) : Hash(String, String) | Nil #

Parses an XTGETTCAP reply (DCS 1 + r <name>=<value>;… ST on success, DCS 0 + r … ST when nothing was recognized). Names and values arrive hex-encoded. Returns the decoded {name => value} pairs, merging across multiple DCS replies until expected names are seen (xterm sends one reply per capability; kitty/foot batch them into one).


[View source]
def read_xtversion_response(io : IO, timeout : Time::Span) : String | Nil #

Parses an XTVERSION reply (DCS > | <name> ST) and returns <name>.


[View source]
def report_cursor(timeout : Time::Span = RESPONSE_TIMEOUT) : Point | Nil #

Requests the current cursor position (DSR 6 / CPR, CSI 6 n) and returns it as a 0-based Point (matching Tput#cursor), or nil if the terminal cannot be queried or does not answer.


[View source]
def req_mouse_pos(param = "", timeout : Time::Span = RESPONSE_TIMEOUT) #

A bare * only opens the keyword-only section — there is nothing to forward for it. Everything after it must be passed by name. A bare * only opens the keyword-only section — there is nothing to forward for it. Everything after it must be passed by name. :nodoc:


def request_color_scheme(timeout : Time::Span = RESPONSE_TIMEOUT) : ColorScheme | Nil #

Queries the terminal's current color scheme via CSI ? 996 n; the reply CSI ? 997 ; Ps n gives the scheme (Ps 1 = dark, 2 = light). Returns the ColorScheme, or nil if the terminal does not answer.


[View source]
def request_locator_position(param = "", timeout : Time::Span = RESPONSE_TIMEOUT) : Array(Int32) | Nil #

Requests the current locator position (DECRQLP, CSI Ps ' |) and returns the reported parameters (Pe ; Prow ; Pcol ; Ppage), or nil.


[View source]
def request_parameters(param = 0, timeout : Time::Span = RESPONSE_TIMEOUT) : Array(Int32) | Nil #

Requests Terminal Parameters (DECREQTPARM, CSI Ps x) and returns the reported parameters, or nil on no answer.


[View source]
def request_termcap(*names : String, timeout : Time::Span = RESPONSE_TIMEOUT) : Hash(String, String) | Nil #

XTGETTCAP (DCS + q <names> ST): queries the terminal directly for one or more terminfo/termcap capabilities by name (e.g. "TN" terminal name, "Co" max colors, "RGB"), returning a {name => value} hash of the ones recognized (empty if none, nil if no answer). Values are hex-decoded per the protocol. Lets a program read capabilities straight from the terminal when terminfo is absent or stale (kitty, foot, WezTerm, recent xterm, …).


[View source]
def request_terminal_version(timeout : Time::Span = RESPONSE_TIMEOUT) : String | Nil #

Requests the terminal name and version via XTVERSION (CSI > 0 q) and returns the reported string (e.g. "kitty(0.32.0)", "WezTerm …"), or nil if the terminal does not answer.


[View source]
def save_reported_cursor(timeout : Time::Span = RESPONSE_TIMEOUT) : Bool #

Queries the cursor position and stores it for #restore_reported_cursor. Returns true if the terminal reported a position.


[View source]
def secondary_device_attributes(timeout : Time::Span = RESPONSE_TIMEOUT) : Array(Int32) | Nil #

Sends a Secondary Device Attributes request (DA2, CSI > c) and returns the reported parameters [type, version, keyboard] (e.g. [0, 276, 0]), or nil on no answer. More reliable than env-var heuristics for identifying the terminal and its version.


[View source]
def send_device_attributes(param = "", timeout : Time::Span = RESPONSE_TIMEOUT) : Array(Int32) | Nil #

Sends a Device Attributes request (DA1, CSI Ps c) and returns the reported attribute parameters (e.g. [62, 1, 6]), or nil on no answer.


[View source]
def supports_color_scheme_notifications?(timeout : Time::Span = RESPONSE_TIMEOUT) : Bool | Nil #

Whether the terminal supports color-scheme change notifications (DEC mode 2031).


[View source]
def supports_grapheme_clustering?(timeout : Time::Span = RESPONSE_TIMEOUT) : Bool | Nil #

Whether the terminal supports Unicode grapheme clustering (DEC mode 2027).


[View source]
def supports_in_band_resize?(timeout : Time::Span = RESPONSE_TIMEOUT) : Bool | Nil #

Whether the terminal supports in-band resize notifications (DEC private mode 2048). Also auto-detected at startup into Features#in_band_resize?.


[View source]
def supports_private_mode?(mode : Int32, timeout : Time::Span = RESPONSE_TIMEOUT) : Bool | Nil #

Queries whether the terminal supports a DEC private mode via DECRQM (CSI ? Pd $ p). Returns true if the terminal reports the mode as recognized (reply Ps of 1–4), false if not recognized, nil on no answer. Used e.g. to detect synchronized output (mode 2026).


[View source]
def supports_synchronized_output?(timeout : Time::Span = RESPONSE_TIMEOUT) : Bool | Nil #

Whether the terminal supports synchronized output (DEC private mode 2026).


[View source]
def xtgettcap(*names : String, timeout : Time::Span = RESPONSE_TIMEOUT) #

A bare * only opens the keyword-only section — there is nothing to forward for it. Everything after it must be passed by name. A bare * only opens the keyword-only section — there is nothing to forward for it. Everything after it must be passed by name. :nodoc:


def xtversion(timeout : Time::Span = RESPONSE_TIMEOUT) #

A bare * only opens the keyword-only section — there is nothing to forward for it. Everything after it must be passed by name. :nodoc: