From 24710414bd9c238cb2df0dbdd88fb5f7be526cc9 Mon Sep 17 00:00:00 2001 From: Yehowshua Immanuel Date: Mon, 23 Dec 2024 15:22:17 -0500 Subject: [PATCH] getting towards terminal integration --- Cargo.lock | 133 ++++++++++++++++++++++++++++- frontend/src/header_panel.rs | 24 ++++++ frontend/src/main.rs | 14 ++++ frontend/src/term.rs | 148 +++++++++++++++++++++++++++++++++ shared/src/lib.rs | 1 + shared/src/term.rs | 25 ++++++ src-tauri/Cargo.toml | 1 + src-tauri/src/lib.rs | 15 ++++ src-tauri/src/main.rs | 2 + src-tauri/src/terminal_size.rs | 55 ++++++++++++ 10 files changed, 417 insertions(+), 1 deletion(-) create mode 100644 frontend/src/term.rs create mode 100644 shared/src/term.rs create mode 100644 src-tauri/src/terminal_size.rs diff --git a/Cargo.lock b/Cargo.lock index 558d3e6..82df3f0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -273,6 +273,29 @@ dependencies = [ "memchr", ] +[[package]] +name = "alacritty_terminal" +version = "0.24.1-dev" +source = "git+https://github.com/alacritty/alacritty?rev=cacdb5bb3b72bad2c729227537979d95af75978f#cacdb5bb3b72bad2c729227537979d95af75978f" +dependencies = [ + "base64 0.22.1", + "bitflags 2.5.0", + "home", + "libc", + "log", + "miow", + "parking_lot 0.12.2", + "piper", + "polling", + "regex-automata", + "rustix-openpty", + "serde", + "signal-hook", + "unicode-width", + "vte", + "windows-sys 0.52.0", +] + [[package]] name = "alloc-no-stdlib" version = "2.0.4" @@ -1240,6 +1263,12 @@ dependencies = [ "syn 2.0.90", ] +[[package]] +name = "cursor-icon" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96a6ac251f4a2aca6b3f91340350eab87ae57c3f127ffeb585e92bd336717991" + [[package]] name = "darling" version = "0.20.8" @@ -1674,6 +1703,7 @@ checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" name = "fastwave" version = "0.1.0" dependencies = [ + "alacritty_terminal", "futures", "once_cell", "reqwest", @@ -2409,12 +2439,27 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" +[[package]] +name = "hermit-abi" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" + [[package]] name = "hex" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "home" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" +dependencies = [ + "windows-sys 0.59.0", +] + [[package]] name = "hsluv" version = "0.1.0" @@ -2718,7 +2763,7 @@ version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f23ff5ef2b80d608d61efee834934d862cd92461afc0560dedf493e4c033738b" dependencies = [ - "hermit-abi", + "hermit-abi 0.3.9", "libc", "windows-sys 0.52.0", ] @@ -3172,6 +3217,15 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "miow" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "359f76430b20a79f9e20e115b3428614e654f04fab314482fc0fda0ebd3c6044" +dependencies = [ + "windows-sys 0.48.0", +] + [[package]] name = "moon" version = "0.1.0" @@ -3966,6 +4020,17 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "piper" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066" +dependencies = [ + "atomic-waker", + "fastrand", + "futures-io", +] + [[package]] name = "pkg-config" version = "0.3.30" @@ -3999,6 +4064,21 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "polling" +version = "3.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a604568c3202727d1507653cb121dbd627a58684eb09a820fd746bee38b4442f" +dependencies = [ + "cfg-if", + "concurrent-queue", + "hermit-abi 0.4.0", + "pin-project-lite", + "rustix", + "tracing", + "windows-sys 0.59.0", +] + [[package]] name = "postcard" version = "1.0.8" @@ -4496,6 +4576,17 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "rustix-openpty" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a25c3aad9fc1424eb82c88087789a7d938e1829724f3e4043163baf0d13cfc12" +dependencies = [ + "errno", + "libc", + "rustix", +] + [[package]] name = "rustls" version = "0.21.11" @@ -4906,6 +4997,16 @@ dependencies = [ "dirs 4.0.0", ] +[[package]] +name = "signal-hook" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" +dependencies = [ + "libc", + "signal-hook-registry", +] + [[package]] name = "signal-hook-registry" version = "1.4.2" @@ -6079,6 +6180,12 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + [[package]] name = "uuid" version = "1.8.0" @@ -6127,6 +6234,30 @@ dependencies = [ "libc", ] +[[package]] +name = "vte" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40eb22ae96f050e0c0d6f7ce43feeae26c348fc4dea56928ca81537cfaa6188b" +dependencies = [ + "bitflags 2.5.0", + "cursor-icon", + "log", + "serde", + "utf8parse", + "vte_generate_state_changes", +] + +[[package]] +name = "vte_generate_state_changes" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e369bee1b05d510a7b4ed645f5faa90619e05437111783ea5848f28d97d3c2e" +dependencies = [ + "proc-macro2", + "quote", +] + [[package]] name = "walkdir" version = "2.5.0" diff --git a/frontend/src/header_panel.rs b/frontend/src/header_panel.rs index 872025d..c64281d 100644 --- a/frontend/src/header_panel.rs +++ b/frontend/src/header_panel.rs @@ -1,6 +1,7 @@ use crate::{platform, theme::*, Filename, Layout, Mode}; use std::sync::Arc; use zoon::*; +use crate::term::TERM_OPEN; pub struct HeaderPanel { hierarchy: Mutable>>, @@ -37,6 +38,7 @@ impl HeaderPanel { .item(self.load_button()) .item(self.layout_switcher()) .item(self.mode_switcher()) + .item(self.open_terminal()) .item(self.open_konata_file()), ) } @@ -212,4 +214,26 @@ impl HeaderPanel { .on_hovered_change(move |is_hovered| hovered.set_neq(is_hovered)) .on_press(move || Task::start(platform::open_konata_file())) } + + fn open_terminal(&self) -> impl Element { + let (hovered, hovered_signal) = Mutable::new_and_signal(false); + Button::new() + .s(Padding::new().x(20).y(10)) + .s(Background::new().color_signal( + hovered_signal.map_bool(|| COLOR_MEDIUM_SLATE_BLUE, || COLOR_SLATE_BLUE), + )) + .s(Align::new().left()) + .s(RoundedCorners::all(15)) + .label( + El::new() + .s(Font::new().no_wrap()) + .child("Open Terminal"), + ) + .on_hovered_change(move |is_hovered| hovered.set_neq(is_hovered)) + .on_press(move || { + let term_open = TERM_OPEN.get(); + TERM_OPEN.set(!term_open); + + }) + } } diff --git a/frontend/src/main.rs b/frontend/src/main.rs index ba113b5..65a40b5 100644 --- a/frontend/src/main.rs +++ b/frontend/src/main.rs @@ -1,4 +1,5 @@ use shared::DiagramConnectorMessage; +use term::TERM_OPEN; use std::{mem, sync::Arc}; use zoon::*; @@ -23,6 +24,8 @@ use command_panel::CommandPanel; pub mod theme; use theme::*; +pub mod term; + #[derive(Clone, Copy, Default)] enum Layout { Tree, @@ -181,4 +184,15 @@ fn root() -> impl Element { } }))) .item(CommandPanel::new()) + .item_signal( + TERM_OPEN.signal_cloned().map( + |term_open| { + match term_open { + true => {El::new().child("Terminal")} + false => {El::new().child("")} + } + } + ) + // El::new() + ) } diff --git a/frontend/src/term.rs b/frontend/src/term.rs new file mode 100644 index 0000000..9586cbd --- /dev/null +++ b/frontend/src/term.rs @@ -0,0 +1,148 @@ +use std::ops::Index; + +use chrono::format; +use zoon::*; +use zoon::{println, eprintln, *}; +use shared::term::{TerminalDownMsg, TerminalScreen, TerminalUpMsg}; + +// use tokio::time::timeout; +pub static TERM_OPEN: Lazy> = Lazy::new(|| {false.into()}); + +static TERMINAL_STATE: Lazy> = + Lazy::new(|| { + Mutable::new(TerminalDownMsg::TermNotStarted) + }); + +// static CONNECTION: Lazy> = Lazy::new(|| { +// Connection::new( +// |down_msg, _| { +// match down_msg { +// DownMsg::TerminalDownMsg(terminal_msg) => { +// TERMINAL_STATE.set(terminal_msg); +// } + +// } +// } +// ) +// }); + +pub fn root() -> impl Element { + term_request(); + let terminal = + El::new() + .s(Width::fill()) + .s(Height::fill()) + .s(Font::new().family([ + FontFamily::new("Lucida Console"), + FontFamily::new("Courier"), + FontFamily::new("monospace") + ])) + .update_raw_el(|raw_el| { + raw_el.global_event_handler(|event: events::KeyDown| { + println!("Pressed key: {}", &event.key()); + send_char( + (&event).key().as_str(), + (&event).ctrl_key(), + ); + }) + }) + .child_signal(TERMINAL_STATE.signal_cloned().map( + |down_msg| { + match down_msg { + TerminalDownMsg::FullTermUpdate(term) => { + make_grid_with_newlines(&term) + }, + TerminalDownMsg::TermNotStarted => { + "Term not yet started!".to_string() + }, + TerminalDownMsg::BackendTermStartFailure(msg) => { + format!("Error: BackendTermStartFailure: {}", msg) + } + } + } + ) + ) + ; + let root = Column::new() + .s(Width::fill()) + .s(Height::fill()) + .s(Align::new().top()) + .item(terminal); + root +} + +// TODO : fill this out +fn term_request() { +} + +fn send_char( + s : &str, + has_control : bool, + ) { + match process_str(s, has_control) { + // TODO : fill this out + Some(c) => { + eprintln!("Sending char: {}", c); + } + None => {eprintln!("Not processing: {}", s)} + } + +} + + +fn make_grid_with_newlines(term : &TerminalScreen) -> String { + let mut formatted = String::new(); + for (i, c) in term.content.chars().enumerate() { + formatted.push(c); + if (i + 1) % term.cols == 0 { + formatted.push('\n'); + } + } + formatted +} + +fn process_str(s: &str, has_ctrl : bool) -> Option { + match s { + "Enter" => {return Some('\n');} + "Escape" => {return Some('\x1B');} + "Backspace" => {return Some('\x08');} + "ArrowUp" => {return Some('\x10');} + "ArrowDown" => {return Some('\x0E');} + "ArrowLeft" => {return Some('\x02');} + "ArrowRight" => {return Some('\x06');} + _ => {} + } + // Check if the string has exactly one character + if s.chars().count() == 1 { + // Safe unwrap because we know the length is 1 + let c = s.chars().next().unwrap(); + let c = process_for_ctrl_char(c, has_ctrl); + return Some(c); + } + None +} + +fn is_lowercase_alpha(c: char) -> bool { + char_is_between_inclusive(c, 'a', 'z') +} + +fn process_for_ctrl_char(c: char, has_ctrl : bool) -> char { + let mut final_ctrl_char = c; + if has_ctrl { + if is_lowercase_alpha(c) { + let c_u8 = (c as u8); + let ctrl_char_u8 = c_u8 - 96; + final_ctrl_char = (ctrl_char_u8 as char); + } else if char_is_between_inclusive(c, '[', '_') { + let c_u8 = (c as u8); + let ctrl_char_u8 = c_u8 - 90; + final_ctrl_char = (ctrl_char_u8 as char); + } + + } + return final_ctrl_char +} + +fn char_is_between_inclusive(c : char, lo_char : char, hi_char : char) -> bool { + c >= lo_char && c <= hi_char +} diff --git a/shared/src/lib.rs b/shared/src/lib.rs index 3d07aaf..6bc697c 100644 --- a/shared/src/lib.rs +++ b/shared/src/lib.rs @@ -7,6 +7,7 @@ mod signal_to_timeline; pub use signal_to_timeline::signal_to_timeline; pub mod wellen_helpers; +pub mod term; #[derive(Serialize, Deserialize, Debug, Default)] #[serde(crate = "serde")] diff --git a/shared/src/term.rs b/shared/src/term.rs new file mode 100644 index 0000000..64d0168 --- /dev/null +++ b/shared/src/term.rs @@ -0,0 +1,25 @@ +use moonlight::*; + +#[derive(Serialize, Deserialize, Debug, Clone)] +#[serde(crate = "serde")] +pub enum TerminalUpMsg { + RequestFullTermState, + RequestIncrementalTermStateUpdate, + SendCharacter(char), +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +#[serde(crate = "serde")] +pub enum TerminalDownMsg { + FullTermUpdate(TerminalScreen), + BackendTermStartFailure(String), + TermNotStarted +} + +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +#[serde(crate = "serde")] +pub struct TerminalScreen { + pub cols : usize, + pub rows : usize, + pub content : String, +} diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 23c8d60..4917733 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -17,6 +17,7 @@ tauri-build = { version = "2.0.3", features = [] } [dependencies] wellen.workspace = true +alacritty_terminal = { git = "https://github.com/alacritty/alacritty", rev = "cacdb5bb3b72bad2c729227537979d95af75978f" } shared = { path = "../shared", features = ["backend"] } serde_json = "1.0" serde = { version = "1.0", features = ["derive"] } diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 270ef68..6bb359b 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -8,6 +8,7 @@ use tauri_plugin_dialog::DialogExt; use tokio::time::sleep; use wasmtime::AsContextMut; use wellen::simple::Waveform; +use tauri::Emitter; type Filename = String; type JavascriptCode = String; @@ -30,6 +31,7 @@ pub static WAVEFORM: Lazy>>>> = Lazy::new( #[derive(Default)] struct Store { waveform: Arc>>, + val : Arc>, } #[tauri::command(rename_all = "snake_case")] @@ -288,6 +290,19 @@ pub fn run() { ]) .setup(|app| { *APP_HANDLE.write().unwrap() = Some(app.handle().to_owned()); + println!("Setting up yay!"); + + std::thread::spawn(move || { + // Simulate emitting a message after a delay + std::thread::sleep(std::time::Duration::from_secs(5)); + + // Use APP_HANDLE to emit the event + if let Some(app_handle) = APP_HANDLE.read().unwrap().clone() { + let payload = serde_json::json!({ "message": "Hello from the backend using APP_HANDLE!" }); + app_handle.emit("backend-message", payload).unwrap(); + } + }); + Ok(()) }) .run(tauri::generate_context!()) diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 69c3a72..6203dc8 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -1,6 +1,8 @@ // Prevents additional console window on Windows in release, DO NOT REMOVE!! #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] +mod terminal_size; + fn main() { app_lib::run(); } diff --git a/src-tauri/src/terminal_size.rs b/src-tauri/src/terminal_size.rs new file mode 100644 index 0000000..055cb07 --- /dev/null +++ b/src-tauri/src/terminal_size.rs @@ -0,0 +1,55 @@ +use alacritty_terminal::event::{WindowSize}; +use alacritty_terminal::grid::{Dimensions}; +use alacritty_terminal::index::{Column, Line}; + +#[derive(Clone, Copy, Debug)] +pub struct TerminalSize { + pub cell_width: u16, + pub cell_height: u16, + pub num_cols: u16, + pub num_lines: u16, +} + +impl TerminalSize { + pub fn new(rows : u16, cols : u16) -> Self { + Self { + cell_width: 1, + cell_height: 1, + num_cols: cols, + num_lines: rows, + } + } +} + +impl Dimensions for TerminalSize { + fn total_lines(&self) -> usize { + self.screen_lines() + } + + fn screen_lines(&self) -> usize { + self.num_lines as usize + } + + fn columns(&self) -> usize { + self.num_cols as usize + } + + fn last_column(&self) -> Column { + Column(self.num_cols as usize - 1) + } + + fn bottommost_line(&self) -> Line { + Line(self.num_lines as i32 - 1) + } +} + +impl From for WindowSize { + fn from(size: TerminalSize) -> Self { + Self { + num_lines: size.num_lines, + num_cols: size.num_cols, + cell_width: size.cell_width, + cell_height: size.cell_height, + } + } +}