1
1
mirror of https://github.com/wez/wezterm.git synced 2024-11-22 13:16:39 +03:00

Add wezterm start --domain DOMAIN --attach + gui-attached event

The motivation here was to remove some similar but not quite the same
logic that existed for starting up when using `wezterm connect`.

Now `wezterm connect DOMAIN` is implemented as `wezterm start --domain
DOMAIN --attach --always-new-process` and a little extra hand-wave to
ensure that the default domain is set correctly.

The startup events have been refactored a bit; a new gui-attached
event is emitted after attaching and starting any default programs
in the selected domain at startup.

That event can be used to maximize windows and so on, if desired.

The gui-attached event is independent of the startup command and fires
for `wezterm connect`, which `gui-startup` did not (and could not) do.
This commit is contained in:
Wez Furlong 2023-01-26 15:50:49 -07:00
parent f15bb186f4
commit d7145561c2
No known key found for this signature in database
GPG Key ID: 7A7F66A31EC9B387
5 changed files with 195 additions and 100 deletions

View File

@ -60,6 +60,8 @@ As features stabilize some brief notes about them will accumulate here.
[tab:rotate_counter_clockwise()](config/lua/MuxTab/rotate_counter_clockwise.md). [tab:rotate_counter_clockwise()](config/lua/MuxTab/rotate_counter_clockwise.md).
[tab:rotate_counter_clockwise()](config/lua/MuxTab/rotate_counter_clockwise.md). [tab:rotate_counter_clockwise()](config/lua/MuxTab/rotate_counter_clockwise.md).
* [wezterm.config_builder()](config/lua/wezterm/config_builder.md) * [wezterm.config_builder()](config/lua/wezterm/config_builder.md)
* [gui-attached](config/lua/gui-events/gui-attached.md) event provides some
more flexibility at startup.
#### Fixed #### Fixed
* X11: hanging or killing the IME could hang wezterm * X11: hanging or killing the IME could hang wezterm
@ -116,6 +118,9 @@ As features stabilize some brief notes about them will accumulate here.
shown, which meant that a number of minor config issues could be overlooked. shown, which meant that a number of minor config issues could be overlooked.
* Referencing `wezterm.GLOBAL` now returns references rather than copies, making * Referencing `wezterm.GLOBAL` now returns references rather than copies, making
it less cumbersome to code read/modify/write with global state it less cumbersome to code read/modify/write with global state
* `wezterm start` now accepts `--domain` and `--attach` options. `wezterm
connect DOMAIN` is now implemented internally as `wezterm start --domain
DOMAIN --attach`.
#### Updated #### Updated
* Bundled harfbuzz updated to version 6.0.0 * Bundled harfbuzz updated to version 6.0.0

View File

@ -0,0 +1,40 @@
# `gui-attached`
*Since: nightly builds only*
This event is triggered when the GUI is starting up after attaching the
selected domain. For example, when you use `wezterm connect DOMAIN` or
`wezterm start --domain DOMAIN` to start the GUI, the `gui-attached` event will
be triggered and passed the [MuxDomain](../MuxDomain/index.md) object
associated with `DOMAIN`. In cases where you don't specify the domain, the
default domain will be passed instead.
This event fires after the [gui-startup](gui-startup.md) event.
Note that the `gui-startup` event does not fire when invoking `wezterm connect
DOMAIN` or `wezterm start --domain DOMAIN --attach`.
You can use this opportunity to take whatever action suits your purpose; some
users like to maximize all of their windows on startup, and this event would
allow you do that:
```lua
local wezterm = require 'wezterm'
local mux = wezterm.mux
wezterm.on('gui-attached', function(domain)
-- maximize all displayed windows on startup
local workspace = mux.get_active_workspace()
for _, window in ipairs(mux.all_windows()) do
if window:get_workspace() == workspace then
window:gui_window():maximize()
end
end
end)
local config = wezterm.config_builder()
return config
```
See also: [gui-startup](gui-startup.md).

