How Sniffnet's Thumbnail Mode Works: A Technical Deep Dive
Sniffnet's thumbnail mode switches the application to a compact 360×222 pixel always-on-top window by setting a boolean flag in the Sniffer struct and dispatching iced window commands to resize, reposition, and remove decorations, while the thumbnail_page function renders a minimal dashboard using truncated traffic data.
Sniffnet is an open-source network traffic analyzer written in Rust that provides a thumbnail view for minimal desktop intrusion. Understanding how Sniffnet's thumbnail mode works requires examining the interplay between the thumbnail boolean state, iced window management commands, and conditional UI rendering pipelines. The implementation spans the core application state in src/gui/sniffer.rs and the compact layout definition in src/gui/pages/thumbnail_page.rs.
The Thumbnail Flag and State Management
The Sniffer Struct Definition
The application stores the thumbnail state as a public boolean field within the Sniffer struct. This flag acts as the single source of truth for the entire UI pipeline.
pub struct Sniffer {
// … other fields …
/// Whether thumbnail mode is currently active
pub thumbnail: bool,
// … other fields …
}
Source: [src/gui/sniffer.rs](https://github.com/GyulyVGC/sniffnet/blob/main/src/gui/sniffer.rs#L33-L35)
Propagating State to Components
When the mode toggles, the flag propagates to child components that require layout awareness. Specifically, the traffic chart receives the update to adjust its rendering dimensions:
self.thumbnail = !self.thumbnail; // ← flip the flag
self.traffic_chart.thumbnail = self.thumbnail; // propagate to the chart
This propagation ensures the real-time preview chart in src/chart/types/preview_chart.rs knows to draw a compact version suitable for the small window constraints.
Toggling Sniffnet's Thumbnail Mode
The toggle_thumbnail Method
The state transition logic resides in Sniffer::toggle_thumbnail, which accepts a triggered_by_resize boolean to handle edge cases where the user manually resizes the window while already in thumbnail mode.
fn toggle_thumbnail(&mut self, triggered_by_resize: bool) -> Task<Message> {
let window_id = self.id.unwrap_or_else(Id::unique);
self.thumbnail = !self.thumbnail;
self.traffic_chart.thumbnail = self.thumbnail;
if self.thumbnail {
// → enter thumbnail
let size = THUMBNAIL_SIZE;
let position = self.conf.window.thumbnail_position();
self.timing_events.thumbnail_enter_now();
Task::batch([
window::maximize(window_id, false),
window::toggle_decorations(window_id),
window::resize(window_id, size),
window::move_to(window_id, position),
window::set_level(window_id, Level::AlwaysOnTop),
])
} else {
// → exit thumbnail
let mut commands = vec![
window::toggle_decorations(window_id),
window::set_level(window_id, Level::Normal),
];
if !triggered_by_resize {
let size = self.conf.window.size();
let position = self.conf.window.position();
commands.push(window::move_to(window_id, position));
commands.push(window::resize(window_id, size));
}
Task::batch(commands)
}
}
Source: [src/gui/sniffer.rs](https://github.com/GyulyVGC/sniffnet/blob/main/src/gui/sniffer.rs#L127-176)
Window Transformation Steps:
window::toggle_decorations– Removes the OS window frame including the title bar and borders.window::resize– Sets the window to the fixedTHUMBNAIL_SIZEconstant (360×222 pixels).window::move_to– Repositions the window to the saved thumbnail coordinates from the configuration.window::set_level(..., AlwaysOnTop)– Elevates the window Z-order to stay above other applications.- When exiting, decorations are restored, Z-order resets to
Level::Normal, and the original size and position are restored unless the call originated from a resize event.
All commands return as a Task<Message> batch executed by the iced runtime, ensuring the packet-capture thread remains unblocked during the transition.
Restricted Input Handling in Thumbnail Mode
Keyboard Shortcuts
The keyboard_subscription method filters available shortcuts when self.thumbnail is true. Only three combinations remain active: Ctrl+T (toggle), Ctrl+Q (quit), and Ctrl+Space (pause/resume).
fn keyboard_subscription(&self) -> Subscription<Message> {
if self.thumbnail {
iced::event::listen_with(|event, _, _| match event {
Keyboard(Event::KeyPressed {
key,
modifiers: Modifiers::COMMAND,
..
}) => match key.as_ref() {
Key::Character("q") => Some(Message::QuitWrapper),
Key::Character("t") => Some(Message::CtrlTPressed),
Key::Named(Named::Space) => Some(Message::CtrlSpacePressed),
_ => None,
},
_ => None,
})
} else {
// Full shortcut set for normal mode
}
}
Source: [src/gui/sniffer.rs](https://github.com/GyulyVGC/sniffnet/blob/main/src/gui/sniffer.rs#L98-L112)
Mouse Drag Interaction
Mouse functionality collapses to drag-to-move only. The mouse_subscription emits a Drag message on any button press, which the drag() method handles by checking a short-term timer (timing_events) to prevent accidental moves immediately after entering thumbnail mode.
fn mouse_subscription(&self) -> Subscription<Message> {
if self.thumbnail {
iced::event::listen_with(|event, _, _| match event {
iced::event::Event::Mouse(ButtonPressed(_)) => Some(Message::Drag),
_ => None,
})
} else {
Subscription::none()
}
}
Source: [src/gui/sniffer.rs](https://github.com/GyulyVGC/sniffnet/blob/main/src/gui/sniffer.rs#L124-L132)
Rendering the Compact Dashboard
View Dispatch Logic
The global view() method checks the thumbnail flag to select between the full interface and the compact layout:
let body = if self.thumbnail {
thumbnail_page(self) // ← compact UI
} else {
// normal page handling …
};
Source: [src/gui/sniffer.rs](https://github.com/GyulyVGC/sniffnet/blob/main/src/gui/sniffer.rs#L81-L84)
The thumbnail_page Layout
The thumbnail_page function in src/gui/pages/thumbnail_page.rs constructs the mini-dashboard using three horizontal sections: a donut chart with traffic preview, and two columns listing top hosts and services.
pub fn thumbnail_page(sniffer: &Sniffer) -> Container<'_, Message, StyleType> {
// 1️⃣ Show an animated “waiting” indicator if no packets yet
if tot_packets == 0 {
return Container::new(...);
}
// 2️⃣ Retrieve the current traffic summary for the donut chart
let (in_data, out_data, dropped) = info_traffic.get_thumbnail_data(sniffer.conf.data_repr);
// 3️⃣ Build the donut + traffic‑preview chart row
let charts = Row::new()
.push(donut_chart(...))
.push(Container::new(sniffer.traffic_chart.view()));
// 4️⃣ Build the host and service columns (max 4 entries each)
let report = Row::new()
.push(host_col(sniffer))
.push(RuleType::Standard.vertical(10))
.push(service_col(sniffer));
// 5️⃣ Compose everything into a vertical Column
Container::new(Column::new().push(charts).push(report))
}
Source: [src/gui/pages/thumbnail_page.rs](https://github.com/GyulyVGC/sniffnet/blob/main/src/gui/pages/thumbnail_page.rs#L23-L73)
Data Sources and Truncation
Traffic Summary Calculation:
The InfoTraffic::get_thumbnail_data method in src/networking/types/info_traffic.rs computes the three numeric values for the donut chart: incoming, outgoing, and dropped data.
pub fn get_thumbnail_data(&self, data_repr: DataRepr) -> (u128, u128, u128) {
let incoming = self.tot_data_info.incoming_data(data_repr);
let outgoing = self.tot_data_info.outgoing_data(data_repr);
let all = incoming + outgoing;
let all_packets = self.tot_data_info.tot_data(DataRepr::Packets);
let dropped = match data_repr {
DataRepr::Packets => u128::from(self.dropped_packets),
DataRepr::Bytes | DataRepr::Bits => {
u128::from(self.dropped_packets) * all / all_packets
}
};
(incoming, outgoing, dropped)
}
Source: [src/networking/types/info_traffic.rs](https://github.com/GyulyVGC/sniffnet/blob/main/src/networking/types/info_traffic.rs#L92-L106)
Favorite Hosts and Services:
The host and service columns display a maximum of MAX_ENTRIES (4) items each, truncated to MAX_CHARS_HOST (26) and MAX_CHARS_SERVICE (13) characters respectively. These constraints ensure the layout remains readable within the 360-pixel width limit.
Summary
- State Control: Sniffnet's thumbnail mode relies on the
thumbnailboolean field insrc/gui/sniffer.rs, which propagates to components liketraffic_chart. - Window Management: The
toggle_thumbnailmethod dispatches iced commands—window::resize,window::move_to, andwindow::set_levelwithAlwaysOnTop—to transform the window geometry and Z-order. - Input Restrictions: User interaction is limited to Ctrl+T (toggle), Ctrl+Q (quit), Ctrl+Space (pause), and drag-to-move gestures when the mode is active.
- Compact Rendering: The
thumbnail_pagefunction insrc/gui/pages/thumbnail_page.rsrenders a four-section layout using data fromInfoTraffic::get_thumbnail_dataand truncated favorite lists.
Frequently Asked Questions
What keyboard shortcut activates Sniffnet's thumbnail mode?
Press Ctrl+T (or Command+T on macOS) to toggle thumbnail mode on or off. This shortcut emits Message::CtrlTPressed, which triggers the toggle_thumbnail method in src/gui/sniffer.rs.
How does Sniffnet stay above other windows in thumbnail mode?
The application calls window::set_level(window_id, Level::AlwaysOnTop) from the iced window API when entering thumbnail mode. This sets the window's Z-order to remain above standard application windows until the mode is exited and Level::Normal is restored.
Why does the traffic chart look different in thumbnail mode?
The traffic_chart component checks its own thumbnail boolean flag to determine dimensions. When true, it renders a compact version of the real-time preview using smaller dimensions defined in src/chart/types/preview_chart.rs, fitting the constrained 360×222 pixel layout.
Can I move the thumbnail window around the screen?
Yes, the thumbnail window supports drag-to-move functionality. The mouse_subscription listens for mouse button presses and emits a Drag message, which invokes iced::window::drag after validating that the click is not part of an accidental interaction using the timing_events cooldown mechanism.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →