1
1
mirror of https://github.com/wez/wezterm.git synced 2024-11-10 15:04:32 +03:00

implement tabs in the glutin gui system

There's a bunch of code duplication that I want to clean up later.
This commit is contained in:
Wez Furlong 2019-02-23 12:55:09 -08:00
parent 5b126b3f1e
commit db5aac6681
5 changed files with 409 additions and 75 deletions

View File

@ -7,12 +7,12 @@ use crate::guiloop::glutinloop::GuiEventLoop;
use crate::guiloop::SessionTerminated;
use crate::opengl::render::Renderer;
use crate::opengl::textureatlas::OutOfTextureSpace;
use crate::Child;
use crate::MasterPty;
use crate::{openpty, Child, MasterPty};
use clipboard::{ClipboardContext, ClipboardProvider};
use glium;
use glium::glutin::dpi::{LogicalPosition, LogicalSize, PhysicalPosition};
use glium::glutin::{self, ElementState, MouseCursor};
use std::cell::RefCell;
use std::io::Write;
use std::rc::Rc;
use term::KeyCode;
@ -23,10 +23,20 @@ use termwiz::hyperlink::Hyperlink;
#[cfg(target_os = "macos")]
use winit::os::macos::WindowExt;
static TAB_ID: ::std::sync::atomic::AtomicUsize = ::std::sync::atomic::ATOMIC_USIZE_INIT;
pub type TabId = usize;
/// Implements `TerminalHost` for a Tab.
/// `TabHost` instances are short lived and borrow references to
/// other state.
struct TabHost<'a> {
pty: &'a mut MasterPty,
host: &'a mut Host,
}
struct Host {
event_loop: Rc<GuiEventLoop>,
display: glium::Display,
pty: MasterPty,
clipboard: Clipboard,
window_position: Option<LogicalPosition>,
/// if is_some, holds position to be restored after exiting
@ -66,7 +76,7 @@ impl Clipboard {
}
}
impl term::TerminalHost for Host {
impl<'a> term::TerminalHost for TabHost<'a> {
fn writer(&mut self) -> &mut Write {
&mut self.pty
}
@ -78,20 +88,22 @@ impl term::TerminalHost for Host {
}
fn get_clipboard(&mut self) -> Result<String, Error> {
self.clipboard.get_clipboard()
self.host.clipboard.get_clipboard()
}
fn set_clipboard(&mut self, clip: Option<String>) -> Result<(), Error> {
self.clipboard.set_clipboard(clip)
self.host.clipboard.set_clipboard(clip)
}
fn set_title(&mut self, title: &str) {
self.display.gl_window().set_title(title);
fn set_title(&mut self, _title: &str) {
// activate_tab_relative calls Terminal::update_title()
// in the appropriate context
self.activate_tab_relative(0);
}
fn toggle_full_screen(&mut self) {
let window = self.display.gl_window();
if let Some(pos) = self.is_fullscreen.take() {
let window = self.host.display.gl_window();
if let Some(pos) = self.host.is_fullscreen.take() {
// Use simple fullscreen mode on macos, as wez personally
// prefers the faster transition to/from this mode than
// the Lion+ slow transition to a new Space. This could
@ -106,7 +118,7 @@ impl term::TerminalHost for Host {
// We use our own idea of the position because get_position()
// appears to only return the initial position of the window
// on Linux.
self.is_fullscreen = self.window_position.take();
self.host.is_fullscreen = self.host.window_position.take();
#[cfg(target_os = "macos")]
window.set_simple_fullscreen(true);
@ -116,48 +128,143 @@ impl term::TerminalHost for Host {
}
fn new_window(&mut self) {
self.event_loop.request_spawn_window().ok();
self.host.event_loop.request_spawn_window().ok();
}
fn new_tab(&mut self) {
self.event_loop
.request_spawn_tab(self.display.gl_window().id())
self.host
.event_loop
.request_spawn_tab(self.host.display.gl_window().id())
.ok();
}
fn activate_tab(&mut self, tab: usize) {
self.host
.event_loop
.request_activate_tab(self.host.display.gl_window().id(), tab)
.ok();
}
fn activate_tab_relative(&mut self, tab: isize) {
self.host
.event_loop
.request_activate_tab_relative(self.host.display.gl_window().id(), tab)
.ok();
}
fn increase_font_size(&mut self) {
self.event_loop
.request_increase_font_size(self.display.gl_window().id())
self.host
.event_loop
.request_increase_font_size(self.host.display.gl_window().id())
.ok();
}
fn decrease_font_size(&mut self) {
self.event_loop
.request_decrease_font_size(self.display.gl_window().id())
self.host
.event_loop
.request_decrease_font_size(self.host.display.gl_window().id())
.ok();
}
fn reset_font_size(&mut self) {
self.event_loop
.request_reset_font_size(self.display.gl_window().id())
self.host
.event_loop
.request_reset_font_size(self.host.display.gl_window().id())
.ok();
}
}
struct Tab {
tab_id: TabId,
terminal: RefCell<Terminal>,
process: RefCell<Child>,
pty: RefCell<MasterPty>,
}
impl Tab {
fn new(terminal: Terminal, process: Child, pty: MasterPty) -> Self {
let tab_id = TAB_ID.fetch_add(1, ::std::sync::atomic::Ordering::Relaxed);
Self {
tab_id,
terminal: RefCell::new(terminal),
process: RefCell::new(process),
pty: RefCell::new(pty),
}
}
}
impl Drop for Tab {
fn drop(&mut self) {
// Avoid lingering zombies
self.process.borrow_mut().kill().ok();
self.process.borrow_mut().wait().ok();
}
}
struct Tabs {
tabs: Vec<Tab>,
active: usize,
}
impl Tabs {
fn new(tab: Tab) -> Self {
Self {
tabs: vec![tab],
active: 0,
}
}
fn get_by_id(&self, id: TabId) -> Result<&Tab, Error> {
for t in &self.tabs {
if t.tab_id == id {
return Ok(t);
}
}
bail!("no such tab id {}", id)
}
fn idx_by_id(&self, id: TabId) -> Option<usize> {
for (idx, t) in self.tabs.iter().enumerate() {
if t.tab_id == id {
return Some(idx);
}
}
None
}
fn remove_by_id(&mut self, id: TabId) {
if let Some(idx) = self.idx_by_id(id) {
self.tabs.remove(idx);
let len = self.tabs.len();
if len > 0 && self.active == idx && idx >= len {
self.set_active(len - 1);
}
}
}
fn get_active(&self) -> &Tab {
&self.tabs[self.active]
}
fn set_active(&mut self, idx: usize) {
assert!(idx < self.tabs.len());
self.active = idx;
self.tabs[idx].terminal.borrow_mut().make_all_lines_dirty();
}
}
pub struct TerminalWindow {
host: Host,
_event_loop: Rc<GuiEventLoop>,
_config: Rc<Config>,
event_loop: Rc<GuiEventLoop>,
config: Rc<Config>,
fonts: Rc<FontConfiguration>,
renderer: Renderer,
width: u16,
height: u16,
cell_height: usize,
cell_width: usize,
terminal: Terminal,
process: Child,
last_mouse_coords: PhysicalPosition,
last_modifiers: KeyModifiers,
allow_received_character: bool,
tabs: Tabs,
}
impl TerminalWindow {
@ -202,7 +309,6 @@ impl TerminalWindow {
let host = Host {
event_loop: Rc::clone(event_loop),
display,
pty,
clipboard: Clipboard::default(),
window_position,
is_fullscreen: None,
@ -214,35 +320,107 @@ impl TerminalWindow {
let cell_height = cell_height.ceil() as usize;
let cell_width = cell_width.ceil() as usize;
let tab = Tab::new(terminal, process, pty);
Ok(TerminalWindow {
host,
_event_loop: Rc::clone(event_loop),
_config: Rc::clone(config),
event_loop: Rc::clone(event_loop),
config: Rc::clone(config),
fonts: Rc::clone(fonts),
renderer,
width,
height,
cell_height,
cell_width,
terminal,
process,
last_mouse_coords: PhysicalPosition::new(0.0, 0.0),
last_modifiers: Default::default(),
allow_received_character: false,
tabs: Tabs::new(tab),
})
}
pub fn get_tab_id_by_idx(&self, tab_idx: usize) -> usize {
self.tabs.tabs[tab_idx].tab_id
}
pub fn spawn_tab(&mut self) -> Result<(), Error> {
let rows = (self.height as usize + 1) / self.cell_height;
let cols = (self.width as usize + 1) / self.cell_width;
let (pty, slave) = openpty(rows as u16, cols as u16, self.width, self.height)?;
let cmd = self.config.build_prog(None)?;
let process = slave.spawn_command(cmd)?;
eprintln!("spawned: {:?}", process);
let terminal = term::Terminal::new(
rows,
cols,
self.config.scrollback_lines.unwrap_or(3500),
self.config.hyperlink_rules.clone(),
);
let cloned_pty = pty.try_clone()?;
let tab = Tab::new(terminal, process, pty);
self.event_loop
.schedule_read_pty(cloned_pty, self.window_id(), tab.tab_id)?;
self.tabs.tabs.push(tab);
let len = self.tabs.tabs.len();
self.activate_tab(len - 1)?;
Ok(())
}
pub fn activate_tab(&mut self, tab: usize) -> Result<(), Error> {
let max = self.tabs.tabs.len();
if tab < max {
self.tabs.set_active(tab);
self.update_title();
}
Ok(())
}
pub fn activate_tab_relative(&mut self, delta: isize) -> Result<(), Error> {
let max = self.tabs.tabs.len();
let active = self.tabs.active as isize;
let tab = active + delta;
let tab = if tab < 0 { max as isize + tab } else { tab };
self.activate_tab(tab as usize % max)
}
fn update_title(&mut self) {
let num_tabs = self.tabs.tabs.len();
if num_tabs == 0 {
return;
}
let tab_no = self.tabs.active;
let terminal = self.tabs.get_active().terminal.borrow();
let title = terminal.get_title();
let window = self.host.display.gl_window();
if num_tabs == 1 {
window.set_title(title)
} else {
window.set_title(&format!("[{}/{}] {}", tab_no + 1, num_tabs, title));
}
}
pub fn window_id(&self) -> glutin::WindowId {
self.host.display.gl_window().id()
}
pub fn pty(&self) -> &MasterPty {
&self.host.pty
pub fn clone_current_pty(&self) -> Result<MasterPty, Error> {
self.tabs.get_active().pty.borrow().try_clone()
}
pub fn paint(&mut self) -> Result<(), Error> {
let mut target = self.host.display.draw();
let res = self.renderer.paint(&mut target, &mut self.terminal);
let res = self.renderer.paint(
&mut target,
&mut self.tabs.get_active().terminal.borrow_mut(),
);
// Ensure that we finish() the target before we let the
// error bubble up, otherwise we lose the context.
target.finish().unwrap();
@ -255,7 +433,11 @@ impl TerminalWindow {
if let Some(&OutOfTextureSpace { size }) = err.downcast_ref::<OutOfTextureSpace>() {
eprintln!("out of texture space, allocating {}", size);
self.renderer.recreate_atlas(&self.host.display, size)?;
self.terminal.make_all_lines_dirty();
self.tabs
.get_active()
.terminal
.borrow_mut()
.make_all_lines_dirty();
// Recursively initiate a new paint
return self.paint();
}
@ -265,8 +447,18 @@ impl TerminalWindow {
}
}
pub fn process_data_read_from_pty(&mut self, data: &[u8]) {
self.terminal.advance_bytes(data, &mut self.host)
pub fn process_data_read_from_pty(&mut self, data: &[u8], tab_id: usize) -> Result<(), Error> {
let tab = self.tabs.get_by_id(tab_id)?;
tab.terminal.borrow_mut().advance_bytes(
data,
&mut TabHost {
pty: &mut *tab.pty.borrow_mut(),
host: &mut self.host,
},
);
Ok(())
}
fn resize_surfaces(&mut self, size: LogicalSize) -> Result<bool, Error> {
@ -288,8 +480,13 @@ impl TerminalWindow {
// so optimistically pretend that we have that extra pixel!
let rows = ((height as usize + 1) / self.cell_height) as u16;
let cols = ((width as usize + 1) / self.cell_width) as u16;
self.host.pty.resize(rows, cols, width, height)?;
self.terminal.resize(rows as usize, cols as usize);
for tab in &self.tabs.tabs {
tab.pty.borrow_mut().resize(rows, cols, width, height)?;
tab.terminal
.borrow_mut()
.resize(rows as usize, cols as usize);
}
Ok(true)
} else {
@ -322,7 +519,7 @@ impl TerminalWindow {
) -> Result<(), Error> {
self.last_mouse_coords = position;
let (x, y): (i32, i32) = position.into();
self.terminal.mouse_event(
self.tabs.get_active().terminal.borrow_mut().mouse_event(
term::MouseEvent {
kind: MouseEventKind::Move,
button: MouseButton::None,
@ -330,7 +527,10 @@ impl TerminalWindow {
y: (y as usize / self.cell_height) as i64,
modifiers: Self::decode_modifiers(modifiers),
},
&mut self.host,
&mut TabHost {
pty: &mut *self.tabs.get_active().pty.borrow_mut(),
host: &mut self.host,
},
)?;
// Deliberately not forcing a paint on mouse move as it
// makes selection feel sluggish
@ -338,7 +538,14 @@ impl TerminalWindow {
// When hovering over a hyperlink, show an appropriate
// mouse cursor to give the cue that it is clickable
let cursor = if self.terminal.current_highlight().is_some() {
let cursor = if self
.tabs
.get_active()
.terminal
.borrow()
.current_highlight()
.is_some()
{
MouseCursor::Hand
} else {
MouseCursor::Text
@ -354,7 +561,7 @@ impl TerminalWindow {
button: glutin::MouseButton,
modifiers: glium::glutin::ModifiersState,
) -> Result<(), Error> {
self.terminal.mouse_event(
self.tabs.get_active().terminal.borrow_mut().mouse_event(
term::MouseEvent {
kind: match state {
ElementState::Pressed => MouseEventKind::Press,
@ -370,7 +577,10 @@ impl TerminalWindow {
y: (self.last_mouse_coords.y as usize / self.cell_height) as i64,
modifiers: Self::decode_modifiers(modifiers),
},
&mut self.host,
&mut TabHost {
pty: &mut *self.tabs.get_active().pty.borrow_mut(),
host: &mut self.host,
},
)?;
self.paint_if_needed()?;
@ -414,7 +624,7 @@ impl TerminalWindow {
_ => return Ok(()),
};
for _ in 0..times {
self.terminal.mouse_event(
self.tabs.get_active().terminal.borrow_mut().mouse_event(
term::MouseEvent {
kind: MouseEventKind::Press,
button,
@ -422,7 +632,10 @@ impl TerminalWindow {
y: (self.last_mouse_coords.y as usize / self.cell_height) as i64,
modifiers: Self::decode_modifiers(modifiers),
},
&mut self.host,
&mut TabHost {
pty: &mut *self.tabs.get_active().pty.borrow_mut(),
host: &mut self.host,
},
)?;
}
self.paint_if_needed()?;
@ -633,8 +846,23 @@ impl TerminalWindow {
if let Some(key) = Self::keycode_from_input(&event) {
// debug!("event {:?} -> {:?}", event, key);
match event.state {
ElementState::Pressed => self.terminal.key_down(key, mods, &mut self.host)?,
ElementState::Released => self.terminal.key_up(key, mods, &mut self.host)?,
ElementState::Pressed => self.tabs.get_active().terminal.borrow_mut().key_down(
key,
mods,
&mut TabHost {
pty: &mut *self.tabs.get_active().pty.borrow_mut(),
host: &mut self.host,
},
)?,
ElementState::Released => self.tabs.get_active().terminal.borrow_mut().key_up(
key,
mods,
&mut TabHost {
pty: &mut *self.tabs.get_active().pty.borrow_mut(),
host: &mut self.host,
},
)?,
}
} else {
eprintln!("event {:?} with no mapping", event);
@ -644,7 +872,10 @@ impl TerminalWindow {
}
pub fn paint_if_needed(&mut self) -> Result<(), Error> {
if self.terminal.has_dirty_lines() {
if self.tabs.tabs.is_empty() {
return Ok(());
}
if self.tabs.get_active().terminal.borrow().has_dirty_lines() {
self.paint()?;
}
Ok(())
@ -659,7 +890,11 @@ impl TerminalWindow {
"TerminalWindow::scaling_changed dpi_scale={} font_scale={}",
dpi_scale, font_scale
);
self.terminal.make_all_lines_dirty();
self.tabs
.get_active()
.terminal
.borrow_mut()
.make_all_lines_dirty();
self.fonts.change_scaling(font_scale, dpi_scale);
let metrics = self.fonts.default_font_metrics()?;
@ -716,10 +951,13 @@ impl TerminalWindow {
// eprintln!("ReceivedCharacter {} {:?}", c as u32, c);
if self.allow_received_character {
self.allow_received_character = false;
self.terminal.key_down(
self.tabs.get_active().terminal.borrow_mut().key_down(
KeyCode::Char(c),
self.last_modifiers,
&mut self.host,
&mut TabHost {
pty: &mut *self.tabs.get_active().pty.borrow_mut(),
host: &mut self.host,
},
)?;
self.paint_if_needed()?;
}
@ -775,11 +1013,31 @@ impl TerminalWindow {
Ok(())
}
pub fn test_for_child_exit(&mut self) -> Result<(), SessionTerminated> {
match self.process.try_wait() {
Ok(Some(status)) => Err(SessionTerminated::ProcessStatus { status }),
Ok(None) => Ok(()),
Err(e) => Err(SessionTerminated::Error { err: e.into() }),
pub fn tab_did_terminate(&mut self, tab_id: TabId) {
self.tabs.remove_by_id(tab_id);
if !self.tabs.tabs.is_empty() {
self.tabs
.get_active()
.terminal
.borrow_mut()
.make_all_lines_dirty();
self.update_title();
}
}
pub fn test_for_child_exit(&mut self) -> bool {
let dead_tabs: Vec<TabId> = self
.tabs
.tabs
.iter()
.filter_map(|tab| match tab.process.borrow_mut().try_wait() {
Ok(None) => None,
_ => Some(tab.tab_id),
})
.collect();
for tab_id in dead_tabs {
self.tab_did_terminate(tab_id);
}
self.tabs.tabs.is_empty()
}
}

View File

@ -57,8 +57,15 @@ pub fn channel<T: Send>(proxy: EventsLoopProxy) -> (GuiSender<T>, Receiver<T>) {
#[derive(Clone)]
pub enum IOEvent {
Data { window_id: WindowId, data: Vec<u8> },
Terminated { window_id: WindowId },
Data {
window_id: WindowId,
tab_id: usize,
data: Vec<u8>,
},
Terminated {
window_id: WindowId,
tab_id: usize,
},
}
/// This struct holds references to Windows.
@ -74,6 +81,8 @@ enum SpawnRequest {
FontLarger(WindowId),
FontSmaller(WindowId),
FontReset(WindowId),
ActivateTab(WindowId, usize),
ActivateTabRelative(WindowId, isize),
}
/// The `GuiEventLoop` represents the combined gui event processor,
@ -116,8 +125,16 @@ impl GlutinGuiSystem {
Ok(SpawnRequest::Window) => {
crate::spawn_window(&*self, None, config, fonts)?;
}
Ok(SpawnRequest::Tab(_window_id)) => {
eprintln!("Spawning tabs is not yet implemented for glutin");
Ok(SpawnRequest::Tab(window_id)) => {
if let Some(window) = self
.event_loop
.windows
.borrow_mut()
.by_id
.get_mut(&window_id)
{
window.spawn_tab().ok();
}
}
Ok(SpawnRequest::FontLarger(window_id)) => {
let scale = fonts.get_font_scale() * 1.1;
@ -154,6 +171,28 @@ impl GlutinGuiSystem {
window.scaling_changed(Some(1.0)).ok();
}
}
Ok(SpawnRequest::ActivateTab(window_id, tab)) => {
if let Some(window) = self
.event_loop
.windows
.borrow_mut()
.by_id
.get_mut(&window_id)
{
window.activate_tab(tab).ok();
}
}
Ok(SpawnRequest::ActivateTabRelative(window_id, tab)) => {
if let Some(window) = self
.event_loop
.windows
.borrow_mut()
.by_id
.get_mut(&window_id)
{
window.activate_tab_relative(tab).ok();
}
}
Err(TryRecvError::Empty) => return Ok(()),
Err(err) => bail!("spawn_rx disconnected {:?}", err),
}
@ -262,14 +301,26 @@ impl GuiEventLoop {
self.spawn_tx.send(SpawnRequest::Tab(window_id))
}
/// Add a window to the event loop and run it.
pub fn add_window(&self, window: gliumwindows::TerminalWindow) -> Result<(), Error> {
let window_id = window.window_id();
let mut pty = window.pty().try_clone()?;
pty.clear_nonblocking()?;
let mut windows = self.windows.borrow_mut();
windows.by_id.insert(window_id, window);
pub fn request_activate_tab(&self, window_id: WindowId, tabidx: usize) -> Result<(), Error> {
self.spawn_tx
.send(SpawnRequest::ActivateTab(window_id, tabidx))
}
pub fn request_activate_tab_relative(
&self,
window_id: WindowId,
tab: isize,
) -> Result<(), Error> {
self.spawn_tx
.send(SpawnRequest::ActivateTabRelative(window_id, tab))
}
pub fn schedule_read_pty(
&self,
mut pty: MasterPty,
window_id: WindowId,
tab_id: usize,
) -> Result<(), Error> {
pty.clear_nonblocking()?;
let tx = self.poll_tx.clone();
thread::spawn(move || {
@ -281,6 +332,7 @@ impl GuiEventLoop {
if tx
.send(IOEvent::Data {
window_id,
tab_id,
data: buf[0..size].to_vec(),
})
.is_err()
@ -289,8 +341,8 @@ impl GuiEventLoop {
}
}
Err(err) => {
eprintln!("window {:?} {:?}", window_id, err);
tx.send(IOEvent::Terminated { window_id }).ok();
eprintln!("window {:?} {} {:?}", window_id, tab_id, err);
tx.send(IOEvent::Terminated { window_id, tab_id }).ok();
return;
}
}
@ -300,6 +352,16 @@ impl GuiEventLoop {
Ok(())
}
/// Add a window to the event loop and run it.
pub fn add_window(&self, window: gliumwindows::TerminalWindow) -> Result<(), Error> {
let window_id = window.window_id();
let pty = window.clone_current_pty()?;
self.schedule_read_pty(pty, window_id, window.get_tab_id_by_idx(0))?;
let mut windows = self.windows.borrow_mut();
windows.by_id.insert(window_id, window);
Ok(())
}
/// Process a single winit event
fn process_gui_event(
&self,
@ -369,15 +431,24 @@ impl GuiEventLoop {
_ => {}
}
match self.poll_rx.try_recv() {
Ok(IOEvent::Data { window_id, data }) => {
Ok(IOEvent::Data {
window_id,
tab_id,
data,
}) => {
let mut windows = self.windows.borrow_mut();
windows.by_id.get_mut(&window_id).map(|window| {
window.process_data_read_from_pty(&data);
window.process_data_read_from_pty(&data, tab_id).ok();
Some(())
});
}
Ok(IOEvent::Terminated { window_id }) => {
self.schedule_window_close(window_id)?;
Ok(IOEvent::Terminated { window_id, tab_id }) => {
eprintln!("window {:?} tab {} terminated", window_id, tab_id);
let mut windows = self.windows.borrow_mut();
windows.by_id.get_mut(&window_id).map(|window| {
window.tab_did_terminate(tab_id);
Some(())
});
}
Err(TryRecvError::Empty) => return Ok(()),
Err(err) => bail!("poll_rx disconnected {:?}", err),
@ -405,8 +476,8 @@ impl GuiEventLoop {
.by_id
.iter_mut()
.filter_map(|(window_id, window)| match window.test_for_child_exit() {
Ok(_) => None,
Err(_) => Some(*window_id),
false => None,
true => Some(*window_id),
})
.collect();

View File

@ -81,6 +81,7 @@ pub mod glutinloop;
pub mod x11;
#[derive(Debug, Fail)]
#[allow(dead_code)]
pub enum SessionTerminated {
#[fail(display = "Process exited: {:?}", status)]
ProcessStatus { status: ExitStatus },

View File

@ -79,7 +79,7 @@ impl Tabs {
if let Some(idx) = self.index_for_fd(fd) {
self.tabs.remove(idx);
let len = self.tabs.len();
if self.active == idx && idx >= len {
if len > 0 && self.active == idx && idx >= len {
self.set_active(len - 1);
}
}
@ -575,6 +575,9 @@ impl TerminalWindow {
fn update_title(&mut self) {
let num_tabs = self.tabs.tabs.len();
if num_tabs == 0 {
return;
}
let tab_no = self.tabs.active;
let terminal = self.tabs.get_active().terminal.borrow();

View File

@ -1814,6 +1814,7 @@ impl<'a> Performer<'a> {
match osc {
OperatingSystemCommand::SetIconNameAndWindowTitle(title)
| OperatingSystemCommand::SetWindowTitle(title) => {
self.title = title.clone();
self.host.set_title(&title);
}
OperatingSystemCommand::SetIconName(_) => {}