Skip to main content

breakwater_egui_overlay/
lib.rs

1pub use eframe;
2pub use egui;
3use egui::Margin;
4
5use crate::colors::{COLOR_BACKGROUND, COLOR_HIGHLIGHT, COLOR_PRIMARY};
6
7/// rustc version that compiled this crate
8//
9// Currently it's not supported to read the rust version from a cargo env,
10// so we need to gather it ourselves.
11// See https://github.com/rust-lang/cargo/issues/4408 for details
12const RUSTC_VERSION: *const std::ffi::c_char = concat!(
13    include_str!(concat!(env!("OUT_DIR"), "/RUSTC_VERSION.txt")),
14    "\0"
15)
16.as_ptr()
17.cast();
18
19const BREAKWATER_VERSION: *const std::ffi::c_char =
20    concat!(env!("CARGO_PKG_VERSION"), "\0").as_ptr().cast();
21
22#[repr(C)]
23#[derive(Clone)]
24pub struct Versions {
25    pub rustc: *const std::ffi::c_char,
26    pub breakwater: *const std::ffi::c_char,
27}
28
29impl std::fmt::Debug for Versions {
30    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
31        use std::ffi::CStr;
32
33        #[derive(Debug)]
34        #[allow(dead_code)]
35        struct Versions<'a> {
36            rustc: &'a CStr,
37            breakwater: &'a CStr,
38        }
39
40        unsafe {
41            Versions {
42                rustc: CStr::from_ptr(self.rustc),
43                breakwater: CStr::from_ptr(self.breakwater),
44            }
45            .fmt(f)
46        }
47    }
48}
49
50pub const VERSIONS: Versions = Versions {
51    rustc: RUSTC_VERSION,
52    breakwater: BREAKWATER_VERSION,
53};
54
55impl PartialEq for Versions {
56    fn eq(&self, other: &Self) -> bool {
57        use std::ffi::CStr;
58
59        unsafe {
60            CStr::from_ptr(self.rustc).eq(CStr::from_ptr(other.rustc))
61                && CStr::from_ptr(self.breakwater).eq(CStr::from_ptr(other.breakwater))
62        }
63    }
64}
65
66pub type New = extern "C" fn(*mut std::ffi::c_void);
67pub type Drop = extern "C" fn(*mut std::ffi::c_void);
68#[allow(improper_ctypes_definitions)] // only called from rust
69pub type DrawUi = extern "C" fn(
70    data: *mut std::ffi::c_void,
71    viewport_idx: u32,
72    ctx: &egui::Context,
73    advertised_endpoints: &[String],
74    connections: u32,
75    ips_v6: u32,
76    ips_v4: u32,
77    bytes_per_s: u64,
78);
79
80#[repr(C)]
81pub struct DynamicOverlay {
82    pub data: *mut std::ffi::c_void,
83    pub new: *const New,
84    pub draw_ui: DrawUi,
85    pub drop: *const Drop,
86}
87
88/// 38c3 colors
89#[allow(dead_code)]
90mod colors {
91    use egui::Color32;
92
93    pub const COLOR_PRIMARY: Color32 = Color32::from_rgb(0xFF, 0x50, 0x53);
94    pub const COLOR_HIGHLIGHT: Color32 = Color32::from_rgb(0xFE, 0xF2, 0xFF);
95    pub const COLOR_ACCENT_A: Color32 = Color32::from_rgb(0xB2, 0xAA, 0xFF);
96    pub const COLOR_ACCENT_B: Color32 = Color32::from_rgb(0x6A, 0x5F, 0xDB);
97    pub const COLOR_ACCENT_C: Color32 = Color32::from_rgb(0x29, 0x11, 0x4C);
98    pub const COLOR_ACCENT_D: Color32 = Color32::from_rgb(0x26, 0x1A, 0x66);
99    pub const COLOR_ACCENT_E: Color32 = Color32::from_rgb(0x19, 0x0B, 0x2F);
100    pub const COLOR_BACKGROUND: Color32 = Color32::from_rgb(0x0F, 0x00, 0x0A);
101}
102
103pub const DEFAULT_OVERLAY: DynamicOverlay = DynamicOverlay {
104    data: std::ptr::null_mut(),
105    new: std::ptr::null(),
106    draw_ui: draw_ui as _,
107    drop: std::ptr::null(),
108};
109
110#[allow(improper_ctypes_definitions)]
111extern "C" fn draw_ui(
112    _: *mut std::ffi::c_void,
113    viewport_idx: u32,
114    ctx: &egui::Context,
115    advertised_endpoints: &[String],
116    connections: u32,
117    ips_v6: u32,
118    ips_v4: u32,
119    bytes_per_s: u64,
120) {
121    // only display on first viewport
122    if viewport_idx > 0 {
123        return;
124    }
125
126    let stats_frame = egui::Frame {
127        fill: COLOR_BACKGROUND.gamma_multiply(0.7),
128        stroke: egui::Stroke::new(1.0_f32, COLOR_PRIMARY),
129        corner_radius: egui::CornerRadius::same(10),
130        shadow: eframe::epaint::Shadow::default(),
131        inner_margin: Margin::same(12),
132        outer_margin: Margin::same(12),
133    };
134
135    egui::Area::new(egui::Id::new("overlay_area"))
136        .movable(true)
137        .default_pos(egui::pos2(20.0, 20.0))
138        .show(ctx, |ui| {
139            stats_frame.show(ui, |ui| {
140                ui.label(
141                    egui::RichText::new("breakwater | Pixelflut")
142                        .size(48.0)
143                        .color(COLOR_HIGHLIGHT),
144                );
145                ui.separator();
146
147                egui::Grid::new(egui::Id::new("stats_header_grid")).show(ui, |ui| {
148                    for ep in advertised_endpoints {
149                        ui.label(
150                            egui::RichText::new(ep)
151                                .color(COLOR_HIGHLIGHT)
152                                .size(32.0)
153                                .strong(),
154                        );
155                        ui.end_row();
156                    }
157                });
158
159                ui.separator();
160                egui::Grid::new(egui::Id::new("stats_metrics_grid")).show(ui, |ui| {
161                    ui.label(
162                        egui::RichText::new("Connections: ")
163                            .color(COLOR_HIGHLIGHT)
164                            .size(24.0),
165                    );
166                    ui.label(
167                        egui::RichText::new(format!("{connections}"))
168                            .color(COLOR_HIGHLIGHT)
169                            .size(24.0)
170                            .strong(),
171                    );
172                    ui.label(
173                        egui::RichText::new("IPv6: ")
174                            .color(COLOR_HIGHLIGHT)
175                            .size(24.0),
176                    );
177                    ui.label(
178                        egui::RichText::new(format!("{ips_v6}"))
179                            .color(COLOR_HIGHLIGHT)
180                            .size(24.0)
181                            .strong(),
182                    );
183                    ui.end_row();
184                    ui.label(
185                        egui::RichText::new("RX: ")
186                            .color(COLOR_HIGHLIGHT)
187                            .size(24.0),
188                    );
189                    ui.label(
190                        egui::RichText::new(format!(
191                            "{:.2} GBit/s       ",
192                            (bytes_per_s * 8) as f32 / 1024.0 / 1024.0 / 1024.0
193                        ))
194                        .color(COLOR_HIGHLIGHT)
195                        .size(24.0)
196                        .strong(),
197                    );
198                    ui.label(
199                        egui::RichText::new("IPv4: ")
200                            .color(COLOR_HIGHLIGHT)
201                            .size(24.0),
202                    );
203                    ui.label(
204                        egui::RichText::new(format!("{ips_v4}"))
205                            .color(COLOR_HIGHLIGHT)
206                            .size(24.0)
207                            .strong(),
208                    );
209                    ui.end_row();
210                });
211            });
212        });
213}