├── .gitignore ├── docs └── image │ └── screenshot.png ├── readme.md ├── Cargo.toml └── src ├── main.rs ├── pingdb.rs └── app.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | /.idea 4 | 5 | -------------------------------------------------------------------------------- /docs/image/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/donpdonp/linkradar/main/docs/image/screenshot.png -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | ![Screenshot](docs/image/screenshot.png) 2 | 3 | A desktop widget that ping's 4.2.2.2 every second and updates the bouncing ball to reflect current network connectivity. 4 | 5 | Built in rust and the egui framework. 6 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "linkradar" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | eframe = "0.21.3" 10 | env_logger = "0.10.0" 11 | fastping-rs = "0.2.3" 12 | log = "0.4.17" 13 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | mod app; 2 | mod pingdb; 3 | 4 | use eframe::egui; 5 | use std::sync::mpsc::{channel, Receiver}; 6 | 7 | fn main() -> Result<(), eframe::Error> { 8 | env_logger::init(); 9 | log::info!("linkradar"); 10 | 11 | let (app_sender, app_receiver) = channel::(); 12 | 13 | let _pingdb = pingdb::Pingdb::new(app_sender); 14 | apploop(app_receiver) 15 | } 16 | 17 | fn apploop(app_receiver: Receiver) -> Result<(), eframe::Error> { 18 | let options = eframe::NativeOptions { 19 | initial_window_size: Some(egui::vec2(40.0, 300.0)), 20 | ..Default::default() 21 | }; 22 | 23 | eframe::run_native( 24 | "LM", 25 | options, 26 | Box::new(|cc| Box::new(app::MyApp::new(cc, app_receiver))), 27 | ) 28 | } 29 | -------------------------------------------------------------------------------- /src/pingdb.rs: -------------------------------------------------------------------------------- 1 | use std::sync::mpsc::{Receiver, Sender}; 2 | use std::thread; 3 | use std::thread::spawn; 4 | use std::time::Duration; 5 | 6 | use fastping_rs::{PingResult, Pinger}; 7 | 8 | pub struct Pingdb {} 9 | 10 | impl Pingdb { 11 | pub(crate) fn new(app_sender: Sender) -> Self { 12 | let (pinger, results) = match Pinger::new(None, Some(56)) { 13 | Ok((pinger, results)) => (pinger, results), 14 | Err(e) => panic!("Error creating pinger: {}", e), 15 | }; 16 | pinger.add_ipaddr("4.2.2.2"); 17 | pinger.run_pinger(); 18 | 19 | spawn(move || netloop(results, app_sender)); 20 | 21 | Pingdb {} 22 | } 23 | } 24 | 25 | fn netloop(results: Receiver, send: Sender) { 26 | loop { 27 | let mut speed = 0.0; 28 | match results.recv() { 29 | Ok(result) => match result { 30 | PingResult::Idle { addr } => { 31 | //log::error!("Idle Address {}.", addr); 32 | } 33 | PingResult::Receive { addr, rtt } => { 34 | log::info!("Receive from Address {} in {:?}.", addr, rtt); 35 | speed = rtt.as_secs_f32().abs(); 36 | } 37 | }, 38 | Err(_) => panic!("Worker threads disconnected before the solution was found!"), 39 | } 40 | send.send(speed).unwrap(); 41 | thread::sleep(Duration::from_secs(1)); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/app.rs: -------------------------------------------------------------------------------- 1 | use std::sync::mpsc::{channel, Receiver, Sender}; 2 | use std::thread; 3 | use std::thread::spawn; 4 | use std::time::Duration; 5 | 6 | use eframe::egui; 7 | use eframe::egui::Context; 8 | use eframe::epaint; 9 | 10 | pub struct MyApp { 11 | height: u32, 12 | momentum: f32, 13 | pub app_receiver: Receiver, 14 | } 15 | 16 | impl MyApp { 17 | pub fn new(cc: &eframe::CreationContext<'_>, ping_receiver: Receiver) -> Self { 18 | // Customize egui here with cc.egui_ctx.set_fonts and cc.egui_ctx.set_visuals. 19 | // Restore app state using cc.storage (requires the "persistence" feature). 20 | // Use the cc.gl (a glow::Context) to create graphics shaders and buffers that you can use 21 | // for e.g. egui::PaintCallback. 22 | let (app_sender, app_receiver) = channel::(); 23 | let ectx = cc.egui_ctx.clone(); 24 | let app = MyApp { 25 | height: 0, 26 | momentum: 0.0, 27 | app_receiver, 28 | }; 29 | spawn(move || netloop(ping_receiver, ectx, app_sender)); 30 | app 31 | } 32 | } 33 | 34 | fn netloop(ping_receiver: Receiver, ectx: Context, send: Sender) { 35 | loop { 36 | match ping_receiver.try_recv() { 37 | Ok(result) => { 38 | send.send(result).unwrap(); 39 | } 40 | Err(_) => {} 41 | } 42 | ectx.request_repaint(); 43 | thread::sleep(Duration::from_millis(100)); 44 | } 45 | } 46 | 47 | impl eframe::App for MyApp { 48 | fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) { 49 | egui::CentralPanel::default().show(ctx, |ui| { 50 | let win_size = frame.info().window_info.size; 51 | //log::info!("height {} momentum {}", self.height, self.momentum); 52 | match self.app_receiver.try_recv() { 53 | Ok(result) => self.momentum = ((result * 1000.0) / win_size.y) * win_size.y, 54 | Err(_) => {} 55 | } 56 | self.momentum -= 1.7; 57 | 58 | let delta = self.momentum; 59 | if delta >= 0.0 { 60 | self.height += delta as u32 61 | } else { 62 | let d = (2.0 * delta).abs() as u32; 63 | self.height -= d.min(self.height) 64 | } 65 | 66 | if self.height < 10 { 67 | self.height = 10; 68 | self.momentum = 0.0; 69 | } 70 | let circle = epaint::CircleShape { 71 | center: egui::Pos2 { 72 | x: win_size.x / 2.0, 73 | y: win_size.y - self.height as f32, 74 | }, 75 | radius: win_size.x * 0.2, 76 | fill: egui::Color32::RED, 77 | stroke: egui::Stroke::new(4.0, egui::Color32::YELLOW), 78 | }; 79 | ui.painter().add(egui::Shape::Circle(circle)); 80 | }); 81 | } 82 | } 83 | --------------------------------------------------------------------------------