piss/src/main.rs

423 lines
13 KiB
Rust

// Copyright (c) 2022 Marceline Cramer
// SPDX-License-Identifier: AGPL-3.0-or-later
use alacritty_terminal::ansi::{Color, NamedColor};
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::{Colors, 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};
static FONT_DATA: &[u8] = include_bytes!("ter-u20n.bdf");
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();
}
}
pub struct App {
font: bdf::Font,
graphics: GraphicsContext<Window>,
should_quit: bool,
cell_width: usize,
cell_height: usize,
colors: Colors,
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(window: Window) -> Self {
let font = bdf::read(FONT_DATA).unwrap();
let cell_width = font.bounds().width as usize;
let cell_height = font.bounds().height as usize;
let term_size = alacritty_terminal::term::SizeInfo::new(
2000.0,
2000.0,
cell_width as f32,
cell_height as f32,
0.0,
0.0,
false,
);
let (sender, term_events) = channel();
let term_config = alacritty_terminal::config::Config {
pty_config: PtyConfig {
shell: Some(alacritty_terminal::config::Program::Just(
"/usr/bin/fish".to_string(),
)),
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 mut colors = term.colors().to_owned();
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::load_colors(&mut colors);
Self {
font,
graphics: unsafe { GraphicsContext::new(window).unwrap() },
should_quit: false,
cell_width,
cell_height,
term,
term_channel,
term_events,
colors,
term_loop: term_loop.spawn(),
}
}
pub fn should_quit(&self) -> bool {
self.should_quit
}
pub fn load_colors(color: &mut Colors) {
use NamedColor::*;
let hex = |hex: u32| -> Rgb {
Rgb {
r: (hex >> 16) as u8,
g: (hex >> 8) as u8,
b: hex as u8,
}
};
// Rose Pine theme
let maps = [
(Foreground, hex(0xe0def4)),
(Background, hex(0x191724)),
(Black, hex(0x6e6a86)),
(Red, hex(0xeb6f92)),
(Green, hex(0x7fb59f)),
(Yellow, hex(0xf6c177)),
(Blue, hex(0x31748f)),
(Magenta, hex(0xc4a7e7)),
(Cyan, hex(0x9ccfd8)),
(White, hex(0xe0def4)),
];
for map in maps.iter() {
color[map.0] = Some(map.1);
}
let dupes = [
(BrightBlack, Black),
(BrightRed, Red),
(BrightGreen, Green),
(BrightYellow, Yellow),
(BrightBlue, Blue),
(BrightMagenta, Magenta),
(BrightCyan, Cyan),
(BrightWhite, White),
];
for (dst, src) in dupes.iter() {
color[*dst] = color[*src];
}
}
pub fn redraw(&mut self) {
let (width, height) = {
let size = self.graphics.window().inner_size();
(size.width as usize, size.height as usize)
};
let mut buffer = vec![0u32; (width * height) as usize];
let term = self.term.lock();
let content = term.renderable_content();
let mut missing_chars = std::collections::HashSet::new();
for cell in content.display_iter.into_iter() {
if let Some(glyph) = self.font.glyphs().get(&cell.c) {
let term_row = cell.point.line.0 as usize;
let term_col = cell.point.column.0 as usize;
if (term_row + 1) * self.cell_height >= height
|| (term_col + 1) * self.cell_width >= width
{
continue;
}
let bg = self.color_to_u32(&cell.bg);
let fg = self.color_to_u32(&cell.fg);
let mut px_row = term_row * width * self.cell_height;
for y in 0..self.cell_height {
let px_start = px_row + term_col * self.cell_width;
for x in 0..self.cell_width {
let color = if glyph.get(x as u32, y as u32) {
fg
} else {
bg
};
buffer[px_start + x as usize] = color;
}
px_row += width;
}
} else {
missing_chars.insert(cell.c);
}
}
if !missing_chars.is_empty() {
log::debug!("Missing characters: {:?}", missing_chars);
}
let term_row = content.cursor.point.line.0 as usize;
let term_col = content.cursor.point.column.0 as usize;
if (term_row + 1) * self.cell_height < height && (term_col + 1) * self.cell_width < width {
let mut px_row = term_row * width * self.cell_height + term_col * self.cell_width;
let cursor_color = 0x00ff00ff;
use alacritty_terminal::ansi::CursorShape;
match content.cursor.shape {
CursorShape::Block => {
for _y in 0..self.cell_height {
for x in 0..self.cell_width {
buffer[px_row + x as usize] = cursor_color;
}
px_row += width;
}
}
CursorShape::Beam => {
for _y in 0..self.cell_height {
buffer[px_row] = cursor_color;
px_row += width;
}
}
CursorShape::Underline => {
px_row += (self.cell_height - 1) * width;
for x in 0..self.cell_width {
buffer[px_row + x as usize] = cursor_color;
}
}
CursorShape::HollowBlock => {
for x in 0..self.cell_width {
buffer[px_row + x as usize] = cursor_color;
}
for _y in 0..(self.cell_height - 1) {
buffer[px_row] = cursor_color;
buffer[px_row + self.cell_width - 1] = cursor_color;
px_row += width;
}
for x in 0..self.cell_width {
buffer[px_row + x as usize] = cursor_color;
}
}
CursorShape::Hidden => {}
}
}
self.graphics
.set_buffer(&buffer, width as u16, height as u16);
}
pub fn color_to_u32(&self, color: &Color) -> u32 {
let rgb = match color {
Color::Named(name) => self.colors[*name].unwrap(),
Color::Spec(rgb) => *rgb,
Color::Indexed(index) => self.colors[*index as usize].unwrap_or(Rgb {
r: 255,
g: 0,
b: 255,
}),
};
((rgb.r as u32) << 16) | ((rgb.g as u32) << 8) | (rgb.b as u32)
}
pub fn update(&mut self) {
while let Ok(event) = self.term_events.try_recv() {
match event {
TermEvent::Exit => self.should_quit = true,
_ => {}
}
}
}
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(c) = virtual_keycode_to_byte(keycode) {
self.on_received_character(c as char);
}
}
}
}
pub fn on_received_character(&mut self, c: char) {
let mut bytes = [0u8; 4];
let len = c.encode_utf8(&mut bytes).len();
let cow = Cow::Owned(bytes[0..len].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.cell_width as f32,
self.cell_height as f32,
0.0,
0.0,
false,
);
self.term_channel.send(TermMsg::Resize(term_size)).unwrap();
}
}
fn virtual_keycode_to_byte(keycode: winit::event::VirtualKeyCode) -> Option<u8> {
use winit::event::VirtualKeyCode::*;
match keycode {
A => Some(b'a'),
B => Some(b'b'),
C => Some(b'c'),
D => Some(b'd'),
E => Some(b'e'),
F => Some(b'f'),
G => Some(b'g'),
H => Some(b'h'),
I => Some(b'i'),
J => Some(b'j'),
K => Some(b'k'),
L => Some(b'l'),
M => Some(b'm'),
N => Some(b'n'),
O => Some(b'o'),
P => Some(b'p'),
Q => Some(b'q'),
R => Some(b'r'),
S => Some(b's'),
T => Some(b't'),
U => Some(b'u'),
V => Some(b'v'),
W => Some(b'w'),
X => Some(b'x'),
Y => Some(b'y'),
Z => Some(b'z'),
Key1 => Some(b'1'),
Key2 => Some(b'2'),
Key3 => Some(b'3'),
Key4 => Some(b'4'),
Key5 => Some(b'5'),
Key6 => Some(b'6'),
Key7 => Some(b'7'),
Key8 => Some(b'8'),
Key9 => Some(b'('),
Key0 => Some(b')'),
Apostrophe => Some(b'\''),
Period => Some(b'.'),
Escape => Some(0x1b),
Backslash => Some(b'\\'),
Colon => Some(b':'),
Return => Some(b'\r'),
Back => Some(0x08),
Tab => Some(b'\t'),
Space => Some(b' '),
_ => None,
}
}
fn main() {
env_logger::init();
let event_loop = EventLoop::new();
let window = WindowBuilder::new().build(&event_loop).unwrap();
let mut app = App::new(window);
let mut counter = 0;
event_loop.run(move |event, _, control_flow| {
*control_flow = ControlFlow::Wait;
match event {
Event::RedrawRequested(window_id) if window_id == app.graphics.window().id() => {
counter += 1;
if counter > 30 {
counter = 0;
app.redraw();
}
}
Event::MainEventsCleared => {
app.graphics.window().request_redraw();
app.update();
}
Event::WindowEvent { event, window_id } if window_id == app.graphics.window().id() => {
match event {
WindowEvent::CloseRequested => {
*control_flow = ControlFlow::Exit;
}
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;
}
});
}