2022-07-07 01:39:43 +03:00
|
|
|
use crate::scripting::guiwin::GuiWin;
|
2022-12-21 10:24:11 +03:00
|
|
|
use crate::spawn::SpawnWhere;
|
2022-01-15 23:48:57 +03:00
|
|
|
use crate::termwindow::TermWindowNotif;
|
2021-02-28 11:11:17 +03:00
|
|
|
use crate::TermWindow;
|
2021-02-28 11:04:10 +03:00
|
|
|
use ::window::*;
|
2022-06-28 18:30:52 +03:00
|
|
|
use anyhow::{Context, Error};
|
2022-12-21 10:24:11 +03:00
|
|
|
use config::keyassignment::{KeyAssignment, SpawnCommand};
|
2022-12-24 10:10:04 +03:00
|
|
|
use config::ConfigSubscription;
|
2021-02-28 11:04:10 +03:00
|
|
|
pub use config::FrontEndSelection;
|
2022-01-16 19:50:16 +03:00
|
|
|
use mux::client::ClientId;
|
|
|
|
use mux::window::WindowId as MuxWindowId;
|
2021-02-28 11:04:10 +03:00
|
|
|
use mux::{Mux, MuxNotification};
|
2022-07-07 04:52:14 +03:00
|
|
|
use promise::{Future, Promise};
|
2021-02-28 11:04:10 +03:00
|
|
|
use std::cell::RefCell;
|
2022-04-08 20:08:10 +03:00
|
|
|
use std::collections::{BTreeMap, HashSet};
|
2021-02-28 11:04:10 +03:00
|
|
|
use std::rc::Rc;
|
2022-01-16 19:50:16 +03:00
|
|
|
use std::sync::Arc;
|
2022-03-31 19:51:06 +03:00
|
|
|
use wezterm_term::{Alert, ClipboardSelection};
|
2021-02-28 11:04:10 +03:00
|
|
|
use wezterm_toast_notification::*;
|
|
|
|
|
|
|
|
pub struct GuiFrontEnd {
|
|
|
|
connection: Rc<Connection>,
|
2022-01-15 23:48:57 +03:00
|
|
|
switching_workspaces: RefCell<bool>,
|
2022-04-08 20:08:10 +03:00
|
|
|
spawned_mux_window: RefCell<HashSet<MuxWindowId>>,
|
2022-01-16 19:50:16 +03:00
|
|
|
known_windows: RefCell<BTreeMap<Window, MuxWindowId>>,
|
|
|
|
client_id: Arc<ClientId>,
|
2022-12-24 10:10:04 +03:00
|
|
|
config_subscription: RefCell<Option<ConfigSubscription>>,
|
2021-02-28 11:04:10 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Drop for GuiFrontEnd {
|
|
|
|
fn drop(&mut self) {
|
|
|
|
::window::shutdown();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl GuiFrontEnd {
|
|
|
|
pub fn try_new() -> anyhow::Result<Rc<GuiFrontEnd>> {
|
|
|
|
let connection = Connection::init()?;
|
2022-12-19 21:06:12 +03:00
|
|
|
connection.set_event_handler(Self::app_event_handler);
|
2022-01-15 23:48:57 +03:00
|
|
|
|
2022-11-24 23:08:01 +03:00
|
|
|
let mux = Mux::get();
|
2022-01-15 23:48:57 +03:00
|
|
|
let client_id = mux.active_identity().expect("to have set my own id");
|
|
|
|
|
|
|
|
let front_end = Rc::new(GuiFrontEnd {
|
|
|
|
connection,
|
|
|
|
switching_workspaces: RefCell::new(false),
|
2022-04-08 20:08:10 +03:00
|
|
|
spawned_mux_window: RefCell::new(HashSet::new()),
|
2022-01-16 19:50:16 +03:00
|
|
|
known_windows: RefCell::new(BTreeMap::new()),
|
|
|
|
client_id: client_id.clone(),
|
2022-12-24 10:10:04 +03:00
|
|
|
config_subscription: RefCell::new(None),
|
2022-01-15 23:48:57 +03:00
|
|
|
});
|
2022-12-19 21:06:12 +03:00
|
|
|
|
2021-02-28 11:04:10 +03:00
|
|
|
mux.subscribe(move |n| {
|
2022-11-24 20:30:08 +03:00
|
|
|
match n {
|
|
|
|
MuxNotification::WindowWorkspaceChanged(_)
|
|
|
|
| MuxNotification::ActiveWorkspaceChanged(_)
|
|
|
|
| MuxNotification::WindowCreated(_)
|
|
|
|
| MuxNotification::WindowRemoved(_) => {
|
|
|
|
promise::spawn::spawn_into_main_thread(async move {
|
|
|
|
let fe = crate::frontend::front_end();
|
|
|
|
if !fe.is_switching_workspace() {
|
|
|
|
fe.reconcile_workspace();
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.detach();
|
|
|
|
}
|
2023-03-24 23:13:31 +03:00
|
|
|
MuxNotification::PaneFocused(_) => {}
|
2022-11-24 20:30:08 +03:00
|
|
|
MuxNotification::TabAddedToWindow { .. } => {}
|
|
|
|
MuxNotification::PaneRemoved(_) => {}
|
|
|
|
MuxNotification::WindowInvalidated(_) => {}
|
|
|
|
MuxNotification::PaneOutput(_) => {}
|
|
|
|
MuxNotification::PaneAdded(_) => {}
|
|
|
|
MuxNotification::Alert {
|
|
|
|
pane_id: _,
|
|
|
|
alert:
|
|
|
|
Alert::ToastNotification {
|
|
|
|
title,
|
|
|
|
body,
|
|
|
|
focus: _,
|
|
|
|
},
|
|
|
|
} => {
|
|
|
|
let message = if title.is_none() { "" } else { &body };
|
|
|
|
let title = title.as_ref().unwrap_or(&body);
|
|
|
|
// FIXME: if notification.focus is true, we should do
|
|
|
|
// something here to arrange to focus pane_id when the
|
|
|
|
// notification is clicked
|
|
|
|
persistent_toast_notification(title, message);
|
|
|
|
}
|
|
|
|
MuxNotification::Alert {
|
|
|
|
pane_id: _,
|
|
|
|
alert: Alert::Bell,
|
|
|
|
} => {
|
|
|
|
// Handled via TermWindowNotif; NOP it here.
|
|
|
|
}
|
|
|
|
MuxNotification::Alert {
|
|
|
|
pane_id: _,
|
|
|
|
alert:
|
|
|
|
Alert::OutputSinceFocusLost
|
|
|
|
| Alert::PaletteChanged
|
|
|
|
| Alert::CurrentWorkingDirectoryChanged
|
|
|
|
| Alert::WindowTitleChanged(_)
|
|
|
|
| Alert::TabTitleChanged(_)
|
|
|
|
| Alert::IconTitleChanged(_)
|
|
|
|
| Alert::SetUserVar { .. },
|
|
|
|
} => {}
|
|
|
|
MuxNotification::Empty => {
|
2023-02-06 02:55:56 +03:00
|
|
|
if config::configuration().quit_when_all_windows_are_closed {
|
2022-12-21 10:24:11 +03:00
|
|
|
promise::spawn::spawn_into_main_thread(async move {
|
|
|
|
if mux::activity::Activity::count() == 0 {
|
|
|
|
log::trace!("Mux is now empty, terminate gui");
|
|
|
|
Connection::get().unwrap().terminate_message_loop();
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.detach();
|
|
|
|
}
|
2022-11-24 20:30:08 +03:00
|
|
|
}
|
|
|
|
MuxNotification::SaveToDownloads { name, data } => {
|
|
|
|
if !config::configuration().allow_download_protocols {
|
|
|
|
log::error!(
|
|
|
|
"Ignoring download request for {:?}, \
|
2022-04-01 06:06:43 +03:00
|
|
|
as allow_download_protocols=false",
|
2022-11-24 20:30:08 +03:00
|
|
|
name
|
|
|
|
);
|
|
|
|
} else if let Err(err) = crate::download::save_to_downloads(name, &*data) {
|
|
|
|
log::error!("save_to_downloads: {:#}", err);
|
2022-04-01 06:06:43 +03:00
|
|
|
}
|
2022-11-24 20:30:08 +03:00
|
|
|
}
|
|
|
|
MuxNotification::AssignClipboard {
|
|
|
|
pane_id,
|
|
|
|
selection,
|
|
|
|
clipboard,
|
|
|
|
} => {
|
|
|
|
promise::spawn::spawn_into_main_thread(async move {
|
|
|
|
let fe = crate::frontend::front_end();
|
2022-03-31 19:51:06 +03:00
|
|
|
log::trace!(
|
|
|
|
"set clipboard in pane {} {:?} {:?}",
|
|
|
|
pane_id,
|
|
|
|
selection,
|
|
|
|
clipboard
|
|
|
|
);
|
|
|
|
if let Some(window) = fe.known_windows.borrow().keys().next() {
|
|
|
|
window.set_clipboard(
|
|
|
|
match selection {
|
|
|
|
ClipboardSelection::Clipboard => Clipboard::Clipboard,
|
|
|
|
ClipboardSelection::PrimarySelection => {
|
|
|
|
Clipboard::PrimarySelection
|
|
|
|
}
|
|
|
|
},
|
|
|
|
clipboard.unwrap_or_else(String::new),
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
log::error!("Cannot assign clipboard as there are no windows");
|
2022-11-24 20:30:08 +03:00
|
|
|
};
|
|
|
|
})
|
|
|
|
.detach();
|
2021-02-28 11:04:10 +03:00
|
|
|
}
|
|
|
|
}
|
2022-11-24 20:30:08 +03:00
|
|
|
true
|
2021-02-28 11:04:10 +03:00
|
|
|
});
|
2022-07-15 02:44:00 +03:00
|
|
|
// Re-evaluate the config so that folks that are using
|
|
|
|
// `wezterm.gui.get_appearance()` can have that take effect
|
|
|
|
// before any windows are created
|
|
|
|
config::reload();
|
macOS: add MenuBar
This took a decent amount of effort to thread through with context;
wrappers around NSMenu and NSMenuItem are added to reduce some of
the objc usability warts, and an additional NSObject wrapper is
added to help copy the KeyAssignment from the existing list
of command palette commands and associate it with the menu item.
When a menu item is selected, macOS will walk through the responder
chain and look for a responder that responds to the selector associated
with the menu item. In practice that means that our window/view class
will be tried first, and then later, our app delegate will be tried.
This commit implements routing from both of these: the window case
routes to the associated TermWindow and drops into the existing
perform_key_assignment method.
In case there is no window (not currently possible, but will be
in the future), the app delegate also has a placeholder for dispatching
key assignments, although it will only be able to perform a subset
of the possible actions.
A couple of things to note:
* Items aren't smart enough to disable themselves or adjust their
caption based on the context. To make that work, we either need
to recreate the entire menubar when any possible context changes
(doable, but feels heavy), or we need to assign a target to each
menu item and implement a validation handler on that target.
That seemed to mess with the responder chain when I briefly
experimented with it.
* There's some disabled code to add a Services menu. It is disabled
because when it is enabled, accessing either Services or Help
from the menu bar sends the process into a busy loop somewhere
in macOS's internals. It's unclear what it is unhappy with.
* No keyboard accelerators are associated with the menubar yet.
That needs some thought, as they would essentially become global
keyboard shortcuts and take precedence over the shortcuts defined
for other keys in the config. This feels like it should be something
that the user has control over, so there needs to be something to
allow that before we go ahead and wire those up.
refs: https://github.com/wez/wezterm/issues/162
refs: https://github.com/wez/wezterm/issues/1485
2022-12-21 05:46:08 +03:00
|
|
|
|
|
|
|
// And build the initial menu bar.
|
|
|
|
// TODO: arrange for this to happen on config reload.
|
|
|
|
crate::commands::CommandDef::recreate_menubar(&config::configuration());
|
|
|
|
|
2021-02-28 11:04:10 +03:00
|
|
|
Ok(front_end)
|
|
|
|
}
|
|
|
|
|
2022-12-19 21:06:12 +03:00
|
|
|
fn app_event_handler(event: ApplicationEvent) {
|
|
|
|
log::trace!("Got app event {event:?}");
|
|
|
|
match event {
|
|
|
|
ApplicationEvent::OpenCommandScript(file_name) => {
|
|
|
|
promise::spawn::spawn(async move {
|
|
|
|
use config::keyassignment::SpawnTabDomain;
|
|
|
|
use wezterm_term::TerminalSize;
|
|
|
|
|
2022-12-20 18:30:07 +03:00
|
|
|
// We send the script to execute to the shell on stdin, rather than ask the
|
|
|
|
// shell to execute it directly, so that we start the shell and read in the
|
|
|
|
// user's rc files before running the script. Without this, wezterm on macOS
|
|
|
|
// is launched with a default and very anemic path, and that is frustrating for
|
|
|
|
// users.
|
2022-12-19 21:06:12 +03:00
|
|
|
|
2022-11-24 23:08:01 +03:00
|
|
|
let mux = Mux::get();
|
2022-12-19 21:06:12 +03:00
|
|
|
let window_id = None;
|
|
|
|
let pane_id = None;
|
2022-12-20 18:30:07 +03:00
|
|
|
let cmd = None;
|
2022-12-19 21:06:12 +03:00
|
|
|
let cwd = None;
|
|
|
|
let workspace = mux.active_workspace();
|
|
|
|
|
|
|
|
match mux
|
|
|
|
.spawn_tab_or_window(
|
|
|
|
window_id,
|
|
|
|
SpawnTabDomain::DomainName("local".to_string()),
|
2022-12-20 18:30:07 +03:00
|
|
|
cmd,
|
2022-12-19 21:06:12 +03:00
|
|
|
cwd,
|
|
|
|
TerminalSize::default(),
|
|
|
|
pane_id,
|
|
|
|
workspace,
|
2023-02-06 07:36:37 +03:00
|
|
|
None, // optional position
|
2022-12-19 21:06:12 +03:00
|
|
|
)
|
|
|
|
.await
|
|
|
|
{
|
|
|
|
Ok((_tab, pane, _window_id)) => {
|
|
|
|
log::trace!("Spawned {file_name} as pane_id {}", pane.pane_id());
|
2022-12-20 18:30:07 +03:00
|
|
|
let mut writer = pane.writer();
|
|
|
|
write!(writer, "{} ; exit\n", shlex::quote(&file_name)).ok();
|
2022-12-19 21:06:12 +03:00
|
|
|
}
|
|
|
|
Err(err) => {
|
|
|
|
log::error!("Failed to spawn {file_name}: {err:#?}");
|
|
|
|
}
|
|
|
|
};
|
|
|
|
})
|
|
|
|
.detach();
|
|
|
|
}
|
macOS: add MenuBar
This took a decent amount of effort to thread through with context;
wrappers around NSMenu and NSMenuItem are added to reduce some of
the objc usability warts, and an additional NSObject wrapper is
added to help copy the KeyAssignment from the existing list
of command palette commands and associate it with the menu item.
When a menu item is selected, macOS will walk through the responder
chain and look for a responder that responds to the selector associated
with the menu item. In practice that means that our window/view class
will be tried first, and then later, our app delegate will be tried.
This commit implements routing from both of these: the window case
routes to the associated TermWindow and drops into the existing
perform_key_assignment method.
In case there is no window (not currently possible, but will be
in the future), the app delegate also has a placeholder for dispatching
key assignments, although it will only be able to perform a subset
of the possible actions.
A couple of things to note:
* Items aren't smart enough to disable themselves or adjust their
caption based on the context. To make that work, we either need
to recreate the entire menubar when any possible context changes
(doable, but feels heavy), or we need to assign a target to each
menu item and implement a validation handler on that target.
That seemed to mess with the responder chain when I briefly
experimented with it.
* There's some disabled code to add a Services menu. It is disabled
because when it is enabled, accessing either Services or Help
from the menu bar sends the process into a busy loop somewhere
in macOS's internals. It's unclear what it is unhappy with.
* No keyboard accelerators are associated with the menubar yet.
That needs some thought, as they would essentially become global
keyboard shortcuts and take precedence over the shortcuts defined
for other keys in the config. This feels like it should be something
that the user has control over, so there needs to be something to
allow that before we go ahead and wire those up.
refs: https://github.com/wez/wezterm/issues/162
refs: https://github.com/wez/wezterm/issues/1485
2022-12-21 05:46:08 +03:00
|
|
|
ApplicationEvent::PerformKeyAssignment(action) => {
|
|
|
|
// We should only get here when there are no windows open
|
|
|
|
// and the user picks an action from the menubar.
|
|
|
|
// This is not currently possible, but could be in the
|
|
|
|
// future.
|
2022-12-21 10:24:11 +03:00
|
|
|
|
|
|
|
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:?}");
|
|
|
|
}
|
|
|
|
}
|
macOS: add MenuBar
This took a decent amount of effort to thread through with context;
wrappers around NSMenu and NSMenuItem are added to reduce some of
the objc usability warts, and an additional NSObject wrapper is
added to help copy the KeyAssignment from the existing list
of command palette commands and associate it with the menu item.
When a menu item is selected, macOS will walk through the responder
chain and look for a responder that responds to the selector associated
with the menu item. In practice that means that our window/view class
will be tried first, and then later, our app delegate will be tried.
This commit implements routing from both of these: the window case
routes to the associated TermWindow and drops into the existing
perform_key_assignment method.
In case there is no window (not currently possible, but will be
in the future), the app delegate also has a placeholder for dispatching
key assignments, although it will only be able to perform a subset
of the possible actions.
A couple of things to note:
* Items aren't smart enough to disable themselves or adjust their
caption based on the context. To make that work, we either need
to recreate the entire menubar when any possible context changes
(doable, but feels heavy), or we need to assign a target to each
menu item and implement a validation handler on that target.
That seemed to mess with the responder chain when I briefly
experimented with it.
* There's some disabled code to add a Services menu. It is disabled
because when it is enabled, accessing either Services or Help
from the menu bar sends the process into a busy loop somewhere
in macOS's internals. It's unclear what it is unhappy with.
* No keyboard accelerators are associated with the menubar yet.
That needs some thought, as they would essentially become global
keyboard shortcuts and take precedence over the shortcuts defined
for other keys in the config. This feels like it should be something
that the user has control over, so there needs to be something to
allow that before we go ahead and wire those up.
refs: https://github.com/wez/wezterm/issues/162
refs: https://github.com/wez/wezterm/issues/1485
2022-12-21 05:46:08 +03:00
|
|
|
}
|
2022-12-19 21:06:12 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-28 11:04:10 +03:00
|
|
|
pub fn run_forever(&self) -> anyhow::Result<()> {
|
2022-06-28 18:30:52 +03:00
|
|
|
self.connection
|
|
|
|
.run_message_loop()
|
|
|
|
.context("running message loop")
|
2021-02-28 11:04:10 +03:00
|
|
|
}
|
2022-01-15 23:48:57 +03:00
|
|
|
|
2023-01-19 06:50:24 +03:00
|
|
|
pub fn gui_windows(&self) -> Vec<GuiWin> {
|
|
|
|
let windows = self.known_windows.borrow();
|
|
|
|
let mut windows: Vec<GuiWin> = windows
|
|
|
|
.iter()
|
|
|
|
.map(|(window, &mux_window_id)| GuiWin {
|
|
|
|
mux_window_id,
|
|
|
|
window: window.clone(),
|
|
|
|
})
|
|
|
|
.collect();
|
|
|
|
windows.sort_by(|a, b| a.window.cmp(&b.window));
|
|
|
|
windows
|
|
|
|
}
|
|
|
|
|
2022-07-07 04:52:14 +03:00
|
|
|
pub fn reconcile_workspace(&self) -> Future<()> {
|
|
|
|
let mut promise = Promise::new();
|
2022-11-24 23:08:01 +03:00
|
|
|
let mux = Mux::get();
|
2022-01-16 19:50:16 +03:00
|
|
|
let workspace = mux.active_workspace_for_client(&self.client_id);
|
|
|
|
|
|
|
|
if mux.is_workspace_empty(&workspace) {
|
|
|
|
// We don't want to silently kill off things that might
|
|
|
|
// be running in other workspaces, so let's pick one
|
|
|
|
// and activate it
|
|
|
|
if self.is_switching_workspace() {
|
2022-07-07 04:52:14 +03:00
|
|
|
promise.ok(());
|
|
|
|
return promise.get_future().unwrap();
|
2022-01-16 19:50:16 +03:00
|
|
|
}
|
|
|
|
for workspace in mux.iter_workspaces() {
|
|
|
|
if !mux.is_workspace_empty(&workspace) {
|
|
|
|
mux.set_active_workspace_for_client(&self.client_id, &workspace);
|
|
|
|
log::debug!("using {} instead, as it is not empty", workspace);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
let workspace = mux.active_workspace_for_client(&self.client_id);
|
|
|
|
log::debug!("workspace is {}, fixup windows", workspace);
|
|
|
|
|
|
|
|
let mut mux_windows = mux.iter_windows_in_workspace(&workspace);
|
2022-01-15 23:48:57 +03:00
|
|
|
|
|
|
|
// First, repurpose existing windows.
|
|
|
|
// Note that both iter_windows_in_workspace and self.known_windows have a
|
|
|
|
// deterministic iteration order, so switching back and forth should result
|
|
|
|
// in a consistent mux <-> gui window mapping.
|
2022-01-16 19:50:16 +03:00
|
|
|
let known_windows = std::mem::take(&mut *self.known_windows.borrow_mut());
|
|
|
|
let mut windows = BTreeMap::new();
|
|
|
|
let mut unused = BTreeMap::new();
|
|
|
|
|
|
|
|
for (window, window_id) in known_windows.into_iter() {
|
|
|
|
if let Some(idx) = mux_windows.iter().position(|&id| id == window_id) {
|
|
|
|
// it already points to the desired mux window
|
|
|
|
windows.insert(window, window_id);
|
|
|
|
mux_windows.remove(idx);
|
|
|
|
} else {
|
|
|
|
unused.insert(window, window_id);
|
2022-01-15 23:48:57 +03:00
|
|
|
}
|
2022-01-16 19:50:16 +03:00
|
|
|
}
|
2022-01-15 23:48:57 +03:00
|
|
|
|
2022-01-16 19:50:16 +03:00
|
|
|
let mut mux_windows = mux_windows.into_iter();
|
|
|
|
|
2022-04-08 20:08:10 +03:00
|
|
|
for (window, old_id) in unused.into_iter() {
|
2022-01-16 19:50:16 +03:00
|
|
|
if let Some(mux_window_id) = mux_windows.next() {
|
|
|
|
window.notify(TermWindowNotif::SwitchToMuxWindow(mux_window_id));
|
|
|
|
windows.insert(window, mux_window_id);
|
|
|
|
} else {
|
|
|
|
// We have more windows than are in the new workspace;
|
|
|
|
// we no longer need this one!
|
|
|
|
window.close();
|
2022-04-08 20:08:10 +03:00
|
|
|
front_end().spawned_mux_window.borrow_mut().remove(&old_id);
|
2022-01-16 19:50:16 +03:00
|
|
|
}
|
2022-01-15 23:48:57 +03:00
|
|
|
}
|
|
|
|
|
2022-04-08 20:08:10 +03:00
|
|
|
log::trace!("reconcile: windows -> {:?}", windows);
|
2022-01-16 19:50:16 +03:00
|
|
|
*self.known_windows.borrow_mut() = windows;
|
|
|
|
|
2022-07-07 04:52:14 +03:00
|
|
|
let future = promise.get_future().unwrap();
|
|
|
|
|
2022-01-15 23:48:57 +03:00
|
|
|
// then spawn any new windows that are needed
|
|
|
|
promise::spawn::spawn(async move {
|
|
|
|
while let Some(mux_window_id) = mux_windows.next() {
|
2022-04-08 20:08:10 +03:00
|
|
|
if front_end().has_mux_window(mux_window_id)
|
|
|
|
|| front_end()
|
|
|
|
.spawned_mux_window
|
|
|
|
.borrow()
|
|
|
|
.contains(&mux_window_id)
|
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
front_end()
|
|
|
|
.spawned_mux_window
|
|
|
|
.borrow_mut()
|
|
|
|
.insert(mux_window_id);
|
|
|
|
log::trace!("Creating TermWindow for mux_window_id={}", mux_window_id);
|
2022-01-15 23:48:57 +03:00
|
|
|
if let Err(err) = TermWindow::new_window(mux_window_id).await {
|
2022-04-05 17:17:07 +03:00
|
|
|
log::error!("Failed to create window: {:#}", err);
|
2022-11-24 23:08:01 +03:00
|
|
|
let mux = Mux::get();
|
2022-01-15 23:48:57 +03:00
|
|
|
mux.kill_window(mux_window_id);
|
2022-04-08 20:08:10 +03:00
|
|
|
front_end()
|
|
|
|
.spawned_mux_window
|
|
|
|
.borrow_mut()
|
|
|
|
.remove(&mux_window_id);
|
2022-01-15 23:48:57 +03:00
|
|
|
}
|
|
|
|
}
|
2022-01-16 19:50:16 +03:00
|
|
|
*front_end().switching_workspaces.borrow_mut() = false;
|
2022-07-07 04:52:14 +03:00
|
|
|
promise.ok(());
|
2022-01-15 23:48:57 +03:00
|
|
|
})
|
|
|
|
.detach();
|
2022-07-07 04:52:14 +03:00
|
|
|
future
|
2022-01-15 23:48:57 +03:00
|
|
|
}
|
|
|
|
|
2022-04-08 20:08:10 +03:00
|
|
|
fn has_mux_window(&self, mux_window_id: MuxWindowId) -> bool {
|
|
|
|
for &mux_id in self.known_windows.borrow().values() {
|
|
|
|
if mux_id == mux_window_id {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
false
|
|
|
|
}
|
|
|
|
|
2022-01-16 19:50:16 +03:00
|
|
|
pub fn switch_workspace(&self, workspace: &str) {
|
2022-11-24 23:08:01 +03:00
|
|
|
let mux = Mux::get();
|
2022-01-16 19:50:16 +03:00
|
|
|
mux.set_active_workspace_for_client(&self.client_id, workspace);
|
|
|
|
*self.switching_workspaces.borrow_mut() = false;
|
|
|
|
self.reconcile_workspace();
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn record_known_window(&self, window: Window, mux_window_id: MuxWindowId) {
|
|
|
|
self.known_windows
|
|
|
|
.borrow_mut()
|
|
|
|
.insert(window, mux_window_id);
|
|
|
|
if !self.is_switching_workspace() {
|
|
|
|
self.reconcile_workspace();
|
|
|
|
}
|
2022-01-15 23:48:57 +03:00
|
|
|
}
|
|
|
|
|
2022-01-16 06:02:14 +03:00
|
|
|
pub fn forget_known_window(&self, window: &Window) {
|
2022-01-16 19:50:16 +03:00
|
|
|
self.known_windows.borrow_mut().remove(window);
|
|
|
|
if !self.is_switching_workspace() {
|
|
|
|
self.reconcile_workspace();
|
|
|
|
}
|
2022-01-15 23:48:57 +03:00
|
|
|
}
|
2022-01-16 06:33:05 +03:00
|
|
|
|
|
|
|
pub fn is_switching_workspace(&self) -> bool {
|
|
|
|
*self.switching_workspaces.borrow()
|
|
|
|
}
|
2022-07-07 01:39:43 +03:00
|
|
|
|
|
|
|
pub fn gui_window_for_mux_window(&self, mux_window_id: MuxWindowId) -> Option<GuiWin> {
|
|
|
|
let windows = self.known_windows.borrow();
|
|
|
|
for (window, v) in windows.iter() {
|
|
|
|
if *v == mux_window_id {
|
|
|
|
return Some(GuiWin {
|
|
|
|
mux_window_id,
|
|
|
|
window: window.clone(),
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
None
|
|
|
|
}
|
2021-02-28 11:04:10 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
thread_local! {
|
|
|
|
static FRONT_END: RefCell<Option<Rc<GuiFrontEnd>>> = RefCell::new(None);
|
|
|
|
}
|
|
|
|
|
2022-05-20 17:52:43 +03:00
|
|
|
pub fn try_front_end() -> Option<Rc<GuiFrontEnd>> {
|
|
|
|
FRONT_END.with(|f| f.borrow().as_ref().map(Rc::clone))
|
|
|
|
}
|
|
|
|
|
2022-01-16 06:33:05 +03:00
|
|
|
pub fn front_end() -> Rc<GuiFrontEnd> {
|
2022-01-15 23:48:57 +03:00
|
|
|
FRONT_END
|
2022-01-16 06:33:05 +03:00
|
|
|
.with(|f| f.borrow().as_ref().map(Rc::clone))
|
|
|
|
.expect("to be called on gui thread")
|
2022-01-15 23:48:57 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
pub struct WorkspaceSwitcher {
|
|
|
|
new_name: String,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl WorkspaceSwitcher {
|
|
|
|
pub fn new(new_name: &str) -> Self {
|
2022-01-16 19:50:16 +03:00
|
|
|
*front_end().switching_workspaces.borrow_mut() = true;
|
2022-01-15 23:48:57 +03:00
|
|
|
Self {
|
|
|
|
new_name: new_name.to_string(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn do_switch(self) {
|
|
|
|
// Drop is invoked, which will complete the switch
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Drop for WorkspaceSwitcher {
|
|
|
|
fn drop(&mut self) {
|
2022-01-16 06:33:05 +03:00
|
|
|
front_end().switch_workspace(&self.new_name);
|
2022-01-15 23:48:57 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-28 11:04:10 +03:00
|
|
|
pub fn shutdown() {
|
|
|
|
FRONT_END.with(|f| drop(f.borrow_mut().take()));
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn try_new() -> Result<Rc<GuiFrontEnd>, Error> {
|
|
|
|
let front_end = GuiFrontEnd::try_new()?;
|
|
|
|
FRONT_END.with(|f| *f.borrow_mut() = Some(Rc::clone(&front_end)));
|
2022-12-24 10:10:04 +03:00
|
|
|
|
|
|
|
let config_subscription = config::subscribe_to_config_reload({
|
|
|
|
move || {
|
|
|
|
promise::spawn::spawn_into_main_thread(async {
|
|
|
|
crate::commands::CommandDef::recreate_menubar(&config::configuration());
|
|
|
|
})
|
|
|
|
.detach();
|
|
|
|
true
|
|
|
|
}
|
|
|
|
});
|
|
|
|
front_end
|
|
|
|
.config_subscription
|
|
|
|
.borrow_mut()
|
|
|
|
.replace(config_subscription);
|
|
|
|
|
2021-02-28 11:04:10 +03:00
|
|
|
Ok(front_end)
|
|
|
|
}
|