diff options
Diffstat (limited to 'alacritty_terminal/src/window.rs')
-rw-r--r-- | alacritty_terminal/src/window.rs | 497 |
1 files changed, 497 insertions, 0 deletions
diff --git a/alacritty_terminal/src/window.rs b/alacritty_terminal/src/window.rs new file mode 100644 index 00000000..89e455dc --- /dev/null +++ b/alacritty_terminal/src/window.rs @@ -0,0 +1,497 @@ +// Copyright 2016 Joe Wilm, The Alacritty Project Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +use std::convert::From; +use std::fmt::Display; + +use crate::gl; +use glutin::dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize}; +#[cfg(not(any(target_os = "macos", windows)))] +use glutin::os::unix::EventsLoopExt; +#[cfg(windows)] +use glutin::Icon; +use glutin::{ + self, ContextBuilder, ControlFlow, Event, EventsLoop, MouseCursor, PossiblyCurrent, + WindowBuilder, +}; +#[cfg(windows)] +use image::ImageFormat; + +use crate::cli::Options; +use crate::config::{Decorations, StartupMode, WindowConfig}; + +#[cfg(windows)] +static WINDOW_ICON: &'static [u8] = include_bytes!("../../extra/windows/alacritty.ico"); + +/// Default Alacritty name, used for window title and class. +pub const DEFAULT_NAME: &str = "Alacritty"; + +/// Window errors +#[derive(Debug)] +pub enum Error { + /// Error creating the window + ContextCreation(glutin::CreationError), + + /// Error manipulating the rendering context + Context(glutin::ContextError), +} + +/// Result of fallible operations concerning a Window. +type Result<T> = ::std::result::Result<T, Error>; + +/// A window which can be used for displaying the terminal +/// +/// Wraps the underlying windowing library to provide a stable API in Alacritty +pub struct Window { + event_loop: EventsLoop, + windowed_context: glutin::WindowedContext<PossiblyCurrent>, + mouse_visible: bool, + + /// Whether or not the window is the focused window. + pub is_focused: bool, +} + +/// Threadsafe APIs for the window +pub struct Proxy { + inner: glutin::EventsLoopProxy, +} + +/// Information about where the window is being displayed +/// +/// Useful for subsystems like the font rasterized which depend on DPI and scale +/// factor. +pub struct DeviceProperties { + /// Scale factor for pixels <-> points. + /// + /// This will be 1. on standard displays and may have a different value on + /// hidpi displays. + pub scale_factor: f64, +} + +impl ::std::error::Error for Error { + fn cause(&self) -> Option<&dyn (::std::error::Error)> { + match *self { + Error::ContextCreation(ref err) => Some(err), + Error::Context(ref err) => Some(err), + } + } + + fn description(&self) -> &str { + match *self { + Error::ContextCreation(ref _err) => "Error creating gl context", + Error::Context(ref _err) => "Error operating on render context", + } + } +} + +impl Display for Error { + fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + match *self { + Error::ContextCreation(ref err) => write!(f, "Error creating GL context; {}", err), + Error::Context(ref err) => write!(f, "Error operating on render context; {}", err), + } + } +} + +impl From<glutin::CreationError> for Error { + fn from(val: glutin::CreationError) -> Error { + Error::ContextCreation(val) + } +} + +impl From<glutin::ContextError> for Error { + fn from(val: glutin::ContextError) -> Error { + Error::Context(val) + } +} + +fn create_gl_window( + mut window: WindowBuilder, + event_loop: &EventsLoop, + srgb: bool, + dimensions: Option<LogicalSize>, +) -> Result<glutin::WindowedContext<PossiblyCurrent>> { + if let Some(dimensions) = dimensions { + window = window.with_dimensions(dimensions); + } + + let windowed_context = ContextBuilder::new() + .with_srgb(srgb) + .with_vsync(true) + .with_hardware_acceleration(None) + .build_windowed(window, event_loop)?; + + // Make the context current so OpenGL operations can run + let windowed_context = unsafe { windowed_context.make_current().map_err(|(_, e)| e)? }; + + Ok(windowed_context) +} + +impl Window { + /// Create a new window + /// + /// This creates a window and fully initializes a window. + pub fn new( + event_loop: EventsLoop, + options: &Options, + window_config: &WindowConfig, + dimensions: Option<LogicalSize>, + ) -> Result<Window> { + let title = options.title.as_ref().map_or(DEFAULT_NAME, |t| t); + let class = options.class.as_ref().map_or(DEFAULT_NAME, |c| c); + let window_builder = Window::get_platform_window(title, class, window_config); + let windowed_context = + create_gl_window(window_builder.clone(), &event_loop, false, dimensions) + .or_else(|_| create_gl_window(window_builder, &event_loop, true, dimensions))?; + let window = windowed_context.window(); + window.show(); + + // Maximize window after mapping in X11 + #[cfg(not(any(target_os = "macos", windows)))] + { + if event_loop.is_x11() && window_config.startup_mode() == StartupMode::Maximized { + window.set_maximized(true); + } + } + + // Set window position + // + // TODO: replace `set_position` with `with_position` once available + // Upstream issue: https://github.com/tomaka/winit/issues/806 + let position = options.position().or_else(|| window_config.position()); + if let Some(position) = position { + let physical = PhysicalPosition::from((position.x, position.y)); + let logical = physical.to_logical(window.get_hidpi_factor()); + window.set_position(logical); + } + + if let StartupMode::Fullscreen = window_config.startup_mode() { + let current_monitor = window.get_current_monitor(); + window.set_fullscreen(Some(current_monitor)); + } + + #[cfg(target_os = "macos")] + { + if let StartupMode::SimpleFullscreen = window_config.startup_mode() { + use glutin::os::macos::WindowExt; + window.set_simple_fullscreen(true); + } + } + + // Text cursor + window.set_cursor(MouseCursor::Text); + + // Set OpenGL symbol loader. This call MUST be after window.make_current on windows. + gl::load_with(|symbol| windowed_context.get_proc_address(symbol) as *const _); + + let window = + Window { event_loop, windowed_context, mouse_visible: true, is_focused: false }; + + window.run_os_extensions(); + + Ok(window) + } + + /// Get some properties about the device + /// + /// Some window properties are provided since subsystems like font + /// rasterization depend on DPI and scale factor. + pub fn device_properties(&self) -> DeviceProperties { + DeviceProperties { scale_factor: self.window().get_hidpi_factor() } + } + + pub fn inner_size_pixels(&self) -> Option<LogicalSize> { + self.window().get_inner_size() + } + + pub fn set_inner_size(&mut self, size: LogicalSize) { + self.window().set_inner_size(size); + } + + #[inline] + pub fn hidpi_factor(&self) -> f64 { + self.window().get_hidpi_factor() + } + + #[inline] + pub fn create_window_proxy(&self) -> Proxy { + Proxy { inner: self.event_loop.create_proxy() } + } + + #[inline] + pub fn swap_buffers(&self) -> Result<()> { + self.windowed_context.swap_buffers().map_err(From::from) + } + + /// Poll for any available events + #[inline] + pub fn poll_events<F>(&mut self, func: F) + where + F: FnMut(Event), + { + self.event_loop.poll_events(func); + } + + #[inline] + pub fn resize(&self, size: PhysicalSize) { + self.windowed_context.resize(size); + } + + /// Block waiting for events + #[inline] + pub fn wait_events<F>(&mut self, func: F) + where + F: FnMut(Event) -> ControlFlow, + { + self.event_loop.run_forever(func); + } + + /// Set the window title + #[inline] + pub fn set_title(&self, title: &str) { + self.window().set_title(title); + } + + #[inline] + pub fn set_mouse_cursor(&self, cursor: MouseCursor) { + self.window().set_cursor(cursor); + } + + /// Set mouse cursor visible + pub fn set_mouse_visible(&mut self, visible: bool) { + if visible != self.mouse_visible { + self.mouse_visible = visible; + self.window().hide_cursor(!visible); + } + } + + #[cfg(not(any(target_os = "macos", windows)))] + pub fn get_platform_window( + title: &str, + class: &str, + window_config: &WindowConfig, + ) -> WindowBuilder { + use glutin::os::unix::WindowBuilderExt; + + let decorations = match window_config.decorations() { + Decorations::None => false, + _ => true, + }; + + WindowBuilder::new() + .with_title(title) + .with_visibility(false) + .with_transparency(true) + .with_decorations(decorations) + .with_maximized(window_config.startup_mode() == StartupMode::Maximized) + // X11 + .with_class(class.into(), DEFAULT_NAME.into()) + // Wayland + .with_app_id(class.into()) + } + + #[cfg(windows)] + pub fn get_platform_window( + title: &str, + _class: &str, + window_config: &WindowConfig, + ) -> WindowBuilder { + let icon = Icon::from_bytes_with_format(WINDOW_ICON, ImageFormat::ICO).unwrap(); + + let decorations = match window_config.decorations() { + Decorations::None => false, + _ => true, + }; + + WindowBuilder::new() + .with_title(title) + .with_visibility(cfg!(windows)) + .with_decorations(decorations) + .with_transparency(true) + .with_maximized(window_config.startup_mode() == StartupMode::Maximized) + .with_window_icon(Some(icon)) + } + + #[cfg(target_os = "macos")] + pub fn get_platform_window( + title: &str, + _class: &str, + window_config: &WindowConfig, + ) -> WindowBuilder { + use glutin::os::macos::WindowBuilderExt; + + let window = WindowBuilder::new() + .with_title(title) + .with_visibility(false) + .with_transparency(true) + .with_maximized(window_config.startup_mode() == StartupMode::Maximized); + + match window_config.decorations() { + Decorations::Full => window, + Decorations::Transparent => window + .with_title_hidden(true) + .with_titlebar_transparent(true) + .with_fullsize_content_view(true), + Decorations::Buttonless => window + .with_title_hidden(true) + .with_titlebar_buttons_hidden(true) + .with_titlebar_transparent(true) + .with_fullsize_content_view(true), + Decorations::None => window.with_titlebar_hidden(true), + } + } + + #[cfg(any( + target_os = "linux", + target_os = "freebsd", + target_os = "dragonfly", + target_os = "openbsd" + ))] + pub fn set_urgent(&self, is_urgent: bool) { + use glutin::os::unix::WindowExt; + self.window().set_urgent(is_urgent); + } + + #[cfg(target_os = "macos")] + pub fn set_urgent(&self, is_urgent: bool) { + use glutin::os::macos::WindowExt; + self.window().request_user_attention(is_urgent); + } + + #[cfg(windows)] + pub fn set_urgent(&self, _is_urgent: bool) {} + + pub fn set_ime_spot(&self, pos: LogicalPosition) { + self.window().set_ime_spot(pos); + } + + #[cfg(not(any(target_os = "macos", target_os = "windows")))] + pub fn get_window_id(&self) -> Option<usize> { + use glutin::os::unix::WindowExt; + + match self.window().get_xlib_window() { + Some(xlib_window) => Some(xlib_window as usize), + None => None, + } + } + + #[cfg(any(target_os = "macos", target_os = "windows"))] + pub fn get_window_id(&self) -> Option<usize> { + None + } + + /// Hide the window + pub fn hide(&self) { + self.window().hide(); + } + + /// Fullscreens the window on the current monitor. + pub fn set_fullscreen(&self, fullscreen: bool) { + let glutin_window = self.window(); + if fullscreen { + let current_monitor = glutin_window.get_current_monitor(); + glutin_window.set_fullscreen(Some(current_monitor)); + } else { + glutin_window.set_fullscreen(None); + } + } + + #[cfg(target_os = "macos")] + pub fn set_simple_fullscreen(&self, fullscreen: bool) { + use glutin::os::macos::WindowExt; + self.window().set_simple_fullscreen(fullscreen); + } + + fn window(&self) -> &glutin::Window { + self.windowed_context.window() + } +} + +pub trait OsExtensions { + fn run_os_extensions(&self) {} +} + +#[cfg(not(any( + target_os = "linux", + target_os = "freebsd", + target_os = "dragonfly", + target_os = "openbsd" +)))] +impl OsExtensions for Window {} + +#[cfg(any( + target_os = "linux", + target_os = "freebsd", + target_os = "dragonfly", + target_os = "openbsd" +))] +impl OsExtensions for Window { + fn run_os_extensions(&self) { + use glutin::os::unix::WindowExt; + use libc::getpid; + use std::ffi::CStr; + use std::ptr; + use x11_dl::xlib::{self, PropModeReplace, XA_CARDINAL}; + + let xlib_display = self.window().get_xlib_display(); + let xlib_window = self.window().get_xlib_window(); + + if let (Some(xlib_window), Some(xlib_display)) = (xlib_window, xlib_display) { + let xlib = xlib::Xlib::open().expect("get xlib"); + + // Set _NET_WM_PID to process pid + unsafe { + let _net_wm_pid = CStr::from_ptr(b"_NET_WM_PID\0".as_ptr() as *const _); + let atom = (xlib.XInternAtom)(xlib_display as *mut _, _net_wm_pid.as_ptr(), 0); + let pid = getpid(); + + (xlib.XChangeProperty)( + xlib_display as _, + xlib_window as _, + atom, + XA_CARDINAL, + 32, + PropModeReplace, + &pid as *const i32 as *const u8, + 1, + ); + } + // Although this call doesn't actually pass any data, it does cause + // WM_CLIENT_MACHINE to be set. WM_CLIENT_MACHINE MUST be set if _NET_WM_PID is set + // (which we do above). + unsafe { + (xlib.XSetWMProperties)( + xlib_display as _, + xlib_window as _, + ptr::null_mut(), + ptr::null_mut(), + ptr::null_mut(), + 0, + ptr::null_mut(), + ptr::null_mut(), + ptr::null_mut(), + ); + } + } + } +} + +impl Proxy { + /// Wakes up the event loop of the window + /// + /// This is useful for triggering a draw when the renderer would otherwise + /// be waiting on user input. + pub fn wakeup_event_loop(&self) { + self.inner.wakeup().unwrap(); + } +} |