Skip to content

Commit

Permalink
Merge pull request #512 from GyulyVGC/improve-thumbnail
Browse files Browse the repository at this point in the history
Thumbnail mode improvements
  • Loading branch information
GyulyVGC committed Jul 5, 2024
2 parents 0662338 + 355d923 commit cddf43c
Show file tree
Hide file tree
Showing 7 changed files with 159 additions and 89 deletions.
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,
}

0 comments on commit cddf43c

Please sign in to comment.