Skip to content

Thumbnail mode improvements #512

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Jul 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,6 @@ include = [
"/build.rs",
]

[target."cfg(windows)"]
rustflags = ["-C", "target-feature=+crt-static"]

#═══════════════════════════════════════════════════════════════════════════════════════════════════════════════════════

[profile.release]
Expand Down
75 changes: 7 additions & 68 deletions src/gui/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,8 @@
//!
//! It also is a wrapper of gui's main two pages: initial and run page.

use std::time::Duration;

use iced::keyboard::key::Named;
use iced::keyboard::{Event, Key, Modifiers};
use iced::mouse::Event::ButtonPressed;
use iced::widget::Column;
use iced::window::Id;
use iced::Event::{Keyboard, Window};
use iced::{executor, window, Application, Command, Element, Subscription};
use iced::{executor, Application, Command, Element, Subscription};

use crate::gui::components::footer::footer;
use crate::gui::components::header::header;
Expand Down Expand Up @@ -122,66 +115,12 @@ impl Application for Sniffer {
}

fn subscription(&self) -> Subscription<Message> {
const NO_MODIFIER: Modifiers = Modifiers::empty();

// Window subscription
let window_sub = iced::event::listen_with(|event, _| match event {
Window(Id::MAIN, window::Event::Focused) => Some(Message::WindowFocused),
Window(Id::MAIN, window::Event::Moved { x, y }) => Some(Message::WindowMoved(x, y)),
Window(Id::MAIN, window::Event::Resized { width, height }) => {
Some(Message::WindowResized(width, height))
}
Window(Id::MAIN, window::Event::CloseRequested) => Some(Message::CloseRequested),
_ => None,
});

// Keyboard subscription
let keyboard_sub = iced::event::listen_with(|event, _| match event {
Keyboard(Event::KeyPressed { key, modifiers, .. }) => match modifiers {
Modifiers::COMMAND => match key.as_ref() {
Key::Character("q") => Some(Message::CloseRequested),
Key::Character(",") => Some(Message::OpenLastSettings),
Key::Named(Named::Backspace) => Some(Message::ResetButtonPressed),
Key::Character("d") => Some(Message::CtrlDPressed),
Key::Named(Named::ArrowLeft) => Some(Message::ArrowPressed(false)),
Key::Named(Named::ArrowRight) => Some(Message::ArrowPressed(true)),
_ => None,
},
Modifiers::SHIFT => match key {
Key::Named(Named::Tab) => Some(Message::SwitchPage(false)),
_ => None,
},
NO_MODIFIER => match key {
Key::Named(Named::Enter) => Some(Message::ReturnKeyPressed),
Key::Named(Named::Escape) => Some(Message::EscKeyPressed),
Key::Named(Named::Tab) => Some(Message::SwitchPage(true)),
_ => None,
},
_ => None,
},
_ => None,
});

// Mouse subscription
let mouse_sub = iced::event::listen_with(|event, _| match event {
iced::event::Event::Mouse(ButtonPressed(_)) => Some(Message::Drag),
_ => None,
});

// Time subscription
let time_sub = if self.running_page.eq(&RunningPage::Init) {
iced::time::every(Duration::from_millis(PERIOD_TICK)).map(|_| Message::TickInit)
} else {
iced::time::every(Duration::from_millis(PERIOD_TICK)).map(|_| Message::TickRun)
};

let mut subscriptions = Vec::from([window_sub, time_sub]);
if self.thumbnail {
subscriptions.push(mouse_sub);
} else {
subscriptions.push(keyboard_sub);
}
Subscription::batch(subscriptions)
Subscription::batch([
self.keyboard_subscription(),
self.mouse_subscription(),
self.time_subscription(),
Sniffer::window_subscription(),
])
}

