diff --git a/src/bridge/handler.rs b/src/bridge/handler.rs index af2b120..db9a910 100644 --- a/src/bridge/handler.rs +++ b/src/bridge/handler.rs @@ -1,5 +1,5 @@ use async_trait::async_trait; -use log::trace; +use log::{trace, error}; use nvim_rs::{compat::tokio::Compat, Handler, Neovim}; use rmpv::Value; use tokio::process::ChildStdin; @@ -8,6 +8,12 @@ use tokio::task; use super::events::handle_redraw_event_group; use crate::settings::SETTINGS; +#[cfg(windows)] +use crate::settings::windows_registry::{ + unregister_rightclick, + register_rightclick_directory, register_rightclick_file +}; + #[derive(Clone)] pub struct NeovimHandler(); @@ -29,6 +35,19 @@ impl Handler for NeovimHandler { "setting_changed" => { SETTINGS.handle_changed_notification(arguments); } + "neovide.register_right_click" => { + if cfg!(windows) && + !(unregister_rightclick() && + register_rightclick_directory() && + register_rightclick_file()) { + error!("Setup of Windows Registry failed, probably no Admin"); + } + } + "neovide.unregister_right_click" => { + if cfg!(windows) && !unregister_rightclick() { + error!("Removal of Windows Registry failed, probably no Admin"); + } + } _ => {} }) .await diff --git a/src/bridge/mod.rs b/src/bridge/mod.rs index 3a92116..96e00ad 100644 --- a/src/bridge/mod.rs +++ b/src/bridge/mod.rs @@ -108,6 +108,19 @@ async fn drain(receiver: &mut UnboundedReceiver) -> Option String { + let nargs: String = if num_args > 1 { "+".to_string() } else { num_args.to_string() }; + if num_args == 0 { + return format!("command! -nargs={} -complete=expression {} call rpcnotify({}, 'neovide.{}')", + nargs, command, channel, event + ); + } else { + return format!("command! -nargs={} -complete=expression {} call rpcnotify({}, 'neovide.{}', )", + nargs, command, channel, event + ); + }; +} + async fn start_process(mut receiver: UnboundedReceiver) { let (width, height) = window_geometry_or_default(); let (mut nvim, io_handler, _) = @@ -153,6 +166,54 @@ async fn start_process(mut receiver: UnboundedReceiver) { .await .ok(); } + + nvim.set_client_info( + "neovide", + vec![(Value::from("major"), Value::from(0u64)), + (Value::from("minor"), Value::from(6u64))], + "ui", + vec![], + vec![] + ).await.ok(); + + // parsing the client list to get the channel id for neovide + let neovide_channel: u64 = + nvim.list_chans().await.ok() + .and_then(|chans| { + for chan in chans.iter() { + let mut found = false; + let mut id = None; + for (key, val) in chan.as_map()?.iter() { + if key.as_str()? == "id" { + id = val.as_u64(); + continue; + } + if key.as_str()? != "client" { + continue; + } + for (attribute, value) in val.as_map()?.iter() { + if attribute.as_str()? == "name" && value.as_str()? == "neovide" { + found = true; + break; + } + } + } + if found { + return id; + } + } + None + }).unwrap_or(0); + info!("Neovide registered to nvim with channel id {}", neovide_channel); + + #[cfg(windows)] + nvim.command(&build_neovide_command(neovide_channel, 0, "NeovideRegisterRightClick", "register_right_click")) + .await.ok(); + + #[cfg(windows)] + nvim.command(&build_neovide_command(neovide_channel, 0, "NeovideUnregisterRightClick", "unregister_right_click")) + .await.ok(); + nvim.ui_attach(width as i64, height as i64, &options) .await .unwrap_or_explained_panic("Could not attach ui to neovim process"); diff --git a/src/settings/mod.rs b/src/settings/mod.rs index 7897681..9d4346d 100644 --- a/src/settings/mod.rs +++ b/src/settings/mod.rs @@ -11,6 +11,7 @@ use parking_lot::RwLock; pub use rmpv::Value; mod from_value; pub use from_value::FromValue; +pub mod windows_registry; use tokio::process::ChildStdin; diff --git a/src/settings/windows_registry.rs b/src/settings/windows_registry.rs new file mode 100644 index 0000000..2b127d5 --- /dev/null +++ b/src/settings/windows_registry.rs @@ -0,0 +1,132 @@ +use std::ffi::{CString}; +use std::ptr::{null, null_mut}; +#[cfg(windows)] +use winapi::{ + shared::minwindef::{HKEY, MAX_PATH, DWORD}, + um::{ + winnt::{ + REG_SZ, REG_OPTION_NON_VOLATILE, KEY_WRITE + }, + winreg::{ + RegCreateKeyExA, RegSetValueExA, RegCloseKey, RegDeleteTreeA, + HKEY_CLASSES_ROOT + }, + libloaderapi::{ + GetModuleFileNameA + }, + } +}; + +#[cfg(target_os = "windows")] +fn get_binary_path() -> String { + let mut buffer = vec![0u8; MAX_PATH]; + unsafe { + GetModuleFileNameA(null_mut(), buffer.as_mut_ptr() as *mut i8, MAX_PATH as DWORD); + CString::from_vec_unchecked(buffer) + .into_string().unwrap_or_else(|_| "".to_string()) + .trim_end_matches(char::from(0)).to_string() + } +} + +#[cfg(target_os = "windows")] +pub fn unregister_rightclick() -> bool { + let str_registry_path_1 = CString::new("Directory\\Background\\shell\\Neovide").unwrap(); + let str_registry_path_2 = CString::new("*\\shell\\Neovide").unwrap(); + unsafe { + let s1 = RegDeleteTreeA(HKEY_CLASSES_ROOT, str_registry_path_1.as_ptr()); + let s2 = RegDeleteTreeA(HKEY_CLASSES_ROOT, str_registry_path_2.as_ptr()); + s1 == 0 && s2 == 0 + } +} + +#[cfg(target_os = "windows")] +pub fn register_rightclick_directory() -> bool { + let neovide_path = get_binary_path(); + let mut registry_key: HKEY = null_mut(); + let str_registry_path = CString::new("Directory\\Background\\shell\\Neovide").unwrap(); + let str_registry_command_path = CString::new("Directory\\Background\\shell\\Neovide\\command").unwrap(); + let str_icon = CString::new("Icon").unwrap(); + let str_command = CString::new(format!("{} \"%V\"", neovide_path).as_bytes()).unwrap(); + let str_description= CString::new("Open with Neovide").unwrap(); + let str_neovide_path = CString::new(neovide_path.as_bytes()).unwrap(); + unsafe { + if RegCreateKeyExA( + HKEY_CLASSES_ROOT, str_registry_path.as_ptr(), + 0, null_mut(), + REG_OPTION_NON_VOLATILE, KEY_WRITE, + null_mut(), &mut registry_key, null_mut()) != 0 { + RegCloseKey(registry_key); + return false; + } + let registry_values = [ + (null(), REG_SZ, str_description.as_ptr() as *const u8, str_description.to_bytes().len() + 1), + (str_icon.as_ptr(), REG_SZ, str_neovide_path.as_ptr() as *const u8, str_neovide_path.to_bytes().len() + 1), + ]; + for &(key, keytype, value_ptr, size_in_bytes) in ®istry_values { + RegSetValueExA(registry_key, key, 0, keytype, value_ptr, size_in_bytes as u32); + } + RegCloseKey(registry_key); + + if RegCreateKeyExA( + HKEY_CLASSES_ROOT, str_registry_command_path.as_ptr(), + 0, null_mut(), + REG_OPTION_NON_VOLATILE, KEY_WRITE, + null_mut(), &mut registry_key, null_mut()) != 0 { + return false; + } + let registry_values = [ + (null(), REG_SZ, str_command.as_ptr() as *const u8, str_command.to_bytes().len() + 1), + ]; + for &(key, keytype, value_ptr, size_in_bytes) in ®istry_values { + RegSetValueExA(registry_key, key, 0, keytype, value_ptr, size_in_bytes as u32); + } + RegCloseKey(registry_key); + } + true +} + +#[cfg(target_os = "windows")] +pub fn register_rightclick_file() -> bool { + let neovide_path = get_binary_path(); + let mut registry_key: HKEY = null_mut(); + let str_registry_path = CString::new("*\\shell\\Neovide").unwrap(); + let str_registry_command_path = CString::new("*\\shell\\Neovide\\command").unwrap(); + let str_icon = CString::new("Icon").unwrap(); + let str_command = CString::new(format!("{} \"%1\"", neovide_path).as_bytes()).unwrap(); + let str_description= CString::new("Open with Neovide").unwrap(); + let str_neovide_path = CString::new(neovide_path.as_bytes()).unwrap(); + unsafe { + if RegCreateKeyExA( + HKEY_CLASSES_ROOT, str_registry_path.as_ptr(), + 0, null_mut(), + REG_OPTION_NON_VOLATILE, KEY_WRITE, + null_mut(), &mut registry_key, null_mut()) != 0 { + RegCloseKey(registry_key); + return false; + } + let registry_values = [ + (null(), REG_SZ, str_description.as_ptr() as *const u8, str_description.to_bytes().len() + 1), + (str_icon.as_ptr(), REG_SZ, str_neovide_path.as_ptr() as *const u8, str_neovide_path.to_bytes().len() + 1), + ]; + for &(key, keytype, value_ptr, size_in_bytes) in ®istry_values { + RegSetValueExA(registry_key, key, 0, keytype, value_ptr, size_in_bytes as u32); + } + RegCloseKey(registry_key); + + if RegCreateKeyExA( + HKEY_CLASSES_ROOT, str_registry_command_path.as_ptr(), + 0, null_mut(), + REG_OPTION_NON_VOLATILE, KEY_WRITE, + null_mut(), &mut registry_key, null_mut()) != 0 { + return false; + } + let registry_values = [ + (null(), REG_SZ, str_command.as_ptr() as *const u8, str_command.to_bytes().len() + 1), + ]; + for &(key, keytype, value_ptr, size_in_bytes) in ®istry_values { + RegSetValueExA(registry_key, key, 0, keytype, value_ptr, size_in_bytes as u32); + } + RegCloseKey(registry_key); + } + true +}