1
1
mirror of https://github.com/wez/wezterm.git synced 2024-12-28 07:55:03 +03:00

macos: allow running when there are no windows

Most of this commit is refactoring the spawn logic so that we
can reuse most of it to handle spawn requests when there is
no GUI window.
This commit is contained in:
Wez Furlong 2022-12-21 00:24:11 -07:00
parent f7eb13dd8d
commit 4b3660d166
No known key found for this signature in database
GPG Key ID: 7A7F66A31EC9B387
6 changed files with 218 additions and 155 deletions

View File

@ -946,15 +946,16 @@ impl Mux {
) -> anyhow::Result<Arc<dyn Domain>> { ) -> anyhow::Result<Arc<dyn Domain>> {
let domain = match domain { let domain = match domain {
SpawnTabDomain::DefaultDomain => self.default_domain(), SpawnTabDomain::DefaultDomain => self.default_domain(),
SpawnTabDomain::CurrentPaneDomain => { SpawnTabDomain::CurrentPaneDomain => match pane_id {
let pane_id = pane_id Some(pane_id) => {
.ok_or_else(|| anyhow!("CurrentPaneDomain used with no current pane"))?; let (pane_domain_id, _window_id, _tab_id) = self
let (pane_domain_id, _window_id, _tab_id) = self .resolve_pane_id(pane_id)
.resolve_pane_id(pane_id) .ok_or_else(|| anyhow!("pane_id {} invalid", pane_id))?;
.ok_or_else(|| anyhow!("pane_id {} invalid", pane_id))?; self.get_domain(pane_domain_id)
self.get_domain(pane_domain_id) .expect("resolve_pane_id to give valid domain_id")
.expect("resolve_pane_id to give valid domain_id") }
} None => self.default_domain(),
},
SpawnTabDomain::DomainId(domain_id) => self SpawnTabDomain::DomainId(domain_id) => self
.get_domain(*domain_id) .get_domain(*domain_id)
.ok_or_else(|| anyhow!("domain id {} is invalid", domain_id))?, .ok_or_else(|| anyhow!("domain id {} is invalid", domain_id))?,

View File

@ -1,8 +1,10 @@
use crate::scripting::guiwin::GuiWin; use crate::scripting::guiwin::GuiWin;
use crate::spawn::SpawnWhere;
use crate::termwindow::TermWindowNotif; use crate::termwindow::TermWindowNotif;
use crate::TermWindow; use crate::TermWindow;
use ::window::*; use ::window::*;
use anyhow::{Context, Error}; use anyhow::{Context, Error};
use config::keyassignment::{KeyAssignment, SpawnCommand};
pub use config::FrontEndSelection; pub use config::FrontEndSelection;
use mux::client::ClientId; use mux::client::ClientId;
use mux::window::WindowId as MuxWindowId; use mux::window::WindowId as MuxWindowId;
@ -98,13 +100,16 @@ impl GuiFrontEnd {
| Alert::SetUserVar { .. }, | Alert::SetUserVar { .. },
} => {} } => {}
MuxNotification::Empty => { MuxNotification::Empty => {
promise::spawn::spawn_into_main_thread(async move { #[cfg(not(target_os = "macos"))]
if mux::activity::Activity::count() == 0 { {
log::trace!("Mux is now empty, terminate gui"); promise::spawn::spawn_into_main_thread(async move {
Connection::get().unwrap().terminate_message_loop(); if mux::activity::Activity::count() == 0 {
} log::trace!("Mux is now empty, terminate gui");
}) Connection::get().unwrap().terminate_message_loop();
.detach(); }
})
.detach();
}
} }
MuxNotification::SaveToDownloads { name, data } => { MuxNotification::SaveToDownloads { name, data } => {
if !config::configuration().allow_download_protocols { if !config::configuration().allow_download_protocols {
@ -211,7 +216,45 @@ impl GuiFrontEnd {
// and the user picks an action from the menubar. // and the user picks an action from the menubar.
// This is not currently possible, but could be in the // This is not currently possible, but could be in the
// future. // future.
log::info!("perform: {action:?}");
fn spawn_command(spawn: &SpawnCommand, spawn_where: SpawnWhere) {
let config = config::configuration();
let dpi = config.dpi.unwrap_or_else(|| ::window::default_dpi()) as u32;
let size = config.initial_size(dpi);
let term_config = Arc::new(config::TermConfig::with_config(config));
crate::spawn::spawn_command_impl(spawn, spawn_where, size, None, term_config)
}
match action {
KeyAssignment::QuitApplication => {
// If we get here, there are no windows that could have received
// the QuitApplication command, therefore it must be ok to quit
// immediately
Connection::get().unwrap().terminate_message_loop();
}
KeyAssignment::SpawnWindow => {
spawn_command(&SpawnCommand::default(), SpawnWhere::NewWindow);
}
KeyAssignment::SpawnTab(spawn_where) => {
spawn_command(
&SpawnCommand {
domain: spawn_where,
..Default::default()
},
SpawnWhere::NewWindow,
);
}
KeyAssignment::SpawnCommandInNewTab(spawn) => {
spawn_command(&spawn, SpawnWhere::NewTab);
}
KeyAssignment::SpawnCommandInNewWindow(spawn) => {
spawn_command(&spawn, SpawnWhere::NewWindow);
}
_ => {
log::warn!("unhandled perform: {action:?}");
}
}
} }
ApplicationEvent::OpenInBrowser(url) => { ApplicationEvent::OpenInBrowser(url) => {
std::thread::spawn(move || { std::thread::spawn(move || {

View File

@ -45,6 +45,7 @@ mod scripting;
mod scrollbar; mod scrollbar;
mod selection; mod selection;
mod shapecache; mod shapecache;
mod spawn;
mod stats; mod stats;
mod tabbar; mod tabbar;
mod termwindow; mod termwindow;

145
wezterm-gui/src/spawn.rs Normal file
View File

@ -0,0 +1,145 @@
use anyhow::{anyhow, bail, Context};
use config::keyassignment::SpawnCommand;
use config::TermConfig;
use mux::activity::Activity;
use mux::domain::SplitSource;
use mux::tab::SplitRequest;
use mux::window::WindowId as MuxWindowId;
use mux::Mux;
use portable_pty::CommandBuilder;
use std::sync::Arc;
use wezterm_term::TerminalSize;
#[derive(Copy, Debug, Clone, Eq, PartialEq)]
pub enum SpawnWhere {
NewWindow,
NewTab,
SplitPane(SplitRequest),
}
pub fn spawn_command_impl(
spawn: &SpawnCommand,
spawn_where: SpawnWhere,
size: TerminalSize,
src_window_id: Option<MuxWindowId>,
term_config: Arc<TermConfig>,
) {
let spawn = spawn.clone();
promise::spawn::spawn(async move {
if let Err(err) =
spawn_command_internal(spawn, spawn_where, size, src_window_id, term_config).await
{
log::error!("Failed to spawn: {:#}", err);
}
})
.detach();
}
pub async fn spawn_command_internal(
spawn: SpawnCommand,
spawn_where: SpawnWhere,
size: TerminalSize,
src_window_id: Option<MuxWindowId>,
term_config: Arc<TermConfig>,
) -> anyhow::Result<()> {
let mux = Mux::get();
let activity = Activity::new();
let current_pane_id = match src_window_id {
Some(window_id) => {
if let Some(tab) = mux.get_active_tab_for_window(window_id) {
tab.get_active_pane().map(|p| p.pane_id())
} else {
None
}
}
None => None,
};
let cwd = if let Some(cwd) = spawn.cwd.as_ref() {
Some(cwd.to_str().map(|s| s.to_owned()).ok_or_else(|| {
anyhow!(
"Domain::spawn requires that the cwd be unicode in {:?}",
cwd
)
})?)
} else {
None
};
let cmd_builder = if let Some(args) = spawn.args {
let mut builder = CommandBuilder::from_argv(args.iter().map(Into::into).collect());
for (k, v) in spawn.set_environment_variables.iter() {
builder.env(k, v);
}
if let Some(cwd) = spawn.cwd {
builder.cwd(cwd);
}
Some(builder)
} else {
None
};
let workspace = mux.active_workspace().clone();
match spawn_where {
SpawnWhere::SplitPane(direction) => {
let src_window_id = match src_window_id {
Some(id) => id,
None => anyhow::bail!("no src window when splitting a pane?"),
};
if let Some(tab) = mux.get_active_tab_for_window(src_window_id) {
let pane = tab
.get_active_pane()
.ok_or_else(|| anyhow!("tab to have a pane"))?;
log::trace!("doing split_pane");
let (pane, _size) = mux
.split_pane(
// tab.tab_id(),
pane.pane_id(),
direction,
SplitSource::Spawn {
command: cmd_builder,
command_dir: cwd,
},
spawn.domain,
)
.await
.context("split_pane")?;
pane.set_config(term_config);
} else {
bail!("there is no active tab while splitting pane!?");
}
}
_ => {
let (_tab, pane, window_id) = mux
.spawn_tab_or_window(
match spawn_where {
SpawnWhere::NewWindow => None,
_ => src_window_id,
},
spawn.domain,
cmd_builder,
cwd,
size,
current_pane_id,
workspace,
)
.await
.context("spawn_tab_or_window")?;
// If it was created in this window, it copies our handlers.
// Otherwise, we'll pick them up when we later respond to
// the new window being created.
if Some(window_id) == src_window_id {
pane.set_config(term_config);
}
}
};
drop(activity);
Ok(())
}

View File

@ -78,8 +78,8 @@ pub mod resize;
mod selection; mod selection;
pub mod spawn; pub mod spawn;
pub mod webgpu; pub mod webgpu;
use crate::spawn::SpawnWhere;
use prevcursor::PrevCursorPos; use prevcursor::PrevCursorPos;
use spawn::SpawnWhere;
const ATLAS_SIZE: usize = 128; const ATLAS_SIZE: usize = 128;
@ -2669,11 +2669,11 @@ impl TermWindow {
let src_window_id = self.mux_window_id; let src_window_id = self.mux_window_id;
promise::spawn::spawn(async move { promise::spawn::spawn(async move {
if let Err(err) = Self::spawn_command_internal( if let Err(err) = crate::spawn::spawn_command_internal(
spawn, spawn,
SpawnWhere::NewWindow, SpawnWhere::NewWindow,
size, size,
src_window_id, Some(src_window_id),
term_config, term_config,
) )
.await .await

View File

@ -1,21 +1,7 @@
use crate::termwindow::MuxWindowId; use crate::spawn::SpawnWhere;
use anyhow::{anyhow, bail, Context};
use config::keyassignment::{SpawnCommand, SpawnTabDomain}; use config::keyassignment::{SpawnCommand, SpawnTabDomain};
use config::TermConfig; use config::TermConfig;
use mux::activity::Activity;
use mux::domain::SplitSource;
use mux::tab::SplitRequest;
use mux::Mux;
use portable_pty::CommandBuilder;
use std::sync::Arc; use std::sync::Arc;
use wezterm_term::TerminalSize;
#[derive(Copy, Debug, Clone, Eq, PartialEq)]
pub enum SpawnWhere {
NewWindow,
NewTab,
SplitPane(SplitRequest),
}
impl super::TermWindow { impl super::TermWindow {
pub fn spawn_command(&self, spawn: &SpawnCommand, spawn_where: SpawnWhere) { pub fn spawn_command(&self, spawn: &SpawnCommand, spawn_where: SpawnWhere) {
@ -26,126 +12,13 @@ impl super::TermWindow {
}; };
let term_config = Arc::new(TermConfig::with_config(self.config.clone())); let term_config = Arc::new(TermConfig::with_config(self.config.clone()));
Self::spawn_command_impl(spawn, spawn_where, size, self.mux_window_id, term_config) crate::spawn::spawn_command_impl(
} spawn,
spawn_where,
fn spawn_command_impl( size,
spawn: &SpawnCommand, Some(self.mux_window_id),
spawn_where: SpawnWhere, term_config,
size: TerminalSize, )
src_window_id: MuxWindowId,
term_config: Arc<TermConfig>,
) {
let spawn = spawn.clone();
promise::spawn::spawn(async move {
if let Err(err) =
Self::spawn_command_internal(spawn, spawn_where, size, src_window_id, term_config)
.await
{
log::error!("Failed to spawn: {:#}", err);
}
})
.detach();
}
pub async fn spawn_command_internal(
spawn: SpawnCommand,
spawn_where: SpawnWhere,
size: TerminalSize,
src_window_id: MuxWindowId,
term_config: Arc<TermConfig>,
) -> anyhow::Result<()> {
let mux = Mux::get();
let activity = Activity::new();
let current_pane_id = if let Some(tab) = mux.get_active_tab_for_window(src_window_id) {
tab.get_active_pane().map(|p| p.pane_id())
} else {
None
};
let cwd = if let Some(cwd) = spawn.cwd.as_ref() {
Some(cwd.to_str().map(|s| s.to_owned()).ok_or_else(|| {
anyhow!(
"Domain::spawn requires that the cwd be unicode in {:?}",
cwd
)
})?)
} else {
None
};
let cmd_builder = if let Some(args) = spawn.args {
let mut builder = CommandBuilder::from_argv(args.iter().map(Into::into).collect());
for (k, v) in spawn.set_environment_variables.iter() {
builder.env(k, v);
}
if let Some(cwd) = spawn.cwd {
builder.cwd(cwd);
}
Some(builder)
} else {
None
};
let workspace = mux.active_workspace().clone();
match spawn_where {
SpawnWhere::SplitPane(direction) => {
if let Some(tab) = mux.get_active_tab_for_window(src_window_id) {
let pane = tab
.get_active_pane()
.ok_or_else(|| anyhow!("tab to have a pane"))?;
log::trace!("doing split_pane");
let (pane, _size) = mux
.split_pane(
// tab.tab_id(),
pane.pane_id(),
direction,
SplitSource::Spawn {
command: cmd_builder,
command_dir: cwd,
},
spawn.domain,
)
.await
.context("split_pane")?;
pane.set_config(term_config);
} else {
bail!("there is no active tab while splitting pane!?");
}
}
_ => {
let (_tab, pane, window_id) = mux
.spawn_tab_or_window(
match spawn_where {
SpawnWhere::NewWindow => None,
_ => Some(src_window_id),
},
spawn.domain,
cmd_builder,
cwd,
size,
current_pane_id,
workspace,
)
.await
.context("spawn_tab_or_window")?;
// If it was created in this window, it copies our handlers.
// Otherwise, we'll pick them up when we later respond to
// the new window being created.
if window_id == src_window_id {
pane.set_config(term_config);
}
}
};
drop(activity);
Ok(())
} }
pub fn spawn_tab(&mut self, domain: &SpawnTabDomain) { pub fn spawn_tab(&mut self, domain: &SpawnTabDomain) {