mirror of
https://github.com/block/buzz.git
synced 2026-10-29 13:31:44 +00:00
## Summary - close the current Buzz window with `Cmd-W` on macOS - keep Buzz Term's existing `Cmd-W` behavior when the terminal owns input, where it closes the active terminal tab - cover the close-window chord and its modifier/composition guards with unit tests ## Why Buzz removes macOS's native Close Window menu accelerator so the webview can conditionally route `Cmd-W` to Buzz Term. That also left normal Buzz mode without the standard close-window shortcut. The new app-level bubble-phase handler restores window closing while allowing the terminal's capture-phase handler to retain priority. For the main window, the existing Rust close-request handler keeps the webview alive and hides it to the tray, matching the titlebar close button. ## Validation - desktop unit tests: 5,093 passed - focused close-window and terminal shortcut tests: 28 passed at `71a2cce42013440e297c7b237d45b20e9638078d` - desktop TypeScript: passed - desktop checks: passed (pre-existing warnings only) - workspace Rust clippy: passed - Tauri Rust clippy/check: passed - desktop build: passed - mobile format/analyze: passed - Tauri tests: 2,592 desktop tests and 26/27 terminal tests passed; the existing `default_prog_child_observes_the_login_argv0` PTY fixture repeatedly timed out waiting for `/bin/sh`, including when run alone, and is unrelated to this TypeScript shortcut change --------- Signed-off-by: Matt Kursmark <kursmark@squareup.com> Co-authored-by: Codex <noreply@openai.com>
112 lines
4.5 KiB
Rust
112 lines
4.5 KiB
Rust
//! The macOS application menu.
|
|
//!
|
|
//! Buzz never called `Builder::menu()`, so Tauri installed `Menu::default()`
|
|
//! for us (`tauri::app::Builder::build`, macOS arm). That default puts a
|
|
//! `close_window` item in both the File and Window submenus, and muda gives
|
|
//! that item a Cmd+W key equivalent bound to `performClose:`.
|
|
//!
|
|
//! That default cannot express Buzz's context-dependent behavior:
|
|
//!
|
|
//! 1. `CloseRequested` on the main window is intercepted in `lib.rs` and turned
|
|
//! into hide-to-tray. Cmd+W should take that path in normal Buzz mode.
|
|
//! 2. macOS resolves a menu key equivalent before the webview receives any key
|
|
//! event, so Buzz Term could never bind Cmd+W to "close this terminal tab"
|
|
//! while the accelerator was claimed here.
|
|
//!
|
|
//! So this module builds the standard menu minus both `close_window` items.
|
|
//! Everything else matches `Menu::default()` deliberately. The webview routes
|
|
//! Cmd+W conditionally instead: Buzz Term consumes it in capture phase while
|
|
//! it owns input, and `useCloseWindowShortcut` closes the current window in
|
|
//! normal Buzz mode.
|
|
|
|
#[cfg(target_os = "macos")]
|
|
use tauri::menu::{
|
|
AboutMetadata, Menu, PredefinedMenuItem, Submenu, HELP_SUBMENU_ID, WINDOW_SUBMENU_ID,
|
|
};
|
|
#[cfg(target_os = "macos")]
|
|
use tauri::AppHandle;
|
|
use tauri::{Builder, Runtime};
|
|
|
|
/// Installs Buzz's menu, replacing the `Menu::default()` Tauri would otherwise
|
|
/// auto-install. A no-op off macOS, where that default is never created and
|
|
/// the Cmd+W accelerator does not exist.
|
|
pub fn install<R: Runtime>(builder: Builder<R>) -> Builder<R> {
|
|
#[cfg(target_os = "macos")]
|
|
let builder = builder.menu(build);
|
|
builder
|
|
}
|
|
|
|
/// Mirrors `Menu::default()` with every `close_window` item omitted.
|
|
///
|
|
/// The Window and Help submenus keep Tauri's well-known ids: `init_app_menu`
|
|
/// looks them up by id to call `set_as_windows_menu_for_nsapp` and
|
|
/// `set_as_help_menu_for_nsapp`, and a plain `with_items` submenu would skip
|
|
/// both silently -- no error, just a Window menu AppKit no longer manages.
|
|
#[cfg(target_os = "macos")]
|
|
pub fn build<R: Runtime>(app: &AppHandle<R>) -> tauri::Result<Menu<R>> {
|
|
let pkg_info = app.package_info();
|
|
let config = app.config();
|
|
let about_metadata = AboutMetadata {
|
|
name: Some(pkg_info.name.clone()),
|
|
version: Some(pkg_info.version.to_string()),
|
|
copyright: config.bundle.copyright.clone(),
|
|
authors: config.bundle.publisher.clone().map(|p| vec![p]),
|
|
..Default::default()
|
|
};
|
|
|
|
Menu::with_items(
|
|
app,
|
|
&[
|
|
&Submenu::with_items(
|
|
app,
|
|
pkg_info.name.clone(),
|
|
true,
|
|
&[
|
|
&PredefinedMenuItem::about(app, None, Some(about_metadata))?,
|
|
&PredefinedMenuItem::separator(app)?,
|
|
&PredefinedMenuItem::services(app, None)?,
|
|
&PredefinedMenuItem::separator(app)?,
|
|
&PredefinedMenuItem::hide(app, None)?,
|
|
&PredefinedMenuItem::hide_others(app, None)?,
|
|
&PredefinedMenuItem::separator(app)?,
|
|
&PredefinedMenuItem::quit(app, None)?,
|
|
],
|
|
)?,
|
|
// `Menu::default()`'s File submenu holds exactly one item on macOS
|
|
// -- close_window -- so dropping that item drops the submenu too.
|
|
&Submenu::with_items(
|
|
app,
|
|
"Edit",
|
|
true,
|
|
&[
|
|
&PredefinedMenuItem::undo(app, None)?,
|
|
&PredefinedMenuItem::redo(app, None)?,
|
|
&PredefinedMenuItem::separator(app)?,
|
|
&PredefinedMenuItem::cut(app, None)?,
|
|
&PredefinedMenuItem::copy(app, None)?,
|
|
&PredefinedMenuItem::paste(app, None)?,
|
|
&PredefinedMenuItem::select_all(app, None)?,
|
|
],
|
|
)?,
|
|
&Submenu::with_items(
|
|
app,
|
|
"View",
|
|
true,
|
|
&[&PredefinedMenuItem::fullscreen(app, None)?],
|
|
)?,
|
|
&Submenu::with_id_and_items(
|
|
app,
|
|
WINDOW_SUBMENU_ID,
|
|
"Window",
|
|
true,
|
|
&[
|
|
&PredefinedMenuItem::minimize(app, None)?,
|
|
&PredefinedMenuItem::maximize(app, None)?,
|
|
],
|
|
)?,
|
|
// Empty upstream too on macOS: About lives in the app submenu.
|
|
&Submenu::with_id_and_items(app, HELP_SUBMENU_ID, "Help", true, &[])?,
|
|
],
|
|
)
|
|
}
|