diff --git a/Cargo.toml b/Cargo.toml index 35b614d..1acb926 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,74 +1,75 @@ -[package] -name = "neovide" -version = "0.6.0" -authors = ["keith "] -edition = "2018" -build = "build.rs" -description = "A simple GUI for Neovim." - -[features] -default = ["sdl2"] -embed-fonts = [] -sdl2 = ["skulpin/skulpin_sdl2"] - -[dependencies] -euclid = "0.20.7" -font-kit = "0.10.0" -skribo = { git = "https://github.com/linebender/skribo" } -lru = "0.4.3" -skulpin = { git = "https://github.com/aclysma/skulpin", branch = "master", features = ["skulpin_sdl2"] } -derive-new = "0.5" -rmpv = "0.4.4" -rust-embed = { version = "5.2.0", features = ["debug-embed"] } -image = "0.22.3" -nvim-rs = { git = "https://github.com/kethku/nvim-rs", features = [ "use_tokio" ] } -tokio = { version = "0.2.9", features = [ "blocking", "process", "time" ] } -async-trait = "0.1.18" -crossfire = "0.1" -lazy_static = "1.4.0" -unicode-segmentation = "1.6.0" -log = "0.4.8" -flexi_logger = { version = "0.14.6", default-features = false } -anyhow = "1.0.26" -parking_lot="0.10.0" -cfg-if = "0.1.10" -which = "4" -dirs = "2" -rand = "0.7" -skia-safe = "0.32.1" - -[dev-dependencies] -mockall = "0.7.0" - -[dev-dependencies.cargo-husky] -version = "1" -default-features = false -features = ["precommit-hook", "run-cargo-test", "run-cargo-clippy", "run-cargo-fmt"] - -[target.'cfg(windows)'.dependencies] -winapi = "0.3.8" - -[target.'cfg(windows)'.build-dependencies] -winres = "0.1.11" -sdl2-sys = { version = "0.34.3", default-features = false, features = ["bundled", "static-link"] } - -[target.'cfg(macos)'.build-dependencies] -sdl2-sys = { version = "0.34.3", default-features = false, features = ["bundled", "static-link"] } - -[profile.release] -debug = true -lto = true -incremental = true - -[package.metadata.bundle] -name = "Neovide" -identifier = "com.kethku.neovide" -icon = ["assets/nvim.ico"] -version = "0.6.0" -resources = [] -copyright = "Copyright (c) keith 2020. All rights reserved." -category = "Productivity" -short_description = "A simple GUI for Neovim." -long_description = """ -This is a simple graphical user interface for Neovim. Where possible there are some graphical improvements, but it should act functionally like the terminal UI. -""" +[package] +name = "neovide" +version = "0.6.0" +authors = ["keith "] +edition = "2018" +build = "build.rs" +description = "A simple GUI for Neovim." + +[features] +default = ["sdl2"] +embed-fonts = [] +sdl2 = ["skulpin/skulpin_sdl2"] +winit = ["skulpin/skulpin_winit"] + +[dependencies] +euclid = "0.20.7" +font-kit = "0.10.0" +skribo = { git = "https://github.com/linebender/skribo" } +lru = "0.4.3" +skulpin = { git = "https://github.com/aclysma/skulpin", branch = "master", features = ["skulpin_sdl2"] } +derive-new = "0.5" +rmpv = "0.4.4" +rust-embed = { version = "5.2.0", features = ["debug-embed"] } +image = "0.22.3" +nvim-rs = { git = "https://github.com/kethku/nvim-rs", features = [ "use_tokio" ] } +tokio = { version = "0.2.9", features = [ "blocking", "process", "time" ] } +async-trait = "0.1.18" +crossfire = "0.1" +lazy_static = "1.4.0" +unicode-segmentation = "1.6.0" +log = "0.4.8" +flexi_logger = { version = "0.14.6", default-features = false } +anyhow = "1.0.26" +parking_lot="0.10.0" +cfg-if = "0.1.10" +which = "4" +dirs = "2" +rand = "0.7" +skia-safe = "0.32.1" + +[dev-dependencies] +mockall = "0.7.0" + +[dev-dependencies.cargo-husky] +version = "1" +default-features = false +features = ["precommit-hook", "run-cargo-test", "run-cargo-clippy", "run-cargo-fmt"] + +[target.'cfg(windows)'.dependencies] +winapi = "0.3.8" + +[target.'cfg(windows)'.build-dependencies] +winres = "0.1.11" +sdl2-sys = { version = "0.34.3", default-features = false, features = ["bundled", "static-link"] } + +[target.'cfg(macos)'.build-dependencies] +sdl2-sys = { version = "0.34.3", default-features = false, features = ["bundled", "static-link"] } + +[profile.release] +debug = true +lto = true +incremental = true + +[package.metadata.bundle] +name = "Neovide" +identifier = "com.kethku.neovide" +icon = ["assets/nvim.ico"] +version = "0.6.0" +resources = [] +copyright = "Copyright (c) keith 2020. All rights reserved." +category = "Productivity" +short_description = "A simple GUI for Neovim." +long_description = """ +This is a simple graphical user interface for Neovim. Where possible there are some graphical improvements, but it should act functionally like the terminal UI. +""" diff --git a/src/bridge/mod.rs b/src/bridge/mod.rs index 2d722e2..7fab44b 100644 --- a/src/bridge/mod.rs +++ b/src/bridge/mod.rs @@ -1,284 +1,280 @@ -#[macro_use] -pub mod layouts; - -mod events; -mod handler; -mod ui_commands; - -use std::env; -use std::path::Path; -use std::process::Stdio; -use std::sync::atomic::{AtomicBool, Ordering}; -use std::sync::Arc; - -use crossfire::mpsc::{RxUnbounded, TxUnbounded}; -use log::{error, info, warn}; -use nvim_rs::{create::tokio as create, UiAttachOptions}; -use rmpv::Value; -use tokio::process::Command; -use tokio::runtime::Runtime; - -use crate::error_handling::ResultPanicExplanation; -use crate::settings::*; -use crate::window::window_geometry_or_default; -pub use events::*; -use handler::NeovimHandler; -pub use layouts::*; -pub use ui_commands::UiCommand; - -#[cfg(windows)] -fn set_windows_creation_flags(cmd: &mut Command) { - cmd.creation_flags(0x0800_0000); // CREATE_NO_WINDOW -} - -#[cfg(windows)] -fn platform_build_nvim_cmd(bin: &str) -> Option { - if !Path::new(&bin).exists() { - return None; - } - - if env::args().any(|arg| arg == "--wsl") { - let mut cmd = Command::new("wsl"); - cmd.arg(bin); - Some(cmd) - } else { - Some(Command::new(bin)) - } -} - -#[cfg(unix)] -fn platform_build_nvim_cmd(bin: &str) -> Option { - if Path::new(&bin).exists() { - Some(Command::new(bin)) - } else { - None - } -} - -fn build_nvim_cmd() -> Command { - if let Ok(path) = env::var("NEOVIM_BIN") { - if let Some(cmd) = platform_build_nvim_cmd(&path) { - return cmd; - } else { - warn!("NEOVIM_BIN is invalid falling back to first bin in PATH"); - } - } - if let Ok(path) = which::which("nvim") { - if let Some(cmd) = platform_build_nvim_cmd(path.to_str().unwrap()) { - cmd - } else { - error!("nvim does not have proper permissions!"); - std::process::exit(1); - } - } else { - error!("nvim not found!"); - std::process::exit(1); - } -} - -pub fn build_neovide_command(channel: u64, num_args: u64, command: &str, event: &str) -> 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 - ); - }; -} - -pub fn create_nvim_command() -> Command { - let mut cmd = build_nvim_cmd(); - - cmd.arg("--embed") - .args(SETTINGS.neovim_arguments.iter().skip(1)) - .stderr(Stdio::inherit()); - - #[cfg(windows)] - set_windows_creation_flags(&mut cmd); - - cmd -} - -async fn start_neovim_runtime( - ui_command_sender: TxUnbounded, - ui_command_receiver: RxUnbounded, - redraw_event_sender: TxUnbounded, - running: Arc, -) { - let (width, height) = window_geometry_or_default(); - let handler = NeovimHandler::new(ui_command_sender.clone(), redraw_event_sender.clone()); - let (mut nvim, io_handler, _) = create::new_child_cmd(&mut create_nvim_command(), handler) - .await - .unwrap_or_explained_panic("Could not locate or start neovim process"); - - if nvim.get_api_info().await.is_err() { - error!("Cannot get neovim api info, either neovide is launched with an unknown command line option or neovim version not supported!"); - std::process::exit(-1); - } - - let close_watcher_running = running.clone(); - tokio::spawn(async move { - info!("Close watcher started"); - match io_handler.await { - Err(join_error) => error!("Error joining IO loop: '{}'", join_error), - Ok(Err(error)) => { - if !error.is_channel_closed() { - error!("Error: '{}'", error); - } - } - Ok(Ok(())) => {} - }; - close_watcher_running.store(false, Ordering::Relaxed); - }); - - if let Ok(Value::Integer(correct_version)) = nvim.eval("has(\"nvim-0.4\")").await { - if correct_version.as_i64() != Some(1) { - error!("Neovide requires version 0.4 or higher"); - std::process::exit(0); - } - } else { - error!("Neovide requires version 0.4 or higher"); - std::process::exit(0); - }; - - nvim.set_var("neovide", Value::Boolean(true)) - .await - .unwrap_or_explained_panic("Could not communicate with neovim process"); - - if let Err(command_error) = nvim.command("runtime! ginit.vim").await { - nvim.command(&format!( - "echomsg \"error encountered in ginit.vim {:?}\"", - command_error - )) - .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(); - - let neovide_channel: u64 = nvim - .list_chans() - .await - .ok() - .and_then(|channel_values| parse_channel_list(channel_values).ok()) - .and_then(|channel_list| { - channel_list.iter().find_map(|channel| match channel { - ChannelInfo { - id, - client: Some(ClientInfo { name, .. }), - .. - } if name == "neovide" => Some(*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.set_option("lazyredraw", Value::Boolean(false)) - .await - .ok(); - - let mut options = UiAttachOptions::new(); - options.set_linegrid_external(true); - if env::args().any(|arg| arg == "--multiGrid") || env::var("NeovideMultiGrid").is_ok() { - options.set_multigrid_external(true); - } - options.set_rgb(true); - nvim.ui_attach(width as i64, height as i64, &options) - .await - .unwrap_or_explained_panic("Could not attach ui to neovim process"); - - info!("Neovim process attached"); - - let nvim = Arc::new(nvim); - - let ui_command_running = running.clone(); - let input_nvim = nvim.clone(); - tokio::spawn(async move { - loop { - if !ui_command_running.load(Ordering::Relaxed) { - break; - } - - match ui_command_receiver.recv().await { - Ok(ui_command) => { - let input_nvim = input_nvim.clone(); - tokio::spawn(async move { - ui_command.execute(&input_nvim).await; - }); - } - Err(_) => { - ui_command_running.store(false, Ordering::Relaxed); - break; - } - } - } - }); - - SETTINGS.read_initial_values(&nvim).await; - SETTINGS.setup_changed_listeners(&nvim).await; -} - -pub struct Bridge { - _runtime: Runtime, // Necessary to keep runtime running -} - -pub fn start_bridge( - ui_command_sender: TxUnbounded, - ui_command_receiver: RxUnbounded, - redraw_event_sender: TxUnbounded, - running: Arc, -) -> Bridge { - let runtime = Runtime::new().unwrap(); - runtime.spawn(start_neovim_runtime( - ui_command_sender, - ui_command_receiver, - redraw_event_sender, - running, - )); - Bridge { _runtime: runtime } -} +mod events; +mod handler; +mod ui_commands; + +use std::env; +use std::path::Path; +use std::process::Stdio; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::Arc; + +use crossfire::mpsc::{RxUnbounded, TxUnbounded}; +use log::{error, info, warn}; +use nvim_rs::{create::tokio as create, UiAttachOptions}; +use rmpv::Value; +use tokio::process::Command; +use tokio::runtime::Runtime; + +use crate::error_handling::ResultPanicExplanation; +use crate::settings::*; +use crate::window::window_geometry_or_default; +pub use events::*; +use handler::NeovimHandler; +pub use ui_commands::UiCommand; + +#[cfg(windows)] +fn set_windows_creation_flags(cmd: &mut Command) { + cmd.creation_flags(0x0800_0000); // CREATE_NO_WINDOW +} + +#[cfg(windows)] +fn platform_build_nvim_cmd(bin: &str) -> Option { + if !Path::new(&bin).exists() { + return None; + } + + if env::args().any(|arg| arg == "--wsl") { + let mut cmd = Command::new("wsl"); + cmd.arg(bin); + Some(cmd) + } else { + Some(Command::new(bin)) + } +} + +#[cfg(unix)] +fn platform_build_nvim_cmd(bin: &str) -> Option { + if Path::new(&bin).exists() { + Some(Command::new(bin)) + } else { + None + } +} + +fn build_nvim_cmd() -> Command { + if let Ok(path) = env::var("NEOVIM_BIN") { + if let Some(cmd) = platform_build_nvim_cmd(&path) { + return cmd; + } else { + warn!("NEOVIM_BIN is invalid falling back to first bin in PATH"); + } + } + if let Ok(path) = which::which("nvim") { + if let Some(cmd) = platform_build_nvim_cmd(path.to_str().unwrap()) { + cmd + } else { + error!("nvim does not have proper permissions!"); + std::process::exit(1); + } + } else { + error!("nvim not found!"); + std::process::exit(1); + } +} + +pub fn build_neovide_command(channel: u64, num_args: u64, command: &str, event: &str) -> 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 + ); + }; +} + +pub fn create_nvim_command() -> Command { + let mut cmd = build_nvim_cmd(); + + cmd.arg("--embed") + .args(SETTINGS.neovim_arguments.iter().skip(1)) + .stderr(Stdio::inherit()); + + #[cfg(windows)] + set_windows_creation_flags(&mut cmd); + + cmd +} + +async fn start_neovim_runtime( + ui_command_sender: TxUnbounded, + ui_command_receiver: RxUnbounded, + redraw_event_sender: TxUnbounded, + running: Arc, +) { + let (width, height) = window_geometry_or_default(); + let handler = NeovimHandler::new(ui_command_sender.clone(), redraw_event_sender.clone()); + let (mut nvim, io_handler, _) = create::new_child_cmd(&mut create_nvim_command(), handler) + .await + .unwrap_or_explained_panic("Could not locate or start neovim process"); + + if nvim.get_api_info().await.is_err() { + error!("Cannot get neovim api info, either neovide is launched with an unknown command line option or neovim version not supported!"); + std::process::exit(-1); + } + + let close_watcher_running = running.clone(); + tokio::spawn(async move { + info!("Close watcher started"); + match io_handler.await { + Err(join_error) => error!("Error joining IO loop: '{}'", join_error), + Ok(Err(error)) => { + if !error.is_channel_closed() { + error!("Error: '{}'", error); + } + } + Ok(Ok(())) => {} + }; + close_watcher_running.store(false, Ordering::Relaxed); + }); + + if let Ok(Value::Integer(correct_version)) = nvim.eval("has(\"nvim-0.4\")").await { + if correct_version.as_i64() != Some(1) { + error!("Neovide requires version 0.4 or higher"); + std::process::exit(0); + } + } else { + error!("Neovide requires version 0.4 or higher"); + std::process::exit(0); + }; + + nvim.set_var("neovide", Value::Boolean(true)) + .await + .unwrap_or_explained_panic("Could not communicate with neovim process"); + + if let Err(command_error) = nvim.command("runtime! ginit.vim").await { + nvim.command(&format!( + "echomsg \"error encountered in ginit.vim {:?}\"", + command_error + )) + .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(); + + let neovide_channel: u64 = nvim + .list_chans() + .await + .ok() + .and_then(|channel_values| parse_channel_list(channel_values).ok()) + .and_then(|channel_list| { + channel_list.iter().find_map(|channel| match channel { + ChannelInfo { + id, + client: Some(ClientInfo { name, .. }), + .. + } if name == "neovide" => Some(*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.set_option("lazyredraw", Value::Boolean(false)) + .await + .ok(); + + let mut options = UiAttachOptions::new(); + options.set_linegrid_external(true); + if env::args().any(|arg| arg == "--multiGrid") || env::var("NeovideMultiGrid").is_ok() { + options.set_multigrid_external(true); + } + options.set_rgb(true); + nvim.ui_attach(width as i64, height as i64, &options) + .await + .unwrap_or_explained_panic("Could not attach ui to neovim process"); + + info!("Neovim process attached"); + + let nvim = Arc::new(nvim); + + let ui_command_running = running.clone(); + let input_nvim = nvim.clone(); + tokio::spawn(async move { + loop { + if !ui_command_running.load(Ordering::Relaxed) { + break; + } + + match ui_command_receiver.recv().await { + Ok(ui_command) => { + let input_nvim = input_nvim.clone(); + tokio::spawn(async move { + ui_command.execute(&input_nvim).await; + }); + } + Err(_) => { + ui_command_running.store(false, Ordering::Relaxed); + break; + } + } + } + }); + + SETTINGS.read_initial_values(&nvim).await; + SETTINGS.setup_changed_listeners(&nvim).await; +} + +pub struct Bridge { + _runtime: Runtime, // Necessary to keep runtime running +} + +pub fn start_bridge( + ui_command_sender: TxUnbounded, + ui_command_receiver: RxUnbounded, + redraw_event_sender: TxUnbounded, + running: Arc, +) -> Bridge { + let runtime = Runtime::new().unwrap(); + runtime.spawn(start_neovim_runtime( + ui_command_sender, + ui_command_receiver, + redraw_event_sender, + running, + )); + Bridge { _runtime: runtime } +} diff --git a/src/main.rs b/src/main.rs index 65ac6e9..ce16a56 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,158 +1,157 @@ -#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] - -#[macro_use] -mod settings; - -mod bridge; -mod editor; -mod error_handling; -mod redraw_scheduler; -mod renderer; -mod window; - -#[macro_use] -extern crate derive_new; -#[macro_use] -extern crate rust_embed; -#[macro_use] -extern crate lazy_static; - -use std::process; -use std::sync::atomic::AtomicBool; -use std::sync::mpsc::channel; -use std::sync::Arc; - -use crossfire::mpsc::unbounded_future; - -use window::window_geometry; - -use bridge::start_bridge; -use editor::start_editor; -use window::create_window; - -pub const INITIAL_DIMENSIONS: (u64, u64) = (100, 50); - -fn main() { - // ----------- - // | DATA FLOW | - // ----------- - // - // Data flows in a circular motion via channels. This allows each component to handle and - // process data on their own thread while not blocking the other components from processing. - // - // This way Neovim continues to produce events, the window doesn't freeze and queues up ui - // commands, and the editor can do the processing necessary to handle the UI events - // effectively. - // - // BRIDGE - // V REDRAW EVENT - // EDITOR - // V DRAW COMMAND - // WINDOW - // V UI COMMAND - // BRIDGE - // - // BRIDGE: - // The bridge is responsible for the connection to the neovim process itself. It is in charge - // of starting and communicating to and from the process. - // - // REDRAW EVENT: - // Redraw events are direct events from the neovim process meant to specify how the editor - // should be drawn to the screen. They also include other things such as whether the mouse is - // enabled. The bridge takes these events, filters out some of them meant only for - // filtering, and forwards them to the editor. - // - // EDITOR: - // The editor is responsible for processing and transforming redraw events into something - // more readily renderable. Ligature support and multi window management requires some - // significant preprocessing of the redraw events in order to capture what exactly should get - // drawn where. Futher this step takes a bit of processing power to accomplish, so it is done - // on it's own thread. Ideally heavily computationally expensive tasks should be done in the - // editor. - // - // DRAW COMMAND: - // The draw commands are distilled render information describing actions to be done at the - // window by window level. - // - // WINDOW: - // The window is responsible for rendering and gathering input events from the user. This - // inncludes taking the draw commands from the editor and turning them into pixels on the - // screen. The ui commands are then forwarded back to the BRIDGE to convert them into - // commands for neovim to handle properly. - // - // UI COMMAND: - // The ui commands are things like text input/key bindings, outer window resizes, and mouse - // inputs. - // - // ------------------ - // | Other Components | - // ------------------ - // - // Neovide also includes some other systems which are globally available via lazy static - // instantiations. - // - // SETTINGS: - // The settings system is live updated from global variables in neovim with the prefix - // "neovide". They allow us to configure and manage the functionality of neovide from neovim - // init scripts and variables. - // - // REDRAW SCHEDULER: - // The redraw scheduler is a simple system in charge of deciding if the renderer should draw - // another frame next frame, or if it can safely skip drawing to save battery and cpu power. - // Multiple other parts of the app "queue_next_frame" function to ensure animations continue - // properly or updates to the graphics are pushed to the screen. - - if let Err(err) = window_geometry() { - eprintln!("{}", err); - process::exit(1); - }; - - #[cfg(target_os = "macos")] - { - use std::env; - if env::var_os("TERM").is_none() { - let mut profile_path = dirs::home_dir().unwrap(); - profile_path.push(".profile"); - let shell = env::var("SHELL").unwrap(); - let cmd = format!( - "(source /etc/profile && source {} && echo $PATH)", - profile_path.to_str().unwrap() - ); - if let Ok(path) = process::Command::new(shell).arg("-c").arg(cmd).output() { - env::set_var("PATH", std::str::from_utf8(&path.stdout).unwrap()); - } - } - } - - bridge::layouts::initialize_settings(); - window::initialize_settings(); - redraw_scheduler::initialize_settings(); - renderer::initialize_settings(); - renderer::cursor_renderer::initialize_settings(); - - let running = Arc::new(AtomicBool::new(true)); - - let (redraw_event_sender, redraw_event_receiver) = unbounded_future(); - let (batched_draw_command_sender, batched_draw_command_receiver) = channel(); - let (ui_command_sender, ui_command_receiver) = unbounded_future(); - let (window_command_sender, window_command_receiver) = channel(); - - // We need to keep the bridge reference around to prevent the tokio runtime from getting freed - let _bridge = start_bridge( - ui_command_sender.clone(), - ui_command_receiver, - redraw_event_sender, - running.clone(), - ); - start_editor( - redraw_event_receiver, - batched_draw_command_sender, - window_command_sender, - ); - create_window( - batched_draw_command_receiver, - window_command_receiver, - ui_command_sender, - running, - ); -} +#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] + +#[macro_use] +mod settings; + +mod bridge; +mod editor; +mod error_handling; +mod redraw_scheduler; +mod renderer; +mod window; + +#[macro_use] +extern crate derive_new; +#[macro_use] +extern crate rust_embed; +#[macro_use] +extern crate lazy_static; + +use std::process; +use std::sync::atomic::AtomicBool; +use std::sync::mpsc::channel; +use std::sync::Arc; + +use crossfire::mpsc::unbounded_future; + +use window::window_geometry; + +use bridge::start_bridge; +use editor::start_editor; +use window::create_window; + +pub const INITIAL_DIMENSIONS: (u64, u64) = (100, 50); + +fn main() { + // ----------- + // | DATA FLOW | + // ----------- + // + // Data flows in a circular motion via channels. This allows each component to handle and + // process data on their own thread while not blocking the other components from processing. + // + // This way Neovim continues to produce events, the window doesn't freeze and queues up ui + // commands, and the editor can do the processing necessary to handle the UI events + // effectively. + // + // BRIDGE + // V REDRAW EVENT + // EDITOR + // V DRAW COMMAND + // WINDOW + // V UI COMMAND + // BRIDGE + // + // BRIDGE: + // The bridge is responsible for the connection to the neovim process itself. It is in charge + // of starting and communicating to and from the process. + // + // REDRAW EVENT: + // Redraw events are direct events from the neovim process meant to specify how the editor + // should be drawn to the screen. They also include other things such as whether the mouse is + // enabled. The bridge takes these events, filters out some of them meant only for + // filtering, and forwards them to the editor. + // + // EDITOR: + // The editor is responsible for processing and transforming redraw events into something + // more readily renderable. Ligature support and multi window management requires some + // significant preprocessing of the redraw events in order to capture what exactly should get + // drawn where. Futher this step takes a bit of processing power to accomplish, so it is done + // on it's own thread. Ideally heavily computationally expensive tasks should be done in the + // editor. + // + // DRAW COMMAND: + // The draw commands are distilled render information describing actions to be done at the + // window by window level. + // + // WINDOW: + // The window is responsible for rendering and gathering input events from the user. This + // inncludes taking the draw commands from the editor and turning them into pixels on the + // screen. The ui commands are then forwarded back to the BRIDGE to convert them into + // commands for neovim to handle properly. + // + // UI COMMAND: + // The ui commands are things like text input/key bindings, outer window resizes, and mouse + // inputs. + // + // ------------------ + // | Other Components | + // ------------------ + // + // Neovide also includes some other systems which are globally available via lazy static + // instantiations. + // + // SETTINGS: + // The settings system is live updated from global variables in neovim with the prefix + // "neovide". They allow us to configure and manage the functionality of neovide from neovim + // init scripts and variables. + // + // REDRAW SCHEDULER: + // The redraw scheduler is a simple system in charge of deciding if the renderer should draw + // another frame next frame, or if it can safely skip drawing to save battery and cpu power. + // Multiple other parts of the app "queue_next_frame" function to ensure animations continue + // properly or updates to the graphics are pushed to the screen. + + if let Err(err) = window_geometry() { + eprintln!("{}", err); + process::exit(1); + }; + + #[cfg(target_os = "macos")] + { + use std::env; + if env::var_os("TERM").is_none() { + let mut profile_path = dirs::home_dir().unwrap(); + profile_path.push(".profile"); + let shell = env::var("SHELL").unwrap(); + let cmd = format!( + "(source /etc/profile && source {} && echo $PATH)", + profile_path.to_str().unwrap() + ); + if let Ok(path) = process::Command::new(shell).arg("-c").arg(cmd).output() { + env::set_var("PATH", std::str::from_utf8(&path.stdout).unwrap()); + } + } + } + + window::initialize_settings(); + redraw_scheduler::initialize_settings(); + renderer::initialize_settings(); + renderer::cursor_renderer::initialize_settings(); + + let running = Arc::new(AtomicBool::new(true)); + + let (redraw_event_sender, redraw_event_receiver) = unbounded_future(); + let (batched_draw_command_sender, batched_draw_command_receiver) = channel(); + let (ui_command_sender, ui_command_receiver) = unbounded_future(); + let (window_command_sender, window_command_receiver) = channel(); + + // We need to keep the bridge reference around to prevent the tokio runtime from getting freed + let _bridge = start_bridge( + ui_command_sender.clone(), + ui_command_receiver, + redraw_event_sender, + running.clone(), + ); + start_editor( + redraw_event_receiver, + batched_draw_command_sender, + window_command_sender, + ); + create_window( + batched_draw_command_receiver, + window_command_receiver, + ui_command_sender, + running, + ); +} diff --git a/src/bridge/layouts/mod.rs b/src/window/keyboard.rs similarity index 55% rename from src/bridge/layouts/mod.rs rename to src/window/keyboard.rs index d4b0ed3..be2ee91 100644 --- a/src/bridge/layouts/mod.rs +++ b/src/window/keyboard.rs @@ -1,114 +1,81 @@ -mod qwerty; - -use log::{error, trace}; -use skulpin::sdl2::keyboard::{Keycode, Mod}; - -use crate::settings::{FromValue, Value, SETTINGS}; - -use qwerty::*; - -pub fn unsupported_key(keycode: Keycode) -> Option { - trace!("Unsupported key: {:?}", keycode); - None -} - -#[derive(Clone)] -pub enum KeyboardLayout { - Qwerty, -} - -impl FromValue for KeyboardLayout { - fn from_value(&mut self, value: Value) { - match value.as_str() { - Some("qwerty") => *self = KeyboardLayout::Qwerty, - _ => error!( - "keyboard_layout setting expected a known keyboard layout name, but received: {}", - value - ), - } - } -} - -impl From for Value { - fn from(layout: KeyboardLayout) -> Self { - match layout { - KeyboardLayout::Qwerty => "qwerty".into(), - } - } -} - -#[derive(Clone)] -struct KeyboardSettings { - layout: KeyboardLayout, -} - -pub fn initialize_settings() { - SETTINGS.set(&KeyboardSettings { - layout: KeyboardLayout::Qwerty, - }); - - register_nvim_setting!("keyboard_layout", KeyboardSettings::layout); -} - -fn append_modifiers( - keycode_text: &str, - special: bool, - shift: bool, - ctrl: bool, - alt: bool, - gui: bool, -) -> String { - let mut result = keycode_text.to_string(); - let mut special = if result == "<" { - result = "lt".to_string(); - true - } else { - special - }; - - if shift { - special = true; - result = format!("S-{}", result); - } - if ctrl { - special = true; - result = format!("C-{}", result); - } - if alt { - special = true; - result = format!("M-{}", result); - } - if cfg!(not(target_os = "windows")) && gui { - special = true; - result = format!("D-{}", result); - } - - if special { - result = format!("<{}>", result); - } - - result -} - -pub fn produce_neovim_keybinding_string( - keycode: Option, - keytext: Option, - modifiers: Mod, -) -> Option { - let shift = modifiers.contains(Mod::LSHIFTMOD) || modifiers.contains(Mod::RSHIFTMOD); - let ctrl = modifiers.contains(Mod::LCTRLMOD) || modifiers.contains(Mod::RCTRLMOD); - let alt = modifiers.contains(Mod::LALTMOD) || modifiers.contains(Mod::RALTMOD); - let gui = modifiers.contains(Mod::LGUIMOD) || modifiers.contains(Mod::RGUIMOD); - if let Some(text) = keytext { - Some(append_modifiers(&text, false, false, ctrl, alt, gui)) - } else if let Some(keycode) = keycode { - (match SETTINGS.get::().layout { - KeyboardLayout::Qwerty => handle_qwerty_layout(keycode, shift, ctrl, alt), - }) - .map(|(transformed_text, special, shift, ctrl, alt)| { - append_modifiers(transformed_text, special, shift, ctrl, alt, gui) - }) - } else { - None - } -} +use log::{error, trace}; + +use crate::settings::*; + +#[derive(Clone)] +pub enum KeyboardLayout { + Qwerty, +} + +impl FromValue for KeyboardLayout { + fn from_value(&mut self, value: Value) { + match value.as_str() { + Some("qwerty") => *self = KeyboardLayout::Qwerty, + _ => error!( + "keyboard_layout setting expected a known keyboard layout name, but received: {}", + value + ), + } + } +} + +impl From for Value { + fn from(layout: KeyboardLayout) -> Self { + match layout { + KeyboardLayout::Qwerty => "qwerty".into(), + } + } +} + +#[derive(Clone)] +pub struct KeyboardSettings { + pub layout: KeyboardLayout, +} + +pub fn initialize_settings() { + SETTINGS.set(&KeyboardSettings { + layout: KeyboardLayout::Qwerty, + }); + + register_nvim_setting!("keyboard_layout", KeyboardSettings::layout); +} + +pub fn append_modifiers( + keycode_text: &str, + special: bool, + shift: bool, + ctrl: bool, + alt: bool, + gui: bool, +) -> String { + let mut result = keycode_text.to_string(); + let mut special = if result == "<" { + result = "lt".to_string(); + true + } else { + special + }; + + if shift { + special = true; + result = format!("S-{}", result); + } + if ctrl { + special = true; + result = format!("C-{}", result); + } + if alt { + special = true; + result = format!("M-{}", result); + } + if cfg!(not(target_os = "windows")) && gui { + special = true; + result = format!("D-{}", result); + } + + if special { + result = format!("<{}>", result); + } + + result +} diff --git a/src/window/mod.rs b/src/window/mod.rs index d82ceef..4c5a81d 100644 --- a/src/window/mod.rs +++ b/src/window/mod.rs @@ -1,5 +1,8 @@ mod settings; +pub mod keyboard; + #[cfg_attr(feature = "sdl2", path = "sdl2/mod.rs")] +#[cfg_attr(feature = "winit", path = "winit/mod.rs")] mod window_wrapper; use std::sync::atomic::AtomicBool; diff --git a/src/window/sdl2/layouts/mod.rs b/src/window/sdl2/layouts/mod.rs new file mode 100644 index 0000000..b9d6ad0 --- /dev/null +++ b/src/window/sdl2/layouts/mod.rs @@ -0,0 +1,36 @@ +mod qwerty; + +use log::trace; +use skulpin::sdl2::keyboard::{Keycode, Mod}; + +use qwerty::*; +use super::keyboard::*; +use crate::settings::*; + +pub fn unsupported_key(keycode: Keycode) -> Option { + trace!("Unsupported key: {:?}", keycode); + None +} + +pub fn produce_neovim_keybinding_string( + keycode: Option, + keytext: Option, + modifiers: Mod, +) -> Option { + let shift = modifiers.contains(Mod::LSHIFTMOD) || modifiers.contains(Mod::RSHIFTMOD); + let ctrl = modifiers.contains(Mod::LCTRLMOD) || modifiers.contains(Mod::RCTRLMOD); + let alt = modifiers.contains(Mod::LALTMOD) || modifiers.contains(Mod::RALTMOD); + let gui = modifiers.contains(Mod::LGUIMOD) || modifiers.contains(Mod::RGUIMOD); + if let Some(text) = keytext { + Some(append_modifiers(&text, false, false, ctrl, alt, gui)) + } else if let Some(keycode) = keycode { + (match SETTINGS.get::().layout { + KeyboardLayout::Qwerty => handle_qwerty_layout(keycode, shift, ctrl, alt), + }) + .map(|(transformed_text, special, shift, ctrl, alt)| { + append_modifiers(transformed_text, special, shift, ctrl, alt, gui) + }) + } else { + None + } +} diff --git a/src/bridge/layouts/qwerty.rs b/src/window/sdl2/layouts/qwerty.rs similarity index 98% rename from src/bridge/layouts/qwerty.rs rename to src/window/sdl2/layouts/qwerty.rs index 0a00c01..4db76cf 100644 --- a/src/bridge/layouts/qwerty.rs +++ b/src/window/sdl2/layouts/qwerty.rs @@ -1,269 +1,269 @@ -use super::unsupported_key; - -use skulpin::sdl2::keyboard::Keycode; - -pub fn handle_qwerty_layout( - keycode: Keycode, - shift: bool, - ctrl: bool, - alt: bool, -) -> Option<(&'static str, bool, bool, bool, bool)> { - match (keycode, shift, ctrl, alt) { - (Keycode::Backspace, shift, ctrl, alt) => Some(("BS", true, shift, ctrl, alt)), - (Keycode::Tab, shift, ctrl, alt) => Some(("Tab", true, shift, ctrl, alt)), - (Keycode::Return, shift, ctrl, alt) => Some(("Enter", true, shift, ctrl, alt)), - (Keycode::Escape, shift, ctrl, alt) => Some(("Esc", true, shift, ctrl, alt)), - (Keycode::Space, shift, ctrl, alt) => Some((" ", false, shift, ctrl, alt)), - (Keycode::Exclaim, shift, ctrl, alt) => Some(("!", false, shift, ctrl, alt)), - (Keycode::Quotedbl, shift, ctrl, alt) => Some(("\"", false, shift, ctrl, alt)), - (Keycode::Hash, shift, ctrl, alt) => Some(("#", false, shift, ctrl, alt)), - (Keycode::Dollar, shift, ctrl, alt) => Some(("$", false, shift, ctrl, alt)), - (Keycode::Percent, shift, ctrl, alt) => Some(("%", false, shift, ctrl, alt)), - (Keycode::Ampersand, shift, ctrl, alt) => Some(("&", false, shift, ctrl, alt)), - (Keycode::Quote, false, ctrl, alt) => Some(("'", false, false, ctrl, alt)), - (Keycode::Quote, true, ctrl, alt) => Some(("\"", false, false, ctrl, alt)), - (Keycode::LeftParen, shift, ctrl, alt) => Some(("(", false, shift, ctrl, alt)), - (Keycode::RightParen, shift, ctrl, alt) => Some((")", false, shift, ctrl, alt)), - (Keycode::Asterisk, shift, ctrl, alt) => Some(("*", false, shift, ctrl, alt)), - (Keycode::Plus, shift, ctrl, alt) => Some(("+", false, shift, ctrl, alt)), - (Keycode::Comma, false, ctrl, alt) => Some((",", false, shift, ctrl, alt)), - (Keycode::Comma, true, ctrl, alt) => Some(("<", false, shift, ctrl, alt)), - (Keycode::Minus, false, ctrl, alt) => Some(("-", false, false, ctrl, alt)), - (Keycode::Minus, true, ctrl, alt) => Some(("_", false, false, ctrl, alt)), - (Keycode::Period, false, ctrl, alt) => Some((".", false, false, ctrl, alt)), - (Keycode::Period, true, ctrl, alt) => Some((">", false, false, ctrl, alt)), - (Keycode::Slash, false, ctrl, alt) => Some(("/", false, false, ctrl, alt)), - (Keycode::Slash, true, ctrl, alt) => Some(("?", false, false, ctrl, alt)), - (Keycode::Num0, false, ctrl, alt) => Some(("0", false, shift, ctrl, alt)), - (Keycode::Num0, true, ctrl, alt) => Some((")", true, shift, ctrl, alt)), - (Keycode::Num1, false, ctrl, alt) => Some(("1", false, shift, ctrl, alt)), - (Keycode::Num1, true, ctrl, alt) => Some(("!", true, shift, ctrl, alt)), - (Keycode::Num2, false, ctrl, alt) => Some(("2", false, false, ctrl, alt)), - (Keycode::Num2, true, ctrl, alt) => Some(("@", false, false, ctrl, alt)), - (Keycode::Num3, false, ctrl, alt) => Some(("3", false, false, ctrl, alt)), - (Keycode::Num3, true, ctrl, alt) => Some(("#", false, false, ctrl, alt)), - (Keycode::Num4, false, ctrl, alt) => Some(("4", false, false, ctrl, alt)), - (Keycode::Num4, true, ctrl, alt) => Some(("$", false, false, ctrl, alt)), - (Keycode::Num5, false, ctrl, alt) => Some(("5", false, false, ctrl, alt)), - (Keycode::Num5, true, ctrl, alt) => Some(("%", false, false, ctrl, alt)), - (Keycode::Num6, false, ctrl, alt) => Some(("6", false, false, ctrl, alt)), - (Keycode::Num6, true, ctrl, alt) => Some(("^", false, false, ctrl, alt)), - (Keycode::Num7, false, ctrl, alt) => Some(("7", false, false, ctrl, alt)), - (Keycode::Num7, true, ctrl, alt) => Some(("&", false, false, ctrl, alt)), - (Keycode::Num8, false, ctrl, alt) => Some(("8", false, false, ctrl, alt)), - (Keycode::Num8, true, ctrl, alt) => Some(("*", false, false, ctrl, alt)), - (Keycode::Num9, false, ctrl, alt) => Some(("9", false, false, ctrl, alt)), - (Keycode::Num9, true, ctrl, alt) => Some(("(", true, false, ctrl, alt)), - (Keycode::Colon, shift, ctrl, alt) => Some((":", false, shift, ctrl, alt)), - (Keycode::Semicolon, false, ctrl, alt) => Some((";", false, false, ctrl, alt)), - (Keycode::Semicolon, true, ctrl, alt) => Some((":", false, false, ctrl, alt)), - (Keycode::Less, shift, ctrl, alt) => Some(("lt", false, shift, ctrl, alt)), - (Keycode::Equals, false, ctrl, alt) => Some(("=", false, false, ctrl, alt)), - (Keycode::Equals, true, ctrl, alt) => Some(("+", false, false, ctrl, alt)), - (Keycode::Greater, shift, ctrl, alt) => Some(("gt", false, shift, ctrl, alt)), - (Keycode::Question, shift, ctrl, alt) => Some(("?", false, shift, ctrl, alt)), - (Keycode::At, shift, ctrl, alt) => Some(("@", false, shift, ctrl, alt)), - (Keycode::LeftBracket, false, ctrl, alt) => Some(("[", false, false, ctrl, alt)), - (Keycode::LeftBracket, true, ctrl, alt) => Some(("{", false, false, ctrl, alt)), - (Keycode::Backslash, false, ctrl, alt) => Some(("\\", false, false, ctrl, alt)), - (Keycode::Backslash, true, ctrl, alt) => Some(("|", false, false, ctrl, alt)), - (Keycode::RightBracket, false, ctrl, alt) => Some(("]", false, false, ctrl, alt)), - (Keycode::RightBracket, true, ctrl, alt) => Some(("}", false, false, ctrl, alt)), - (Keycode::Caret, shift, ctrl, alt) => Some(("^", false, shift, ctrl, alt)), - (Keycode::Underscore, shift, ctrl, alt) => Some(("_", false, shift, ctrl, alt)), - (Keycode::Backquote, false, ctrl, alt) => Some(("`", false, false, ctrl, alt)), - (Keycode::Backquote, true, ctrl, alt) => Some(("~", false, false, ctrl, alt)), - (Keycode::A, shift, ctrl, alt) => Some(("a", false, shift, ctrl, alt)), - (Keycode::B, shift, ctrl, alt) => Some(("b", false, shift, ctrl, alt)), - (Keycode::C, shift, ctrl, alt) => Some(("c", false, shift, ctrl, alt)), - (Keycode::D, shift, ctrl, alt) => Some(("d", false, shift, ctrl, alt)), - (Keycode::E, shift, ctrl, alt) => Some(("e", false, shift, ctrl, alt)), - (Keycode::F, shift, ctrl, alt) => Some(("f", false, shift, ctrl, alt)), - (Keycode::G, shift, ctrl, alt) => Some(("g", false, shift, ctrl, alt)), - (Keycode::H, shift, ctrl, alt) => Some(("h", false, shift, ctrl, alt)), - (Keycode::I, shift, ctrl, alt) => Some(("i", false, shift, ctrl, alt)), - (Keycode::J, shift, ctrl, alt) => Some(("j", false, shift, ctrl, alt)), - (Keycode::K, shift, ctrl, alt) => Some(("k", false, shift, ctrl, alt)), - (Keycode::L, shift, ctrl, alt) => Some(("l", false, shift, ctrl, alt)), - (Keycode::M, shift, ctrl, alt) => Some(("m", false, shift, ctrl, alt)), - (Keycode::N, shift, ctrl, alt) => Some(("n", false, shift, ctrl, alt)), - (Keycode::O, shift, ctrl, alt) => Some(("o", false, shift, ctrl, alt)), - (Keycode::P, shift, ctrl, alt) => Some(("p", false, shift, ctrl, alt)), - (Keycode::Q, shift, ctrl, alt) => Some(("q", false, shift, ctrl, alt)), - (Keycode::R, shift, ctrl, alt) => Some(("r", false, shift, ctrl, alt)), - (Keycode::S, shift, ctrl, alt) => Some(("s", false, shift, ctrl, alt)), - (Keycode::T, shift, ctrl, alt) => Some(("t", false, shift, ctrl, alt)), - (Keycode::U, shift, ctrl, alt) => Some(("u", false, shift, ctrl, alt)), - (Keycode::V, shift, ctrl, alt) => Some(("v", false, shift, ctrl, alt)), - (Keycode::W, shift, ctrl, alt) => Some(("w", false, shift, ctrl, alt)), - (Keycode::X, shift, ctrl, alt) => Some(("x", false, shift, ctrl, alt)), - (Keycode::Y, shift, ctrl, alt) => Some(("y", false, shift, ctrl, alt)), - (Keycode::Z, shift, ctrl, alt) => Some(("z", false, shift, ctrl, alt)), - (Keycode::Delete, shift, ctrl, alt) => Some(("Delete", true, shift, ctrl, alt)), - (Keycode::CapsLock, _, _, _) => unsupported_key(Keycode::CapsLock), - (Keycode::F1, shift, ctrl, alt) => Some(("F1", true, shift, ctrl, alt)), - (Keycode::F2, shift, ctrl, alt) => Some(("F2", true, shift, ctrl, alt)), - (Keycode::F3, shift, ctrl, alt) => Some(("F3", true, shift, ctrl, alt)), - (Keycode::F4, shift, ctrl, alt) => Some(("F4", true, shift, ctrl, alt)), - (Keycode::F5, shift, ctrl, alt) => Some(("F5", true, shift, ctrl, alt)), - (Keycode::F6, shift, ctrl, alt) => Some(("F6", true, shift, ctrl, alt)), - (Keycode::F7, shift, ctrl, alt) => Some(("F7", true, shift, ctrl, alt)), - (Keycode::F8, shift, ctrl, alt) => Some(("F8", true, shift, ctrl, alt)), - (Keycode::F9, shift, ctrl, alt) => Some(("F9", true, shift, ctrl, alt)), - (Keycode::F10, shift, ctrl, alt) => Some(("F10", true, shift, ctrl, alt)), - (Keycode::F11, shift, ctrl, alt) => Some(("F11", true, shift, ctrl, alt)), - (Keycode::F12, shift, ctrl, alt) => Some(("F12", true, shift, ctrl, alt)), - (Keycode::PrintScreen, _, _, _) => unsupported_key(Keycode::PrintScreen), - (Keycode::ScrollLock, _, _, _) => unsupported_key(Keycode::ScrollLock), - (Keycode::Pause, _, _, _) => unsupported_key(Keycode::Pause), - (Keycode::Insert, shift, ctrl, alt) => Some(("Insert", true, shift, ctrl, alt)), - (Keycode::Home, shift, ctrl, alt) => Some(("Home", true, shift, ctrl, alt)), - (Keycode::PageUp, shift, ctrl, alt) => Some(("PageUp", true, shift, ctrl, alt)), - (Keycode::End, shift, ctrl, alt) => Some(("End", true, shift, ctrl, alt)), - (Keycode::PageDown, shift, ctrl, alt) => Some(("PageDown", true, shift, ctrl, alt)), - (Keycode::Right, shift, ctrl, alt) => Some(("Right", true, shift, ctrl, alt)), - (Keycode::Left, shift, ctrl, alt) => Some(("Left", true, shift, ctrl, alt)), - (Keycode::Down, shift, ctrl, alt) => Some(("Down", true, shift, ctrl, alt)), - (Keycode::Up, shift, ctrl, alt) => Some(("Up", true, shift, ctrl, alt)), - (Keycode::NumLockClear, _, _, _) => unsupported_key(Keycode::NumLockClear), - (Keycode::KpDivide, shift, ctrl, alt) => Some(("/", true, shift, ctrl, alt)), - (Keycode::KpMultiply, shift, ctrl, alt) => Some(("*", true, shift, ctrl, alt)), - (Keycode::KpMinus, shift, ctrl, alt) => Some(("-", true, shift, ctrl, alt)), - (Keycode::KpPlus, shift, ctrl, alt) => Some(("+", true, shift, ctrl, alt)), - (Keycode::KpEnter, shift, ctrl, alt) => Some(("Enter", true, shift, ctrl, alt)), - (Keycode::Kp0, shift, ctrl, alt) => Some(("0", false, shift, ctrl, alt)), - (Keycode::Kp1, shift, ctrl, alt) => Some(("1", false, shift, ctrl, alt)), - (Keycode::Kp2, shift, ctrl, alt) => Some(("2", false, shift, ctrl, alt)), - (Keycode::Kp3, shift, ctrl, alt) => Some(("3", false, shift, ctrl, alt)), - (Keycode::Kp4, shift, ctrl, alt) => Some(("4", false, shift, ctrl, alt)), - (Keycode::Kp5, shift, ctrl, alt) => Some(("5", false, shift, ctrl, alt)), - (Keycode::Kp6, shift, ctrl, alt) => Some(("6", false, shift, ctrl, alt)), - (Keycode::Kp7, shift, ctrl, alt) => Some(("7", false, shift, ctrl, alt)), - (Keycode::Kp8, shift, ctrl, alt) => Some(("8", false, shift, ctrl, alt)), - (Keycode::Kp9, shift, ctrl, alt) => Some(("9", false, shift, ctrl, alt)), - (Keycode::KpPeriod, shift, ctrl, alt) => Some((".", false, shift, ctrl, alt)), - (Keycode::Application, _, _, _) => unsupported_key(Keycode::Application), - (Keycode::Power, _, _, _) => unsupported_key(Keycode::Power), - (Keycode::KpEquals, shift, ctrl, alt) => Some(("=", false, shift, ctrl, alt)), - (Keycode::F13, shift, ctrl, alt) => Some(("F13", true, shift, ctrl, alt)), - (Keycode::F14, shift, ctrl, alt) => Some(("F14", true, shift, ctrl, alt)), - (Keycode::F15, shift, ctrl, alt) => Some(("F15", true, shift, ctrl, alt)), - (Keycode::F16, shift, ctrl, alt) => Some(("F16", true, shift, ctrl, alt)), - (Keycode::F17, shift, ctrl, alt) => Some(("F17", true, shift, ctrl, alt)), - (Keycode::F18, shift, ctrl, alt) => Some(("F18", true, shift, ctrl, alt)), - (Keycode::F19, shift, ctrl, alt) => Some(("F19", true, shift, ctrl, alt)), - (Keycode::F20, shift, ctrl, alt) => Some(("F20", true, shift, ctrl, alt)), - (Keycode::F21, shift, ctrl, alt) => Some(("F21", true, shift, ctrl, alt)), - (Keycode::F22, shift, ctrl, alt) => Some(("F22", true, shift, ctrl, alt)), - (Keycode::F23, shift, ctrl, alt) => Some(("F23", true, shift, ctrl, alt)), - (Keycode::F24, shift, ctrl, alt) => Some(("F24", true, shift, ctrl, alt)), - (Keycode::Execute, _, _, _) => unsupported_key(Keycode::Execute), - (Keycode::Help, _, _, _) => unsupported_key(Keycode::Help), - (Keycode::Menu, _, _, _) => unsupported_key(Keycode::Menu), - (Keycode::Select, _, _, _) => unsupported_key(Keycode::Select), - (Keycode::Stop, _, _, _) => unsupported_key(Keycode::Stop), - (Keycode::Again, _, _, _) => unsupported_key(Keycode::Again), - (Keycode::Undo, _, _, _) => unsupported_key(Keycode::Undo), - (Keycode::Cut, _, _, _) => unsupported_key(Keycode::Cut), - (Keycode::Copy, _, _, _) => unsupported_key(Keycode::Copy), - (Keycode::Paste, _, _, _) => unsupported_key(Keycode::Paste), - (Keycode::Find, _, _, _) => unsupported_key(Keycode::Find), - (Keycode::Mute, _, _, _) => unsupported_key(Keycode::Mute), - (Keycode::VolumeUp, _, _, _) => unsupported_key(Keycode::VolumeUp), - (Keycode::VolumeDown, _, _, _) => unsupported_key(Keycode::VolumeDown), - (Keycode::KpComma, _, _, _) => unsupported_key(Keycode::KpComma), - (Keycode::KpEqualsAS400, _, _, _) => unsupported_key(Keycode::KpEqualsAS400), - (Keycode::AltErase, _, _, _) => unsupported_key(Keycode::AltErase), - (Keycode::Sysreq, _, _, _) => unsupported_key(Keycode::Sysreq), - (Keycode::Cancel, _, _, _) => unsupported_key(Keycode::Cancel), - (Keycode::Clear, _, _, _) => unsupported_key(Keycode::Clear), - (Keycode::Prior, _, _, _) => unsupported_key(Keycode::Prior), - (Keycode::Return2, _, _, _) => unsupported_key(Keycode::Return2), - (Keycode::Separator, _, _, _) => unsupported_key(Keycode::Separator), - (Keycode::Out, _, _, _) => unsupported_key(Keycode::Out), - (Keycode::Oper, _, _, _) => unsupported_key(Keycode::Oper), - (Keycode::ClearAgain, _, _, _) => unsupported_key(Keycode::ClearAgain), - (Keycode::CrSel, _, _, _) => unsupported_key(Keycode::CrSel), - (Keycode::ExSel, _, _, _) => unsupported_key(Keycode::ExSel), - (Keycode::Kp00, _, _, _) => unsupported_key(Keycode::Kp00), - (Keycode::Kp000, _, _, _) => unsupported_key(Keycode::Kp000), - (Keycode::ThousandsSeparator, _, _, _) => unsupported_key(Keycode::ThousandsSeparator), - (Keycode::DecimalSeparator, _, _, _) => unsupported_key(Keycode::DecimalSeparator), - (Keycode::CurrencyUnit, _, _, _) => unsupported_key(Keycode::CurrencyUnit), - (Keycode::CurrencySubUnit, _, _, _) => unsupported_key(Keycode::CurrencySubUnit), - (Keycode::KpLeftParen, shift, ctrl, alt) => Some(("(", false, shift, ctrl, alt)), - (Keycode::KpRightParen, shift, ctrl, alt) => Some(("(", false, shift, ctrl, alt)), - (Keycode::KpLeftBrace, shift, ctrl, alt) => Some(("[", false, shift, ctrl, alt)), - (Keycode::KpRightBrace, shift, ctrl, alt) => Some(("]", false, shift, ctrl, alt)), - (Keycode::KpTab, shift, ctrl, alt) => Some(("TAB", true, shift, ctrl, alt)), - (Keycode::KpBackspace, shift, ctrl, alt) => Some(("BS", true, shift, ctrl, alt)), - (Keycode::KpA, shift, ctrl, alt) => Some(("A", false, shift, ctrl, alt)), - (Keycode::KpB, shift, ctrl, alt) => Some(("B", false, shift, ctrl, alt)), - (Keycode::KpC, shift, ctrl, alt) => Some(("C", false, shift, ctrl, alt)), - (Keycode::KpD, shift, ctrl, alt) => Some(("D", false, shift, ctrl, alt)), - (Keycode::KpE, shift, ctrl, alt) => Some(("E", false, shift, ctrl, alt)), - (Keycode::KpF, shift, ctrl, alt) => Some(("F", false, shift, ctrl, alt)), - (Keycode::KpXor, _, _, _) => unsupported_key(Keycode::KpXor), - (Keycode::KpPower, shift, ctrl, alt) => Some(("^", false, shift, ctrl, alt)), - (Keycode::KpPercent, shift, ctrl, alt) => Some(("%", false, shift, ctrl, alt)), - (Keycode::KpLess, shift, ctrl, alt) => Some(("lt", true, shift, ctrl, alt)), - (Keycode::KpGreater, shift, ctrl, alt) => Some(("gt", true, shift, ctrl, alt)), - (Keycode::KpAmpersand, shift, ctrl, alt) => Some(("&", false, shift, ctrl, alt)), - (Keycode::KpDblAmpersand, _, _, _) => unsupported_key(Keycode::KpDblAmpersand), - (Keycode::KpVerticalBar, shift, ctrl, alt) => Some(("|", false, shift, ctrl, alt)), - (Keycode::KpDblVerticalBar, _, _, _) => unsupported_key(Keycode::KpDblVerticalBar), - (Keycode::KpColon, shift, ctrl, alt) => Some((":", false, shift, ctrl, alt)), - (Keycode::KpHash, shift, ctrl, alt) => Some(("#", false, shift, ctrl, alt)), - (Keycode::KpSpace, shift, ctrl, alt) => Some((" ", false, shift, ctrl, alt)), - (Keycode::KpAt, shift, ctrl, alt) => Some(("@", false, shift, ctrl, alt)), - (Keycode::KpExclam, shift, ctrl, alt) => Some(("!", false, shift, ctrl, alt)), - (Keycode::KpMemStore, _, _, _) => unsupported_key(Keycode::KpMemStore), - (Keycode::KpMemRecall, _, _, _) => unsupported_key(Keycode::KpMemRecall), - (Keycode::KpMemClear, _, _, _) => unsupported_key(Keycode::KpMemClear), - (Keycode::KpMemAdd, _, _, _) => unsupported_key(Keycode::KpMemAdd), - (Keycode::KpMemSubtract, _, _, _) => unsupported_key(Keycode::KpMemSubtract), - (Keycode::KpMemMultiply, _, _, _) => unsupported_key(Keycode::KpMemMultiply), - (Keycode::KpMemDivide, _, _, _) => unsupported_key(Keycode::KpMemDivide), - (Keycode::KpPlusMinus, _, _, _) => unsupported_key(Keycode::KpPlusMinus), - (Keycode::KpClear, _, _, _) => unsupported_key(Keycode::KpClear), - (Keycode::KpClearEntry, _, _, _) => unsupported_key(Keycode::KpClearEntry), - (Keycode::KpBinary, _, _, _) => unsupported_key(Keycode::KpBinary), - (Keycode::KpOctal, _, _, _) => unsupported_key(Keycode::KpOctal), - (Keycode::KpDecimal, _, _, _) => unsupported_key(Keycode::KpDecimal), - (Keycode::KpHexadecimal, _, _, _) => unsupported_key(Keycode::KpHexadecimal), - (Keycode::LCtrl, _, _, _) => None, - (Keycode::LShift, _, _, _) => None, - (Keycode::LAlt, _, _, _) => None, - (Keycode::LGui, _, _, _) => None, - (Keycode::RCtrl, _, _, _) => None, - (Keycode::RShift, _, _, _) => None, - (Keycode::RAlt, _, _, _) => None, - (Keycode::RGui, _, _, _) => None, - (Keycode::Mode, _, _, _) => unsupported_key(Keycode::Mode), - (Keycode::AudioNext, _, _, _) => unsupported_key(Keycode::AudioNext), - (Keycode::AudioPrev, _, _, _) => unsupported_key(Keycode::AudioPrev), - (Keycode::AudioStop, _, _, _) => unsupported_key(Keycode::AudioStop), - (Keycode::AudioPlay, _, _, _) => unsupported_key(Keycode::AudioPlay), - (Keycode::AudioMute, _, _, _) => unsupported_key(Keycode::AudioMute), - (Keycode::MediaSelect, _, _, _) => unsupported_key(Keycode::MediaSelect), - (Keycode::Www, _, _, _) => unsupported_key(Keycode::Www), - (Keycode::Mail, _, _, _) => unsupported_key(Keycode::Mail), - (Keycode::Calculator, _, _, _) => unsupported_key(Keycode::Calculator), - (Keycode::Computer, _, _, _) => unsupported_key(Keycode::Computer), - (Keycode::AcSearch, _, _, _) => unsupported_key(Keycode::AcSearch), - (Keycode::AcHome, _, _, _) => unsupported_key(Keycode::AcHome), - (Keycode::AcBack, _, _, _) => unsupported_key(Keycode::AcBack), - (Keycode::AcForward, _, _, _) => unsupported_key(Keycode::AcForward), - (Keycode::AcStop, _, _, _) => unsupported_key(Keycode::AcStop), - (Keycode::AcRefresh, _, _, _) => unsupported_key(Keycode::AcRefresh), - (Keycode::AcBookmarks, _, _, _) => unsupported_key(Keycode::AcBookmarks), - (Keycode::BrightnessDown, _, _, _) => unsupported_key(Keycode::BrightnessDown), - (Keycode::BrightnessUp, _, _, _) => unsupported_key(Keycode::BrightnessUp), - (Keycode::DisplaySwitch, _, _, _) => unsupported_key(Keycode::DisplaySwitch), - (Keycode::KbdIllumToggle, _, _, _) => unsupported_key(Keycode::KbdIllumToggle), - (Keycode::KbdIllumDown, _, _, _) => unsupported_key(Keycode::KbdIllumDown), - (Keycode::KbdIllumUp, _, _, _) => unsupported_key(Keycode::KbdIllumUp), - (Keycode::Eject, _, _, _) => unsupported_key(Keycode::Eject), - (Keycode::Sleep, _, _, _) => unsupported_key(Keycode::Sleep), - } -} +use super::unsupported_key; + +use skulpin::sdl2::keyboard::Keycode; + +pub fn handle_qwerty_layout( + keycode: Keycode, + shift: bool, + ctrl: bool, + alt: bool, +) -> Option<(&'static str, bool, bool, bool, bool)> { + match (keycode, shift, ctrl, alt) { + (Keycode::Backspace, shift, ctrl, alt) => Some(("BS", true, shift, ctrl, alt)), + (Keycode::Tab, shift, ctrl, alt) => Some(("Tab", true, shift, ctrl, alt)), + (Keycode::Return, shift, ctrl, alt) => Some(("Enter", true, shift, ctrl, alt)), + (Keycode::Escape, shift, ctrl, alt) => Some(("Esc", true, shift, ctrl, alt)), + (Keycode::Space, shift, ctrl, alt) => Some((" ", false, shift, ctrl, alt)), + (Keycode::Exclaim, shift, ctrl, alt) => Some(("!", false, shift, ctrl, alt)), + (Keycode::Quotedbl, shift, ctrl, alt) => Some(("\"", false, shift, ctrl, alt)), + (Keycode::Hash, shift, ctrl, alt) => Some(("#", false, shift, ctrl, alt)), + (Keycode::Dollar, shift, ctrl, alt) => Some(("$", false, shift, ctrl, alt)), + (Keycode::Percent, shift, ctrl, alt) => Some(("%", false, shift, ctrl, alt)), + (Keycode::Ampersand, shift, ctrl, alt) => Some(("&", false, shift, ctrl, alt)), + (Keycode::Quote, false, ctrl, alt) => Some(("'", false, false, ctrl, alt)), + (Keycode::Quote, true, ctrl, alt) => Some(("\"", false, false, ctrl, alt)), + (Keycode::LeftParen, shift, ctrl, alt) => Some(("(", false, shift, ctrl, alt)), + (Keycode::RightParen, shift, ctrl, alt) => Some((")", false, shift, ctrl, alt)), + (Keycode::Asterisk, shift, ctrl, alt) => Some(("*", false, shift, ctrl, alt)), + (Keycode::Plus, shift, ctrl, alt) => Some(("+", false, shift, ctrl, alt)), + (Keycode::Comma, false, ctrl, alt) => Some((",", false, shift, ctrl, alt)), + (Keycode::Comma, true, ctrl, alt) => Some(("<", false, shift, ctrl, alt)), + (Keycode::Minus, false, ctrl, alt) => Some(("-", false, false, ctrl, alt)), + (Keycode::Minus, true, ctrl, alt) => Some(("_", false, false, ctrl, alt)), + (Keycode::Period, false, ctrl, alt) => Some((".", false, false, ctrl, alt)), + (Keycode::Period, true, ctrl, alt) => Some((">", false, false, ctrl, alt)), + (Keycode::Slash, false, ctrl, alt) => Some(("/", false, false, ctrl, alt)), + (Keycode::Slash, true, ctrl, alt) => Some(("?", false, false, ctrl, alt)), + (Keycode::Num0, false, ctrl, alt) => Some(("0", false, shift, ctrl, alt)), + (Keycode::Num0, true, ctrl, alt) => Some((")", true, shift, ctrl, alt)), + (Keycode::Num1, false, ctrl, alt) => Some(("1", false, shift, ctrl, alt)), + (Keycode::Num1, true, ctrl, alt) => Some(("!", true, shift, ctrl, alt)), + (Keycode::Num2, false, ctrl, alt) => Some(("2", false, false, ctrl, alt)), + (Keycode::Num2, true, ctrl, alt) => Some(("@", false, false, ctrl, alt)), + (Keycode::Num3, false, ctrl, alt) => Some(("3", false, false, ctrl, alt)), + (Keycode::Num3, true, ctrl, alt) => Some(("#", false, false, ctrl, alt)), + (Keycode::Num4, false, ctrl, alt) => Some(("4", false, false, ctrl, alt)), + (Keycode::Num4, true, ctrl, alt) => Some(("$", false, false, ctrl, alt)), + (Keycode::Num5, false, ctrl, alt) => Some(("5", false, false, ctrl, alt)), + (Keycode::Num5, true, ctrl, alt) => Some(("%", false, false, ctrl, alt)), + (Keycode::Num6, false, ctrl, alt) => Some(("6", false, false, ctrl, alt)), + (Keycode::Num6, true, ctrl, alt) => Some(("^", false, false, ctrl, alt)), + (Keycode::Num7, false, ctrl, alt) => Some(("7", false, false, ctrl, alt)), + (Keycode::Num7, true, ctrl, alt) => Some(("&", false, false, ctrl, alt)), + (Keycode::Num8, false, ctrl, alt) => Some(("8", false, false, ctrl, alt)), + (Keycode::Num8, true, ctrl, alt) => Some(("*", false, false, ctrl, alt)), + (Keycode::Num9, false, ctrl, alt) => Some(("9", false, false, ctrl, alt)), + (Keycode::Num9, true, ctrl, alt) => Some(("(", true, false, ctrl, alt)), + (Keycode::Colon, shift, ctrl, alt) => Some((":", false, shift, ctrl, alt)), + (Keycode::Semicolon, false, ctrl, alt) => Some((";", false, false, ctrl, alt)), + (Keycode::Semicolon, true, ctrl, alt) => Some((":", false, false, ctrl, alt)), + (Keycode::Less, shift, ctrl, alt) => Some(("lt", false, shift, ctrl, alt)), + (Keycode::Equals, false, ctrl, alt) => Some(("=", false, false, ctrl, alt)), + (Keycode::Equals, true, ctrl, alt) => Some(("+", false, false, ctrl, alt)), + (Keycode::Greater, shift, ctrl, alt) => Some(("gt", false, shift, ctrl, alt)), + (Keycode::Question, shift, ctrl, alt) => Some(("?", false, shift, ctrl, alt)), + (Keycode::At, shift, ctrl, alt) => Some(("@", false, shift, ctrl, alt)), + (Keycode::LeftBracket, false, ctrl, alt) => Some(("[", false, false, ctrl, alt)), + (Keycode::LeftBracket, true, ctrl, alt) => Some(("{", false, false, ctrl, alt)), + (Keycode::Backslash, false, ctrl, alt) => Some(("\\", false, false, ctrl, alt)), + (Keycode::Backslash, true, ctrl, alt) => Some(("|", false, false, ctrl, alt)), + (Keycode::RightBracket, false, ctrl, alt) => Some(("]", false, false, ctrl, alt)), + (Keycode::RightBracket, true, ctrl, alt) => Some(("}", false, false, ctrl, alt)), + (Keycode::Caret, shift, ctrl, alt) => Some(("^", false, shift, ctrl, alt)), + (Keycode::Underscore, shift, ctrl, alt) => Some(("_", false, shift, ctrl, alt)), + (Keycode::Backquote, false, ctrl, alt) => Some(("`", false, false, ctrl, alt)), + (Keycode::Backquote, true, ctrl, alt) => Some(("~", false, false, ctrl, alt)), + (Keycode::A, shift, ctrl, alt) => Some(("a", false, shift, ctrl, alt)), + (Keycode::B, shift, ctrl, alt) => Some(("b", false, shift, ctrl, alt)), + (Keycode::C, shift, ctrl, alt) => Some(("c", false, shift, ctrl, alt)), + (Keycode::D, shift, ctrl, alt) => Some(("d", false, shift, ctrl, alt)), + (Keycode::E, shift, ctrl, alt) => Some(("e", false, shift, ctrl, alt)), + (Keycode::F, shift, ctrl, alt) => Some(("f", false, shift, ctrl, alt)), + (Keycode::G, shift, ctrl, alt) => Some(("g", false, shift, ctrl, alt)), + (Keycode::H, shift, ctrl, alt) => Some(("h", false, shift, ctrl, alt)), + (Keycode::I, shift, ctrl, alt) => Some(("i", false, shift, ctrl, alt)), + (Keycode::J, shift, ctrl, alt) => Some(("j", false, shift, ctrl, alt)), + (Keycode::K, shift, ctrl, alt) => Some(("k", false, shift, ctrl, alt)), + (Keycode::L, shift, ctrl, alt) => Some(("l", false, shift, ctrl, alt)), + (Keycode::M, shift, ctrl, alt) => Some(("m", false, shift, ctrl, alt)), + (Keycode::N, shift, ctrl, alt) => Some(("n", false, shift, ctrl, alt)), + (Keycode::O, shift, ctrl, alt) => Some(("o", false, shift, ctrl, alt)), + (Keycode::P, shift, ctrl, alt) => Some(("p", false, shift, ctrl, alt)), + (Keycode::Q, shift, ctrl, alt) => Some(("q", false, shift, ctrl, alt)), + (Keycode::R, shift, ctrl, alt) => Some(("r", false, shift, ctrl, alt)), + (Keycode::S, shift, ctrl, alt) => Some(("s", false, shift, ctrl, alt)), + (Keycode::T, shift, ctrl, alt) => Some(("t", false, shift, ctrl, alt)), + (Keycode::U, shift, ctrl, alt) => Some(("u", false, shift, ctrl, alt)), + (Keycode::V, shift, ctrl, alt) => Some(("v", false, shift, ctrl, alt)), + (Keycode::W, shift, ctrl, alt) => Some(("w", false, shift, ctrl, alt)), + (Keycode::X, shift, ctrl, alt) => Some(("x", false, shift, ctrl, alt)), + (Keycode::Y, shift, ctrl, alt) => Some(("y", false, shift, ctrl, alt)), + (Keycode::Z, shift, ctrl, alt) => Some(("z", false, shift, ctrl, alt)), + (Keycode::Delete, shift, ctrl, alt) => Some(("Delete", true, shift, ctrl, alt)), + (Keycode::CapsLock, _, _, _) => unsupported_key(Keycode::CapsLock), + (Keycode::F1, shift, ctrl, alt) => Some(("F1", true, shift, ctrl, alt)), + (Keycode::F2, shift, ctrl, alt) => Some(("F2", true, shift, ctrl, alt)), + (Keycode::F3, shift, ctrl, alt) => Some(("F3", true, shift, ctrl, alt)), + (Keycode::F4, shift, ctrl, alt) => Some(("F4", true, shift, ctrl, alt)), + (Keycode::F5, shift, ctrl, alt) => Some(("F5", true, shift, ctrl, alt)), + (Keycode::F6, shift, ctrl, alt) => Some(("F6", true, shift, ctrl, alt)), + (Keycode::F7, shift, ctrl, alt) => Some(("F7", true, shift, ctrl, alt)), + (Keycode::F8, shift, ctrl, alt) => Some(("F8", true, shift, ctrl, alt)), + (Keycode::F9, shift, ctrl, alt) => Some(("F9", true, shift, ctrl, alt)), + (Keycode::F10, shift, ctrl, alt) => Some(("F10", true, shift, ctrl, alt)), + (Keycode::F11, shift, ctrl, alt) => Some(("F11", true, shift, ctrl, alt)), + (Keycode::F12, shift, ctrl, alt) => Some(("F12", true, shift, ctrl, alt)), + (Keycode::PrintScreen, _, _, _) => unsupported_key(Keycode::PrintScreen), + (Keycode::ScrollLock, _, _, _) => unsupported_key(Keycode::ScrollLock), + (Keycode::Pause, _, _, _) => unsupported_key(Keycode::Pause), + (Keycode::Insert, shift, ctrl, alt) => Some(("Insert", true, shift, ctrl, alt)), + (Keycode::Home, shift, ctrl, alt) => Some(("Home", true, shift, ctrl, alt)), + (Keycode::PageUp, shift, ctrl, alt) => Some(("PageUp", true, shift, ctrl, alt)), + (Keycode::End, shift, ctrl, alt) => Some(("End", true, shift, ctrl, alt)), + (Keycode::PageDown, shift, ctrl, alt) => Some(("PageDown", true, shift, ctrl, alt)), + (Keycode::Right, shift, ctrl, alt) => Some(("Right", true, shift, ctrl, alt)), + (Keycode::Left, shift, ctrl, alt) => Some(("Left", true, shift, ctrl, alt)), + (Keycode::Down, shift, ctrl, alt) => Some(("Down", true, shift, ctrl, alt)), + (Keycode::Up, shift, ctrl, alt) => Some(("Up", true, shift, ctrl, alt)), + (Keycode::NumLockClear, _, _, _) => unsupported_key(Keycode::NumLockClear), + (Keycode::KpDivide, shift, ctrl, alt) => Some(("/", true, shift, ctrl, alt)), + (Keycode::KpMultiply, shift, ctrl, alt) => Some(("*", true, shift, ctrl, alt)), + (Keycode::KpMinus, shift, ctrl, alt) => Some(("-", true, shift, ctrl, alt)), + (Keycode::KpPlus, shift, ctrl, alt) => Some(("+", true, shift, ctrl, alt)), + (Keycode::KpEnter, shift, ctrl, alt) => Some(("Enter", true, shift, ctrl, alt)), + (Keycode::Kp0, shift, ctrl, alt) => Some(("0", false, shift, ctrl, alt)), + (Keycode::Kp1, shift, ctrl, alt) => Some(("1", false, shift, ctrl, alt)), + (Keycode::Kp2, shift, ctrl, alt) => Some(("2", false, shift, ctrl, alt)), + (Keycode::Kp3, shift, ctrl, alt) => Some(("3", false, shift, ctrl, alt)), + (Keycode::Kp4, shift, ctrl, alt) => Some(("4", false, shift, ctrl, alt)), + (Keycode::Kp5, shift, ctrl, alt) => Some(("5", false, shift, ctrl, alt)), + (Keycode::Kp6, shift, ctrl, alt) => Some(("6", false, shift, ctrl, alt)), + (Keycode::Kp7, shift, ctrl, alt) => Some(("7", false, shift, ctrl, alt)), + (Keycode::Kp8, shift, ctrl, alt) => Some(("8", false, shift, ctrl, alt)), + (Keycode::Kp9, shift, ctrl, alt) => Some(("9", false, shift, ctrl, alt)), + (Keycode::KpPeriod, shift, ctrl, alt) => Some((".", false, shift, ctrl, alt)), + (Keycode::Application, _, _, _) => unsupported_key(Keycode::Application), + (Keycode::Power, _, _, _) => unsupported_key(Keycode::Power), + (Keycode::KpEquals, shift, ctrl, alt) => Some(("=", false, shift, ctrl, alt)), + (Keycode::F13, shift, ctrl, alt) => Some(("F13", true, shift, ctrl, alt)), + (Keycode::F14, shift, ctrl, alt) => Some(("F14", true, shift, ctrl, alt)), + (Keycode::F15, shift, ctrl, alt) => Some(("F15", true, shift, ctrl, alt)), + (Keycode::F16, shift, ctrl, alt) => Some(("F16", true, shift, ctrl, alt)), + (Keycode::F17, shift, ctrl, alt) => Some(("F17", true, shift, ctrl, alt)), + (Keycode::F18, shift, ctrl, alt) => Some(("F18", true, shift, ctrl, alt)), + (Keycode::F19, shift, ctrl, alt) => Some(("F19", true, shift, ctrl, alt)), + (Keycode::F20, shift, ctrl, alt) => Some(("F20", true, shift, ctrl, alt)), + (Keycode::F21, shift, ctrl, alt) => Some(("F21", true, shift, ctrl, alt)), + (Keycode::F22, shift, ctrl, alt) => Some(("F22", true, shift, ctrl, alt)), + (Keycode::F23, shift, ctrl, alt) => Some(("F23", true, shift, ctrl, alt)), + (Keycode::F24, shift, ctrl, alt) => Some(("F24", true, shift, ctrl, alt)), + (Keycode::Execute, _, _, _) => unsupported_key(Keycode::Execute), + (Keycode::Help, _, _, _) => unsupported_key(Keycode::Help), + (Keycode::Menu, _, _, _) => unsupported_key(Keycode::Menu), + (Keycode::Select, _, _, _) => unsupported_key(Keycode::Select), + (Keycode::Stop, _, _, _) => unsupported_key(Keycode::Stop), + (Keycode::Again, _, _, _) => unsupported_key(Keycode::Again), + (Keycode::Undo, _, _, _) => unsupported_key(Keycode::Undo), + (Keycode::Cut, _, _, _) => unsupported_key(Keycode::Cut), + (Keycode::Copy, _, _, _) => unsupported_key(Keycode::Copy), + (Keycode::Paste, _, _, _) => unsupported_key(Keycode::Paste), + (Keycode::Find, _, _, _) => unsupported_key(Keycode::Find), + (Keycode::Mute, _, _, _) => unsupported_key(Keycode::Mute), + (Keycode::VolumeUp, _, _, _) => unsupported_key(Keycode::VolumeUp), + (Keycode::VolumeDown, _, _, _) => unsupported_key(Keycode::VolumeDown), + (Keycode::KpComma, _, _, _) => unsupported_key(Keycode::KpComma), + (Keycode::KpEqualsAS400, _, _, _) => unsupported_key(Keycode::KpEqualsAS400), + (Keycode::AltErase, _, _, _) => unsupported_key(Keycode::AltErase), + (Keycode::Sysreq, _, _, _) => unsupported_key(Keycode::Sysreq), + (Keycode::Cancel, _, _, _) => unsupported_key(Keycode::Cancel), + (Keycode::Clear, _, _, _) => unsupported_key(Keycode::Clear), + (Keycode::Prior, _, _, _) => unsupported_key(Keycode::Prior), + (Keycode::Return2, _, _, _) => unsupported_key(Keycode::Return2), + (Keycode::Separator, _, _, _) => unsupported_key(Keycode::Separator), + (Keycode::Out, _, _, _) => unsupported_key(Keycode::Out), + (Keycode::Oper, _, _, _) => unsupported_key(Keycode::Oper), + (Keycode::ClearAgain, _, _, _) => unsupported_key(Keycode::ClearAgain), + (Keycode::CrSel, _, _, _) => unsupported_key(Keycode::CrSel), + (Keycode::ExSel, _, _, _) => unsupported_key(Keycode::ExSel), + (Keycode::Kp00, _, _, _) => unsupported_key(Keycode::Kp00), + (Keycode::Kp000, _, _, _) => unsupported_key(Keycode::Kp000), + (Keycode::ThousandsSeparator, _, _, _) => unsupported_key(Keycode::ThousandsSeparator), + (Keycode::DecimalSeparator, _, _, _) => unsupported_key(Keycode::DecimalSeparator), + (Keycode::CurrencyUnit, _, _, _) => unsupported_key(Keycode::CurrencyUnit), + (Keycode::CurrencySubUnit, _, _, _) => unsupported_key(Keycode::CurrencySubUnit), + (Keycode::KpLeftParen, shift, ctrl, alt) => Some(("(", false, shift, ctrl, alt)), + (Keycode::KpRightParen, shift, ctrl, alt) => Some(("(", false, shift, ctrl, alt)), + (Keycode::KpLeftBrace, shift, ctrl, alt) => Some(("[", false, shift, ctrl, alt)), + (Keycode::KpRightBrace, shift, ctrl, alt) => Some(("]", false, shift, ctrl, alt)), + (Keycode::KpTab, shift, ctrl, alt) => Some(("TAB", true, shift, ctrl, alt)), + (Keycode::KpBackspace, shift, ctrl, alt) => Some(("BS", true, shift, ctrl, alt)), + (Keycode::KpA, shift, ctrl, alt) => Some(("A", false, shift, ctrl, alt)), + (Keycode::KpB, shift, ctrl, alt) => Some(("B", false, shift, ctrl, alt)), + (Keycode::KpC, shift, ctrl, alt) => Some(("C", false, shift, ctrl, alt)), + (Keycode::KpD, shift, ctrl, alt) => Some(("D", false, shift, ctrl, alt)), + (Keycode::KpE, shift, ctrl, alt) => Some(("E", false, shift, ctrl, alt)), + (Keycode::KpF, shift, ctrl, alt) => Some(("F", false, shift, ctrl, alt)), + (Keycode::KpXor, _, _, _) => unsupported_key(Keycode::KpXor), + (Keycode::KpPower, shift, ctrl, alt) => Some(("^", false, shift, ctrl, alt)), + (Keycode::KpPercent, shift, ctrl, alt) => Some(("%", false, shift, ctrl, alt)), + (Keycode::KpLess, shift, ctrl, alt) => Some(("lt", true, shift, ctrl, alt)), + (Keycode::KpGreater, shift, ctrl, alt) => Some(("gt", true, shift, ctrl, alt)), + (Keycode::KpAmpersand, shift, ctrl, alt) => Some(("&", false, shift, ctrl, alt)), + (Keycode::KpDblAmpersand, _, _, _) => unsupported_key(Keycode::KpDblAmpersand), + (Keycode::KpVerticalBar, shift, ctrl, alt) => Some(("|", false, shift, ctrl, alt)), + (Keycode::KpDblVerticalBar, _, _, _) => unsupported_key(Keycode::KpDblVerticalBar), + (Keycode::KpColon, shift, ctrl, alt) => Some((":", false, shift, ctrl, alt)), + (Keycode::KpHash, shift, ctrl, alt) => Some(("#", false, shift, ctrl, alt)), + (Keycode::KpSpace, shift, ctrl, alt) => Some((" ", false, shift, ctrl, alt)), + (Keycode::KpAt, shift, ctrl, alt) => Some(("@", false, shift, ctrl, alt)), + (Keycode::KpExclam, shift, ctrl, alt) => Some(("!", false, shift, ctrl, alt)), + (Keycode::KpMemStore, _, _, _) => unsupported_key(Keycode::KpMemStore), + (Keycode::KpMemRecall, _, _, _) => unsupported_key(Keycode::KpMemRecall), + (Keycode::KpMemClear, _, _, _) => unsupported_key(Keycode::KpMemClear), + (Keycode::KpMemAdd, _, _, _) => unsupported_key(Keycode::KpMemAdd), + (Keycode::KpMemSubtract, _, _, _) => unsupported_key(Keycode::KpMemSubtract), + (Keycode::KpMemMultiply, _, _, _) => unsupported_key(Keycode::KpMemMultiply), + (Keycode::KpMemDivide, _, _, _) => unsupported_key(Keycode::KpMemDivide), + (Keycode::KpPlusMinus, _, _, _) => unsupported_key(Keycode::KpPlusMinus), + (Keycode::KpClear, _, _, _) => unsupported_key(Keycode::KpClear), + (Keycode::KpClearEntry, _, _, _) => unsupported_key(Keycode::KpClearEntry), + (Keycode::KpBinary, _, _, _) => unsupported_key(Keycode::KpBinary), + (Keycode::KpOctal, _, _, _) => unsupported_key(Keycode::KpOctal), + (Keycode::KpDecimal, _, _, _) => unsupported_key(Keycode::KpDecimal), + (Keycode::KpHexadecimal, _, _, _) => unsupported_key(Keycode::KpHexadecimal), + (Keycode::LCtrl, _, _, _) => None, + (Keycode::LShift, _, _, _) => None, + (Keycode::LAlt, _, _, _) => None, + (Keycode::LGui, _, _, _) => None, + (Keycode::RCtrl, _, _, _) => None, + (Keycode::RShift, _, _, _) => None, + (Keycode::RAlt, _, _, _) => None, + (Keycode::RGui, _, _, _) => None, + (Keycode::Mode, _, _, _) => unsupported_key(Keycode::Mode), + (Keycode::AudioNext, _, _, _) => unsupported_key(Keycode::AudioNext), + (Keycode::AudioPrev, _, _, _) => unsupported_key(Keycode::AudioPrev), + (Keycode::AudioStop, _, _, _) => unsupported_key(Keycode::AudioStop), + (Keycode::AudioPlay, _, _, _) => unsupported_key(Keycode::AudioPlay), + (Keycode::AudioMute, _, _, _) => unsupported_key(Keycode::AudioMute), + (Keycode::MediaSelect, _, _, _) => unsupported_key(Keycode::MediaSelect), + (Keycode::Www, _, _, _) => unsupported_key(Keycode::Www), + (Keycode::Mail, _, _, _) => unsupported_key(Keycode::Mail), + (Keycode::Calculator, _, _, _) => unsupported_key(Keycode::Calculator), + (Keycode::Computer, _, _, _) => unsupported_key(Keycode::Computer), + (Keycode::AcSearch, _, _, _) => unsupported_key(Keycode::AcSearch), + (Keycode::AcHome, _, _, _) => unsupported_key(Keycode::AcHome), + (Keycode::AcBack, _, _, _) => unsupported_key(Keycode::AcBack), + (Keycode::AcForward, _, _, _) => unsupported_key(Keycode::AcForward), + (Keycode::AcStop, _, _, _) => unsupported_key(Keycode::AcStop), + (Keycode::AcRefresh, _, _, _) => unsupported_key(Keycode::AcRefresh), + (Keycode::AcBookmarks, _, _, _) => unsupported_key(Keycode::AcBookmarks), + (Keycode::BrightnessDown, _, _, _) => unsupported_key(Keycode::BrightnessDown), + (Keycode::BrightnessUp, _, _, _) => unsupported_key(Keycode::BrightnessUp), + (Keycode::DisplaySwitch, _, _, _) => unsupported_key(Keycode::DisplaySwitch), + (Keycode::KbdIllumToggle, _, _, _) => unsupported_key(Keycode::KbdIllumToggle), + (Keycode::KbdIllumDown, _, _, _) => unsupported_key(Keycode::KbdIllumDown), + (Keycode::KbdIllumUp, _, _, _) => unsupported_key(Keycode::KbdIllumUp), + (Keycode::Eject, _, _, _) => unsupported_key(Keycode::Eject), + (Keycode::Sleep, _, _, _) => unsupported_key(Keycode::Sleep), + } +} diff --git a/src/window/sdl2/mod.rs b/src/window/sdl2/mod.rs index e3419a3..1e0fae7 100644 --- a/src/window/sdl2/mod.rs +++ b/src/window/sdl2/mod.rs @@ -1,3 +1,6 @@ +#[macro_use] +mod layouts; + use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::mpsc::Receiver; use std::sync::Arc; @@ -20,12 +23,14 @@ use skulpin::{ use super::handle_new_grid_size; use super::settings::*; -use crate::bridge::{produce_neovim_keybinding_string, UiCommand}; +pub use super::keyboard; +use crate::bridge::UiCommand; use crate::editor::WindowCommand; use crate::error_handling::ResultPanicExplanation; use crate::redraw_scheduler::REDRAW_SCHEDULER; use crate::renderer::Renderer; use crate::settings::*; +use layouts::produce_neovim_keybinding_string; #[derive(RustEmbed)] #[folder = "assets/"] diff --git a/src/window/settings.rs b/src/window/settings.rs index 62d36b1..597f98f 100644 --- a/src/window/settings.rs +++ b/src/window/settings.rs @@ -1,27 +1,30 @@ -use crate::settings::*; - -#[derive(Clone)] -pub struct WindowSettings { - pub refresh_rate: u64, - pub transparency: f32, - pub no_idle: bool, - pub fullscreen: bool, -} - -pub fn initialize_settings() { - let no_idle = SETTINGS - .neovim_arguments - .contains(&String::from("--noIdle")); - - SETTINGS.set(&WindowSettings { - refresh_rate: 60, - transparency: 1.0, - no_idle, - fullscreen: false, - }); - - register_nvim_setting!("refresh_rate", WindowSettings::refresh_rate); - register_nvim_setting!("transparency", WindowSettings::transparency); - register_nvim_setting!("no_idle", WindowSettings::no_idle); - register_nvim_setting!("fullscreen", WindowSettings::fullscreen); -} +use crate::settings::*; +use super::keyboard::initialize_settings as keyboard_initialize_settings; + +#[derive(Clone)] +pub struct WindowSettings { + pub refresh_rate: u64, + pub transparency: f32, + pub no_idle: bool, + pub fullscreen: bool, +} + +pub fn initialize_settings() { + let no_idle = SETTINGS + .neovim_arguments + .contains(&String::from("--noIdle")); + + SETTINGS.set(&WindowSettings { + refresh_rate: 60, + transparency: 1.0, + no_idle, + fullscreen: false, + }); + + register_nvim_setting!("refresh_rate", WindowSettings::refresh_rate); + register_nvim_setting!("transparency", WindowSettings::transparency); + register_nvim_setting!("no_idle", WindowSettings::no_idle); + register_nvim_setting!("fullscreen", WindowSettings::fullscreen); + + keyboard_initialize_settings(); +} diff --git a/src/window/winit/layouts/mod.rs b/src/window/winit/layouts/mod.rs new file mode 100644 index 0000000..55888dc --- /dev/null +++ b/src/window/winit/layouts/mod.rs @@ -0,0 +1,44 @@ +mod qwerty; + +use log::trace; +use skulpin::winit::event::ModifiersState; +use skulpin::winit::event::VirtualKeyCode as Keycode; + +use qwerty::*; +use super::keyboard::*; +use crate::settings::*; + +pub fn unsupported_key(keycode: Keycode) -> Option { + trace!("Unsupported key: {:?}", keycode); + None +} + +pub fn produce_neovim_keybinding_string( + keycode: Option, + keytext: Option, + modifiers: Option, +) -> Option { + let mut shift = false; + let mut ctrl = false; + let mut alt = false; + let mut gui = false; + if let Some(modifiers) = modifiers { + shift = modifiers.shift(); + ctrl = modifiers.ctrl(); + alt = modifiers.alt(); + gui = modifiers.logo(); + } + + if let Some(text) = keytext { + Some(append_modifiers(&text, false, false, ctrl, alt, gui)) + } else if let Some(keycode) = keycode { + (match SETTINGS.get::().layout { + KeyboardLayout::Qwerty => handle_qwerty_layout(keycode, shift, ctrl, alt), + }) + .map(|(transformed_text, special, shift, ctrl, alt)| { + append_modifiers(transformed_text, special, shift, ctrl, alt, gui) + }) + } else { + None + } +} diff --git a/src/window/winit/layouts/qwerty.rs b/src/window/winit/layouts/qwerty.rs new file mode 100644 index 0000000..e234ac0 --- /dev/null +++ b/src/window/winit/layouts/qwerty.rs @@ -0,0 +1,164 @@ +use super::unsupported_key; + +use skulpin::winit::event::VirtualKeyCode as Keycode; + +pub fn handle_qwerty_layout( + keycode: Keycode, + shift: bool, + ctrl: bool, + alt: bool, +) -> Option<(&'static str, bool, bool, bool, bool)> { + match (keycode, shift, ctrl, alt) { + (Keycode::Back, shift, ctrl, alt) => Some(("BS", true, shift, ctrl, alt)), + (Keycode::Tab, shift, ctrl, alt) => Some(("Tab", true, shift, ctrl, alt)), + (Keycode::Return, shift, ctrl, alt) => Some(("Enter", true, shift, ctrl, alt)), + (Keycode::Escape, shift, ctrl, alt) => Some(("Esc", true, shift, ctrl, alt)), + (Keycode::Space, shift, ctrl, alt) => Some((" ", false, shift, ctrl, alt)), + (Keycode::Apostrophe, false, ctrl, alt) => Some(("'", false, false, ctrl, alt)), + (Keycode::Apostrophe, true, ctrl, alt) => Some(("\"", false, false, ctrl, alt)), + (Keycode::Comma, false, ctrl, alt) => Some((",", false, shift, ctrl, alt)), + (Keycode::Comma, true, ctrl, alt) => Some(("<", false, shift, ctrl, alt)), + (Keycode::Minus, false, ctrl, alt) => Some(("-", false, false, ctrl, alt)), + (Keycode::Minus, true, ctrl, alt) => Some(("_", false, false, ctrl, alt)), + (Keycode::Period, false, ctrl, alt) => Some((".", false, false, ctrl, alt)), + (Keycode::Period, true, ctrl, alt) => Some((">", false, false, ctrl, alt)), + (Keycode::Slash, false, ctrl, alt) => Some(("/", false, false, ctrl, alt)), + (Keycode::Slash, true, ctrl, alt) => Some(("?", false, false, ctrl, alt)), + (Keycode::Key0, false, ctrl, alt) => Some(("0", false, shift, ctrl, alt)), + (Keycode::Key0, true, ctrl, alt) => Some((")", true, shift, ctrl, alt)), + (Keycode::Key1, false, ctrl, alt) => Some(("1", false, shift, ctrl, alt)), + (Keycode::Key1, true, ctrl, alt) => Some(("!", true, shift, ctrl, alt)), + (Keycode::Key2, false, ctrl, alt) => Some(("2", false, false, ctrl, alt)), + (Keycode::Key2, true, ctrl, alt) => Some(("@", false, false, ctrl, alt)), + (Keycode::Key3, false, ctrl, alt) => Some(("3", false, false, ctrl, alt)), + (Keycode::Key3, true, ctrl, alt) => Some(("#", false, false, ctrl, alt)), + (Keycode::Key4, false, ctrl, alt) => Some(("4", false, false, ctrl, alt)), + (Keycode::Key4, true, ctrl, alt) => Some(("$", false, false, ctrl, alt)), + (Keycode::Key5, false, ctrl, alt) => Some(("5", false, false, ctrl, alt)), + (Keycode::Key5, true, ctrl, alt) => Some(("%", false, false, ctrl, alt)), + (Keycode::Key6, false, ctrl, alt) => Some(("6", false, false, ctrl, alt)), + (Keycode::Key6, true, ctrl, alt) => Some(("^", false, false, ctrl, alt)), + (Keycode::Key7, false, ctrl, alt) => Some(("7", false, false, ctrl, alt)), + (Keycode::Key7, true, ctrl, alt) => Some(("&", false, false, ctrl, alt)), + (Keycode::Key8, false, ctrl, alt) => Some(("8", false, false, ctrl, alt)), + (Keycode::Key8, true, ctrl, alt) => Some(("*", false, false, ctrl, alt)), + (Keycode::Key9, false, ctrl, alt) => Some(("9", false, false, ctrl, alt)), + (Keycode::Key9, true, ctrl, alt) => Some(("(", true, false, ctrl, alt)), + (Keycode::Colon, shift, ctrl, alt) => Some((":", false, shift, ctrl, alt)), + (Keycode::Semicolon, false, ctrl, alt) => Some((";", false, false, ctrl, alt)), + (Keycode::Semicolon, true, ctrl, alt) => Some((":", false, false, ctrl, alt)), + (Keycode::Equals, false, ctrl, alt) => Some(("=", false, false, ctrl, alt)), + (Keycode::Equals, true, ctrl, alt) => Some(("+", false, false, ctrl, alt)), + (Keycode::At, shift, ctrl, alt) => Some(("@", false, shift, ctrl, alt)), + (Keycode::LBracket, false, ctrl, alt) => Some(("[", false, false, ctrl, alt)), + (Keycode::LBracket, true, ctrl, alt) => Some(("{", false, false, ctrl, alt)), + (Keycode::Backslash, false, ctrl, alt) => Some(("\\", false, false, ctrl, alt)), + (Keycode::Backslash, true, ctrl, alt) => Some(("|", false, false, ctrl, alt)), + (Keycode::RBracket, false, ctrl, alt) => Some(("]", false, false, ctrl, alt)), + (Keycode::RBracket, true, ctrl, alt) => Some(("}", false, false, ctrl, alt)), + (Keycode::Caret, shift, ctrl, alt) => Some(("^", false, shift, ctrl, alt)), + (Keycode::Grave, false, ctrl, alt) => Some(("`", false, false, ctrl, alt)), + (Keycode::Grave, true, ctrl, alt) => Some(("~", false, false, ctrl, alt)), + (Keycode::A, shift, ctrl, alt) => Some(("a", false, shift, ctrl, alt)), + (Keycode::B, shift, ctrl, alt) => Some(("b", false, shift, ctrl, alt)), + (Keycode::C, shift, ctrl, alt) => Some(("c", false, shift, ctrl, alt)), + (Keycode::D, shift, ctrl, alt) => Some(("d", false, shift, ctrl, alt)), + (Keycode::E, shift, ctrl, alt) => Some(("e", false, shift, ctrl, alt)), + (Keycode::F, shift, ctrl, alt) => Some(("f", false, shift, ctrl, alt)), + (Keycode::G, shift, ctrl, alt) => Some(("g", false, shift, ctrl, alt)), + (Keycode::H, shift, ctrl, alt) => Some(("h", false, shift, ctrl, alt)), + (Keycode::I, shift, ctrl, alt) => Some(("i", false, shift, ctrl, alt)), + (Keycode::J, shift, ctrl, alt) => Some(("j", false, shift, ctrl, alt)), + (Keycode::K, shift, ctrl, alt) => Some(("k", false, shift, ctrl, alt)), + (Keycode::L, shift, ctrl, alt) => Some(("l", false, shift, ctrl, alt)), + (Keycode::M, shift, ctrl, alt) => Some(("m", false, shift, ctrl, alt)), + (Keycode::N, shift, ctrl, alt) => Some(("n", false, shift, ctrl, alt)), + (Keycode::O, shift, ctrl, alt) => Some(("o", false, shift, ctrl, alt)), + (Keycode::P, shift, ctrl, alt) => Some(("p", false, shift, ctrl, alt)), + (Keycode::Q, shift, ctrl, alt) => Some(("q", false, shift, ctrl, alt)), + (Keycode::R, shift, ctrl, alt) => Some(("r", false, shift, ctrl, alt)), + (Keycode::S, shift, ctrl, alt) => Some(("s", false, shift, ctrl, alt)), + (Keycode::T, shift, ctrl, alt) => Some(("t", false, shift, ctrl, alt)), + (Keycode::U, shift, ctrl, alt) => Some(("u", false, shift, ctrl, alt)), + (Keycode::V, shift, ctrl, alt) => Some(("v", false, shift, ctrl, alt)), + (Keycode::W, shift, ctrl, alt) => Some(("w", false, shift, ctrl, alt)), + (Keycode::X, shift, ctrl, alt) => Some(("x", false, shift, ctrl, alt)), + (Keycode::Y, shift, ctrl, alt) => Some(("y", false, shift, ctrl, alt)), + (Keycode::Z, shift, ctrl, alt) => Some(("z", false, shift, ctrl, alt)), + (Keycode::Delete, shift, ctrl, alt) => Some(("Delete", true, shift, ctrl, alt)), + (Keycode::F1, shift, ctrl, alt) => Some(("F1", true, shift, ctrl, alt)), + (Keycode::F2, shift, ctrl, alt) => Some(("F2", true, shift, ctrl, alt)), + (Keycode::F3, shift, ctrl, alt) => Some(("F3", true, shift, ctrl, alt)), + (Keycode::F4, shift, ctrl, alt) => Some(("F4", true, shift, ctrl, alt)), + (Keycode::F5, shift, ctrl, alt) => Some(("F5", true, shift, ctrl, alt)), + (Keycode::F6, shift, ctrl, alt) => Some(("F6", true, shift, ctrl, alt)), + (Keycode::F7, shift, ctrl, alt) => Some(("F7", true, shift, ctrl, alt)), + (Keycode::F8, shift, ctrl, alt) => Some(("F8", true, shift, ctrl, alt)), + (Keycode::F9, shift, ctrl, alt) => Some(("F9", true, shift, ctrl, alt)), + (Keycode::F10, shift, ctrl, alt) => Some(("F10", true, shift, ctrl, alt)), + (Keycode::F11, shift, ctrl, alt) => Some(("F11", true, shift, ctrl, alt)), + (Keycode::F12, shift, ctrl, alt) => Some(("F12", true, shift, ctrl, alt)), + (Keycode::Snapshot, _, _, _) => unsupported_key(Keycode::Snapshot), + (Keycode::Pause, _, _, _) => unsupported_key(Keycode::Pause), + (Keycode::Insert, shift, ctrl, alt) => Some(("Insert", true, shift, ctrl, alt)), + (Keycode::Home, shift, ctrl, alt) => Some(("Home", true, shift, ctrl, alt)), + (Keycode::PageUp, shift, ctrl, alt) => Some(("PageUp", true, shift, ctrl, alt)), + (Keycode::End, shift, ctrl, alt) => Some(("End", true, shift, ctrl, alt)), + (Keycode::PageDown, shift, ctrl, alt) => Some(("PageDown", true, shift, ctrl, alt)), + (Keycode::Right, shift, ctrl, alt) => Some(("Right", true, shift, ctrl, alt)), + (Keycode::Left, shift, ctrl, alt) => Some(("Left", true, shift, ctrl, alt)), + (Keycode::Down, shift, ctrl, alt) => Some(("Down", true, shift, ctrl, alt)), + (Keycode::Up, shift, ctrl, alt) => Some(("Up", true, shift, ctrl, alt)), + (Keycode::Numpad0, shift, ctrl, alt) => Some(("0", false, shift, ctrl, alt)), + (Keycode::Numpad1, shift, ctrl, alt) => Some(("1", false, shift, ctrl, alt)), + (Keycode::Numpad2, shift, ctrl, alt) => Some(("2", false, shift, ctrl, alt)), + (Keycode::Numpad3, shift, ctrl, alt) => Some(("3", false, shift, ctrl, alt)), + (Keycode::Numpad4, shift, ctrl, alt) => Some(("4", false, shift, ctrl, alt)), + (Keycode::Numpad5, shift, ctrl, alt) => Some(("5", false, shift, ctrl, alt)), + (Keycode::Numpad6, shift, ctrl, alt) => Some(("6", false, shift, ctrl, alt)), + (Keycode::Numpad7, shift, ctrl, alt) => Some(("7", false, shift, ctrl, alt)), + (Keycode::Numpad8, shift, ctrl, alt) => Some(("8", false, shift, ctrl, alt)), + (Keycode::Numpad9, shift, ctrl, alt) => Some(("9", false, shift, ctrl, alt)), + (Keycode::Apps, _, _, _) => unsupported_key(Keycode::Apps), + (Keycode::Power, _, _, _) => unsupported_key(Keycode::Power), + (Keycode::F13, shift, ctrl, alt) => Some(("F13", true, shift, ctrl, alt)), + (Keycode::F14, shift, ctrl, alt) => Some(("F14", true, shift, ctrl, alt)), + (Keycode::F15, shift, ctrl, alt) => Some(("F15", true, shift, ctrl, alt)), + (Keycode::F16, shift, ctrl, alt) => Some(("F16", true, shift, ctrl, alt)), + (Keycode::F17, shift, ctrl, alt) => Some(("F17", true, shift, ctrl, alt)), + (Keycode::F18, shift, ctrl, alt) => Some(("F18", true, shift, ctrl, alt)), + (Keycode::F19, shift, ctrl, alt) => Some(("F19", true, shift, ctrl, alt)), + (Keycode::F20, shift, ctrl, alt) => Some(("F20", true, shift, ctrl, alt)), + (Keycode::F21, shift, ctrl, alt) => Some(("F21", true, shift, ctrl, alt)), + (Keycode::F22, shift, ctrl, alt) => Some(("F22", true, shift, ctrl, alt)), + (Keycode::F23, shift, ctrl, alt) => Some(("F23", true, shift, ctrl, alt)), + (Keycode::F24, shift, ctrl, alt) => Some(("F24", true, shift, ctrl, alt)), + (Keycode::Cut, _, _, _) => unsupported_key(Keycode::Cut), + (Keycode::Copy, _, _, _) => unsupported_key(Keycode::Copy), + (Keycode::Paste, _, _, _) => unsupported_key(Keycode::Paste), + (Keycode::Mute, _, _, _) => unsupported_key(Keycode::Mute), + (Keycode::VolumeUp, _, _, _) => unsupported_key(Keycode::VolumeUp), + (Keycode::VolumeDown, _, _, _) => unsupported_key(Keycode::VolumeDown), + (Keycode::Sysrq, _, _, _) => unsupported_key(Keycode::Sysrq), + (Keycode::LControl, _, _, _) => None, + (Keycode::LShift, _, _, _) => None, + (Keycode::LAlt, _, _, _) => None, + (Keycode::RControl, _, _, _) => None, + (Keycode::RShift, _, _, _) => None, + (Keycode::RAlt, _, _, _) => None, + (Keycode::MediaStop, _, _, _) => unsupported_key(Keycode::MediaStop), + (Keycode::MediaSelect, _, _, _) => unsupported_key(Keycode::MediaSelect), + (Keycode::Calculator, _, _, _) => unsupported_key(Keycode::Calculator), + (Keycode::MyComputer, _, _, _) => unsupported_key(Keycode::MyComputer), + (Keycode::WebSearch, _, _, _) => unsupported_key(Keycode::WebSearch), + (Keycode::WebHome, _, _, _) => unsupported_key(Keycode::WebHome), + (Keycode::WebBack, _, _, _) => unsupported_key(Keycode::WebBack), + (Keycode::WebForward, _, _, _) => unsupported_key(Keycode::WebForward), + (Keycode::NavigateBackward, _, _, _) => unsupported_key(Keycode::NavigateBackward), + (Keycode::NavigateForward, _, _, _) => unsupported_key(Keycode::NavigateForward), + (Keycode::WebStop, _, _, _) => unsupported_key(Keycode::WebStop), + (Keycode::WebRefresh, _, _, _) => unsupported_key(Keycode::WebRefresh), + (Keycode::Sleep, _, _, _) => unsupported_key(Keycode::Sleep), + _ => None, + } +} diff --git a/src/window/winit/mod.rs b/src/window/winit/mod.rs new file mode 100644 index 0000000..67fce2b --- /dev/null +++ b/src/window/winit/mod.rs @@ -0,0 +1,473 @@ +#[macro_use] +mod layouts; + +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::mpsc::Receiver; +use std::sync::Arc; +use std::thread::sleep; +use std::time::{Duration, Instant}; + +use crossfire::mpsc::TxUnbounded; +use image::{load_from_memory, GenericImageView, Pixel}; +use log::{debug, error, info, trace}; +use skulpin::winit; +use skulpin::winit::dpi::Size; +use skulpin::winit::event::VirtualKeyCode as Keycode; +use skulpin::winit::event::{ + ElementState, Event, ModifiersState, MouseButton, MouseScrollDelta, WindowEvent, +}; +use skulpin::winit::event_loop::{ControlFlow, EventLoop}; +use skulpin::winit::window::{Fullscreen, Icon}; +use skulpin::{ + CoordinateSystem, LogicalSize, PhysicalSize, PresentMode, Renderer as SkulpinRenderer, + RendererBuilder, Window, WinitWindow, +}; + +use super::handle_new_grid_size; +use super::settings::*; +pub use super::keyboard; +use crate::bridge::UiCommand; +use crate::editor::WindowCommand; +use crate::error_handling::ResultPanicExplanation; +use crate::redraw_scheduler::REDRAW_SCHEDULER; +use crate::renderer::Renderer; +use crate::settings::*; +use layouts::produce_neovim_keybinding_string; + +#[derive(RustEmbed)] +#[folder = "assets/"] +struct Asset; + +pub struct WinitWindowWrapper { + window: winit::window::Window, + skulpin_renderer: SkulpinRenderer, + renderer: Renderer, + mouse_down: bool, + mouse_position: LogicalSize, + mouse_enabled: bool, + grid_id_under_mouse: u64, + title: String, + previous_size: LogicalSize, + transparency: f32, + fullscreen: bool, + cached_size: (u32, u32), + cached_position: (i32, i32), + ui_command_sender: TxUnbounded, + window_command_receiver: Receiver, + running: Arc, +} + +impl WinitWindowWrapper { + pub fn toggle_fullscreen(&mut self) { + if self.fullscreen { + self.window.set_fullscreen(None); + + // Use cached size and position + self.window.set_inner_size(self.cached_size.into::()); + self.window.set_outer_position(self.cached_position.into::()); + } else { + self.cached_size = self.window.inner_size().into(); + self.cached_position = self.window.outer_position().unwrap().into(); + let handle = self.window.current_monitor(); + self.window + .set_fullscreen(Some(Fullscreen::Borderless(handle))); + } + + self.fullscreen = !self.fullscreen; + } + + pub fn synchronize_settings(&mut self) { + let editor_title = { EDITOR.lock().title.clone() }; + + if self.title != editor_title { + self.title = editor_title; + self.window.set_title(&self.title); + } + + let fullscreen = { SETTINGS.get::().fullscreen }; + + if self.fullscreen != fullscreen { + self.toggle_fullscreen(); + } + } + + pub fn handle_quit(&mut self) { + self.running.store(false, Ordering::Relaxed); + } + + pub fn handle_keyboard_input( + &mut self, + keycode: Option, + modifiers: Option, + ) { + if keycode.is_some() { + trace!( + "Keyboard Input Received: keycode-{:?} modifiers-{:?} ", + keycode, + modifiers + ); + } + + if let Some(keybinding_string) = produce_neovim_keybinding_string(keycode, None, modifiers) + { + self.ui_command_sender + .send(UiCommand::Keyboard(keybinding_string)) + .unwrap_or_explained_panic( + "Could not send UI command from the window system to the neovim process.", + ); + } + } + + pub fn handle_pointer_motion(&mut self, x: i32, y: i32) { + let previous_position = self.mouse_position; + let winit_window_wrapper = WinitWindow::new(&self.window); + let logical_position = + PhysicalSize::new(x as u32, y as u32).to_logical(winit_window_wrapper.scale_factor()); + + let mut top_window_position = (0.0, 0.0); + let mut top_grid_position = None; + + for details in self.renderer.window_regions.iter() { + if logical_position.width >= details.region.left as u32 + && logical_position.width < details.region.right as u32 + && logical_position.height >= details.region.top as u32 + && logical_position.height < details.region.bottom as u32 + { + top_window_position = (details.region.left, details.region.top); + top_grid_position = Some(( + details.id, + LogicalSize::new( + logical_position.width - details.region.left as u32, + logical_position.height - details.region.top as u32, + ), + details.floating, + )); + } + } + + if let Some((grid_id, grid_position, grid_floating)) = top_grid_position { + self.grid_id_under_mouse = grid_id; + self.mouse_position = LogicalSize::new( + (grid_position.width as f32 / self.renderer.font_width) as u32, + (grid_position.height as f32 / self.renderer.font_height) as u32, + ); + + if self.mouse_enabled && self.mouse_down && previous_position != self.mouse_position { + let (window_left, window_top) = top_window_position; + + // Until https://github.com/neovim/neovim/pull/12667 is merged, we have to special + // case non floating windows. Floating windows correctly transform mouse positions + // into grid coordinates, but non floating windows do not. + let position = if grid_floating { + (self.mouse_position.width, self.mouse_position.height) + } else { + let adjusted_drag_left = + self.mouse_position.width + (window_left / self.renderer.font_width) as u32; + let adjusted_drag_top = self.mouse_position.height + + (window_top / self.renderer.font_height) as u32; + (adjusted_drag_left, adjusted_drag_top) + }; + + self.ui_command_sender + .send(UiCommand::Drag { + grid_id: self.grid_id_under_mouse, + position, + }) + .ok(); + } + } + } + + pub fn handle_pointer_down(&mut self) { + if self.mouse_enabled { + self.ui_command_sender + .send(UiCommand::MouseButton { + action: String::from("press"), + grid_id: self.grid_id_under_mouse, + position: (self.mouse_position.width, self.mouse_position.height), + }) + .ok(); + } + self.mouse_down = true; + } + + pub fn handle_pointer_up(&mut self) { + if self.mouse_enabled { + self.ui_command_sender + .send(UiCommand::MouseButton { + action: String::from("release"), + grid_id: self.grid_id_under_mouse, + position: (self.mouse_position.width, self.mouse_position.height), + }) + .ok(); + } + self.mouse_down = false; + } + + pub fn handle_mouse_wheel(&mut self, x: i32, y: i32) { + if !self.mouse_enabled { + return; + } + + let vertical_input_type = match y { + _ if y > 0 => Some("up"), + _ if y < 0 => Some("down"), + _ => None, + }; + + if let Some(input_type) = vertical_input_type { + self.ui_command_sender + .send(UiCommand::Scroll { + direction: input_type.to_string(), + grid_id: self.grid_id_under_mouse, + position: (self.mouse_position.width, self.mouse_position.height), + }) + .ok(); + } + + let horizontal_input_type = match y { + _ if x > 0 => Some("right"), + _ if x < 0 => Some("left"), + _ => None, + }; + + if let Some(input_type) = horizontal_input_type { + self.ui_command_sender + .send(UiCommand::Scroll { + direction: input_type.to_string(), + grid_id: self.grid_id_under_mouse, + position: (self.mouse_position.width, self.mouse_position.height), + }) + .ok(); + } + } + + pub fn handle_focus_lost(&mut self) { + self.ui_command_sender.send(UiCommand::FocusLost).ok(); + } + + pub fn handle_focus_gained(&mut self) { + self.ui_command_sender.send(UiCommand::FocusGained).ok(); + REDRAW_SCHEDULER.queue_next_frame(); + } + + pub fn handle_event(&mut self, event: Event<()>) { + let mut keycode = None; + let mut modifiers = None; + let mut ignore_text_this_frame = false; + + match event { + Event::LoopDestroyed => { + self.handle_quit(); + } + Event::WindowEvent { + event: WindowEvent::CloseRequested, + .. + } => { + self.handle_quit(); + } + Event::WindowEvent { + event: WindowEvent::DroppedFile(path), + .. + } => { + self.ui_command_sender.send(UiCommand::FileDrop( + path.into_os_string().into_string().unwrap(), + )).ok(); + } + Event::WindowEvent { + event: WindowEvent::KeyboardInput { input, .. }, + .. + } => { + if input.state == ElementState::Pressed { + keycode = input.virtual_keycode; + } + } + Event::WindowEvent { + event: WindowEvent::ModifiersChanged(m), + .. + } => { + modifiers = Some(m); + } + Event::WindowEvent { + event: WindowEvent::CursorMoved { position, .. }, + .. + } => self.handle_pointer_motion(position.x as i32, position.y as i32), + Event::WindowEvent { + event: + WindowEvent::MouseWheel { + delta: MouseScrollDelta::LineDelta(x, y), + .. + }, + .. + } => self.handle_mouse_wheel(x as i32, y as i32), + + Event::WindowEvent { + event: + WindowEvent::MouseInput { + button: MouseButton::Left, + state, + .. + }, + .. + } => { + if state == ElementState::Pressed { + self.handle_pointer_down(); + } else { + self.handle_pointer_up(); + } + } + Event::WindowEvent { + event: WindowEvent::Focused(focus), + .. + } => { + if focus { + ignore_text_this_frame = true; // Ignore any text events on the first frame when focus is regained. https://github.com/Kethku/neovide/issues/193 + self.handle_focus_gained(); + } else { + self.handle_focus_lost(); + } + } + Event::WindowEvent { .. } => REDRAW_SCHEDULER.queue_next_frame(), + _ => {} + } + + if !ignore_text_this_frame { + window.handle_keyboard_input(keycode, modifiers); + } + } + + pub fn draw_frame(&mut self, dt: f32) -> VkResult { + let winit_window_wrapper = WinitWindow::new(&self.window); + let new_size = winit_window_wrapper.logical_size(); + if self.previous_size != new_size { + handle_new_grid_size(new_size, &self.renderer, &self.ui_command_sender); + self.previous_size = new_size; + } + + let current_size = self.previous_size; + let ui_command_sender = self.ui_command_sender.clone(); + + if REDRAW_SCHEDULER.should_draw() || SETTINGS.get::().no_idle { + debug!("Render Triggered"); + + let renderer = &mut self.renderer; + self.skulpin_renderer.draw( + &sdl_window_wrapper, + |canvas, coordinate_system_helper| { + if renderer.draw_frame(canvas, &coordinate_system_helper, dt) { + handle_new_grid_size(current_size, &renderer, &ui_command_sender); + } + }, + )?; + + Ok(true) + } else { + Ok(false) + } + } +} + +pub fn start_loop( + window_command_receiver: Receiver, + ui_command_sender: TxUnbounded, + running: Arc, + logical_size: LogicalSize, + renderer: Renderer, +) { + let icon = { + let icon_data = Asset::get("nvim.ico").expect("Failed to read icon data"); + let icon = load_from_memory(&icon_data).expect("Failed to parse icon data"); + let (width, height) = icon.dimensions(); + let mut rgba = Vec::with_capacity((width * height) as usize * 4); + for (_, _, pixel) in icon.pixels() { + rgba.extend_from_slice(&pixel.to_rgba().0); + } + Icon::from_rgba(rgba, width, height).expect("Failed to create icon object") + }; + info!("icon created"); + + let winit_window = winit::window::WindowBuilder::new() + .with_title("Neovide") + .with_inner_size((logical_size.width, logical_size.height).into()) + .with_window_icon(Some(icon)) + .build(event_loop) + .expect("Failed to create window"); + info!("window created"); + + let scale_factor = winit_window.scale_factor(); + + let skulpin_renderer = { + let winit_window_wrapper = WinitWindow::new(&winit_window); + RendererBuilder::new() + .prefer_integrated_gpu() + .use_vulkan_debug_layer(false) + .present_mode_priority(vec![PresentMode::Immediate]) + .coordinate_system(CoordinateSystem::Logical) + .build(&winit_window_wrapper) + .expect("Failed to create renderer") + }; + + let mut window_wrapper = WinitWindowWrapper { + window: winit_window, + skulpin_renderer, + renderer, + mouse_down: false, + mouse_position: LogicalSize { + width: 0, + height: 0, + }, + mouse_enabled: true, + grid_id_under_mouse: 0, + title: String::from("Neovide"), + previous_size: logical_size, + transparency: 1.0, + fullscreen: false, + cached_size: (0, 0), + cached_position: (0, 0), + ui_command_sender, + window_command_receiver, + running: running.clone(), + }; + + let mut was_animating = false; + let mut previous_frame_start = Instant::now(); + + let event_loop = EventLoop::new(); + event_loop.run(move |e, _window_target, control_flow| { + if !running.load(Ordering::Relaxed) { + *control_flow = ControlFlow::Exit; + return; + } + + let frame_start = Instant::now(); + + let refresh_rate = { SETTINGS.get::().refresh_rate as f32 }; + let dt = if was_animating { + previous_frame_start.elapsed().as_secs_f32() + } else { + 1.0 / refresh_rate + }; + + window_wrapper.synchronize_settings(); + + window_wrapper.handle_event(e); + + match window_wrapper.draw_frame(dt) { + Ok(animating) => { + was_animating = animating; + } + Err(error) => { + error!("Render failed: {}", error); + self.running.store(false, Ordering::Relaxed); + return; + } + } + + let elapsed = frame_start.elapsed(); + let frame_length = Duration::from_secs_f32(1.0 / refresh_rate); + + if elapsed < frame_length { + *control_flow = ControlFlow::WaitUntil(Instant::now() + frame_length); + } + }); +}