mod actions;
mod bridge;
pub mod egui_menu;
pub mod model;
mod model_sections;
mod model_window;
mod native;
pub(crate) mod registry_accel;
pub mod state;
pub mod sync;
#[cfg(target_os = "macos")]
pub(super) mod macos;
#[cfg(any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
))]
pub(super) mod linux;
pub use actions::MenuAction;
pub use bridge::{dispatch, drain_pending_actions, request_toggle};
pub use egui_menu::AppMenuUi;
#[cfg(test)]
pub(crate) fn bridge_test_lock() -> std::sync::MutexGuard<'static, ()> {
bridge::TEST_LOCK
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
}
use crate::profile::Profile;
use anyhow::Result;
use muda::MenuEvent;
use native::NativeMenu;
use par_term_config::KeyBinding;
use par_term_keybindings::KeybindingRegistry;
use state::MenuState;
use std::sync::Arc;
use winit::window::Window;
pub struct MenuManager {
native: NativeMenu,
sync: sync::MenuSync,
profiles: Vec<Profile>,
}
impl MenuManager {
pub fn new_with(keybindings: &[KeyBinding]) -> Result<Self> {
let mut sync = sync::MenuSync::new();
sync.record(keybindings, None);
Ok(Self {
native: build_native(keybindings, &None)?,
sync,
profiles: Vec::new(),
})
}
pub fn rebuild_reason(
&self,
keybindings: &[KeyBinding],
capture: &state::Capture,
) -> Option<sync::RebuildReason> {
self.sync.rebuild_reason(keybindings, capture)
}
pub fn rebuild(
&mut self,
keybindings: &[KeyBinding],
capture: state::Capture,
windows: &[Arc<Window>],
) -> Result<()> {
let fresh = build_native(keybindings, &capture)?;
let old = std::mem::replace(&mut self.native, fresh);
drop(old);
let release = capture.is_some();
self.sync.record(keybindings, capture);
let profiles: Vec<Profile> = std::mem::take(&mut self.profiles);
self.update_profiles(&profiles.iter().collect::<Vec<_>>());
self.init_global()?;
#[cfg(not(target_os = "macos"))]
for window in windows {
self.init_for_window(window)?;
}
#[cfg(target_os = "macos")]
let _ = windows;
log::info!(
"Menu rebuilt from {} keybindings{}",
keybindings.len(),
if release {
" (captured-key accelerators released)"
} else {
""
}
);
Ok(())
}
pub fn init_global(&self) -> Result<()> {
#[cfg(target_os = "macos")]
macos::init_for_nsapp(
&self.native.menu,
self.native.window_menu.as_ref(),
self.native.help_menu.as_ref(),
);
Ok(())
}
#[allow(unused_variables)] pub fn init_for_window(&self, window: &Arc<Window>) -> Result<()> {
#[cfg(target_os = "macos")]
{
self.init_global()
}
#[cfg(target_os = "windows")]
{
use winit::raw_window_handle::{HasWindowHandle, RawWindowHandle};
if let Ok(handle) = window.window_handle()
&& let RawWindowHandle::Win32(win32_handle) = handle.as_raw()
{
unsafe {
self.native
.menu
.init_for_hwnd(win32_handle.hwnd.get() as _)?;
}
log::info!("Initialized Windows menu bar for window");
}
return Ok(());
}
#[cfg(any(
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
))]
{
linux::init_for_window(window)
}
#[cfg(not(any(
target_os = "macos",
target_os = "windows",
target_os = "linux",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd"
)))]
{
log::warn!("Menu bar not supported on this platform");
Ok(())
}
}
pub fn poll_events(&mut self) -> Vec<MenuAction> {
let mut actions = Vec::new();
while let Ok(event) = MenuEvent::receiver().try_recv() {
if let Some(action) = self.native.action_map.get(&event.id) {
actions.push(*action);
}
}
if !actions.is_empty() {
self.native.invalidate();
}
actions
}
pub fn apply_state(&mut self, state: &MenuState) {
self.native.apply(state);
}
pub fn update_profiles(&mut self, profiles: &[&Profile]) {
self.native.set_profiles(profiles);
self.profiles = profiles.iter().map(|p| (*p).clone()).collect();
log::info!("Updated profiles menu with {} items", profiles.len());
}
pub fn update_profiles_from_manager(&mut self, manager: &crate::profile::ProfileManager) {
let profiles: Vec<&Profile> = manager.profiles_ordered();
self.update_profiles(&profiles);
}
}
fn build_native(keybindings: &[KeyBinding], capture: &state::Capture) -> Result<NativeMenu> {
let registry = KeybindingRegistry::from_config(keybindings);
let mut sections = model::menu_model_with_registry(cfg!(target_os = "macos"), ®istry);
if let Some(open) = capture {
state::release_captured_accelerators(&mut sections, open);
}
let quit = registry
.chord_for_action("quit")
.and_then(|combo| registry_accel::accelerator_from_combo(&combo));
NativeMenu::build(§ions, quit, registry, capture.is_some())
}