mod application;
mod error_window;
mod keyboard_manager;
#[cfg(target_os = "macos")]
pub mod macos;
mod mouse_manager;
mod settings;
mod window_wrapper;
#[cfg(target_os = "linux")]
use std::env;
use glamour::Size2;
use winit::{
dpi::{PhysicalSize, Size},
event::DeviceId,
event_loop::{ActiveEventLoop, EventLoop},
window::{Cursor, Icon, Theme, Window},
};
#[cfg(target_os = "macos")]
use winit::platform::macos::WindowAttributesExtMacOS;
#[cfg(target_os = "linux")]
use winit::platform::{
startup_notify::{self, EventLoopExtStartupNotify, WindowAttributesExtStartupNotify},
wayland::WindowAttributesExtWayland,
x11::WindowAttributesExtX11,
};
#[cfg(target_os = "windows")]
use winit::platform::windows::WindowAttributesExtWindows;
#[cfg(target_os = "macos")]
use winit::platform::macos::EventLoopBuilderExtMacOS;
use image::{GenericImageView, Pixel, load_from_memory};
use keyboard_manager::KeyboardManager;
use mouse_manager::MouseManager;
use std::fs::File;
use std::io::Read;
use std::sync::atomic::{AtomicU64, Ordering};
use crate::{
bridge::RestartDetails,
cmd_line::{CmdLineSettings, GeometryArgs},
frame::Frame,
renderer::{DrawCommand, WindowConfig, build_window_config},
settings::{
HotReloadConfigs, PersistentWindowSettings, Settings, SettingsChanged, clamped_grid_size,
load_last_window_settings, save_window_size,
},
units::{Grid, GridSize},
utils::expand_tilde,
};
pub use application::Application;
pub use application::ShouldRender;
pub use error_window::show_error_window;
pub use mouse_manager::{MessageSelectionEvent, OverlayEvent};
pub use settings::{ThemeSettings, WindowSettings, WindowSettingsChanged};
pub use window_wrapper::WinitWindowWrapper;
static DEFAULT_ICON: &[u8] = include_bytes!("../../assets/neovide.ico");
const DEFAULT_WINDOW_SIZE: PhysicalSize<u32> = PhysicalSize { width: 500, height: 500 };
const MIN_PERSISTENT_WINDOW_SIZE: PhysicalSize<u32> = PhysicalSize { width: 300, height: 150 };
const MAX_PERSISTENT_WINDOW_SIZE: PhysicalSize<u32> = PhysicalSize { width: 8192, height: 8192 };
#[allow(dead_code)]
#[derive(Clone, Debug, PartialEq)]
pub struct Pressure {
device_id: DeviceId,
pressure: f32,
stage: i64,
}
#[cfg(target_os = "macos")]
#[derive(Clone, Debug, PartialEq)]
pub enum ForceClickKind {
Text,
Url,
File,
}
#[cfg(target_os = "macos")]
impl From<&str> for ForceClickKind {
fn from(value: &str) -> Self {
match value {
"url" => ForceClickKind::Url,
"file" => ForceClickKind::File,
_ => ForceClickKind::Text,
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum WindowCommand {
TitleChanged(String),
SetMouseEnabled(bool),
ListAvailableFonts,
FocusWindow,
#[cfg(target_os = "macos")]
TouchpadPressure {
col: i64,
row: i64,
entity: String,
guifont: String,
kind: ForceClickKind,
},
#[cfg(target_os = "macos")]
HighlightMatchingPair {
grid: u64,
row: u64,
column: u64,
text: Option<String>,
},
Minimize,
ThemeChanged(Option<Theme>),
#[cfg(windows)]
RegisterRightClick,
#[cfg(windows)]
UnregisterRightClick,
}
#[cfg(target_os = "macos")]
#[derive(Clone, Debug, PartialEq)]
pub enum MacShortcutCommand {
TogglePinnedWindow,
ShowEditorSwitcher,
}
#[derive(Clone, Debug, PartialEq)]
pub enum UserEvent {
DrawCommandBatch(Vec<DrawCommand>),
#[cfg(target_os = "macos")]
OpenFiles {
files: Vec<String>,
cwd: Option<String>,
caller_cwd: Option<String>,
tabs: bool,
new_window: bool,
},
WindowCommand(WindowCommand),
SettingsChanged(SettingsChanged),
ConfigsChanged(Box<HotReloadConfigs>),
#[allow(dead_code)]
RedrawRequested,
NeovimExited,
NeovimLaunchError {
message: String,
},
NeovimRestart(RestartDetails),
ShowProgressBar {
percent: f32,
},
#[cfg(target_os = "macos")]
CreateWindow,
#[cfg(target_os = "macos")]
MacShortcut(MacShortcutCommand),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EventTarget {
#[cfg_attr(not(target_os = "macos"), allow(dead_code))]
Window(winit::window::WindowId),
Route(RouteId),
Focused,
All,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct RouteId(u64);
impl RouteId {
pub fn next() -> Self {
static NEXT_ROUTE_ID: AtomicU64 = AtomicU64::new(1);
Self(NEXT_ROUTE_ID.fetch_add(1, Ordering::Relaxed))
}
}
#[derive(Debug, Clone)]
pub struct EventPayload {
pub payload: UserEvent,
pub target: EventTarget,
}
impl EventPayload {
#[cfg_attr(not(target_os = "macos"), allow(dead_code))]
pub fn for_window(payload: UserEvent, window_id: winit::window::WindowId) -> Self {
Self { payload, target: EventTarget::Window(window_id) }
}
pub fn for_route(payload: UserEvent, route_id: RouteId) -> Self {
Self { payload, target: EventTarget::Route(route_id) }
}
pub fn all(payload: UserEvent) -> Self {
Self { payload, target: EventTarget::All }
}
}
impl From<Vec<DrawCommand>> for UserEvent {
fn from(value: Vec<DrawCommand>) -> Self {
UserEvent::DrawCommandBatch(value)
}
}
impl From<WindowCommand> for UserEvent {
fn from(value: WindowCommand) -> Self {
UserEvent::WindowCommand(value)
}
}
#[cfg(target_os = "macos")]
impl From<MacShortcutCommand> for EventPayload {
fn from(value: MacShortcutCommand) -> Self {
EventPayload::all(UserEvent::MacShortcut(value))
}
}
impl From<SettingsChanged> for UserEvent {
fn from(value: SettingsChanged) -> Self {
UserEvent::SettingsChanged(value)
}
}
impl From<HotReloadConfigs> for UserEvent {
fn from(value: HotReloadConfigs) -> Self {
UserEvent::ConfigsChanged(Box::new(value))
}
}
pub fn create_event_loop() -> EventLoop<EventPayload> {
let mut builder = EventLoop::with_user_event();
#[cfg(target_os = "macos")]
builder.with_default_menu(false);
let event_loop = builder.build().expect("Failed to create winit event loop");
#[cfg(target_os = "macos")]
macos::register_file_handler();
#[allow(clippy::let_and_return)]
event_loop
}
pub fn create_window(
event_loop: &ActiveEventLoop,
maximized: bool,
title: &str,
settings: &Settings,
theme: Option<Theme>,
) -> WindowConfig {
let cmd_line_settings = settings.get::<CmdLineSettings>();
let icon = load_icon(cmd_line_settings.icon.as_ref());
let persistent_window_settings = load_last_window_settings().ok();
let previous_position = match persistent_window_settings {
Some(PersistentWindowSettings::Windowed { position, .. }) => Some(position),
_ => None,
};
let mouse_cursor_icon = cmd_line_settings.mouse_cursor_icon;
let window_attributes = Window::default_attributes()
.with_title(title)
.with_cursor(Cursor::Icon(mouse_cursor_icon.parse()))
.with_maximized(maximized)
.with_transparent(true)
.with_visible(false)
.with_theme(theme);
#[cfg(target_family = "unix")]
let window_attributes = window_attributes.with_window_icon(Some(icon));
#[cfg(target_os = "windows")]
let window_attributes =
window_attributes.with_window_icon(Some(icon.clone())).with_taskbar_icon(Some(icon));
#[cfg(target_os = "windows")]
let window_attributes = if !cmd_line_settings.opengl {
WindowAttributesExtWindows::with_no_redirection_bitmap(window_attributes, true)
} else {
window_attributes
};
let frame_decoration = cmd_line_settings.frame;
#[cfg(target_os = "macos")]
let title_hidden = cmd_line_settings.title_hidden;
#[cfg(not(target_os = "macos"))]
let mut window_attributes = window_attributes.with_decorations(frame_decoration == Frame::Full);
#[cfg(target_os = "macos")]
let mut window_attributes = match frame_decoration {
Frame::Full => window_attributes.with_title_hidden(title_hidden),
Frame::None => window_attributes.with_decorations(false),
Frame::Buttonless => window_attributes
.with_title_hidden(title_hidden)
.with_titlebar_buttons_hidden(true)
.with_titlebar_transparent(true)
.with_fullsize_content_view(true),
Frame::Transparent => window_attributes
.with_title_hidden(title_hidden)
.with_titlebar_transparent(true)
.with_fullsize_content_view(true),
};
if let Some(previous_position) = previous_position {
window_attributes = window_attributes.with_position(previous_position);
}
#[cfg(target_os = "linux")]
let window_attributes = {
let window_attributes =
if let Some(token) = EventLoopExtStartupNotify::read_token_from_env(event_loop) {
startup_notify::reset_activation_token_env();
WindowAttributesExtStartupNotify::with_activation_token(window_attributes, token)
} else {
window_attributes
};
if env::var("WAYLAND_DISPLAY").is_ok() {
let app_id = &cmd_line_settings.wayland_app_id;
WindowAttributesExtWayland::with_name(window_attributes, app_id.clone(), "neovide")
} else {
let class = &cmd_line_settings.x11_wm_class;
let instance = &cmd_line_settings.x11_wm_class_instance;
WindowAttributesExtX11::with_name(window_attributes, class, instance)
}
};
#[cfg(target_os = "macos")]
let window_attributes = window_attributes.with_accepts_first_mouse(false);
#[allow(clippy::let_and_return)]
let window_config = build_window_config(window_attributes, event_loop, settings);
#[cfg(target_os = "macos")]
if let Some(previous_position) = previous_position {
window_config.window.set_outer_position(previous_position);
}
window_config
}
#[derive(Clone, Debug)]
pub enum WindowSize {
Size(PhysicalSize<u32>),
Maximized,
Grid(GridSize<u32>),
NeovimGrid, }
pub fn determine_window_size(
window_settings: Option<&PersistentWindowSettings>,
settings: &Settings,
) -> WindowSize {
let cmd_line = settings.get::<CmdLineSettings>();
match cmd_line.geometry {
GeometryArgs { grid: Some(Some(dimensions)), .. } => {
WindowSize::Grid(clamped_grid_size(&GridSize::new(
dimensions.width.try_into().unwrap(),
dimensions.height.try_into().unwrap(),
)))
}
GeometryArgs { grid: Some(None), .. } => WindowSize::NeovimGrid,
GeometryArgs { size: Some(dimensions), .. } => WindowSize::Size(dimensions.into()),
GeometryArgs { maximized: true, .. } => WindowSize::Maximized,
_ => match window_settings {
Some(PersistentWindowSettings::Maximized { .. }) => WindowSize::Maximized,
Some(PersistentWindowSettings::Windowed { pixel_size: Some(pixel_size), .. }) => {
let size = Size::new(*pixel_size);
let scale = 1.0;
WindowSize::Size(
Size::clamp(
size,
MIN_PERSISTENT_WINDOW_SIZE.into(),
MAX_PERSISTENT_WINDOW_SIZE.into(),
scale,
)
.to_physical(scale),
)
}
_ => WindowSize::Size(DEFAULT_WINDOW_SIZE),
},
}
}
pub fn determine_grid_size(
window_size: &WindowSize,
window_settings: Option<PersistentWindowSettings>,
) -> Option<Size2<Grid<u32>>> {
match window_size {
WindowSize::Grid(grid_size) => Some(*grid_size),
#[allow(clippy::manual_unwrap_or_default)]
_ => match window_settings {
Some(PersistentWindowSettings::Maximized { grid_size, .. }) => grid_size,
Some(PersistentWindowSettings::Windowed { grid_size, .. }) => grid_size,
_ => None,
},
}
}
pub fn load_icon(path: Option<&String>) -> Icon {
let icon_result = path
.and_then(|path| {
let expanded_path = expand_tilde(path);
let mut file = File::open(expanded_path).ok()?;
let mut data = Vec::new();
file.read_to_end(&mut data).ok()?;
Some(data)
})
.map(|data| load_from_memory(&data));
let icon = match icon_result {
Some(Ok(icon)) => icon,
_ => load_from_memory(DEFAULT_ICON).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")
}