mirror of
https://github.com/Astatin3/raylock.git
synced 2026-06-09 00:28:00 -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
|
# 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