mirror of
https://github.com/asciinema/asciinema.git
synced 2025-12-16 03:38:03 +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::server;
|
||||||
use crate::session::{self, KeyBindings, SessionStarter};
|
use crate::session::{self, KeyBindings, SessionStarter};
|
||||||
use crate::stream::Stream;
|
use crate::stream::Stream;
|
||||||
use crate::tty::{DevTty, FixedSizeTty, NullTty};
|
use crate::tty::{DevTty, FixedSizeTty, NullTty, Tty};
|
||||||
use crate::util;
|
use crate::util;
|
||||||
|
|
||||||
impl cli::Session {
|
impl cli::Session {
|
||||||
@@ -43,6 +43,7 @@ impl cli::Session {
|
|||||||
let keys = get_key_bindings(cmd_config)?;
|
let keys = get_key_bindings(cmd_config)?;
|
||||||
let notifier = notifier::threaded(get_notifier(config));
|
let notifier = notifier::threaded(get_notifier(config));
|
||||||
let record_input = self.input || cmd_config.input;
|
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 env = capture_env(self.env.clone(), cmd_config);
|
||||||
|
|
||||||
let path = self
|
let path = self
|
||||||
@@ -53,7 +54,15 @@ impl cli::Session {
|
|||||||
|
|
||||||
let file_writer = path
|
let file_writer = path
|
||||||
.as_ref()
|
.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()?;
|
.transpose()?;
|
||||||
|
|
||||||
let mut listener = self
|
let mut listener = self
|
||||||
@@ -66,7 +75,7 @@ impl cli::Session {
|
|||||||
let mut relay = self
|
let mut relay = self
|
||||||
.relay
|
.relay
|
||||||
.take()
|
.take()
|
||||||
.map(|target| get_relay(target, config, &env))
|
.map(|target| get_relay(target, config, term_version, &env))
|
||||||
.transpose()?;
|
.transpose()?;
|
||||||
|
|
||||||
let relay_id = relay.as_ref().map(|r| r.id());
|
let relay_id = relay.as_ref().map(|r| r.id());
|
||||||
@@ -212,6 +221,7 @@ impl cli::Session {
|
|||||||
&self,
|
&self,
|
||||||
path: &str,
|
path: &str,
|
||||||
config: &config::Session,
|
config: &config::Session,
|
||||||
|
term_version: Option<String>,
|
||||||
env: &HashMap<String, String>,
|
env: &HashMap<String, String>,
|
||||||
notifier: N,
|
notifier: N,
|
||||||
) -> Result<FileWriterStarter> {
|
) -> Result<FileWriterStarter> {
|
||||||
@@ -256,7 +266,7 @@ impl cli::Session {
|
|||||||
0
|
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 notifier = Box::new(notifier);
|
||||||
|
|
||||||
let writer = match format {
|
let writer = match format {
|
||||||
@@ -300,12 +310,17 @@ impl cli::Session {
|
|||||||
Ok(writer)
|
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> {
|
fn get_command(&self, config: &config::Session) -> Option<String> {
|
||||||
self.command.as_ref().cloned().or(config.command.clone())
|
self.command.as_ref().cloned().or(config.command.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_asciicast_metadata(
|
fn build_asciicast_metadata(
|
||||||
&self,
|
&self,
|
||||||
|
term_version: Option<String>,
|
||||||
env: &HashMap<String, String>,
|
env: &HashMap<String, String>,
|
||||||
config: &config::Session,
|
config: &config::Session,
|
||||||
) -> Metadata {
|
) -> Metadata {
|
||||||
@@ -313,6 +328,7 @@ impl cli::Session {
|
|||||||
let command = self.get_command(config);
|
let command = self.get_command(config);
|
||||||
|
|
||||||
Metadata {
|
Metadata {
|
||||||
|
term_version,
|
||||||
idle_time_limit,
|
idle_time_limit,
|
||||||
command,
|
command,
|
||||||
title: self.title.clone(),
|
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));
|
let (cols, rows) = self.tty_size.unwrap_or((None, None));
|
||||||
|
|
||||||
if self.headless {
|
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 {
|
match target {
|
||||||
RelayTarget::StreamId(id) => {
|
RelayTarget::StreamId(id) => {
|
||||||
let stream = api::create_user_stream(id, config)?;
|
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 {
|
Ok(Relay {
|
||||||
ws_producer_url,
|
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 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 shell = env::var("SHELL").ok().unwrap_or_default();
|
||||||
|
|
||||||
let mut params = vec![
|
let mut params = vec![
|
||||||
("term[type]".to_string(), term.clone()),
|
("term[type]".to_string(), term_type.clone()),
|
||||||
("shell".to_string(), shell.clone()),
|
("shell".to_string(), shell.clone()),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
if let Some(version) = term_version {
|
||||||
|
params.push(("term[version]".to_string(), version));
|
||||||
|
}
|
||||||
|
|
||||||
for (k, v) in env {
|
for (k, v) in env {
|
||||||
params.push((format!("env[{k}]"), v.to_string()));
|
params.push((format!("env[{k}]"), v.to_string()));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ pub struct FileWriter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub struct Metadata {
|
pub struct Metadata {
|
||||||
|
pub term_version: Option<String>,
|
||||||
pub idle_time_limit: Option<f64>,
|
pub idle_time_limit: Option<f64>,
|
||||||
pub command: Option<String>,
|
pub command: Option<String>,
|
||||||
pub title: 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 {
|
pub trait Tty: io::Write + io::Read + AsFd {
|
||||||
fn get_size(&self) -> pty::Winsize;
|
fn get_size(&self) -> pty::Winsize;
|
||||||
fn get_theme(&self) -> Option<TtyTheme>;
|
fn get_theme(&self) -> Option<TtyTheme>;
|
||||||
|
fn get_version(&self) -> Option<String>;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@@ -70,6 +71,77 @@ impl DevTty {
|
|||||||
|
|
||||||
Ok(Self { file })
|
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> {
|
fn parse_color(rgb: &str) -> Option<RGB8> {
|
||||||
@@ -89,7 +161,9 @@ fn parse_color(rgb: &str) -> Option<RGB8> {
|
|||||||
Some(RGB8::new(r, g, b))
|
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 {
|
impl Tty for DevTty {
|
||||||
fn get_size(&self) -> pty::Winsize {
|
fn get_size(&self) -> pty::Winsize {
|
||||||
@@ -106,65 +180,7 @@ impl Tty for DevTty {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn get_theme(&self) -> Option<TtyTheme> {
|
fn get_theme(&self) -> Option<TtyTheme> {
|
||||||
let mut query = &COLORS_QUERY[..];
|
let response = self.query(COLORS_QUERY).ok()?;
|
||||||
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 = String::from_utf8_lossy(response.as_slice());
|
let response = String::from_utf8_lossy(response.as_slice());
|
||||||
let mut colors = response.match_indices("rgb:");
|
let mut colors = response.match_indices("rgb:");
|
||||||
let (idx, _) = colors.next()?;
|
let (idx, _) = colors.next()?;
|
||||||
@@ -181,6 +197,16 @@ impl Tty for DevTty {
|
|||||||
|
|
||||||
Some(TtyTheme { fg, bg, palette })
|
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 {
|
impl io::Read for DevTty {
|
||||||
@@ -233,6 +259,10 @@ impl Tty for NullTty {
|
|||||||
fn get_theme(&self) -> Option<TtyTheme> {
|
fn get_theme(&self) -> Option<TtyTheme> {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_version(&self) -> Option<String> {
|
||||||
|
None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl io::Read for NullTty {
|
impl io::Read for NullTty {
|
||||||
@@ -291,6 +321,10 @@ impl Tty for FixedSizeTty {
|
|||||||
fn get_theme(&self) -> Option<TtyTheme> {
|
fn get_theme(&self) -> Option<TtyTheme> {
|
||||||
self.inner.get_theme()
|
self.inner.get_theme()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_version(&self) -> Option<String> {
|
||||||
|
self.inner.get_version()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AsFd for FixedSizeTty {
|
impl AsFd for FixedSizeTty {
|
||||||
|
|||||||
Reference in New Issue
Block a user