Skip to main content

iterm2_client/
window.rs

1//! High-level handle to an iTerm2 window.
2
3use crate::connection::Connection;
4use crate::error::{Error, Result};
5use crate::proto;
6use crate::request;
7use crate::validate;
8use std::sync::Arc;
9use tokio::io::{AsyncRead, AsyncWrite};
10
11/// A handle to an iTerm2 window.
12pub struct Window<S> {
13    /// The unique window identifier.
14    pub id: String,
15    conn: Arc<Connection<S>>,
16}
17
18impl<S: AsyncRead + AsyncWrite + Unpin + Send + 'static> Window<S> {
19    /// Create a window handle. Validates the window ID.
20    pub fn new(id: String, conn: Arc<Connection<S>>) -> Result<Self> {
21        validate::identifier(&id, "window")?;
22        Ok(Self { id, conn })
23    }
24
25    pub(crate) fn new_unchecked(id: String, conn: Arc<Connection<S>>) -> Self {
26        Self { id, conn }
27    }
28
29    /// Create a new tab in this window, optionally using a named profile.
30    pub async fn create_tab(&self, profile_name: Option<&str>) -> Result<CreateTabResult> {
31        let resp = self
32            .conn
33            .call(request::create_tab(profile_name, Some(&self.id)))
34            .await?;
35        match resp.submessage {
36            Some(proto::server_originated_message::Submessage::CreateTabResponse(r)) => {
37                check_status_i32(r.status, "CreateTab")?;
38                Ok(CreateTabResult {
39                    tab_id: r.tab_id.map(|id| id.to_string()),
40                    session_id: r.session_id,
41                    window_id: r.window_id,
42                })
43            }
44            _ => Err(Error::UnexpectedResponse {
45                expected: "CreateTabResponse",
46            }),
47        }
48    }
49
50    /// Activate this window (bring it to the front).
51    pub async fn activate(&self) -> Result<()> {
52        let resp = self.conn.call(request::activate_window(&self.id)).await?;
53        match resp.submessage {
54            Some(proto::server_originated_message::Submessage::ActivateResponse(r)) => {
55                check_status_i32(r.status, "Activate")
56            }
57            _ => Err(Error::UnexpectedResponse {
58                expected: "ActivateResponse",
59            }),
60        }
61    }
62
63    /// Close this window. If `force` is true, skip the confirmation prompt.
64    pub async fn close(&self, force: bool) -> Result<()> {
65        let resp = self
66            .conn
67            .call(request::close_windows(vec![self.id.clone()], force))
68            .await?;
69        match resp.submessage {
70            Some(proto::server_originated_message::Submessage::CloseResponse(_)) => Ok(()),
71            _ => Err(Error::UnexpectedResponse {
72                expected: "CloseResponse",
73            }),
74        }
75    }
76
77    /// Get a window property (e.g. `"frame"`, `"fullscreen"`). Returns JSON-encoded value.
78    pub async fn get_property(&self, name: &str) -> Result<Option<String>> {
79        let resp = self
80            .conn
81            .call(request::get_property_window(&self.id, name))
82            .await?;
83        match resp.submessage {
84            Some(proto::server_originated_message::Submessage::GetPropertyResponse(r)) => {
85                check_status_i32(r.status, "GetProperty")?;
86                Ok(r.json_value)
87            }
88            _ => Err(Error::UnexpectedResponse {
89                expected: "GetPropertyResponse",
90            }),
91        }
92    }
93
94    /// Set a window property. Value must be valid JSON.
95    pub async fn set_property(&self, name: &str, json_value: &str) -> Result<()> {
96        validate::json_value(json_value)?;
97        let resp = self
98            .conn
99            .call(request::set_property_window(&self.id, name, json_value))
100            .await?;
101        match resp.submessage {
102            Some(proto::server_originated_message::Submessage::SetPropertyResponse(r)) => {
103                check_status_i32(r.status, "SetProperty")
104            }
105            _ => Err(Error::UnexpectedResponse {
106                expected: "SetPropertyResponse",
107            }),
108        }
109    }
110
111    /// Get a window variable by name. Returns JSON-encoded value.
112    pub async fn get_variable(&self, name: &str) -> Result<Option<String>> {
113        let resp = self
114            .conn
115            .call(request::get_variable_window(
116                &self.id,
117                vec![name.to_string()],
118            ))
119            .await?;
120        match resp.submessage {
121            Some(proto::server_originated_message::Submessage::VariableResponse(r)) => {
122                check_status_i32(r.status, "Variable")?;
123                Ok(r.values.into_iter().next())
124            }
125            _ => Err(Error::UnexpectedResponse {
126                expected: "VariableResponse",
127            }),
128        }
129    }
130
131    /// Get a reference to the underlying connection.
132    pub fn connection(&self) -> &Connection<S> {
133        &self.conn
134    }
135}
136
137/// Result of [`Window::create_tab`].
138pub struct CreateTabResult {
139    pub tab_id: Option<String>,
140    pub session_id: Option<String>,
141    pub window_id: Option<String>,
142}
143
144fn check_status_i32(status: Option<i32>, op: &str) -> Result<()> {
145    match status {
146        Some(0) | None => Ok(()),
147        Some(code) => Err(Error::Status(format!("{op} returned status {code}"))),
148    }
149}