123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132(** Terminal color querying utilities.
Provides functions to query xterm-compatible terminals for their current
foreground and background colors. *)(** Terminal foreground and background colors. *)typeterminal_colours={fg:(Gg.v4,string)result;(** Foreground color or error message *)bg:(Gg.v4,string)result;(** Background color or error message *)}(*
xterm compatible terminals should support xlib queries
which allow us to learn the current foreground and background
colours used
for more heuristics which may work on other terminals see:
https://github.com/egberts/shell-term-background/blob/master/term-background.bash
*)(** Xterm-compatible terminal query functions. *)moduleXterm=struct(** Set terminal to raw mode for direct input/output.
@param set_when When to apply the settings (default: TCSAFLUSH)
Translation of Python's `tty.setraw`
https://github.com/python/cpython/blob/main/Lib/tty.py#L18 *)letset_raw?(set_when=Unix.TCSAFLUSH)fd=letmode:Unix.terminal_io={(Unix.tcgetattrfd)withc_brkint=false;c_icrnl=false;c_inpck=false;c_istrip=false;c_ixon=false;c_opost=false;c_csize=8;c_parenb=false;c_echo=false;c_icanon=false;(* c_iexten = false; ...does not exist on Unix.terminal_io *)c_ixoff=false;(* IEXTEN and IXOFF appear to set the same bit *)c_isig=false;c_vmin=1;c_vtime=0;}inUnix.tcsetattrfdset_whenmode(** Query terminal using xterm control codes (internal implementation).
Based on a Python version here:
https://stackoverflow.com/a/45467190/202168 *)letqueryfdcode=letfdname=iffd==Unix.stdinthen"stdin"elseiffd==Unix.stdoutthen"stdout"elseiffd==Unix.stderrthen"stderr"else"fd"inifUnix.isattyfdthenletold_settings=Unix.tcgetattrfdinset_rawfd;Fun.protect~finally:(fun()->Unix.tcsetattrfdUnix.TCSADRAINold_settings)(fun()->Printf.printf"\o033]%s;?\o007"code;flushstdout;letr,_,_=Unix.select[fd][][]0.1inletbuf=Bytes.create256in(* Printf.printf ">> len r: %d\n" (List.length r); *)letreadlen=matchList.exists(fun(el)->el==fd)rwith|true->Unix.readfdbuf0256|false->failwith@@Printf.sprintf"Nothing to read on [%s]"fdnamein(* Printf.printf ">> len buf: %d\n" readlen; *)Bytes.subbuf0readlen|>Bytes.escaped|>Bytes.to_string)elseinvalid_arg@@Printf.sprintf"[%s] is not a tty"fdname(** Query terminal using xterm control codes.
@param fd File descriptor to query (must be a TTY)
@param code Control code to query (e.g., "10" for foreground, "11" for background)
@return Result with terminal response string or error message *)letqueryfdcode=tryOk(queryfdcode)withFailuree|Invalid_argumente->Errore(** Convert hexadecimal string to 8-bit integer (0-255) with scaling.
Used for parsing xterm color responses.
Translates a hexadecimal string, of any width, to an 8-bit int.
The value will be 'scaled' according to number of hex chars,
where each char is worth 4-bits.
e.g.
C -> C/F * FF = 204
CCCC -> CCCC/FFFF * FF = 204
C3 -> C3/FF * FF = 195
C3B -> C3B/FFF * FF = 195
C3C3 -> C3C3/FFFF * FF = 195
see: https://stackoverflow.com/q/70962440/202168 *)lethex_to_8bits=ifString.lengths=0theninvalid_arg"hex_to_8bit: empty string";letscale=(16.**float_of_int(String.lengths))-.1.inletvalue=int_of_string@@Printf.sprintf"0x%s"sinfloat_of_intvalue/.scale*.255.|>Utils.int_round(** Parse xterm RGB color string (format: "rgb:RRRR/GGGG/BBBB").
@param s Color string from terminal query response
@return Result with parsed color or error message
xterm returns colours in a 48-bit hex format *)letparse_colours=letrex=Re.Pcre.re{|rgb:([0-9a-f]{1,4})/([0-9a-f]{1,4})/([0-9a-f]{1,4})|}|>Re.compileinmatchRe.exec_optrexswith|Somegroups->letr=Re.Group.getgroups1inletg=Re.Group.getgroups2inletb=Re.Group.getgroups3inOk(Color.Rgb.(v(hex_to_8bitr)(hex_to_8bitg)(hex_to_8bitb)|>to_gg))|None->Error(Printf.sprintf"Unrecognised colour string: %s"s)(** Get current terminal foreground and background colors.
@param fd File descriptor to query (typically Unix.stdin)
@return Record with fg and bg colors (or errors) *)letget_coloursfd={fg=queryfd"10"|>Result.mapparse_colour|>Result.join;bg=queryfd"11"|>Result.mapparse_colour|>Result.join;}end