mirror of
https://github.com/neovide/neovide.git
synced 2026-09-10 07:16:25 -04:00
feat: settings for menu shortcuts on macOS (#3481)
* feat: settings for menu shortcuts on macOS --------- Co-authored-by: Alexsander Falcucci <alex.falcucci@gmail.com>
This commit is contained in:
parent
b5363a36b7
commit
c8b801493a
143
src/cmd_line.rs
143
src/cmd_line.rs
|
|
@ -118,6 +118,69 @@ pub struct CmdLineSettings {
|
|||
#[arg(long = "no-system-native-tabs", action = ArgAction::SetTrue, value_parser = FalseyValueParser::new())]
|
||||
_no_system_native_tabs: bool,
|
||||
|
||||
/// Set the Window > New Window shortcut
|
||||
#[cfg(target_os = "macos")]
|
||||
#[arg(
|
||||
long = "system-new-window-hotkey",
|
||||
env = "NEOVIDE_SYSTEM_NEW_WINDOW_HOTKEY",
|
||||
default_value = "cmd+n"
|
||||
)]
|
||||
pub system_new_window_hotkey: String,
|
||||
|
||||
/// Set the Neovide > Hide shortcut
|
||||
#[cfg(target_os = "macos")]
|
||||
#[arg(
|
||||
long = "system-hide-hotkey",
|
||||
env = "NEOVIDE_SYSTEM_HIDE_HOTKEY",
|
||||
default_value = "cmd+h"
|
||||
)]
|
||||
pub system_hide_hotkey: String,
|
||||
|
||||
/// Set the Neovide > Hide Others shortcut
|
||||
#[cfg(target_os = "macos")]
|
||||
#[arg(
|
||||
long = "system-hide-others-hotkey",
|
||||
env = "NEOVIDE_SYSTEM_HIDE_OTHERS_HOTKEY",
|
||||
default_value = "cmd+alt+h"
|
||||
)]
|
||||
pub system_hide_others_hotkey: String,
|
||||
|
||||
/// Set the Neovide > Quit shortcut
|
||||
#[cfg(target_os = "macos")]
|
||||
#[arg(
|
||||
long = "system-quit-hotkey",
|
||||
env = "NEOVIDE_SYSTEM_QUIT_HOTKEY",
|
||||
default_value = "cmd+q"
|
||||
)]
|
||||
pub system_quit_hotkey: String,
|
||||
|
||||
/// Set the Window > Minimize shortcut
|
||||
#[cfg(target_os = "macos")]
|
||||
#[arg(
|
||||
long = "system-minimize-hotkey",
|
||||
env = "NEOVIDE_SYSTEM_MINIMIZE_HOTKEY",
|
||||
default_value = "cmd+m"
|
||||
)]
|
||||
pub system_minimize_hotkey: String,
|
||||
|
||||
/// Set the Window > Enter Full Screen shortcut
|
||||
#[cfg(target_os = "macos")]
|
||||
#[arg(
|
||||
long = "system-fullscreen-hotkey",
|
||||
env = "NEOVIDE_SYSTEM_FULLSCREEN_HOTKEY",
|
||||
default_value = "cmd+ctrl+f"
|
||||
)]
|
||||
pub system_fullscreen_hotkey: String,
|
||||
|
||||
/// Set the Window > Editors shortcut when native tabs are visible
|
||||
#[cfg(target_os = "macos")]
|
||||
#[arg(
|
||||
long = "system-show-all-tabs-hotkey",
|
||||
env = "NEOVIDE_SYSTEM_SHOW_ALL_TABS_HOTKEY",
|
||||
default_value = "cmd+shift+e"
|
||||
)]
|
||||
pub system_show_all_tabs_hotkey: String,
|
||||
|
||||
/// Cycle to the previous system tab when pressed inside Neovide
|
||||
#[cfg(target_os = "macos")]
|
||||
#[arg(
|
||||
|
|
@ -458,6 +521,86 @@ mod tests {
|
|||
assert!(handle_command_line_arguments(args, &settings).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(target_os = "macos")]
|
||||
fn test_system_new_window_hotkey_flag() {
|
||||
let settings = Settings::new();
|
||||
let args: Vec<String> = ["neovide", "--system-new-window-hotkey", "cmd+shift+n"]
|
||||
.iter()
|
||||
.map(|s| s.to_string())
|
||||
.collect();
|
||||
|
||||
handle_command_line_arguments(args, &settings).expect("Could not parse arguments");
|
||||
assert_eq!(settings.get::<CmdLineSettings>().system_new_window_hotkey, "cmd+shift+n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(target_os = "macos")]
|
||||
fn test_system_new_window_hotkey_environment_variable() {
|
||||
let settings = Settings::new();
|
||||
let args: Vec<String> = ["neovide"].iter().map(|s| s.to_string()).collect();
|
||||
|
||||
let _env = ScopedEnv::set("NEOVIDE_SYSTEM_NEW_WINDOW_HOTKEY", "ctrl+shift+n");
|
||||
handle_command_line_arguments(args, &settings).expect("Could not parse arguments");
|
||||
assert_eq!(settings.get::<CmdLineSettings>().system_new_window_hotkey, "ctrl+shift+n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(target_os = "macos")]
|
||||
fn test_system_menu_hotkey_flags() {
|
||||
let settings = Settings::new();
|
||||
let args: Vec<String> = [
|
||||
"neovide",
|
||||
"--system-hide-hotkey",
|
||||
"ctrl+h",
|
||||
"--system-hide-others-hotkey",
|
||||
"ctrl+alt+h",
|
||||
"--system-quit-hotkey",
|
||||
"cmd+shift+q",
|
||||
"--system-minimize-hotkey",
|
||||
"cmd+shift+m",
|
||||
"--system-fullscreen-hotkey",
|
||||
"ctrl+alt+f",
|
||||
"--system-show-all-tabs-hotkey",
|
||||
"cmd+e",
|
||||
]
|
||||
.iter()
|
||||
.map(|s| s.to_string())
|
||||
.collect();
|
||||
|
||||
handle_command_line_arguments(args, &settings).expect("Could not parse arguments");
|
||||
let cmdline = settings.get::<CmdLineSettings>();
|
||||
assert_eq!(cmdline.system_hide_hotkey, "ctrl+h");
|
||||
assert_eq!(cmdline.system_hide_others_hotkey, "ctrl+alt+h");
|
||||
assert_eq!(cmdline.system_quit_hotkey, "cmd+shift+q");
|
||||
assert_eq!(cmdline.system_minimize_hotkey, "cmd+shift+m");
|
||||
assert_eq!(cmdline.system_fullscreen_hotkey, "ctrl+alt+f");
|
||||
assert_eq!(cmdline.system_show_all_tabs_hotkey, "cmd+e");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(target_os = "macos")]
|
||||
fn test_system_menu_hotkey_environment_variables() {
|
||||
let settings = Settings::new();
|
||||
let args: Vec<String> = ["neovide"].iter().map(|s| s.to_string()).collect();
|
||||
|
||||
let _hide = ScopedEnv::set("NEOVIDE_SYSTEM_HIDE_HOTKEY", "ctrl+h");
|
||||
let _hide_others = ScopedEnv::set("NEOVIDE_SYSTEM_HIDE_OTHERS_HOTKEY", "ctrl+alt+h");
|
||||
let _quit = ScopedEnv::set("NEOVIDE_SYSTEM_QUIT_HOTKEY", "ctrl+q");
|
||||
let _minimize = ScopedEnv::set("NEOVIDE_SYSTEM_MINIMIZE_HOTKEY", "ctrl+m");
|
||||
let _fullscreen = ScopedEnv::set("NEOVIDE_SYSTEM_FULLSCREEN_HOTKEY", "ctrl+shift+f");
|
||||
let _show_all_tabs = ScopedEnv::set("NEOVIDE_SYSTEM_SHOW_ALL_TABS_HOTKEY", "ctrl+shift+e");
|
||||
|
||||
handle_command_line_arguments(args, &settings).expect("Could not parse arguments");
|
||||
let cmdline = settings.get::<CmdLineSettings>();
|
||||
assert_eq!(cmdline.system_hide_hotkey, "ctrl+h");
|
||||
assert_eq!(cmdline.system_hide_others_hotkey, "ctrl+alt+h");
|
||||
assert_eq!(cmdline.system_quit_hotkey, "ctrl+q");
|
||||
assert_eq!(cmdline.system_minimize_hotkey, "ctrl+m");
|
||||
assert_eq!(cmdline.system_fullscreen_hotkey, "ctrl+shift+f");
|
||||
assert_eq!(cmdline.system_show_all_tabs_hotkey, "ctrl+shift+e");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_grid() {
|
||||
let settings = Settings::new();
|
||||
|
|
|
|||
|
|
@ -15,9 +15,9 @@ use objc2::{
|
|||
};
|
||||
|
||||
use objc2_app_kit::{
|
||||
NSApplication, NSAutoresizingMaskOptions, NSColor, NSEvent, NSEventModifierFlags, NSFont,
|
||||
NSFontAttributeName, NSFontDescriptor, NSFontWeight, NSFontWeightLight, NSImage, NSMenu,
|
||||
NSMenuDelegate, NSMenuItem, NSTextView, NSView, NSWindow, NSWindowDidBecomeKeyNotification,
|
||||
NSApplication, NSAutoresizingMaskOptions, NSColor, NSEvent, NSFont, NSFontAttributeName,
|
||||
NSFontDescriptor, NSFontWeight, NSFontWeightLight, NSImage, NSMenu, NSMenuDelegate, NSMenuItem,
|
||||
NSTextView, NSView, NSWindow, NSWindowDidBecomeKeyNotification,
|
||||
NSWindowDidBecomeMainNotification, NSWindowStyleMask, NSWindowTabbingMode,
|
||||
NSWindowTitleVisibility, NSWorkspace,
|
||||
};
|
||||
|
|
@ -36,6 +36,7 @@ use crate::bridge::{
|
|||
use crate::renderer::fonts::font_options::FontOptions;
|
||||
use crate::settings::Settings;
|
||||
use crate::utils::expand_tilde;
|
||||
use crate::window::macos::tab_navigation::KeyCombo;
|
||||
use crate::{cmd_line::CmdLineSettings, frame::Frame};
|
||||
use winit::{event_loop::EventLoopProxy, window::Window};
|
||||
|
||||
|
|
@ -316,6 +317,22 @@ fn open_external_url(url: &str) {
|
|||
}
|
||||
}
|
||||
|
||||
fn apply_menu_item_hotkey(item: &NSMenuItem, raw: &str, setting_name: &str) {
|
||||
let Some(shortcut) = KeyCombo::parse(raw) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(key) = shortcut.to_key() else {
|
||||
log::warn!(
|
||||
"macOS menu shortcut '{raw}' for {setting_name} uses an unsupported named key; ignoring"
|
||||
);
|
||||
return;
|
||||
};
|
||||
|
||||
item.setKeyEquivalent(key.as_ref());
|
||||
item.setKeyEquivalentModifierMask(shortcut.to_modifiers());
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct MacosWindowFeature {
|
||||
ns_window: Retained<NSWindow>,
|
||||
|
|
@ -1111,7 +1128,7 @@ impl MacosWindowFeature {
|
|||
return;
|
||||
}
|
||||
|
||||
*menu_cell.borrow_mut() = Some(Menu::new(mtm));
|
||||
*menu_cell.borrow_mut() = Some(Menu::new(mtm, self.settings.as_ref()));
|
||||
let app = NSApplication::sharedApplication(mtm);
|
||||
#[allow(deprecated)]
|
||||
app.activateIgnoringOtherApps(true);
|
||||
|
|
@ -1516,7 +1533,7 @@ struct Menu {
|
|||
}
|
||||
|
||||
impl Menu {
|
||||
fn new(mtm: MainThreadMarker) -> Self {
|
||||
fn new(mtm: MainThreadMarker, settings: &Settings) -> Self {
|
||||
let menu = Menu {
|
||||
quit_handler: QuitHandler::new(mtm),
|
||||
help_menu_handler: HelpMenuHandler::new(mtm),
|
||||
|
|
@ -1526,11 +1543,11 @@ impl Menu {
|
|||
_window_menu_observer: WindowMenuNotificationHandler::register(mtm),
|
||||
window_menu_delegate: WindowMenuDelegate::new(mtm),
|
||||
};
|
||||
menu.add_menus(mtm);
|
||||
menu.add_menus(mtm, &settings.get::<CmdLineSettings>());
|
||||
menu
|
||||
}
|
||||
|
||||
fn add_app_menu(&self, mtm: MainThreadMarker) -> Retained<NSMenu> {
|
||||
fn add_app_menu(&self, mtm: MainThreadMarker, settings: &CmdLineSettings) -> Retained<NSMenu> {
|
||||
unsafe {
|
||||
let app_menu = NSMenu::new(mtm);
|
||||
let process_name = NSProcessInfo::processInfo().processName();
|
||||
|
|
@ -1551,15 +1568,16 @@ impl Menu {
|
|||
// application window operations
|
||||
let hide_item = NSMenuItem::new(mtm);
|
||||
hide_item.setTitle(&ns_string!("Hide ").stringByAppendingString(&process_name));
|
||||
hide_item.setKeyEquivalent(ns_string!("h"));
|
||||
apply_menu_item_hotkey(&hide_item, &settings.system_hide_hotkey, "system_hide_hotkey");
|
||||
hide_item.setAction(Some(sel!(hide:)));
|
||||
app_menu.addItem(&hide_item);
|
||||
|
||||
let hide_others_item = NSMenuItem::new(mtm);
|
||||
hide_others_item.setTitle(ns_string!("Hide Others"));
|
||||
hide_others_item.setKeyEquivalent(ns_string!("h"));
|
||||
hide_others_item.setKeyEquivalentModifierMask(
|
||||
NSEventModifierFlags::Option | NSEventModifierFlags::Command,
|
||||
apply_menu_item_hotkey(
|
||||
&hide_others_item,
|
||||
&settings.system_hide_others_hotkey,
|
||||
"system_hide_others_hotkey",
|
||||
);
|
||||
hide_others_item.setAction(Some(sel!(hideOtherApplications:)));
|
||||
app_menu.addItem(&hide_others_item);
|
||||
|
|
@ -1574,7 +1592,7 @@ impl Menu {
|
|||
|
||||
let quit_item = NSMenuItem::new(mtm);
|
||||
quit_item.setTitle(&ns_string!("Quit ").stringByAppendingString(&process_name));
|
||||
quit_item.setKeyEquivalent(ns_string!("q"));
|
||||
apply_menu_item_hotkey(&quit_item, &settings.system_quit_hotkey, "system_quit_hotkey");
|
||||
quit_item.setAction(Some(sel!(quit:)));
|
||||
quit_item.setTarget(Some(&self.quit_handler));
|
||||
app_menu.addItem(&quit_item);
|
||||
|
|
@ -1583,12 +1601,12 @@ impl Menu {
|
|||
}
|
||||
}
|
||||
|
||||
fn add_menus(&self, mtm: MainThreadMarker) {
|
||||
fn add_menus(&self, mtm: MainThreadMarker, settings: &CmdLineSettings) {
|
||||
let app = NSApplication::sharedApplication(mtm);
|
||||
|
||||
let main_menu = NSMenu::new(mtm);
|
||||
|
||||
let app_menu = self.add_app_menu(mtm);
|
||||
let app_menu = self.add_app_menu(mtm, settings);
|
||||
let app_menu_item = NSMenuItem::new(mtm);
|
||||
app_menu_item.setSubmenu(Some(&app_menu));
|
||||
if let Some(services_menu) = app_menu.itemWithTitle(ns_string!("Services")) {
|
||||
|
|
@ -1596,7 +1614,7 @@ impl Menu {
|
|||
}
|
||||
main_menu.addItem(&app_menu_item);
|
||||
|
||||
let win_menu = self.add_window_menu(mtm);
|
||||
let win_menu = self.add_window_menu(mtm, settings);
|
||||
let win_menu_item = NSMenuItem::new(mtm);
|
||||
win_menu_item.setSubmenu(Some(&win_menu));
|
||||
main_menu.addItem(&win_menu_item);
|
||||
|
|
@ -1612,7 +1630,11 @@ impl Menu {
|
|||
app.setMainMenu(Some(&main_menu));
|
||||
}
|
||||
|
||||
fn add_window_menu(&self, mtm: MainThreadMarker) -> Retained<NSMenu> {
|
||||
fn add_window_menu(
|
||||
&self,
|
||||
mtm: MainThreadMarker,
|
||||
settings: &CmdLineSettings,
|
||||
) -> Retained<NSMenu> {
|
||||
unsafe {
|
||||
let menu = NSMenu::new(mtm);
|
||||
menu.setTitle(ns_string!("Window"));
|
||||
|
|
@ -1622,16 +1644,21 @@ impl Menu {
|
|||
|
||||
let full_screen_item = NSMenuItem::new(mtm);
|
||||
full_screen_item.setTitle(ns_string!("Enter Full Screen"));
|
||||
full_screen_item.setKeyEquivalent(ns_string!("f"));
|
||||
full_screen_item.setAction(Some(sel!(toggleFullScreen:)));
|
||||
full_screen_item.setKeyEquivalentModifierMask(
|
||||
NSEventModifierFlags::Control | NSEventModifierFlags::Command,
|
||||
apply_menu_item_hotkey(
|
||||
&full_screen_item,
|
||||
&settings.system_fullscreen_hotkey,
|
||||
"system_fullscreen_hotkey",
|
||||
);
|
||||
full_screen_item.setAction(Some(sel!(toggleFullScreen:)));
|
||||
menu.addItem(&full_screen_item);
|
||||
|
||||
let create_new_window = NSMenuItem::new(mtm);
|
||||
create_new_window.setTitle(ns_string!("New Window"));
|
||||
create_new_window.setKeyEquivalent(ns_string!("n"));
|
||||
apply_menu_item_hotkey(
|
||||
&create_new_window,
|
||||
&settings.system_new_window_hotkey,
|
||||
"system_new_window_hotkey",
|
||||
);
|
||||
create_new_window.setAction(Some(sel!(neovideCreateWindow:)));
|
||||
create_new_window.setTarget(Some(&self.new_window_handler));
|
||||
menu.addItem(&create_new_window);
|
||||
|
|
@ -1639,9 +1666,10 @@ impl Menu {
|
|||
if should_show_native_tab_bar() {
|
||||
let show_all_tabs_item = NSMenuItem::new(mtm);
|
||||
show_all_tabs_item.setTitle(ns_string!("Editors"));
|
||||
show_all_tabs_item.setKeyEquivalent(ns_string!("e"));
|
||||
show_all_tabs_item.setKeyEquivalentModifierMask(
|
||||
NSEventModifierFlags::Command | NSEventModifierFlags::Shift,
|
||||
apply_menu_item_hotkey(
|
||||
&show_all_tabs_item,
|
||||
&settings.system_show_all_tabs_hotkey,
|
||||
"system_show_all_tabs_hotkey",
|
||||
);
|
||||
show_all_tabs_item.setAction(Some(sel!(neovideShowAllTabs:)));
|
||||
show_all_tabs_item.setTarget(Some(&self.tab_overview_handler));
|
||||
|
|
@ -1650,7 +1678,11 @@ impl Menu {
|
|||
|
||||
let min_item = NSMenuItem::new(mtm);
|
||||
min_item.setTitle(ns_string!("Minimize"));
|
||||
min_item.setKeyEquivalent(ns_string!("m"));
|
||||
apply_menu_item_hotkey(
|
||||
&min_item,
|
||||
&settings.system_minimize_hotkey,
|
||||
"system_minimize_hotkey",
|
||||
);
|
||||
min_item.setAction(Some(sel!(performMiniaturize:)));
|
||||
menu.addItem(&min_item);
|
||||
menu
|
||||
|
|
|
|||
|
|
@ -3,7 +3,10 @@
|
|||
use std::{env, fs, sync::mpsc, time::Duration};
|
||||
|
||||
use notify_debouncer_full::{new_debouncer, notify::RecursiveMode};
|
||||
use serde::Deserialize;
|
||||
use serde::{
|
||||
Deserialize, Deserializer,
|
||||
de::{Error as DeError, Unexpected},
|
||||
};
|
||||
use winit::event_loop::EventLoopProxy;
|
||||
|
||||
use crate::{
|
||||
|
|
@ -45,6 +48,27 @@ pub fn config_path() -> PathBuf {
|
|||
})
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(untagged)]
|
||||
enum HotkeyConfigValue {
|
||||
String(String),
|
||||
Bool(bool),
|
||||
}
|
||||
|
||||
fn deserialize_optional_hotkey<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
match Option::<HotkeyConfigValue>::deserialize(deserializer)? {
|
||||
None => Ok(None),
|
||||
Some(HotkeyConfigValue::String(value)) => Ok(Some(value)),
|
||||
Some(HotkeyConfigValue::Bool(false)) => Ok(Some("false".to_string())),
|
||||
Some(HotkeyConfigValue::Bool(true)) => {
|
||||
Err(D::Error::invalid_value(Unexpected::Bool(true), &"a shortcut string or false"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Default, Clone)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub struct Config {
|
||||
|
|
@ -67,9 +91,27 @@ pub struct Config {
|
|||
pub vsync: Option<bool>,
|
||||
pub wsl: Option<bool>,
|
||||
pub backtraces_path: Option<PathBuf>,
|
||||
#[serde(default, deserialize_with = "deserialize_optional_hotkey")]
|
||||
pub system_pinned_hotkey: Option<String>,
|
||||
#[serde(default, deserialize_with = "deserialize_optional_hotkey")]
|
||||
pub system_switcher_hotkey: Option<String>,
|
||||
#[serde(default, deserialize_with = "deserialize_optional_hotkey")]
|
||||
pub system_new_window_hotkey: Option<String>,
|
||||
#[serde(default, deserialize_with = "deserialize_optional_hotkey")]
|
||||
pub system_hide_hotkey: Option<String>,
|
||||
#[serde(default, deserialize_with = "deserialize_optional_hotkey")]
|
||||
pub system_hide_others_hotkey: Option<String>,
|
||||
#[serde(default, deserialize_with = "deserialize_optional_hotkey")]
|
||||
pub system_quit_hotkey: Option<String>,
|
||||
#[serde(default, deserialize_with = "deserialize_optional_hotkey")]
|
||||
pub system_minimize_hotkey: Option<String>,
|
||||
#[serde(default, deserialize_with = "deserialize_optional_hotkey")]
|
||||
pub system_fullscreen_hotkey: Option<String>,
|
||||
#[serde(default, deserialize_with = "deserialize_optional_hotkey")]
|
||||
pub system_show_all_tabs_hotkey: Option<String>,
|
||||
#[serde(default, deserialize_with = "deserialize_optional_hotkey")]
|
||||
pub system_tab_prev_hotkey: Option<String>,
|
||||
#[serde(default, deserialize_with = "deserialize_optional_hotkey")]
|
||||
pub system_tab_next_hotkey: Option<String>,
|
||||
pub icon: Option<String>,
|
||||
pub chdir: Option<PathBuf>,
|
||||
|
|
@ -179,6 +221,27 @@ impl Config {
|
|||
if let Some(switcher_hotkey) = &self.system_switcher_hotkey {
|
||||
unsafe { env::set_var("NEOVIDE_SYSTEM_SWITCHER_HOTKEY", switcher_hotkey) };
|
||||
}
|
||||
if let Some(new_window_hotkey) = &self.system_new_window_hotkey {
|
||||
unsafe { env::set_var("NEOVIDE_SYSTEM_NEW_WINDOW_HOTKEY", new_window_hotkey) };
|
||||
}
|
||||
if let Some(hide_hotkey) = &self.system_hide_hotkey {
|
||||
unsafe { env::set_var("NEOVIDE_SYSTEM_HIDE_HOTKEY", hide_hotkey) };
|
||||
}
|
||||
if let Some(hide_others_hotkey) = &self.system_hide_others_hotkey {
|
||||
unsafe { env::set_var("NEOVIDE_SYSTEM_HIDE_OTHERS_HOTKEY", hide_others_hotkey) };
|
||||
}
|
||||
if let Some(quit_hotkey) = &self.system_quit_hotkey {
|
||||
unsafe { env::set_var("NEOVIDE_SYSTEM_QUIT_HOTKEY", quit_hotkey) };
|
||||
}
|
||||
if let Some(minimize_hotkey) = &self.system_minimize_hotkey {
|
||||
unsafe { env::set_var("NEOVIDE_SYSTEM_MINIMIZE_HOTKEY", minimize_hotkey) };
|
||||
}
|
||||
if let Some(fullscreen_hotkey) = &self.system_fullscreen_hotkey {
|
||||
unsafe { env::set_var("NEOVIDE_SYSTEM_FULLSCREEN_HOTKEY", fullscreen_hotkey) };
|
||||
}
|
||||
if let Some(show_all_tabs_hotkey) = &self.system_show_all_tabs_hotkey {
|
||||
unsafe { env::set_var("NEOVIDE_SYSTEM_SHOW_ALL_TABS_HOTKEY", show_all_tabs_hotkey) };
|
||||
}
|
||||
if let Some(tab_prev_hotkey) = &self.system_tab_prev_hotkey {
|
||||
unsafe { env::set_var("NEOVIDE_SYSTEM_TAB_PREV_HOTKEY", tab_prev_hotkey) };
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,7 @@
|
|||
use log::warn;
|
||||
use objc2::rc::Retained;
|
||||
use objc2_app_kit::NSEventModifierFlags;
|
||||
use objc2_foundation::NSString;
|
||||
use winit::{
|
||||
event::{ElementState, KeyEvent, Modifiers},
|
||||
keyboard::{Key, NamedKey},
|
||||
|
|
@ -7,19 +10,19 @@ use winit::{
|
|||
use crate::{CmdLineSettings, settings::Settings};
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub(crate) enum TabNavigationAction {
|
||||
pub enum TabNavigationAction {
|
||||
Next,
|
||||
Previous,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct TabNavigationHotkeys {
|
||||
pub struct TabNavigationHotkeys {
|
||||
next: Option<KeyCombo>,
|
||||
prev: Option<KeyCombo>,
|
||||
}
|
||||
|
||||
impl TabNavigationHotkeys {
|
||||
pub(crate) fn new(settings: &Settings) -> Self {
|
||||
pub fn new(settings: &Settings) -> Self {
|
||||
let cmdline = settings.get::<CmdLineSettings>();
|
||||
Self {
|
||||
next: KeyCombo::parse(&cmdline.system_tab_next_hotkey),
|
||||
|
|
@ -27,7 +30,7 @@ impl TabNavigationHotkeys {
|
|||
}
|
||||
}
|
||||
|
||||
pub(crate) fn action_for(
|
||||
pub fn action_for(
|
||||
&self,
|
||||
event: &KeyEvent,
|
||||
modifiers: &Modifiers,
|
||||
|
|
@ -45,8 +48,8 @@ impl TabNavigationHotkeys {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
struct KeyCombo {
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct KeyCombo {
|
||||
command: bool,
|
||||
control: bool,
|
||||
option: bool,
|
||||
|
|
@ -55,7 +58,7 @@ struct KeyCombo {
|
|||
}
|
||||
|
||||
impl KeyCombo {
|
||||
fn parse(raw: &str) -> Option<Self> {
|
||||
pub fn parse(raw: &str) -> Option<Self> {
|
||||
let trimmed = raw.trim();
|
||||
if trimmed.is_empty() || is_disabled_keyword(trimmed) {
|
||||
return None;
|
||||
|
|
@ -72,6 +75,41 @@ impl KeyCombo {
|
|||
.build(raw)
|
||||
}
|
||||
|
||||
pub fn to_modifiers(self) -> NSEventModifierFlags {
|
||||
let mut flags = NSEventModifierFlags::empty();
|
||||
|
||||
if self.command {
|
||||
flags |= NSEventModifierFlags::Command;
|
||||
}
|
||||
|
||||
if self.control {
|
||||
flags |= NSEventModifierFlags::Control;
|
||||
}
|
||||
|
||||
if self.option {
|
||||
flags |= NSEventModifierFlags::Option;
|
||||
}
|
||||
|
||||
if self.shift {
|
||||
flags |= NSEventModifierFlags::Shift;
|
||||
}
|
||||
|
||||
flags
|
||||
}
|
||||
|
||||
/// Constructs an `NSString` representing the key component of this combo, if it's a character
|
||||
/// key. Named keys will return `None`.
|
||||
pub fn to_key(self) -> Option<Retained<NSString>> {
|
||||
match self.key {
|
||||
KeyMatch::Char(character) => Some(NSString::from_str(&character.to_string())),
|
||||
KeyMatch::Named(_named) => {
|
||||
// TODO: Figure out how to represent named keys in a way that can be used with
|
||||
// NSEvent. For now, we don't support this.
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn matches(&self, event: &KeyEvent, modifiers: &Modifiers) -> bool {
|
||||
if !self.modifiers_match(modifiers) {
|
||||
return false;
|
||||
|
|
@ -121,15 +159,12 @@ fn parse_token(value: &str, raw: &str) -> Option<ParsedToken> {
|
|||
fn parse_character_key(value: &str, raw: &str) -> Option<char> {
|
||||
let mut chars = value.chars();
|
||||
let Some(ch) = chars.next() else {
|
||||
warn!("macOS tab navigation shortcut '{}' has no key; ignoring", raw);
|
||||
warn!("macOS shortcut '{}' has no key; ignoring", raw);
|
||||
return None;
|
||||
};
|
||||
|
||||
if chars.next().is_some() {
|
||||
warn!(
|
||||
"macOS tab navigation shortcut '{}' must end with a single character key; ignoring",
|
||||
raw
|
||||
);
|
||||
warn!("macOS shortcut '{}' must end with a single character key; ignoring", raw);
|
||||
return None;
|
||||
}
|
||||
|
||||
|
|
@ -166,7 +201,7 @@ impl ParseState {
|
|||
fn set_key(&mut self, value: KeyMatch, raw: &str) -> Option<()> {
|
||||
match self.key {
|
||||
Some(_) => {
|
||||
warn!("macOS tab navigation shortcut '{}' has multiple keys; ignoring", raw);
|
||||
warn!("macOS shortcut '{}' has multiple keys; ignoring", raw);
|
||||
None
|
||||
}
|
||||
None => {
|
||||
|
|
@ -183,10 +218,7 @@ impl ParseState {
|
|||
option: self.option,
|
||||
shift: self.shift,
|
||||
key: self.key.or_else(|| {
|
||||
warn!(
|
||||
"macOS tab navigation shortcut '{}' is missing a key component; ignoring",
|
||||
raw
|
||||
);
|
||||
warn!("macOS shortcut '{}' is missing a key component; ignoring", raw);
|
||||
None
|
||||
})?,
|
||||
})
|
||||
|
|
@ -197,7 +229,7 @@ fn is_disabled_keyword(value: &str) -> bool {
|
|||
value.trim().eq_ignore_ascii_case("false")
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
enum KeyMatch {
|
||||
Char(char),
|
||||
Named(NamedKey),
|
||||
|
|
|
|||
|
|
@ -264,6 +264,23 @@ default to mimic a standalone window. Enable this option to keep the tab bar vis
|
|||
shows up as a tab immediately. The setting applies to windows opened through both global shortcuts
|
||||
and the Editors menu entry.
|
||||
|
||||
### Menu Shortcuts
|
||||
|
||||
```sh
|
||||
--system-hide-hotkey <combo> or $NEOVIDE_SYSTEM_HIDE_HOTKEY
|
||||
--system-hide-others-hotkey <combo> or $NEOVIDE_SYSTEM_HIDE_OTHERS_HOTKEY
|
||||
--system-quit-hotkey <combo> or $NEOVIDE_SYSTEM_QUIT_HOTKEY
|
||||
--system-new-window-hotkey <combo> or $NEOVIDE_SYSTEM_NEW_WINDOW_HOTKEY
|
||||
--system-minimize-hotkey <combo> or $NEOVIDE_SYSTEM_MINIMIZE_HOTKEY
|
||||
--system-fullscreen-hotkey <combo> or $NEOVIDE_SYSTEM_FULLSCREEN_HOTKEY
|
||||
--system-show-all-tabs-hotkey <combo> or $NEOVIDE_SYSTEM_SHOW_ALL_TABS_HOTKEY
|
||||
```
|
||||
|
||||
Remaps the macOS menu shortcuts used by Neovide. The defaults are `cmd+h`, `cmd+alt+h`, `cmd+q`,
|
||||
`cmd+n`, `cmd+m`, `cmd+ctrl+f`, and `cmd+shift+e` for Hide, Hide Others, Quit, New Window,
|
||||
Minimize, Enter Full Screen, and Editors respectively. Set any of them to `false` or leave them
|
||||
empty to remove the shortcut while keeping the menu item available.
|
||||
|
||||
### System Tab Navigation
|
||||
|
||||
```sh
|
||||
|
|
|
|||
|
|
@ -48,6 +48,13 @@ tabs = true
|
|||
system-native-tabs = false # macOS only
|
||||
system-pinned-hotkey = "cmd+ctrl+z" # macOS only
|
||||
system-switcher-hotkey = "cmd+ctrl+n" # macOS only, requires system-native-tabs = true
|
||||
system-new-window-hotkey = "cmd+n" # macOS only
|
||||
system-hide-hotkey = "cmd+h" # macOS only
|
||||
system-hide-others-hotkey = "cmd+alt+h" # macOS only
|
||||
system-quit-hotkey = "cmd+q" # macOS only
|
||||
system-minimize-hotkey = "cmd+m" # macOS only
|
||||
system-fullscreen-hotkey = "cmd+ctrl+f" # macOS only
|
||||
system-show-all-tabs-hotkey = "cmd+shift+e" # macOS only
|
||||
system-tab-prev-hotkey = "cmd+shift+[" # macOS only
|
||||
system-tab-next-hotkey = "cmd+shift+]" # macOS only
|
||||
title-hidden = false
|
||||
|
|
|
|||
|
|
@ -918,8 +918,8 @@ single host window.
|
|||
Set `system-native-tabs = true` to merge windows into a tab group. The native tab bar stays hidden
|
||||
until more than one tab exists to keep a clean single-window look.
|
||||
|
||||
Use Window > New Window (cmd+n) or the Dock menu to open another Neovide window. If native tabs
|
||||
are enabled, new windows become tabs in the host window.
|
||||
Use Window > New Window (default: `cmd+n`) or the Dock menu to open another Neovide window. If
|
||||
native tabs are enabled, new windows become tabs in the host window.
|
||||
|
||||
If you have native tabs enabled, the Window menu shows an Editors entry and the Editors hotkey
|
||||
becomes available. You can also remap the in-app tab cycling shortcuts.
|
||||
|
|
@ -957,6 +957,21 @@ system-pinned-hotkey = "ctrl+shift+z"
|
|||
system-switcher-hotkey = "ctrl+shift+n"
|
||||
```
|
||||
|
||||
You can also remap the macOS application and Window menu shortcuts:
|
||||
|
||||
```toml
|
||||
system-hide-hotkey = "cmd+h"
|
||||
system-hide-others-hotkey = "cmd+alt+h"
|
||||
system-quit-hotkey = "cmd+q"
|
||||
system-new-window-hotkey = "cmd+n"
|
||||
system-minimize-hotkey = "cmd+m"
|
||||
system-fullscreen-hotkey = "cmd+ctrl+f"
|
||||
system-show-all-tabs-hotkey = "cmd+shift+e"
|
||||
```
|
||||
|
||||
Set any of them to `false` (or an empty value) to remove the menu shortcut while keeping the menu
|
||||
item.
|
||||
|
||||
When `system-native-tabs` is enabled, you can also customize the in-app tab navigation shortcuts:
|
||||
|
||||
```toml
|
||||
|
|
|
|||
Loading…
Reference in a new issue