# How Sniffnet's Thumbnail Mode Works: A Technical Deep Dive

> Understand how Sniffnet's thumbnail mode works by diving into its technical implementation. Learn how it uses a compact always-on-top window and a minimal dashboard to display truncated traffic data.

- Repository: [Giuliano Bellini/sniffnet](https://github.com/GyulyVGC/sniffnet)
- Tags: deep-dive
- Published: 2026-04-28

---

**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`](https://github.com/GyulyVGC/sniffnet/blob/main/src/gui/sniffer.rs) and the compact layout definition in [`src/gui/pages/thumbnail_page.rs`](https://github.com/GyulyVGC/sniffnet/blob/main/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.

```rust
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)](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:

```rust
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`](https://github.com/GyulyVGC/sniffnet/blob/main/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.

```rust
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)](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).

```rust
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)](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.

```rust
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)](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:

```rust
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)](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`](https://github.com/GyulyVGC/sniffnet/blob/main/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.

```rust
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)](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`](https://github.com/GyulyVGC/sniffnet/blob/main/src/networking/types/info_traffic.rs) computes the three numeric values for the donut chart: incoming, outgoing, and dropped data.

```rust
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)](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`](https://github.com/GyulyVGC/sniffnet/blob/main/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`](https://github.com/GyulyVGC/sniffnet/blob/main/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`](https://github.com/GyulyVGC/sniffnet/blob/main/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`](https://github.com/GyulyVGC/sniffnet/blob/main/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.