mirror of
https://github.com/Astatin3/raylock.git
synced 2026-06-08 16:18:04 -06:00
Code
This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
[package]
|
||||
name = "raylock"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
eframe = "0.29.1"
|
||||
egui = "0.29.1"
|
||||
@@ -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
@@ -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
@@ -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),
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
#[derive(Default)]
|
||||
pub struct AuthState {
|
||||
pub password: String,
|
||||
pub to_be_submitted: bool,
|
||||
pub failed_attempts: u16,
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user