diff --git a/Cargo.toml b/Cargo.toml index 04ae3ac..a64564d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,5 +4,10 @@ version = "0.1.0" edition = "2021" [dependencies] -eframe = "0.29.1" -egui = "0.29.1" +eframe = "0.28" +egui = "0.28" +portable-pty = "0.8.1" +termwiz = "0.22.0" +vte = "0.13.0" +egui-terminal = { git = "https://github.com/Quinntyx/egui-terminal" } +ecolor = "0.29.1" diff --git a/README.md b/README.md index f7722d7..c984902 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,8 @@ Unfortunatly this is not the most secure desktop locker, as it involves using sw ``` # Add this to your sway config: -mode "lock" { } -for_window [title="^raylock$"] sticky enable, fullscreen +for_window [title="^raylock$"] sticky enable, floating enable, resize set 1920 px 1080 px, title_format "", border pixel none +mode "lock" { + bindsym $mod+Shift+q exec true +} ``` diff --git a/src/input.rs b/src/input.rs index cadaf8f..0dfd56c 100644 --- a/src/input.rs +++ b/src/input.rs @@ -1,19 +1,45 @@ use egui::Key; +use std::fs::{exists, File}; +use std::path::Path; use std::process::Command; use std::thread; +const LOCK_FILEPATH: &str = "/tmp/.raylock.lock"; + pub fn sway_lock_input() { thread::spawn(move || { let _ = Command::new("swaymsg").args(["mode", "lock"]).spawn(); + let _ = Command::new("swaymsg") + .args(["input", "type:touchpad", "events", "disabled"]) + .spawn(); }); } pub fn sway_unlock_input() { thread::spawn(move || { let _ = Command::new("swaymsg").args(["mode", "default"]).spawn(); + let _ = Command::new("swaymsg") + .args(["input", "type:touchpad", "events", "enabled"]) + .spawn(); }); } +pub fn create_lock() { + let _ = File::create(LOCK_FILEPATH).unwrap(); +} + +pub fn is_locked() -> bool { + Path::exists(Path::new(LOCK_FILEPATH)) +} + +pub fn remove_lock() { + if !is_locked() { + return; + } + + std::fs::remove_file(Path::new(LOCK_FILEPATH)); +} + pub fn format_key(key: Key, shift_pressed: bool) -> String { match key { // Letters diff --git a/src/main.rs b/src/main.rs index 9815c0b..85a9118 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,17 +1,69 @@ -use eframe::egui; +use eframe::{egui, App}; +use egui::FontFamily; use egui::Key; +use egui_terminal; +use input::is_locked; +use std::collections::BTreeMap; +use std::collections::HashMap; use std::process::{Command, Stdio}; use std::sync::{Arc, Mutex}; use std::thread; use std::time::Duration; +// use egui::epaint::text::{FontInsert, InsertFontFamily}; + +use eframe::CreationContext; +use egui::ecolor::Color32; +use egui::FontId; +use egui::Stroke; +use egui_terminal::prelude::*; +use egui_terminal::render::CursorType; + +use egui::ecolor::HexColor; + mod input; +mod splitter; mod structs; mod ui; +// Demonstrates how to add a font to the existing ones +fn add_font(ctx: &egui::Context) { + // Start with the default fonts (we will be adding to them rather than replacing them). + let mut fonts = egui::FontDefinitions::default(); + + // Install my own font (maybe supporting non-latin characters). + // .ttf and .otf files supported. + fonts.font_data.insert( + "my_font".to_owned(), + egui::FontData::from_static(include_bytes!( + "/home/astatin3/.fonts/UbuntuMono/UbuntuMonoNerdFontMono-Regular.ttf" + )), + ); + + // Put my font first (highest priority) for proportional text: + // fonts + // .families + // .entry(egui::FontFamily::Proportional) + // .or_default() + // .insert(0, "my_font".to_owned()); + + // Put my font as last fallback for monospace: + fonts + .families + .entry(egui::FontFamily::Monospace) + .or_default() + .push("my_font".to_owned()); + + // Tell egui to use these fonts: + ctx.set_fonts(fonts); +} + +use splitter::Splitter; + #[derive(Default)] struct ExampleApp { auth_state: Arc>, + terminals: HashMap, } impl ExampleApp { @@ -21,9 +73,34 @@ impl ExampleApp { } impl eframe::App for ExampleApp { + fn clear_color(&self, _visuals: &egui::Visuals) -> [f32; 4] { + egui::Rgba::from_rgba_unmultiplied(0.1, 0.1, 0.1, 0.9).to_array() + } + fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { + // let s1 = self.terminals.get_mut("0").unwrap(); + // s1.style() + // s1.cursor_trail = false; + // s1.cursor_trail_color = Some(HexColor::Hex8(Color32::LIGHT_BLUE.gamma_multiply(0.5))); + + // s1.default_focus_cursor = CursorType::OpenBlock(HexColor::Hex8(Color32::RED)); + // s1.default_unfocus_cursor = CursorType::None; + // for str in egui::FontDefinitions::builtin_font_names() { + // println!("{}", str); + // } + // s1.cursor_stroke = Stroke::new(1., Color32::WHITE); + + // add_font(ctx); + + // let font = FontId { + // size: 9., + // family: FontFamily::Name("my_font".into()), + // }; + + // s1.font = font; + let mut state = self.auth_state.lock().unwrap(); - ctx.set_pixels_per_point(1.5); + //ctx.set_pixels_per_point(1.5); if ctx.input(|i| i.events.len() > 0) { ctx.input(|i| { @@ -62,13 +139,46 @@ impl eframe::App for ExampleApp { }); } - egui::CentralPanel::default().show(ctx, |ui| { - // ui.add_space(200.0); - // ui.heading("*".repeat(state.password.clone().len())); - // ui.add_space(20.0); - // - ui::update(state, ctx, _frame, ui); - }); + // let sine_points = PlotPoints::from_explicit_callback(|x| x.sin(), .., 5000); + // let plot_a_lines = [Line::new(sine_points).name("Sine")]; + + // let sine_points2 = PlotPoints::from_explicit_callback(|x| x.sin(), .., 5000); + // let plot_b_lines = [Line::new(sine_points2).name("Sine")]; + + egui::CentralPanel::default() + .frame(egui::Frame::none()) + .show(ctx, |ui| { + // let ht = ui.available_height(); + // for (_idx, (_id, term)) in self.terminals.iter_mut().enumerate() { + // ui.terminal_sized(term, egui::vec2(ui.available_width(), ht)); + // } + // ui.add_space(200.0); + // ui.heading("*".repeat(state.password.clone().len())); + // ui.add_space(20.0); + // + + // ui.terminal_sized( + // self.terminals.get_mut("0").unwrap(), + // egui::vec2(ui.available_width(), ui.available_height()), + // ); + // Splitter::new("some_unique_id", splitter::SplitterAxis::Horizontal).show( + // ui, + // |ui_a, ui_b| { + // ui_a.terminal_sized( + // self.terminals.get_mut("0").unwrap(), + // egui::vec2(ui_a.available_width(), ui_a.available_height()), + // ); + + // ui_b.terminal_sized( + // self.terminals.get_mut("1h-1").unwrap(), + // egui::vec2(ui_a.available_width(), ui_a.available_height()), + // ); + + // }, + // ); + + ui::update_password_viewer(state, ctx, _frame, ui); + }); } } @@ -78,6 +188,10 @@ fn main() -> eframe::Result<()> { ..eframe::NativeOptions::default() }; + // let system_shell = std::env::var("SHELL") + // .expect("SHELL variable is not defined") + // .to_string(); + let state = Arc::new(Mutex::new(structs::AuthState { password: String::new(), to_be_submitted: false, @@ -94,12 +208,13 @@ fn main() -> eframe::Result<()> { let result = try_sudo(&state.password); match result { Ok(true) => { - println!("True"); + // println!("True"); input::sway_unlock_input(); + input::remove_lock(); std::process::exit(0); } Ok(false) => { - println!("False"); + // println!("False"); state.failed_attempts += 1; state.password.clear(); } @@ -113,12 +228,28 @@ fn main() -> eframe::Result<()> { thread::sleep(Duration::from_millis(100)); }); + let mut map = HashMap::new(); + // map.insert(String::from("0"), TermHandler::new_from_str("btop")); + // map.insert(String::from("1h-0"), TermHandler::new_from_str("nvitop")); + // map.insert(String::from("1h-1"), TermHandler::new_from_str("neofetch")); input::sway_lock_input(); + if input::is_locked() { + println!("Raylock is already running!"); + std::process::exit(1); + } + + input::create_lock(); + eframe::run_native( ExampleApp::name(), native_options, - Box::new(|_| Ok(Box::::new(ExampleApp { auth_state: state }))), + Box::new(|_| { + Ok(Box::::new(ExampleApp { + auth_state: state, + terminals: map, + })) + }), ) } diff --git a/src/splitter.rs b/src/splitter.rs new file mode 100644 index 0000000..e39249a --- /dev/null +++ b/src/splitter.rs @@ -0,0 +1,190 @@ +/// https://gist.github.com/mkalte666/f9a982be0ac0276080d3434ab9ea4655 +use std::hash::Hash; + +use egui::{CursorIcon, Id, Layout, Pos2, Rect, Rounding, Sense, Ui, Vec2}; + +/// An axis that a Splitter can use +#[derive(Copy, Clone, Debug)] +pub enum SplitterAxis { + Horizontal, + Vertical, +} + +/// The internal data used by a splitter. Stored into memory +#[derive(Debug, Clone)] +struct SplitterData { + axis: SplitterAxis, + pos: f32, + min_size: f32, +} + +/// Splits a ui in half, using a draggable separator in the middle. +/// +pub struct Splitter { + id: Id, + data: SplitterData, +} +impl Splitter { + /// Create a new Splitter + pub fn new(id_source: impl Hash, axis: SplitterAxis) -> Self { + Self { + id: Id::new(id_source), + data: SplitterData { + axis, + pos: 0.5, + min_size: 0.0, + }, + } + } + + /// Sets the minimum allowed size for the area + pub fn min_size(mut self, points: f32) -> Self { + self.data.min_size = points; + self + } + + /// Thes the default position of the splitter separator. Usually it sits in the center, this moves it around. + pub fn default_pos(mut self, pos: f32) -> Self { + self.data.pos = pos; + self + } + + /// Show the splitter and fill it with content. + /// + /// ``` + /// Splitter::new("some_plot_split", SplitterAxis::Vertical) + /// .min_size(250.0) + /// .default_pos(2.0 / 3.0) + /// .show(ui, |ui_a, ui_b| { + /// Plot::new("plot_a") + /// .legend(Legend::default()) + /// .x_axis_formatter(log_formatter) + /// .y_axis_formatter(log_formatter) + /// .x_axis_label("X Axis") + /// .y_axis_label("A Axis") + /// .link_axis("axis_link", true, false) + /// .link_cursor("cursor_link", true, false) + /// .show(ui_a, |plot_ui| { + /// for line in plot_a_lines { + /// plot_ui.line(line); + /// } + /// }); + /// + /// Plot::new("plot_b") + /// .legend(Legend::default()) + /// .x_axis_formatter(log_formatter) + /// .x_axis_label("X Axis") + /// .y_axis_label("Y Axis") + /// .link_axis("axis_link", true, false) + /// .link_cursor("cursor_link", true, false) + /// .show(ui_b, |plot_ui| { + /// for line in plot_b_lines { + /// plot_ui.line(line); + /// } + /// }); + /// }); + /// ``` + pub fn show(mut self, ui: &mut Ui, add_contents: impl FnOnce(&mut Ui, &mut Ui)) { + let mut data = if let Some(d) = ui.memory(|mem| mem.data.get_temp(self.id)) { + d + } else { + self.data.clone() + }; + + let sep_size = 10.0; + let sep_stroke = 2.0; + let whole_area = ui.available_size(); + + let split_axis_size = match data.axis { + SplitterAxis::Horizontal => whole_area.x, + SplitterAxis::Vertical => whole_area.y, + }; + let split_a_size = ((split_axis_size - sep_size) * data.pos); + let split_b_size = split_axis_size - sep_size - split_a_size; + + let child_size_a = match data.axis { + SplitterAxis::Horizontal => Vec2::new(split_a_size, whole_area.y), + SplitterAxis::Vertical => Vec2::new(whole_area.x, split_a_size), + }; + + let child_size_b = match data.axis { + SplitterAxis::Horizontal => Vec2::new(split_b_size, whole_area.y), + SplitterAxis::Vertical => Vec2::new(whole_area.x, split_b_size), + }; + + let child_rect_a = Rect::from_min_size(ui.next_widget_position(), child_size_a); + let mut ui_a = ui.child_ui(child_rect_a, Layout::default(), None); + + let sep_rect = match data.axis { + SplitterAxis::Horizontal => Rect::from_min_size( + Pos2::new(child_rect_a.max.x, child_rect_a.min.y), + Vec2::new(sep_size, whole_area.y), + ), + SplitterAxis::Vertical => Rect::from_min_size( + Pos2::new(child_rect_a.min.x, child_rect_a.max.y), + Vec2::new(whole_area.x, sep_size), + ), + }; + + let resp = ui.allocate_rect(sep_rect, Sense::hover().union(Sense::click_and_drag())); + + let sep_draw_rect = match data.axis { + SplitterAxis::Horizontal => Rect::from_min_size( + Pos2::new( + sep_rect.min.x + sep_size / 2.0 - sep_stroke / 2.0, + sep_rect.min.y, + ), + Vec2::new(sep_stroke, sep_rect.height()), + ), + SplitterAxis::Vertical => Rect::from_min_size( + Pos2::new( + sep_rect.min.x, + sep_rect.min.y + sep_size / 2.0 - sep_stroke / 2.0, + ), + Vec2::new(sep_rect.width(), sep_stroke), + ), + }; + ui.painter().rect_filled( + sep_draw_rect, + Rounding::ZERO, + ui.style().visuals.noninteractive().bg_stroke.color, + ); + + let child_rect_b = match data.axis { + SplitterAxis::Horizontal => { + Rect::from_min_size(Pos2::new(sep_rect.max.x, sep_rect.min.y), child_size_b) + } + SplitterAxis::Vertical => { + Rect::from_min_size(Pos2::new(sep_rect.min.x, sep_rect.max.y), child_size_b) + } + }; + let mut ui_b = ui.child_ui(child_rect_b, Layout::default(), None); + + add_contents(&mut ui_a, &mut ui_b); + + if resp.hovered() { + match data.axis { + SplitterAxis::Horizontal => ui.ctx().set_cursor_icon(CursorIcon::ResizeVertical), + SplitterAxis::Vertical => ui.ctx().set_cursor_icon(CursorIcon::ResizeHorizontal), + } + } + + if resp.dragged() { + let delta_pos = match data.axis { + SplitterAxis::Horizontal => resp.drag_delta().x / whole_area.x, + SplitterAxis::Vertical => resp.drag_delta().y / whole_area.y, + }; + + data.pos += delta_pos; + } + + // clip pos + let min_pos = (data.min_size / split_axis_size).min(1.0); + let max_pos = (1.0 - min_pos).max(0.0); + data.pos = data.pos.max(min_pos).min(max_pos); + + ui.memory_mut(|mem| { + mem.data.insert_temp(self.id, data); + }) + } +} diff --git a/src/ui.rs b/src/ui.rs index c7ee7b7..b2f1a7a 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -33,7 +33,7 @@ const LOGIN_FAIL_COUNT_CIRCLE_STROKE: Stroke = Stroke { color: LOGIN_FAIL_COLOR, }; -fn rotCircle(i: i16, center: Pos2, rad: f32, offset_ang: f32, ang_per_num: f32) -> Pos2 { +fn rot_circle(i: i16, center: Pos2, rad: f32, offset_ang: f32, ang_per_num: f32) -> Pos2 { center + (egui::Vec2 { x: rad * f32::cos(i as f32 * ang_per_num + offset_ang), @@ -41,7 +41,7 @@ fn rotCircle(i: i16, center: Pos2, rad: f32, offset_ang: f32, ang_per_num: f32) }) } -pub fn update( +pub fn update_password_viewer( wstate: MutexGuard<'_, structs::AuthState>, ctx: &egui::Context, frame: &mut eframe::Frame, @@ -71,7 +71,7 @@ pub fn update( let ang_per_fail = 2. * PI / state.failed_attempts as f32; let len: i16 = state.password.len() as i16; - let mut last_pos = rotCircle( + let mut last_pos = rot_circle( len - 1, center, LOGIN_CIRCLE_RADIUS, @@ -84,7 +84,7 @@ pub fn update( if state.failed_attempts <= 1 { center } else { - rotCircle( + rot_circle( i as i16, center, LOGIN_FAIL_COUNT_CIRCLE_RADIUS, @@ -107,7 +107,7 @@ pub fn update( if len <= 1 { center } else { - rotCircle( + rot_circle( i, center, LOGIN_CIRCLE_RADIUS,