This commit is contained in:
Astatin3
2024-10-30 19:49:27 -06:00
parent c5ac3f4a30
commit 0597b0db04
6 changed files with 532 additions and 1 deletions
+8
View File
@@ -0,0 +1,8 @@
[package]
name = "raylock"
version = "0.1.0"
edition = "2021"
[dependencies]
eframe = "0.29.1"
egui = "0.29.1"
+9 -1
View File
@@ -1,2 +1,10 @@
# raylock
Swaylock alternitive made in rust
##### Swaylock alternitive made in rust
---
Unfortunatly this is not the most secure desktop locker, as it involves using sway config, and not PAM for key validiation. But it seems to work just fine.
```
# Add this to your sway config:
mode "lock" { }
for_window [title="^raylock$"] sticky enable, fullscreen
```
+235
View File
@@ -0,0 +1,235 @@
use egui::Key;
use std::process::Command;
use std::thread;
pub fn sway_lock_input() {
thread::spawn(move || {
let _ = Command::new("swaymsg").args(["mode", "lock"]).spawn();
});
}
pub fn sway_unlock_input() {
thread::spawn(move || {
let _ = Command::new("swaymsg").args(["mode", "default"]).spawn();
});
}
pub fn format_key(key: Key, shift_pressed: bool) -> String {
match key {
// Letters
Key::A
| Key::B
| Key::C
| Key::D
| Key::E
| Key::F
| Key::G
| Key::H
| Key::I
| Key::J
| Key::K
| Key::L
| Key::M
| Key::N
| Key::O
| Key::P
| Key::Q
| Key::R
| Key::S
| Key::T
| Key::U
| Key::V
| Key::W
| Key::X
| Key::Y
| Key::Z => {
let base_char = (key as u8 - Key::A as u8 + b'a') as char;
if shift_pressed {
base_char.to_uppercase().to_string()
} else {
base_char.to_string()
}
}
// Numbers and their shift symbols
Key::Num0 => {
if shift_pressed {
")".to_string()
} else {
"0".to_string()
}
}
Key::Num1 => {
if shift_pressed {
"!".to_string()
} else {
"1".to_string()
}
}
Key::Num2 => {
if shift_pressed {
"@".to_string()
} else {
"2".to_string()
}
}
Key::Num3 => {
if shift_pressed {
"#".to_string()
} else {
"3".to_string()
}
}
Key::Num4 => {
if shift_pressed {
"$".to_string()
} else {
"4".to_string()
}
}
Key::Num5 => {
if shift_pressed {
"%".to_string()
} else {
"5".to_string()
}
}
Key::Num6 => {
if shift_pressed {
"^".to_string()
} else {
"6".to_string()
}
}
Key::Num7 => {
if shift_pressed {
"&".to_string()
} else {
"7".to_string()
}
}
Key::Num8 => {
if shift_pressed {
"*".to_string()
} else {
"8".to_string()
}
}
Key::Num9 => {
if shift_pressed {
"(".to_string()
} else {
"9".to_string()
}
}
// Special characters
Key::Space => " ".to_string(),
// Key::Tab => "Tab".to_string(),
// Key::Enter => "Enter".to_string(),
// Key::Backspace => "Backspace".to_string(),
// Key::Escape => "Esc".to_string(),
// Key::Delete => "Del".to_string(),
// Arrow keys
// Key::ArrowLeft => "←".to_string(),
// Key::ArrowRight => "→".to_string(),
// Key::ArrowUp => "↑".to_string(),
// Key::ArrowDown => "↓".to_string(),
// Punctuation and symbols
Key::Minus => {
if shift_pressed {
"_".to_string()
} else {
"-".to_string()
}
}
Key::Equals => {
if shift_pressed {
"+".to_string()
} else {
"=".to_string()
}
}
Key::OpenBracket => {
if shift_pressed {
"{".to_string()
} else {
"[".to_string()
}
}
Key::CloseBracket => {
if shift_pressed {
"}".to_string()
} else {
"]".to_string()
}
}
Key::Backslash => {
if shift_pressed {
"|".to_string()
} else {
"\\".to_string()
}
}
Key::Semicolon => {
if shift_pressed {
":".to_string()
} else {
";".to_string()
}
}
Key::Quote => {
if shift_pressed {
"\"".to_string()
} else {
"'".to_string()
}
}
Key::Comma => {
if shift_pressed {
"<".to_string()
} else {
",".to_string()
}
}
Key::Period => {
if shift_pressed {
">".to_string()
} else {
".".to_string()
}
}
Key::Slash => {
if shift_pressed {
"?".to_string()
} else {
"/".to_string()
}
}
Key::Backtick => {
if shift_pressed {
"~".to_string()
} else {
"`".to_string()
}
}
// Function keys
// Key::F1..=Key::F12 => format!("F{}", (key as u8 - Key::F1 as u8 + 1)),
// Modifier keys
// Key::Control => "Ctrl".to_string(),
// Key::Alt => "Alt".to_string(),
// Key::Shift => "Shift".to_string(),
// Key::Insert => "Insert".to_string(),
// Key::Home => "Home".to_string(),
// Key::End => "End".to_string(),
// Key::PageUp => "PgUp".to_string(),
// Key::PageDown => "PgDn".to_string(),
// Catch any other keys
_ => "".to_string(),
}
}
+141
View File
@@ -0,0 +1,141 @@
use eframe::egui;
use egui::Key;
use std::process::{Command, Stdio};
use std::sync::{Arc, Mutex};
use std::thread;
use std::time::Duration;
mod input;
mod structs;
mod ui;
#[derive(Default)]
struct ExampleApp {
auth_state: Arc<Mutex<structs::AuthState>>,
}
impl ExampleApp {
fn name() -> &'static str {
"raylock"
}
}
impl eframe::App for ExampleApp {
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
let mut state = self.auth_state.lock().unwrap();
ctx.set_pixels_per_point(1.5);
if ctx.input(|i| i.events.len() > 0) {
ctx.input(|i| {
for event in &i.events {
if let egui::Event::Key {
key,
pressed,
modifiers,
..
} = event
{
if *pressed {
match key {
Key::Enter => {
state.to_be_submitted = true;
}
Key::Backspace => {
let len = state.password.len();
if len != 0 {
state.password.remove(len - 1 as usize);
}
}
_ => {
let str = &input::format_key(*key, modifiers.shift);
state.password += str;
}
}
//let mod_str = if modifiers.is_empty() {
// String::new()
//} else {
// format!(" + {:?}", modifiers)
//};
}
}
}
});
}
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);
});
}
}
fn main() -> eframe::Result<()> {
let native_options = eframe::NativeOptions {
viewport: egui::ViewportBuilder::default().with_inner_size((400.0, 400.0)),
..eframe::NativeOptions::default()
};
let state = Arc::new(Mutex::new(structs::AuthState {
password: String::new(),
to_be_submitted: false,
failed_attempts: 0,
}));
let auth_state_clone = state.clone();
// Spawn authentication thread
thread::spawn(move || loop {
let mut state = auth_state_clone.lock().unwrap();
if state.to_be_submitted {
let result = try_sudo(&state.password);
match result {
Ok(true) => {
println!("True");
input::sway_unlock_input();
std::process::exit(0);
}
Ok(false) => {
println!("False");
state.failed_attempts += 1;
state.password.clear();
}
Err(_) => {
state.password.clear();
}
}
state.to_be_submitted = false;
}
drop(state);
thread::sleep(Duration::from_millis(100));
});
input::sway_lock_input();
eframe::run_native(
ExampleApp::name(),
native_options,
Box::new(|_| Ok(Box::<ExampleApp>::new(ExampleApp { auth_state: state }))),
)
}
fn try_sudo(password: &str) -> Result<bool, std::io::Error> {
let mut child = Command::new("sudo")
.args(["-kS", "true"]) // Use -S to read password from stdin
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()?;
if let Some(mut stdin) = child.stdin.take() {
use std::io::Write;
writeln!(stdin, "{}", password)?;
}
match child.wait() {
Ok(status) => Ok(status.success()),
Err(e) => Err(e),
}
}
+6
View File
@@ -0,0 +1,6 @@
#[derive(Default)]
pub struct AuthState {
pub password: String,
pub to_be_submitted: bool,
pub failed_attempts: u16,
}
+133
View File
@@ -0,0 +1,133 @@
use crate::structs;
use eframe::egui;
use egui::Color32;
use egui::Pos2;
use egui::Stroke;
use std::f32::consts::PI;
use std::sync::MutexGuard;
const TEXT_COLOR: Color32 = Color32::from_rgb(255, 255, 255);
const LOGIN_CIRCLE_RADIUS: f32 = 50.;
const LOGIN_SUBCIRCLE_START_ANG: f32 = -PI / 4.;
const LOGIN_SUBCIRCLE_RADIUS: f32 = 4.;
const LOGIN_SUBCIRCLE_COLOR: Color32 = Color32::TRANSPARENT;
const LOGIN_SUBCIRCLE_STROKE: Stroke = Stroke {
width: 2.0,
color: Color32::from_rgb(255, 255, 255),
};
const LOGIN_CIRCLE_LINE_STROKE: Stroke = Stroke {
width: 2.0,
color: TEXT_COLOR,
};
const LOGIN_FAIL_COLOR: Color32 = Color32::from_rgb(184, 41, 11);
const LOGIN_FAIL_CIRCLE_STROKE: Stroke = Stroke {
width: 5.,
color: LOGIN_FAIL_COLOR,
};
const LOGIN_FAIL_COUNT_CIRCLE_RADIUS: f32 = 15.;
const LOGIN_FAIL_COUNT_CIRCLE_COLOR: Color32 = Color32::TRANSPARENT;
const LOGIN_FAIL_COUNT_CIRCLE_STROKE: Stroke = Stroke {
width: 2.,
color: LOGIN_FAIL_COLOR,
};
fn rotCircle(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),
y: rad * f32::sin(i as f32 * ang_per_num + offset_ang),
})
}
pub fn update(
wstate: MutexGuard<'_, structs::AuthState>,
ctx: &egui::Context,
frame: &mut eframe::Frame,
ui: &mut egui::Ui,
) {
let mut state = wstate;
let rect = ui.clip_rect();
let center = Pos2 {
x: rect.width() / 2.,
y: rect.height() / 2.,
};
let painter = ui.painter();
// Login Circle
if state.failed_attempts > 0 {
painter.circle(
center,
LOGIN_CIRCLE_RADIUS - LOGIN_FAIL_CIRCLE_STROKE.width,
Color32::TRANSPARENT,
LOGIN_FAIL_CIRCLE_STROKE,
);
}
let ang_per_char = 2. * PI / state.password.len() as f32;
let ang_per_fail = 2. * PI / state.failed_attempts as f32;
let len: i16 = state.password.len() as i16;
let mut last_pos = rotCircle(
len - 1,
center,
LOGIN_CIRCLE_RADIUS,
LOGIN_SUBCIRCLE_START_ANG,
ang_per_char,
);
for i in 0..state.failed_attempts {
let pos: egui::Pos2 = {
if state.failed_attempts <= 1 {
center
} else {
rotCircle(
i as i16,
center,
LOGIN_FAIL_COUNT_CIRCLE_RADIUS,
LOGIN_SUBCIRCLE_START_ANG,
ang_per_fail,
)
}
};
painter.circle(
pos,
LOGIN_SUBCIRCLE_RADIUS,
LOGIN_FAIL_COUNT_CIRCLE_COLOR,
LOGIN_FAIL_COUNT_CIRCLE_STROKE,
);
}
for i in 0..len {
let pos: egui::Pos2 = {
if len <= 1 {
center
} else {
rotCircle(
i,
center,
LOGIN_CIRCLE_RADIUS,
LOGIN_SUBCIRCLE_START_ANG,
ang_per_char,
)
}
};
painter.circle(
pos,
LOGIN_SUBCIRCLE_RADIUS,
LOGIN_SUBCIRCLE_COLOR,
LOGIN_SUBCIRCLE_STROKE,
);
if len > 1 {
painter.line_segment([last_pos, pos], LOGIN_CIRCLE_LINE_STROKE);
last_pos = pos;
}
}
}