piss/src/main.rs

268 lines
8.1 KiB
Rust

// Copyright (c) 2022 Marceline Cramer
// SPDX-License-Identifier: AGPL-3.0-or-later
use alacritty_terminal::ansi::Color;
use alacritty_terminal::config::PtyConfig;
use alacritty_terminal::event::{Event as TermEvent, EventListener};
use alacritty_terminal::event_loop::{
EventLoop as TermEventLoop, Msg as TermMsg, State as TermState,
};
use alacritty_terminal::sync::FairMutex;
use alacritty_terminal::term::color::Rgb;
use alacritty_terminal::tty::Pty;
use alacritty_terminal::Term;
use mio_extras::channel::Sender as MioSender;
use softbuffer::GraphicsContext;
use std::borrow::Cow;
use std::sync::mpsc::{channel, Receiver, Sender};
use std::sync::Arc;
use std::thread::JoinHandle;
use winit::event::{Event, WindowEvent};
use winit::event_loop::{ControlFlow, EventLoop};
use winit::window::{Window, WindowBuilder, WindowId};
pub mod config;
pub mod graphics;
use config::Config;
use graphics::Graphics;
pub struct TermListener {
sender: Sender<TermEvent>,
}
impl TermListener {
pub fn new(sender: Sender<TermEvent>) -> Self {
Self { sender }
}
}
impl EventListener for TermListener {
fn send_event(&self, event: TermEvent) {
self.sender.send(event).unwrap();
}
}
fn get_login_shell(config: Config) -> String {
if config.system.shell != "" {
config.system.shell
} else {
match std::env::consts::OS {
"linux" | "openbsd" | "netbsd" | "dragonfly" | "solaris" | "macos" => {
std::env::var("SHELL").unwrap_or("/bin/sh".to_string())
}
"windows" => r#"C:\Windows\System32\cmd.exe"#.to_string(),
_ => unimplemented!("Unrecognized operating system; cannot get user's shell"),
}
}
}
pub struct App {
graphics: Graphics,
graphics_context: GraphicsContext<Window>,
should_quit: bool,
term_loop: JoinHandle<(TermEventLoop<Pty, TermListener>, TermState)>,
term_channel: MioSender<TermMsg>,
term_events: Receiver<TermEvent>,
term: Arc<FairMutex<Term<TermListener>>>,
}
impl App {
pub fn new(config: Config, window: Window) -> Self {
let graphics = Graphics::new(&config);
let term_size = alacritty_terminal::term::SizeInfo::new(
graphics.canvas.width as f32,
graphics.canvas.height as f32,
graphics.cell_width as f32,
graphics.cell_height as f32,
graphics.pad_x as f32,
graphics.pad_y as f32,
false,
);
let (sender, term_events) = channel();
let term_config = alacritty_terminal::config::Config {
pty_config: PtyConfig {
shell: Some(alacritty_terminal::config::Program::Just(get_login_shell(
config,
))),
working_directory: None,
hold: false,
},
..Default::default()
};
alacritty_terminal::tty::setup_env(&term_config);
let term_listener = TermListener::new(sender.clone());
let term = Term::new(&term_config, term_size, term_listener);
let term = FairMutex::new(term);
let term = Arc::new(term);
let pty = alacritty_terminal::tty::new(&term_config.pty_config, &term_size, None).unwrap();
let term_listener = TermListener::new(sender);
let term_loop = TermEventLoop::new(term.clone(), term_listener, pty, false, false);
let term_channel = term_loop.channel();
Self {
graphics,
graphics_context: unsafe { GraphicsContext::new(window).unwrap() },
should_quit: false,
term,
term_channel,
term_events,
term_loop: term_loop.spawn(),
}
}
pub fn should_quit(&self) -> bool {
self.should_quit
}
pub fn update(&mut self) {
while let Ok(event) = self.term_events.try_recv() {
match event {
TermEvent::ColorRequest(index, format) => {
let rgb = self.graphics.get_color_idx(index);
self.send_input(&format(rgb));
}
TermEvent::PtyWrite(text) => self.send_input(&text),
TermEvent::Wakeup => self.request_redraw(),
TermEvent::Exit => self.should_quit = true,
event => eprintln!("unhandled event: {:#?}", event),
}
}
}
pub fn request_redraw(&self) {
self.graphics_context.window().request_redraw();
}
pub fn redraw(&mut self) {
let term = self.term.lock();
self.graphics.redraw(&term, &mut self.graphics_context);
}
pub fn virtual_keycode_to_string(
keycode: winit::event::VirtualKeyCode,
) -> Option<&'static str> {
use winit::event::VirtualKeyCode::*;
match keycode {
Back => Some("\x7f"),
Up => Some("\x1b[A"),
Down => Some("\x1b[B"),
Right => Some("\x1b[C"),
Left => Some("\x1b[D"),
Home => Some("\x1b[1~"),
Insert => Some("\x1b[2~"),
Delete => Some("\x1b[3~"),
End => Some("\x1b[4~"),
PageUp => Some("\x1b[5~"),
PageDown => Some("\x1b[6~"),
_ => None,
}
}
pub fn on_keyboard_input(&mut self, input: &winit::event::KeyboardInput) {
if input.state == winit::event::ElementState::Pressed {
if let Some(keycode) = input.virtual_keycode {
if let Some(input) = Self::virtual_keycode_to_string(keycode) {
self.send_input(input);
}
}
}
}
pub fn on_received_character(&mut self, c: char) {
log::debug!("Received character: {:?}", c);
match c {
'\u{7f}' | '\u{8}' => {
// We use a special escape code for the delete and backspace keys.
log::debug!("Ignoring.");
return;
}
_ => {}
}
let string = c.to_string();
self.send_input(string.as_str());
}
pub fn send_input(&mut self, input: &str) {
let bytes = input.as_bytes();
let cow = Cow::Owned(bytes.to_owned().into());
self.term_channel.send(TermMsg::Input(cow)).unwrap();
}
pub fn on_resize(&mut self, width: u32, height: u32) {
let term_size = alacritty_terminal::term::SizeInfo::new(
width as f32,
height as f32,
self.graphics.cell_width as f32,
self.graphics.cell_height as f32,
self.graphics.pad_x as f32,
self.graphics.pad_y as f32,
false,
);
self.term.lock().resize(term_size);
self.term_channel.send(TermMsg::Resize(term_size)).unwrap();
self.request_redraw();
}
pub fn window_id(&self) -> WindowId {
self.graphics_context.window().id()
}
}
fn main() {
let config: Config = confy::load("piss", Some("config")).unwrap();
env_logger::init();
let event_loop = EventLoop::new();
let window = WindowBuilder::new().build(&event_loop).unwrap();
let mut app = App::new(config, window);
event_loop.run(move |event, _, control_flow| {
*control_flow = ControlFlow::Poll;
app.update();
match event {
Event::RedrawRequested(window_id) if window_id == app.window_id() => {
app.redraw();
}
Event::WindowEvent { event, window_id } if window_id == app.window_id() => {
match event {
WindowEvent::CloseRequested => {
*control_flow = ControlFlow::Exit;
}
WindowEvent::KeyboardInput { input, .. } => {
app.on_keyboard_input(&input);
}
WindowEvent::ReceivedCharacter(c) => {
app.on_received_character(c);
}
WindowEvent::Resized(size) => {
app.on_resize(size.width, size.height);
}
_ => {}
}
}
_ => {}
}
if app.should_quit() {
*control_flow = ControlFlow::Exit;
}
});
}