fn theme(&self) -> Self::Theme {
Expand Down
35 changes: 24 additions & 11 deletions src/gui/pages/thumbnail_page.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use crate::gui::styles::style_constants::FONT_SIZE_FOOTER;
use crate::gui::styles::types::style_type::StyleType;
use crate::gui::types::message::Message;
use crate::gui::types::sniffer::Sniffer;
use crate::networking::types::host::Host;
use crate::networking::types::host::{Host, ThumbnailHost};
use crate::networking::types::info_traffic::InfoTraffic;
use crate::report::get_report_entries::{get_host_entries, get_service_entries};
use crate::report::types::sort_type::SortType;
Expand Down Expand Up @@ -71,22 +71,35 @@ fn host_col(
.spacing(3)
.width(Length::FillPortion(2));
let hosts = get_host_entries(info_traffic, chart_type, SortType::Neutral);
let n_entry = min(hosts.len(), MAX_ENTRIES);
for (host, data_info_host) in hosts.get(..n_entry).unwrap_or_default() {
let flag = get_flag_tooltip(
host.country,
data_info_host,
Language::default(),
font,
true,
);
let mut thumbnail_hosts = Vec::new();

for (host, data_info_host) in &hosts {
let text = host_text(host);
let country = host.country;
let thumbnail_host = ThumbnailHost {
country,
text: text.clone(),
};

if thumbnail_hosts.contains(&thumbnail_host) {
continue;
}

thumbnail_hosts.push(thumbnail_host);

let flag = get_flag_tooltip(country, data_info_host, Language::default(), font, true);
let host_row = Row::new()
.align_items(Alignment::Center)
.spacing(5)
.push(flag)
.push(Text::new(host_text(host)).font(font).size(FONT_SIZE_FOOTER));
.push(Text::new(text).font(font).size(FONT_SIZE_FOOTER));
host_col = host_col.push(host_row);

if thumbnail_hosts.len() >= MAX_ENTRIES {
break;
}
}

host_col
}

Expand Down
2 changes: 2 additions & 0 deletions src/gui/types/message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,4 +115,6 @@ pub enum Message {
ToggleThumbnail(bool),
/// Drag the window
Drag,
/// Ctrl+T keys have been pressed
CtrlTPressed,
}
103 changes: 98 additions & 5 deletions src/gui/types/sniffer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,20 @@ use std::collections::{HashSet, VecDeque};
use std::path::PathBuf;
use std::sync::{Arc, Mutex};
use std::thread;
use std::time::Duration;

use iced::keyboard::key::Named;
use iced::keyboard::{Event, Key, Modifiers};
use iced::mouse::Event::ButtonPressed;
use iced::window::{Id, Level};
use iced::{window, Command};
use iced::Event::{Keyboard, Window};
use iced::{window, Command, Subscription};
use pcap::Device;
use rfd::FileHandle;

use crate::chart::manage_chart_data::update_charts_data;
use crate::configs::types::config_window::{ConfigWindow, ScaleAndCheck, ToPoint, ToSize};
use crate::gui::app::PERIOD_TICK;
use crate::gui::components::types::my_modal::MyModal;
use crate::gui::pages::types::running_page::RunningPage;
use crate::gui::pages::types::settings_page::SettingsPage;
Expand Down Expand Up @@ -142,6 +148,83 @@ impl Sniffer {
}
}

pub(crate) fn keyboard_subscription(&self) -> Subscription<Message> {
const NO_MODIFIER: Modifiers = Modifiers::empty();

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::CloseRequested),
Key::Character("t") => Some(Message::CtrlTPressed),
_ => None,
},
_ => None,
})
} else {
iced::event::listen_with(|event, _| match event {
Keyboard(Event::KeyPressed { key, modifiers, .. }) => match modifiers {
Modifiers::COMMAND => match key.as_ref() {
Key::Character("q") => Some(Message::CloseRequested),
Key::Character("t") => Some(Message::CtrlTPressed),
Key::Character(",") => Some(Message::OpenLastSettings),
Key::Named(Named::Backspace) => Some(Message::ResetButtonPressed),
Key::Character("d") => Some(Message::CtrlDPressed),
Key::Named(Named::ArrowLeft) => Some(Message::ArrowPressed(false)),
Key::Named(Named::ArrowRight) => Some(Message::ArrowPressed(true)),
_ => None,
},
Modifiers::SHIFT => match key {
Key::Named(Named::Tab) => Some(Message::SwitchPage(false)),
_ => None,
},
NO_MODIFIER => match key {
Key::Named(Named::Enter) => Some(Message::ReturnKeyPressed),
Key::Named(Named::Escape) => Some(Message::EscKeyPressed),
Key::Named(Named::Tab) => Some(Message::SwitchPage(true)),
_ => None,
},
_ => None,
},
_ => None,
})
}
}

pub(crate) 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()
}
}

pub(crate) fn time_subscription(&self) -> Subscription<Message> {
if self.running_page.eq(&RunningPage::Init) {
iced::time::every(Duration::from_millis(PERIOD_TICK)).map(|_| Message::TickInit)
} else {
iced::time::every(Duration::from_millis(PERIOD_TICK)).map(|_| Message::TickRun)
}
}