View File

@ -15,6 +15,10 @@ program will be spawned.
This event is useful for starting a set of programs in a standard This event is useful for starting a set of programs in a standard
configuration to save you the effort of doing it manually each time. configuration to save you the effort of doing it manually each time.
This event fires before [gui-attached](gui-attached.md).
This event does not fire for `wezterm connect` invocations.
*Since: 20220807-113146-c2fee766* *Since: 20220807-113146-c2fee766*
The event receives an optional [SpawnCommand](../SpawnCommand.md) argument that The event receives an optional [SpawnCommand](../SpawnCommand.md) argument that
@ -105,3 +109,4 @@ return {}
See also: See also:
* [wezterm.mux](../wezterm.mux/index.md) * [wezterm.mux](../wezterm.mux/index.md)
* [gui-attached](gui-attached.md).

View File

@ -204,6 +204,19 @@ pub struct StartCommand {
#[arg(long, verbatim_doc_comment)] #[arg(long, verbatim_doc_comment)]
pub position: Option<GuiPosition>, pub position: Option<GuiPosition>,
/// Name of the multiplexer domain section from the configuration
/// to which you'd like to connect. If omitted, the default domain
/// will be used.
#[arg(long)]
pub domain: Option<String>,
/// When used with --domain, if the domain already has running panes,
/// wezterm will simply attach and will NOT spawn the specified PROG.
/// If you omit --attach when using --domain, wezterm will attach
/// AND then spawn PROG.
#[arg(long, requires = "domain")]
pub attach: bool,
/// Instead of executing your shell, run PROG. /// Instead of executing your shell, run PROG.
/// For example: `wezterm start -- bash -l` will spawn bash /// For example: `wezterm start -- bash -l` will spawn bash
/// as if it were a login shell. /// as if it were a login shell.

View File

@ -7,12 +7,13 @@ use ::window::*;
use anyhow::{anyhow, Context}; use anyhow::{anyhow, Context};
use clap::builder::ValueParser; use clap::builder::ValueParser;
use clap::{Parser, ValueHint}; use clap::{Parser, ValueHint};
use config::keyassignment::SpawnCommand; use config::keyassignment::{SpawnCommand, SpawnTabDomain};
use config::{ConfigHandle, SshDomain, SshMultiplexing}; use config::{ConfigHandle, SshDomain, SshMultiplexing};
use mux::activity::Activity; use mux::activity::Activity;
use mux::domain::{Domain, LocalDomain}; use mux::domain::{Domain, LocalDomain};
use mux::ssh::RemoteSshDomain; use mux::ssh::RemoteSshDomain;
use mux::Mux; use mux::Mux;
use mux_lua::MuxDomain;
use portable_pty::cmdbuilder::CommandBuilder; use portable_pty::cmdbuilder::CommandBuilder;
use promise::spawn::block_on; use promise::spawn::block_on;
use std::borrow::Cow; use std::borrow::Cow;
@ -240,84 +241,14 @@ fn client_domains(config: &config::ConfigHandle) -> Vec<ClientDomainConfig> {
domains domains
} }
async fn async_run_with_domain_as_default( async fn spawn_tab_in_domain_if_mux_is_empty(
domain: Arc<dyn Domain>,
cmd: Option<CommandBuilder>,
) -> anyhow::Result<()> {
let mux = Mux::get();
crate::update::load_last_release_info_and_set_banner();
// Allow spawning local commands into new tabs/panes
let local_domain: Arc<dyn Domain> = Arc::new(LocalDomain::new("local")?);
mux.add_domain(&local_domain);
// And configure their desired domain as the default
mux.add_domain(&domain);
mux.set_default_domain(&domain);
let is_connecting = true;
spawn_tab_in_default_domain_if_mux_is_empty(cmd, is_connecting).await
}
async fn async_run_mux_client(opts: ConnectCommand) -> anyhow::Result<()> {
if let Some(cls) = opts.class.as_ref() {
crate::set_window_class(cls);
}
if let Some(pos) = opts.position.as_ref() {
set_window_position(pos.clone());
}
let unix_socket_path =
config::RUNTIME_DIR.join(format!("gui-sock-{}", unsafe { libc::getpid() }));
std::env::set_var("WEZTERM_UNIX_SOCKET", unix_socket_path.clone());
let should_publish = false;
if let Err(err) = spawn_mux_server(unix_socket_path, should_publish) {
log::warn!("{:#}", err);
}
let domain = Mux::get()
.get_domain_by_name(&opts.domain_name)
.ok_or_else(|| {
anyhow!(
"no multiplexer domain with name `{}` was found in the configuration",
opts.domain_name
)
})?;
let opts = opts.clone();
let cmd = if !opts.prog.is_empty() {
let builder = CommandBuilder::from_argv(opts.prog);
Some(builder)
} else {
None
};
async_run_with_domain_as_default(domain, cmd).await
}
fn run_mux_client(opts: ConnectCommand) -> anyhow::Result<()> {
let activity = Activity::new();
build_initial_mux(&config::configuration(), None, opts.workspace.as_deref())?;
let gui = crate::frontend::try_new()?;
promise::spawn::spawn(async {
if let Err(err) = async_run_mux_client(opts).await {
terminate_with_error(err);
}
drop(activity);
})
.detach();
gui.run_forever()
}
async fn spawn_tab_in_default_domain_if_mux_is_empty(
cmd: Option<CommandBuilder>, cmd: Option<CommandBuilder>,
is_connecting: bool, is_connecting: bool,
domain: Option<Arc<dyn Domain>>,
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
let mux = Mux::get(); let mux = Mux::get();
let domain = mux.default_domain(); let domain = domain.unwrap_or_else(|| mux.default_domain());
if !is_connecting { if !is_connecting {
let have_panes_in_domain = mux let have_panes_in_domain = mux
@ -350,6 +281,7 @@ async fn spawn_tab_in_default_domain_if_mux_is_empty(
.any(|p| p.domain_id() == domain.domain_id()); .any(|p| p.domain_id() == domain.domain_id());
if have_panes_in_domain { if have_panes_in_domain {
trigger_and_log_gui_attached(MuxDomain(domain.domain_id())).await;
return Ok(()); return Ok(());
} }
@ -369,6 +301,7 @@ async fn spawn_tab_in_default_domain_if_mux_is_empty(
let _tab = domain let _tab = domain
.spawn(config.initial_size(dpi), cmd, None, window_id) .spawn(config.initial_size(dpi), cmd, None, window_id)
.await?; .await?;
trigger_and_log_gui_attached(MuxDomain(domain.domain_id())).await;
Ok(()) Ok(())
} }
@ -437,6 +370,46 @@ async fn connect_to_auto_connect_domains() -> anyhow::Result<()> {
Ok(()) Ok(())
} }
async fn trigger_gui_startup(
lua: Option<Rc<mlua::Lua>>,
spawn: Option<SpawnCommand>,
) -> anyhow::Result<()> {
if let Some(lua) = lua {
let args = lua.pack_multi(spawn)?;
config::lua::emit_event(&lua, ("gui-startup".to_string(), args)).await?;
}
Ok(())
}
async fn trigger_and_log_gui_startup(spawn_command: Option<SpawnCommand>) {
if let Err(err) =
config::with_lua_config_on_main_thread(move |lua| trigger_gui_startup(lua, spawn_command))
.await
{
let message = format!("while processing gui-startup event: {:#}", err);
log::error!("{}", message);
persistent_toast_notification("Error", &message);
}
}
async fn trigger_gui_attached(lua: Option<Rc<mlua::Lua>>, domain: MuxDomain) -> anyhow::Result<()> {
if let Some(lua) = lua {
let args = lua.pack_multi(domain)?;
config::lua::emit_event(&lua, ("gui-attached".to_string(), args)).await?;
}
Ok(())
}
async fn trigger_and_log_gui_attached(domain: MuxDomain) {
if let Err(err) =
config::with_lua_config_on_main_thread(move |lua| trigger_gui_attached(lua, domain)).await
{
let message = format!("while processing gui-attached event: {:#}", err);
log::error!("{}", message);
persistent_toast_notification("Error", &message);
}
}
async fn async_run_terminal_gui( async fn async_run_terminal_gui(
cmd: Option<CommandBuilder>, cmd: Option<CommandBuilder>,
opts: StartCommand, opts: StartCommand,
@ -454,33 +427,65 @@ async fn async_run_terminal_gui(
connect_to_auto_connect_domains().await?; connect_to_auto_connect_domains().await?;
} }
async fn trigger_gui_startup(
lua: Option<Rc<mlua::Lua>>,
spawn: Option<SpawnCommand>,
) -> anyhow::Result<()> {
if let Some(lua) = lua {
let args = lua.pack_multi(spawn)?;
config::lua::emit_event(&lua, ("gui-startup".to_string(), args)).await?;
}
Ok(())
}
let spawn_command = match &cmd { let spawn_command = match &cmd {
Some(cmd) => Some(SpawnCommand::from_command_builder(cmd)?), Some(cmd) => Some(SpawnCommand::from_command_builder(cmd)?),
None => None, None => None,
}; };
if let Err(err) = // Apply the domain to the command
config::with_lua_config_on_main_thread(move |lua| trigger_gui_startup(lua, spawn_command)) let spawn_command = match (spawn_command, &opts.domain) {
.await (Some(spawn), Some(name)) => Some(SpawnCommand {
{ domain: SpawnTabDomain::DomainName(name.to_string()),
let message = format!("while processing gui-startup event: {:#}", err); ..spawn
log::error!("{}", message); }),
persistent_toast_notification("Error", &message); (None, Some(name)) => Some(SpawnCommand {
domain: SpawnTabDomain::DomainName(name.to_string()),
..SpawnCommand::default()
}),
(spawn, None) => spawn,
};
let mux = Mux::get();
let domain = if let Some(name) = &opts.domain {
let domain = mux
.get_domain_by_name(name)
.ok_or_else(|| anyhow!("invalid domain {name}"))?;
Some(domain)
} else {
None
};
if !opts.attach {
trigger_and_log_gui_startup(spawn_command).await;
} }
let is_connecting = false; let is_connecting = opts.attach;
spawn_tab_in_default_domain_if_mux_is_empty(cmd, is_connecting).await
if let Some(domain) = &domain {
if !opts.attach {
let window_id = {
// Force the builder to notify the frontend early,
// so that the attach await below doesn't block it.
let builder = mux.new_empty_window(None);
*builder
};
domain.attach(Some(window_id)).await?;
let config = config::configuration();
let dpi = config.dpi.unwrap_or_else(|| ::window::default_dpi()) as u32;
let tab = domain
.spawn(config.initial_size(dpi), cmd.clone(), None, window_id)
.await?;
let mut window = mux
.get_window_mut(window_id)
.ok_or_else(|| anyhow!("failed to get mux window id {window_id}"))?;
if let Some(tab_idx) = window.idx_by_id(tab.tab_id()) {
window.set_active_without_saving(tab_idx);
}
trigger_and_log_gui_attached(MuxDomain(domain.domain_id())).await;
}
}
spawn_tab_in_domain_if_mux_is_empty(cmd, is_connecting, domain).await
} }
#[derive(Debug)] #[derive(Debug)]
@ -528,6 +533,7 @@ impl Publish {
cmd: Option<CommandBuilder>, cmd: Option<CommandBuilder>,
config: &ConfigHandle, config: &ConfigHandle,
workspace: Option<&str>, workspace: Option<&str>,
domain: SpawnTabDomain,
) -> anyhow::Result<bool> { ) -> anyhow::Result<bool> {
if let Publish::TryPathOrPublish(gui_sock) = &self { if let Publish::TryPathOrPublish(gui_sock) = &self {
let dom = config::UnixDomain { let dom = config::UnixDomain {
@ -559,7 +565,7 @@ impl Publish {
} }
client client
.spawn_v2(codec::SpawnV2 { .spawn_v2(codec::SpawnV2 {
domain: config::keyassignment::SpawnTabDomain::DefaultDomain, domain,
window_id: None, window_id: None,
command, command,
command_dir: None, command_dir: None,
@ -675,7 +681,7 @@ fn build_initial_mux(
setup_mux(domain, config, default_domain_name, default_workspace_name) setup_mux(domain, config, default_domain_name, default_workspace_name)
} }
fn run_terminal_gui(opts: StartCommand) -> anyhow::Result<()> { fn run_terminal_gui(opts: StartCommand, default_domain_name: Option<String>) -> anyhow::Result<()> {
if let Some(cls) = opts.class.as_ref() { if let Some(cls) = opts.class.as_ref() {
crate::set_window_class(cls); crate::set_window_class(cls);
} }
@ -705,7 +711,11 @@ fn run_terminal_gui(opts: StartCommand) -> anyhow::Result<()> {
None None
}; };
let mux = build_initial_mux(&config, None, opts.workspace.as_deref())?; let mux = build_initial_mux(
&config,
default_domain_name.as_deref(),
opts.workspace.as_deref(),
)?;
// First, let's see if we can ask an already running wezterm to do this. // First, let's see if we can ask an already running wezterm to do this.
// We must do this before we start the gui frontend as the scheduler // We must do this before we start the gui frontend as the scheduler
@ -716,7 +726,15 @@ fn run_terminal_gui(opts: StartCommand) -> anyhow::Result<()> {
opts.always_new_process || opts.position.is_some(), opts.always_new_process || opts.position.is_some(),
); );
log::trace!("{:?}", publish); log::trace!("{:?}", publish);
if publish.try_spawn(cmd.clone(), &config, opts.workspace.as_deref())? { if publish.try_spawn(
cmd.clone(),
&config,
opts.workspace.as_deref(),
match &opts.domain {
Some(name) => SpawnTabDomain::DomainName(name.to_string()),
None => SpawnTabDomain::DefaultDomain,
},
)? {
return Ok(()); return Ok(());
} }
@ -1169,11 +1187,25 @@ fn run() -> anyhow::Result<()> {
match sub { match sub {
SubCommand::Start(start) => { SubCommand::Start(start) => {
log::trace!("Using configuration: {:#?}\nopts: {:#?}", config, opts); log::trace!("Using configuration: {:#?}\nopts: {:#?}", config, opts);
run_terminal_gui(start) run_terminal_gui(start, None)
} }
SubCommand::Ssh(ssh) => run_ssh(ssh), SubCommand::Ssh(ssh) => run_ssh(ssh),
SubCommand::Serial(serial) => run_serial(config, &serial), SubCommand::Serial(serial) => run_serial(config, &serial),
SubCommand::Connect(connect) => run_mux_client(connect), SubCommand::Connect(connect) => run_terminal_gui(
StartCommand {
domain: Some(connect.domain_name.clone()),
class: connect.class,
workspace: connect.workspace,
position: connect.position,
prog: connect.prog,
always_new_process: true,
attach: true,
_cmd: false,
no_auto_connect: false,
cwd: None,
},
Some(connect.domain_name),
),
SubCommand::LsFonts(cmd) => run_ls_fonts(config, &cmd), SubCommand::LsFonts(cmd) => run_ls_fonts(config, &cmd),
SubCommand::ShowKeys(cmd) => run_show_keys(config, &cmd), SubCommand::ShowKeys(cmd) => run_show_keys(config, &cmd),
} }