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:

  1. window::toggle_decorations – Removes the OS window frame including the title bar and borders.
  2. window::resize – Sets the window to the fixed THUMBNAIL_SIZE constant (360×222 pixels).
  3. window::move_to – Repositions the window to the saved thumbnail coordinates from the configuration.
  4. window::set_level(..., AlwaysOnTop) – Elevates the window Z-order to stay above other applications.
  5. 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 thumbnail boolean field in src/gui/sniffer.rs, which propagates to components like traffic_chart.
  • Window Management: The toggle_thumbnail method dispatches iced commands—window::resize, window::move_to, and window::set_level with AlwaysOnTop—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_page function in src/gui/pages/thumbnail_page.rs renders a four-section layout using data from InfoTraffic::get_thumbnail_data and 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →