mirror of
https://github.com/asciinema/asciinema.git
synced 2025-12-15 19:28:00 +01:00
Query for XTVERSION, pass it to file writer and stream forwarder
This commit is contained in:
@@ -31,7 +31,7 @@ use crate::pty;
|
||||
use crate::server;
|
||||
use crate::session::{self, KeyBindings, SessionStarter};
|
||||
use crate::stream::Stream;
|
||||
use crate::tty::{DevTty, FixedSizeTty, NullTty};
|
||||
use crate::tty::{DevTty, FixedSizeTty, NullTty, Tty};
|
||||
use crate::util;
|
||||
|
||||
impl cli::Session {
|
||||
@@ -43,6 +43,7 @@ impl cli::Session {
|
||||
let keys = get_key_bindings(cmd_config)?;
|
||||
let notifier = notifier::threaded(get_notifier(config));
|
||||
let record_input = self.input || cmd_config.input;
|
||||
let term_version = self.get_term_version()?;
|
||||
let env = capture_env(self.env.clone(), cmd_config);
|
||||
|
||||
let path = self
|
||||
@@ -53,7 +54,15 @@ impl cli::Session {
|
||||
|
||||
let file_writer = path
|
||||
.as_ref()
|
||||
.map(|path| self.get_file_writer(path, cmd_config, &env, notifier.clone()))
|
||||
.map(|path| {
|
||||
self.get_file_writer(
|
||||
path,
|
||||
cmd_config,
|
||||
term_version.clone(),
|
||||
&env,
|
||||
notifier.clone(),
|
||||
)
|
||||
})
|
||||
.transpose()?;
|
||||
|
||||
let mut listener = self
|
||||
@@ -66,7 +75,7 @@ impl cli::Session {
|
||||
let mut relay = self
|
||||
.relay
|
||||
.take()
|
||||
.map(|target| get_relay(target, config, &env))
|
||||
.map(|target| get_relay(target, config, term_version, &env))
|
||||
.transpose()?;
|
||||
|
||||
let relay_id = relay.as_ref().map(|r| r.id());
|
||||
@@ -212,6 +221,7 @@ impl cli::Session {
|
||||
&self,
|
||||
path: &str,
|
||||
config: &config::Session,
|
||||
term_version: Option<String>,
|
||||
env: &HashMap<String, String>,
|
||||
notifier: N,
|
||||
) -> Result<FileWriterStarter> {
|
||||
@@ -256,7 +266,7 @@ impl cli::Session {
|
||||
0
|
||||
};
|
||||
|
||||
let metadata = self.build_asciicast_metadata(env, config);
|
||||
let metadata = self.build_asciicast_metadata(term_version, env, config);
|
||||
let notifier = Box::new(notifier);
|
||||
|
||||
let writer = match format {
|
||||
@@ -300,12 +310,17 @@ impl cli::Session {
|
||||
Ok(writer)
|
||||
}
|
||||
|
||||
fn get_term_version(&self) -> Result<Option<String>> {
|
||||
self.get_tty().map(|tty| tty.get_version())
|
||||
}
|
||||
|
||||
fn get_command(&self, config: &config::Session) -> Option<String> {
|
||||
self.command.as_ref().cloned().or(config.command.clone())
|
||||
}
|
||||
|
||||
fn build_asciicast_metadata(
|
||||
&self,
|
||||
term_version: Option<String>,
|
||||
env: &HashMap<String, String>,
|
||||
config: &config::Session,
|
||||
) -> Metadata {
|
||||
@@ -313,6 +328,7 @@ impl cli::Session {
|
||||
let command = self.get_command(config);
|
||||
|
||||
Metadata {
|
||||
term_version,
|
||||
idle_time_limit,
|
||||
command,
|
||||
title: self.title.clone(),
|
||||
@@ -320,7 +336,7 @@ impl cli::Session {
|
||||
}
|
||||
}
|
||||
|
||||
fn get_tty(&self) -> Result<FixedSizeTty> {
|
||||
fn get_tty(&self) -> Result<impl Tty> {
|
||||
let (cols, rows) = self.tty_size.unwrap_or((None, None));
|
||||
|
||||
if self.headless {
|
||||
@@ -370,11 +386,16 @@ impl Relay {
|
||||
}
|
||||
}
|
||||
|
||||
fn get_relay(target: RelayTarget, config: &Config, env: &HashMap<String, String>) -> Result<Relay> {
|
||||
fn get_relay(
|
||||
target: RelayTarget,
|
||||
config: &Config,
|
||||
term_version: Option<String>,
|
||||
env: &HashMap<String, String>,
|
||||
) -> Result<Relay> {
|
||||
match target {
|
||||
RelayTarget::StreamId(id) => {
|
||||
let stream = api::create_user_stream(id, config)?;
|
||||
let ws_producer_url = build_producer_url(&stream.ws_producer_url, env)?;
|
||||
let ws_producer_url = build_producer_url(&stream.ws_producer_url, term_version, env)?;
|
||||
|
||||
Ok(Relay {
|
||||
ws_producer_url,
|
||||
@@ -389,16 +410,24 @@ fn get_relay(target: RelayTarget, config: &Config, env: &HashMap<String, String>
|
||||
}
|
||||
}
|
||||
|
||||
fn build_producer_url(url: &str, env: &HashMap<String, String>) -> Result<Url> {
|
||||
fn build_producer_url(
|
||||
url: &str,
|
||||
term_version: Option<String>,
|
||||
env: &HashMap<String, String>,
|
||||
) -> Result<Url> {
|
||||
let mut url: Url = url.parse()?;
|
||||
let term = env::var("TERM").ok().unwrap_or_default();
|
||||
let term_type = env::var("TERM").ok().unwrap_or_default();
|
||||
let shell = env::var("SHELL").ok().unwrap_or_default();
|
||||
|
||||
let mut params = vec![
|
||||
("term[type]".to_string(), term.clone()),
|
||||
("term[type]".to_string(), term_type.clone()),
|
||||
("shell".to_string(), shell.clone()),
|
||||
];
|
||||
|
||||
if let Some(version) = term_version {
|
||||
params.push(("term[version]".to_string(), version));
|
||||
}
|
||||
|
||||
for (k, v) in env {
|
||||
params.push((format!("env[{k}]"), v.to_string()));
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ pub struct FileWriter {
|
||||
}
|
||||
|
||||
pub struct Metadata {
|
||||
pub term_version: Option<String>,
|
||||
pub idle_time_limit: Option<f64>,
|
||||
pub command: Option<String>,
|
||||
pub title: Option<String>,
|
||||
|
||||
154
src/tty.rs
154
src/tty.rs
@@ -45,6 +45,7 @@ impl From<TtySize> for (u16, u16) {
|
||||
pub trait Tty: io::Write + io::Read + AsFd {
|
||||
fn get_size(&self) -> pty::Winsize;
|
||||
fn get_theme(&self) -> Option<TtyTheme>;
|
||||
fn get_version(&self) -> Option<String>;
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
@@ -70,6 +71,77 @@ impl DevTty {
|
||||
|
||||
Ok(Self { file })
|
||||
}
|
||||
|
||||
fn query(&self, query: &str) -> Result<Vec<u8>> {
|
||||
let mut query = query.to_string().into_bytes();
|
||||
query.extend_from_slice(b"\x1b[c");
|
||||
let mut query = &query[..];
|
||||
let mut response = Vec::new();
|
||||
let mut buf = [0u8; 1024];
|
||||
let fd = self.as_fd().as_raw_fd();
|
||||
|
||||
loop {
|
||||
let mut timeout = TimeVal::new(0, 100_000);
|
||||
let mut rfds = FdSet::new();
|
||||
let mut wfds = FdSet::new();
|
||||
rfds.insert(self);
|
||||
|
||||
if !query.is_empty() {
|
||||
wfds.insert(self);
|
||||
}
|
||||
|
||||
match select(None, &mut rfds, &mut wfds, None, &mut timeout) {
|
||||
Ok(0) => break,
|
||||
|
||||
Ok(_) => {
|
||||
if rfds.contains(self) {
|
||||
let n = unistd::read(fd, &mut buf)?;
|
||||
response.extend_from_slice(&buf[..n]);
|
||||
let mut reversed = response.iter().rev();
|
||||
let mut got_da_response = false;
|
||||
let mut da_len = 0;
|
||||
|
||||
if let Some(b'c') = reversed.next() {
|
||||
da_len += 1;
|
||||
|
||||
for b in reversed {
|
||||
if *b == b'[' {
|
||||
got_da_response = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if *b != b';' && *b != b'?' && !b.is_ascii_digit() {
|
||||
break;
|
||||
}
|
||||
|
||||
da_len += 1;
|
||||
}
|
||||
}
|
||||
|
||||
if got_da_response {
|
||||
response.truncate(response.len() - da_len - 2);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if wfds.contains(self) {
|
||||
let n = unistd::write(fd, query)?;
|
||||
query = &query[n..];
|
||||
}
|
||||
}
|
||||
|
||||
Err(e) => {
|
||||
if e == Errno::EINTR {
|
||||
continue;
|
||||
} else {
|
||||
return Err(e.into());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(response)
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_color(rgb: &str) -> Option<RGB8> {
|
||||
@@ -89,7 +161,9 @@ fn parse_color(rgb: &str) -> Option<RGB8> {
|
||||
Some(RGB8::new(r, g, b))
|
||||
}
|
||||
|
||||
static COLORS_QUERY: &[u8; 151] = b"\x1b]10;?\x07\x1b]11;?\x07\x1b]4;0;?\x07\x1b]4;1;?\x07\x1b]4;2;?\x07\x1b]4;3;?\x07\x1b]4;4;?\x07\x1b]4;5;?\x07\x1b]4;6;?\x07\x1b]4;7;?\x07\x1b]4;8;?\x07\x1b]4;9;?\x07\x1b]4;10;?\x07\x1b]4;11;?\x07\x1b]4;12;?\x07\x1b]4;13;?\x07\x1b]4;14;?\x07\x1b]4;15;?\x07\x1b[c";
|
||||
static COLORS_QUERY: &str = "\x1b]10;?\x07\x1b]11;?\x07\x1b]4;0;?\x07\x1b]4;1;?\x07\x1b]4;2;?\x07\x1b]4;3;?\x07\x1b]4;4;?\x07\x1b]4;5;?\x07\x1b]4;6;?\x07\x1b]4;7;?\x07\x1b]4;8;?\x07\x1b]4;9;?\x07\x1b]4;10;?\x07\x1b]4;11;?\x07\x1b]4;12;?\x07\x1b]4;13;?\x07\x1b]4;14;?\x07\x1b]4;15;?\x07";
|
||||
|
||||
static XTVERSION_QUERY: &str = "\x1b[>0q";
|
||||
|
||||
impl Tty for DevTty {
|
||||
fn get_size(&self) -> pty::Winsize {
|
||||
@@ -106,65 +180,7 @@ impl Tty for DevTty {
|
||||
}
|
||||
|
||||
fn get_theme(&self) -> Option<TtyTheme> {
|
||||
let mut query = &COLORS_QUERY[..];
|
||||
let mut response = Vec::new();
|
||||
let mut buf = [0u8; 1024];
|
||||
let fd = self.as_fd().as_raw_fd();
|
||||
|
||||
loop {
|
||||
let mut timeout = TimeVal::new(0, 100_000);
|
||||
let mut rfds = FdSet::new();
|
||||
let mut wfds = FdSet::new();
|
||||
rfds.insert(self);
|
||||
|
||||
if !query.is_empty() {
|
||||
wfds.insert(self);
|
||||
}
|
||||
|
||||
match select(None, &mut rfds, &mut wfds, None, &mut timeout) {
|
||||
Ok(0) => return None,
|
||||
|
||||
Ok(_) => {
|
||||
if rfds.contains(self) {
|
||||
let n = unistd::read(fd, &mut buf).ok()?;
|
||||
response.extend_from_slice(&buf[..n]);
|
||||
let mut reversed = response.iter().rev();
|
||||
let mut got_da_response = false;
|
||||
|
||||
if let Some(b'c') = reversed.next() {
|
||||
for b in reversed {
|
||||
if *b == b'[' {
|
||||
got_da_response = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if *b != b';' && *b != b'?' && !b.is_ascii_digit() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if got_da_response {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if wfds.contains(self) {
|
||||
let n = unistd::write(fd, query).ok()?;
|
||||
query = &query[n..];
|
||||
}
|
||||
}
|
||||
|
||||
Err(e) => {
|
||||
if e == Errno::EINTR {
|
||||
continue;
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let response = self.query(COLORS_QUERY).ok()?;
|
||||
let response = String::from_utf8_lossy(response.as_slice());
|
||||
let mut colors = response.match_indices("rgb:");
|
||||
let (idx, _) = colors.next()?;
|
||||
@@ -181,6 +197,16 @@ impl Tty for DevTty {
|
||||
|
||||
Some(TtyTheme { fg, bg, palette })
|
||||
}
|
||||
|
||||
fn get_version(&self) -> Option<String> {
|
||||
let response = self.query(XTVERSION_QUERY).ok()?;
|
||||
|
||||
if let [b'\x1b', b'P', b'>', b'|', version @ .., b'\x1b', b'\\'] = &response[..] {
|
||||
Some(String::from_utf8_lossy(version).to_string())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl io::Read for DevTty {
|
||||
@@ -233,6 +259,10 @@ impl Tty for NullTty {
|
||||
fn get_theme(&self) -> Option<TtyTheme> {
|
||||
None
|
||||
}
|
||||
|
||||
fn get_version(&self) -> Option<String> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl io::Read for NullTty {
|
||||
@@ -291,6 +321,10 @@ impl Tty for FixedSizeTty {
|
||||
fn get_theme(&self) -> Option<TtyTheme> {
|
||||
self.inner.get_theme()
|
||||
}
|
||||
|
||||
fn get_version(&self) -> Option<String> {
|
||||
self.inner.get_version()
|
||||
}
|
||||
}
|
||||
|
||||
impl AsFd for FixedSizeTty {
|
||||
|
||||
Reference in New Issue
Block a user