pub(crate) fn window_subscription() -> Subscription<Message> {
iced::event::listen_with(|event, _| match event {
Window(Id::MAIN, window::Event::Focused) => Some(Message::WindowFocused),
Window(Id::MAIN, window::Event::Moved { x, y }) => Some(Message::WindowMoved(x, y)),
Window(Id::MAIN, window::Event::Resized { width, height }) => {
Some(Message::WindowResized(width, height))
}
Window(Id::MAIN, window::Event::CloseRequested) => Some(Message::CloseRequested),
_ => None,
})
}

pub fn update(&mut self, message: Message) -> Command<Message> {
match message {
Message::TickRun => return self.refresh_data(),
Expand Down Expand Up @@ -390,7 +473,20 @@ impl Sniffer {
};
}
Message::Drag => {
return window::drag(Id::MAIN);
let was_just_thumbnail_click = self.timing_events.was_just_thumbnail_click();
self.timing_events.thumbnail_click_now();
if was_just_thumbnail_click {
return window::drag(Id::MAIN);
}
}
Message::CtrlTPressed => {
if self.running_page.ne(&RunningPage::Init)
&& self.settings_page.is_none()
&& self.modal.is_none()
&& !self.timing_events.was_just_thumbnail_enter()
{
return self.update(Message::ToggleThumbnail(false));
}
}
Message::TickInit => {}
}
Expand Down Expand Up @@ -703,10 +799,8 @@ mod tests {

use std::collections::{HashSet, VecDeque};
use std::fs::remove_file;
use std::ops::Sub;
use std::path::Path;
use std::sync::{Arc, Mutex};
use std::time::Duration;

use serial_test::{parallel, serial};

Expand Down Expand Up @@ -1600,7 +1694,6 @@ mod tests {
#[parallel] // needed to not collide with other tests generating configs files
fn test_correctly_switch_running_and_settings_pages() {
let mut sniffer = new_sniffer();
sniffer.timing_events.focus = std::time::Instant::now().sub(Duration::from_millis(400));

// initial status
assert_eq!(sniffer.settings_page, None);
Expand Down
18 changes: 16 additions & 2 deletions src/gui/types/timing_events.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,22 @@
use std::ops::Sub;
use std::time::Duration;

pub struct TimingEvents {
/// Instant of the last window focus
pub(crate) focus: std::time::Instant,
focus: std::time::Instant,
/// Instant of the last press on Copy IP button, with the related IP address
copy_ip: (std::time::Instant, String),
/// Instant of the last thumbnail mode enter
thumbnail_enter: std::time::Instant,
/// Instant of the last click on the thumbnail window
thumbnail_click: std::time::Instant,
}

impl TimingEvents {
const TIMEOUT_FOCUS: u64 = 200;
const TIMEOUT_COPY_IP: u64 = 1500;
const TIMEOUT_THUMBNAIL_ENTER: u64 = 1000;
const TIMEOUT_THUMBNAIL_CLICK: u64 = 800;

pub fn focus_now(&mut self) {
self.focus = std::time::Instant::now();
Expand All @@ -39,14 +43,24 @@ impl TimingEvents {
self.thumbnail_enter.elapsed()
< Duration::from_millis(TimingEvents::TIMEOUT_THUMBNAIL_ENTER)
}

pub fn thumbnail_click_now(&mut self) {
self.thumbnail_click = std::time::Instant::now();
}

pub fn was_just_thumbnail_click(&self) -> bool {
self.thumbnail_click.elapsed()
< Duration::from_millis(TimingEvents::TIMEOUT_THUMBNAIL_CLICK)
}
}

impl Default for TimingEvents {
fn default() -> Self {
Self {
focus: std::time::Instant::now(),
focus: std::time::Instant::now().sub(Duration::from_millis(400)),
copy_ip: (std::time::Instant::now(), String::new()),
thumbnail_enter: std::time::Instant::now(),
thumbnail_click: std::time::Instant::now(),
}
}
}
12 changes: 12 additions & 0 deletions src/networking/types/host.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,15 @@ pub struct Host {
/// Country
pub country: Country,
}

/// Struct to represent a network host for representation in the thumbnail
///
/// This is necessary to remove possible duplicates in the thumbnail host list
#[allow(clippy::module_name_repetitions)]
#[derive(PartialEq)]
pub struct ThumbnailHost {
/// Country
pub country: Country,
/// Text describing the host in the thumbnail
pub text: String,
}