diff --git a/Cargo.lock b/Cargo.lock
index 3052e7f1..f097cf2c 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -485,7 +485,7 @@ dependencies = [
"directories",
"serde",
"thiserror",
- "toml",
+ "toml 0.5.11",
]
[[package]]
@@ -2500,7 +2500,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919"
dependencies = [
"once_cell",
- "toml_edit",
+ "toml_edit 0.19.14",
]
[[package]]
@@ -3032,6 +3032,24 @@ dependencies = [
"serde",
]
+[[package]]
+name = "serde_spanned"
+version = "0.6.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "96426c9936fd7a0124915f9185ea1d20aa9445cc9821142f0a73bc9207a2e186"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "serde_test"
+version = "1.0.176"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5a2f49ace1498612d14f7e0b8245519584db8299541dfe31a06374a828d620ab"
+dependencies = [
+ "serde",
+]
+
[[package]]
name = "serde_urlencoded"
version = "0.7.1"
@@ -3142,6 +3160,8 @@ dependencies = [
"rodio",
"rstest",
"serde",
+ "serde_test",
+ "toml 0.8.0",
"winres",
]
@@ -3505,11 +3525,26 @@ dependencies = [
"serde",
]
+[[package]]
+name = "toml"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c226a7bba6d859b63c92c4b4fe69c5b6b72d0cb897dbc8e6012298e6154cb56e"
+dependencies = [
+ "serde",
+ "serde_spanned",
+ "toml_datetime",
+ "toml_edit 0.20.0",
+]
+
[[package]]
name = "toml_datetime"
version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b"
+dependencies = [
+ "serde",
+]
[[package]]
name = "toml_edit"
@@ -3522,6 +3557,19 @@ dependencies = [
"winnow",
]
+[[package]]
+name = "toml_edit"
+version = "0.20.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ff63e60a958cefbb518ae1fd6566af80d9d4be430a33f3723dfc47d1d411d95"
+dependencies = [
+ "indexmap 2.0.0",
+ "serde",
+ "serde_spanned",
+ "toml_datetime",
+ "winnow",
+]
+
[[package]]
name = "tower-service"
version = "0.3.2"
@@ -4421,7 +4469,7 @@ version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b68db261ef59e9e52806f688020631e987592bd83619edccda9c47d42cde4f6c"
dependencies = [
- "toml",
+ "toml 0.5.11",
]
[[package]]
diff --git a/Cargo.toml b/Cargo.toml
index 9011cb40..bc8b95f7 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -45,6 +45,7 @@ confy = "0.5.1"
serde = { version = "1.0.188", default_features = false, features = ["derive"] }
rodio = { version = "0.17.1", default_features = false, features = ["mp3"] }
dns-lookup = "2.0.2"
+toml = "0.8.0"
[target.'cfg(target_arch = "powerpc64")'.dependencies]
reqwest = { version = "0.11.20", features = ["json", "blocking"] }
@@ -55,6 +56,7 @@ reqwest = { version = "0.11.20", default-features = false, features = ["json", "
#───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
[dev-dependencies]
+serde_test = "1.0.176"
rstest = "0.18.2"
#───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
diff --git a/README.md b/README.md
index 171d45ab..ca107c11 100644
--- a/README.md
+++ b/README.md
@@ -1,12 +1,12 @@
-
+
-
+
Application to comfortably monitor your Internet traffic
Multithreaded, cross-platform, reliable
🌐 www.sniffnet.net
@@ -78,17 +78,17 @@ You can install Sniffnet in one of the following ways:
[32-bit](https://github.com/GyulyVGC/sniffnet/releases/latest/download/Sniffnet_Windows_32-bit.msi)
### macOS
-
+
- [Intel](https://github.com/GyulyVGC/sniffnet/releases/latest/download/Sniffnet_macOS_Intel.dmg) |
[Apple silicon](https://github.com/GyulyVGC/sniffnet/releases/latest/download/Sniffnet_macOS_AppleSilicon.dmg)
### Linux
- deb: [amd64](https://github.com/GyulyVGC/sniffnet/releases/latest/download/Sniffnet_LinuxDEB_amd64.deb) |
-[arm64](https://github.com/GyulyVGC/sniffnet/releases/latest/download/Sniffnet_LinuxDEB_arm64.deb) |
+[arm64](https://github.com/GyulyVGC/sniffnet/releases/latest/download/Sniffnet_LinuxDEB_arm64.deb) |
[i386](https://github.com/GyulyVGC/sniffnet/releases/latest/download/Sniffnet_LinuxDEB_i386.deb) |
[armhf](https://github.com/GyulyVGC/sniffnet/releases/latest/download/Sniffnet_LinuxDEB_armhf.deb)
-
+
- rpm: [x86_64](https://github.com/GyulyVGC/sniffnet/releases/latest/download/Sniffnet_LinuxRPM_x86_64.rpm) |
[aarch64](https://github.com/GyulyVGC/sniffnet/releases/latest/download/Sniffnet_LinuxRPM_aarch64.rpm)
@@ -245,7 +245,7 @@ sudo sniffnet
- 🌍 get information about the country of the remote hosts (IP geolocation)
- ⭐ save your favorite network hosts
- 🔉 set custom notifications to inform you when defined network events occur
-- 🎨 choose the style that fits you the most from 4 different available themes
+- 🎨 choose the style that fits you the most from 12 different available themes, plus custom theme support
- 🕵️ inspect each of your network connections in real time
- 📁 save complete textual reports with detailed information for each network connection:
* source and destination IP addresses
@@ -267,13 +267,13 @@ sudo sniffnet
Geolocation and network providers (ASN) refer to the remote IP address of each connection. They are retrieved performing lookups against [MMDB files](https://maxmind.github.io/MaxMind-DB/):
> **Note**
- >
+ >
> The MMDB (MaxMind database) format has been developed especially for IP lookup.
> It is optimized to perform lookups on data indexed by IP network ranges quickly and efficiently.
> It permits the best performance on IP lookups, and it's suitable for use in a production environment.
- >
+ >
> This product includes GeoLite2 data created by MaxMind, available from https://www.maxmind.com
-
+
This file format potentially allows Sniffnet to execute hundreds of different IP lookups in a matter of a few milliseconds.
@@ -284,23 +284,23 @@ sudo sniffnet
See details
-
+
-
+
Application layer protocols are inferred from the transport port numbers,
following the convention maintained by [IANA](https://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.xhtml).
Please, remember that this is just a convention:
> **Warning**
- >
+ >
> The Internet Assigned Numbers Authority (IANA) is responsible for maintaining
> the official assignments of port numbers for specific uses.
> However, many unofficial uses of well-known port numbers occur in practice.
The following table reports the port-to-service mappings used by Sniffnet,
chosen from the most common assignments by IANA.
-
+
|Port number(s)|Application protocol | Description |
@@ -372,6 +372,35 @@ The currently usable hotkeys are reported in the following.
+## Custom themes
+
+
+ See details
+
+ Custom themes are specified as a TOML file.
+
+ The TOML must follow this format:
+ ```toml
+ # Colors are in RGB/RGBA hexadecimal.
+ primary = "#1e1e2e" # Background
+ secondary = "#89b4fa" # Headers / incoming connections
+ buttons = "#313244" # Buttons
+ outgoing = "#f5c2e7" # Outgoing connections
+ text_headers = "#11111b" # Text headers
+ text_body = "#cdd6f4" # Text body
+ starred = "#f9e2afaa" # Favorites
+
+ # The following parameters are in the range [0.0, 1.0].
+ round_borders_alpha = 0.3 # Borders opacity
+ round_containers_alpha = 0.15 # Containers opacity
+ chart_badge_alpha = 0.2 # Chart opacity
+
+ # Set to true if the theme is dark, false if it's light.
+ nightly = true
+ ```
+
+ The example theme above uses colors from [Catppuccin Mocha](https://github.com/catppuccin/catppuccin).
+
## Troubleshooting
@@ -388,9 +417,9 @@ Check the [required dependencies](#required-dependencies) section for instructio
### Rendering problems
In some circumstances, especially if you are running on an old architecture or your graphical drivers are not up-to-date,
-the `wgpu` default renderer used by [iced](https://github.com/iced-rs/iced)
+the `wgpu` default renderer used by [iced](https://github.com/iced-rs/iced)
may cause problems (country icons are completely black, or the interface glitches).
-In these cases you can download an alternative version of the application,
+In these cases you can download an alternative version of the application,
which is based on `tiny-skia`, a CPU-only software renderer that should work properly on every environment:
[Windows](https://github.com/GyulyVGC/sniffnet/suites/14909529200/artifacts/849640695) |
[macOS](https://github.com/GyulyVGC/sniffnet/suites/14909529200/artifacts/849640694) |
diff --git a/resources/themes/catppuccin_mocha.toml b/resources/themes/catppuccin_mocha.toml
new file mode 100644
index 00000000..1b7cc74f
--- /dev/null
+++ b/resources/themes/catppuccin_mocha.toml
@@ -0,0 +1,16 @@
+# Colors are in RGB/RGBA hexadecimal.
+primary = "#1e1e2e" # Background
+secondary = "#89b4fa" # Headers / incoming connections
+buttons = "#313244" # Buttons
+outgoing = "#f5c2e7" # Outgoing connections
+text_headers = "#11111b" # Text headers
+text_body = "#cdd6f4" # Text body
+starred = "#f9e2afaa" # Favorites
+
+# The following parameters are in the range [0.0, 1.0].
+round_borders_alpha = 0.3 # Borders opacity
+round_containers_alpha = 0.15 # Containers opacity
+chart_badge_alpha = 0.2 # Chart opacity
+
+# Set to true if the theme is dark, false if it's light.
+nightly = true
\ No newline at end of file
diff --git a/src/configs/types/config_advanced_settings.rs b/src/configs/types/config_advanced_settings.rs
index a71e2e5f..fd175bcc 100644
--- a/src/configs/types/config_advanced_settings.rs
+++ b/src/configs/types/config_advanced_settings.rs
@@ -1,16 +1,19 @@
//! Module defining the `ConfigAdvancedSettings` struct, which allows to save and reload
//! the application advanced settings.
-use crate::utils::formatted_strings::get_default_report_directory;
-use serde::{Deserialize, Serialize};
use std::path::PathBuf;
+use serde::{Deserialize, Serialize};
+
+use crate::utils::formatted_strings::get_default_report_directory;
+
#[derive(Serialize, Deserialize, Clone, PartialEq)]
pub struct ConfigAdvancedSettings {
pub scale_factor: f64,
pub mmdb_country: String,
pub mmdb_asn: String,
pub output_path: PathBuf,
+ pub style_path: PathBuf,
}
impl ConfigAdvancedSettings {
@@ -42,6 +45,7 @@ impl Default for ConfigAdvancedSettings {
mmdb_country: String::new(),
mmdb_asn: String::new(),
output_path: get_default_report_directory(),
+ style_path: PathBuf::new(),
}
}
}
diff --git a/src/gui/pages/inspect_page.rs b/src/gui/pages/inspect_page.rs
index e152ba81..5687a122 100644
--- a/src/gui/pages/inspect_page.rs
+++ b/src/gui/pages/inspect_page.rs
@@ -1,3 +1,5 @@
+use std::path::Path;
+
use iced::alignment::{Horizontal, Vertical};
use iced::widget::scrollable::Direction;
use iced::widget::tooltip::Position;
@@ -6,7 +8,6 @@ use iced::widget::{
lazy, Button, Checkbox, Column, Container, PickList, Row, Scrollable, Text, TextInput, Tooltip,
};
use iced::{alignment, Alignment, Font, Length, Renderer};
-use std::path::Path;
use crate::gui::components::tab::get_pages_tabs;
use crate::gui::components::types::my_modal::MyModal;
diff --git a/src/gui/pages/settings_advanced_page.rs b/src/gui/pages/settings_advanced_page.rs
index 36dc44d9..2e5940bb 100644
--- a/src/gui/pages/settings_advanced_page.rs
+++ b/src/gui/pages/settings_advanced_page.rs
@@ -1,3 +1,15 @@
+use std::path::PathBuf;
+use std::sync::Arc;
+
+use iced::advanced::widget::Text;
+use iced::alignment::{Horizontal, Vertical};
+use iced::widget::tooltip::Position;
+use iced::widget::{
+ button, horizontal_space, vertical_space, Column, Container, Row, Slider, TextInput, Tooltip,
+};
+use iced::Length::Fixed;
+use iced::{Alignment, Font, Length, Renderer};
+
use crate::gui::components::tab::get_settings_tabs;
use crate::gui::pages::settings_notifications_page::settings_header;
use crate::gui::pages::types::settings_page::SettingsPage;
@@ -5,27 +17,18 @@ use crate::gui::styles::container::ContainerType;
use crate::gui::styles::style_constants::{get_font, get_font_headers, FONT_SIZE_SUBTITLE};
use crate::gui::styles::text::TextType;
use crate::gui::styles::text_input::TextInputType;
+use crate::gui::styles::types::custom_palette::CustomPalette;
use crate::gui::types::message::Message;
use crate::translations::translations_2::country_translation;
use crate::translations::translations_3::{
- advanced_settings_translation, file_path_translation, info_mmdb_paths_translation,
- mmdb_paths_translation, params_not_editable_translation, restore_defaults_translation,
- scale_factor_translation,
+ advanced_settings_translation, custom_style_translation, file_path_translation,
+ info_mmdb_paths_translation, mmdb_paths_translation, params_not_editable_translation,
+ restore_defaults_translation, scale_factor_translation,
};
use crate::utils::asn::MmdbReader;
use crate::utils::formatted_strings::get_default_report_directory;
use crate::utils::types::icon::Icon;
use crate::{ConfigAdvancedSettings, Language, Sniffer, Status, StyleType};
-use iced::advanced::widget::Text;
-use iced::alignment::{Horizontal, Vertical};
-use iced::widget::tooltip::Position;
-use iced::widget::{
- button, horizontal_space, vertical_space, Column, Container, Row, Slider, TextInput, Tooltip,
-};
-use iced::Length::Fixed;
-use iced::{Alignment, Font, Length, Renderer};
-use std::path::PathBuf;
-use std::sync::Arc;
pub fn settings_advanced_page(sniffer: &Sniffer) -> Container
> {
let font = get_font(sniffer.style);
@@ -58,6 +61,11 @@ pub fn settings_advanced_page(sniffer: &Sniffer) -> Container Row<'static, Message, Renderer> {
+ let path_str = &custom_path.to_string_lossy().to_string();
+
+ let is_error = if path_str.is_empty() {
+ false
+ } else {
+ CustomPalette::from_file(custom_path).is_err()
+ };
+
+ let input = TextInput::new("-", path_str)
+ .on_input(Message::LoadStyle)
+ .on_submit(Message::LoadStyle(path_str.clone()))
+ .padding([0, 5])
+ .font(font)
+ .width(Length::Fixed(200.0))
+ .style(if is_error {
+ TextInputType::Error
+ } else {
+ TextInputType::Standard
+ });
+
+ Row::new()
+ .spacing(5)
+ .push(Text::new(format!("{}:", custom_style_translation(language))).font(font))
+ .push(input)
+}
diff --git a/src/gui/styles/custom_themes/dracula.rs b/src/gui/styles/custom_themes/dracula.rs
index 58fcdf4b..58a9cef6 100644
--- a/src/gui/styles/custom_themes/dracula.rs
+++ b/src/gui/styles/custom_themes/dracula.rs
@@ -23,6 +23,7 @@ pub(in crate::gui::styles) fn dracula_dark() -> CustomPalette {
round_borders_alpha: 0.1,
round_containers_alpha: 0.04,
chart_badge_alpha: 0.15,
+ nightly: true,
},
}
}
@@ -43,6 +44,7 @@ pub(in crate::gui::styles) fn dracula_light() -> CustomPalette {
chart_badge_alpha: 0.75,
round_borders_alpha: 0.45,
round_containers_alpha: 0.25,
+ nightly: false,
},
}
}
diff --git a/src/gui/styles/custom_themes/gruvbox.rs b/src/gui/styles/custom_themes/gruvbox.rs
index 2cd7b9fc..7f37cc90 100644
--- a/src/gui/styles/custom_themes/gruvbox.rs
+++ b/src/gui/styles/custom_themes/gruvbox.rs
@@ -24,6 +24,7 @@ pub(in crate::gui::styles) fn gruvbox_dark() -> CustomPalette {
chart_badge_alpha: 0.15,
round_borders_alpha: 0.12,
round_containers_alpha: 0.05,
+ nightly: true,
},
}
}
@@ -44,6 +45,7 @@ pub(in crate::gui::styles) fn gruvbox_light() -> CustomPalette {
chart_badge_alpha: 0.75,
round_borders_alpha: 0.45,
round_containers_alpha: 0.2,
+ nightly: false,
},
}
}
diff --git a/src/gui/styles/custom_themes/nord.rs b/src/gui/styles/custom_themes/nord.rs
index e6016595..3386f78c 100644
--- a/src/gui/styles/custom_themes/nord.rs
+++ b/src/gui/styles/custom_themes/nord.rs
@@ -22,6 +22,7 @@ pub(in crate::gui::styles) fn nord_dark() -> CustomPalette {
chart_badge_alpha: 0.2,
round_borders_alpha: 0.35,
round_containers_alpha: 0.15,
+ nightly: true,
},
}
}
@@ -41,6 +42,7 @@ pub(in crate::gui::styles) fn nord_light() -> CustomPalette {
chart_badge_alpha: 0.6,
round_borders_alpha: 0.35,
round_containers_alpha: 0.15,
+ nightly: false,
},
}
}
diff --git a/src/gui/styles/custom_themes/solarized.rs b/src/gui/styles/custom_themes/solarized.rs
index 8d09fa7f..d877a7b8 100644
--- a/src/gui/styles/custom_themes/solarized.rs
+++ b/src/gui/styles/custom_themes/solarized.rs
@@ -23,6 +23,7 @@ pub(in crate::gui::styles) fn solarized_light() -> CustomPalette {
chart_badge_alpha: 0.75,
round_borders_alpha: 0.35,
round_containers_alpha: 0.15,
+ nightly: false,
},
}
}
@@ -43,6 +44,7 @@ pub(in crate::gui::styles) fn solarized_dark() -> CustomPalette {
chart_badge_alpha: 0.25,
round_borders_alpha: 0.15,
round_containers_alpha: 0.08,
+ nightly: true,
},
}
}
diff --git a/src/gui/styles/types/color_remote.rs b/src/gui/styles/types/color_remote.rs
new file mode 100644
index 00000000..32073d3d
--- /dev/null
+++ b/src/gui/styles/types/color_remote.rs
@@ -0,0 +1,211 @@
+//! Remote implemention of [`serde::Deserialize`] and [`serde::Serialize`] for [`iced::Color`].
+//!
+//! This implementation deserializes hexadecimal RGB(A) as string to float RGB(A) and back.
+//! NOTE: The alpha channel is optional and defaults to #ff or 1.0.
+//! `#ffffffff` deserializes to `1.0`, `1.0`, `1.0`, `1.0`.
+//! `1.0`, `1.0`, `1.0`, `1.0` serializes to #ffffffff
+
+use std::hash::{Hash, Hasher};
+
+use iced::Color;
+use serde::{
+ de::{Error as DeErrorTrait, Unexpected},
+ Deserialize, Deserializer, Serializer,
+};
+
+// #aabbcc is seven bytes long
+const HEX_STR_BASE_LEN: usize = 7;
+// #aabbccdd is nine bytes long
+const HEX_STR_ALPHA_LEN: usize = 9;
+
+pub(super) fn deserialize_color<'de, D>(deserializer: D) -> Result
+where
+ D: Deserializer<'de>,
+{
+ // Field should be a hex string i.e. #aabbcc
+ let hex = String::deserialize(deserializer)?;
+
+ // The string should be seven bytes long (octothorpe + six hex chars).
+ // Safety: Hexadecimal is ASCII so bytes are okay here.
+ let hex_len = hex.len();
+ if hex_len == HEX_STR_BASE_LEN || hex_len == HEX_STR_ALPHA_LEN {
+ let color = hex
+ .strip_prefix('#') // Remove the octothorpe or fail
+ .ok_or_else(|| {
+ DeErrorTrait::invalid_value(
+ Unexpected::Char(hex.chars().next().unwrap_or_default()),
+ &"#",
+ )
+ })?
+ // Iterating over bytes is safe because hex is ASCII.
+ // If the hex is not ASCII or invalid hex, then the iterator will short circuit and fail on `from_str_radix`
+ // TODO: This can be cleaned up when `iter_array_chunks` is stablized (https://github.com/rust-lang/rust/issues/100450)
+ .bytes()
+ .step_by(2) // Step by every first hex char of the two char sequence
+ .zip(hex.bytes().skip(2).step_by(2)) // Step by every second hex char
+ .map(|(first, second)| {
+ // Parse hex strings
+ let maybe_hex = [first, second];
+ std::str::from_utf8(&maybe_hex)
+ .map_err(|_| {
+ DeErrorTrait::invalid_value(Unexpected::Str(&hex), &"valid hexadecimal")
+ })
+ .and_then(|s| {
+ u8::from_str_radix(s, 16)
+ .map_err(DeErrorTrait::custom)
+ .map(|rgb| f32::from(rgb) / 255.0)
+ })
+ })
+ .collect::, _>>()?;
+
+ // Alpha isn't always part of the color scheme. The resulting Vec should always have at least three elements.
+ // Accessing the first three elements without [slice::get] is okay because I checked the length of the hex string earlier.
+ Ok(Color {
+ r: color[0],
+ g: color[1],
+ b: color[2],
+ a: *color.get(3).unwrap_or(&1.0),
+ })
+ } else {
+ Err(DeErrorTrait::invalid_length(
+ hex_len,
+ &&*format!("{HEX_STR_BASE_LEN} or {HEX_STR_ALPHA_LEN}"),
+ ))
+ }
+}
+
+/// Hash delegate for [`iced::Color`] that hashes RGBA in lieu of floats.
+#[inline]
+pub(super) fn color_hash(color: Color, state: &mut H) {
+ // Hash isn't implemented for floats, so I hash the color as RGBA instead.
+ let color = color.into_rgba8();
+ color.hash(state);
+}
+
+/// Serialize [`iced::Color`] as a hex string.
+#[inline]
+pub(super) fn serialize_color(color: &Color, serializer: S) -> Result
+where
+ S: Serializer,
+{
+ // iced::Color to [u8; 4]
+ let color = color.into_rgba8();
+
+ // [u8; 4] to hex string, precluding the alpha if it's 0xff.
+ let hex_color = if color[3] == 255 {
+ format!("#{:02x}{:02x}{:02x}", color[0], color[1], color[2])
+ } else {
+ format!(
+ "#{:02x}{:02x}{:02x}{:02x}",
+ color[0], color[1], color[2], color[3]
+ )
+ };
+
+ // Serialize the hex string
+ serializer.serialize_str(&hex_color)
+}
+
+#[cfg(test)]
+mod tests {
+ use iced::Color;
+ use serde::{Deserialize, Serialize};
+ use serde_test::{assert_de_tokens_error, assert_tokens, Token};
+
+ use super::{deserialize_color, serialize_color};
+
+ // https://github.com/catppuccin/catppuccin
+ const CATPPUCCIN_PINK_HEX: &str = "#f5c2e7";
+ const CATPPUCCIN_PINK: Color = Color {
+ r: 245.0 / 255.0,
+ g: 194.0 / 255.0,
+ b: 231.0 / 255.0,
+ a: 1.0,
+ };
+
+ const CATPPUCCIN_PINK_HEX_ALPHA: &str = "#f5c2e780";
+ const CATPPUCCIN_PINK_ALPHA: Color = Color {
+ r: 245.0 / 255.0,
+ g: 194.0 / 255.0,
+ b: 231.0 / 255.0,
+ a: 128.0 / 255.0,
+ };
+
+ #[derive(Debug, PartialEq, Deserialize, Serialize)]
+ #[serde(transparent)]
+ struct DelegateTest {
+ #[serde(
+ flatten,
+ deserialize_with = "deserialize_color",
+ serialize_with = "serialize_color"
+ )]
+ color: Color,
+ }
+
+ const CATPPUCCIN_PINK_DELEGATE: DelegateTest = DelegateTest {
+ color: CATPPUCCIN_PINK,
+ };
+
+ const CATPPUCCIN_PINK_ALPHA_DELEGATE: DelegateTest = DelegateTest {
+ color: CATPPUCCIN_PINK_ALPHA,
+ };
+
+ // Invalid hex colors
+ const CATPPUCCIN_PINK_NO_OCTO: &str = "%f5c2e7";
+ const CATPPUCCIN_PINK_TRUNCATED: &str = "#c2e7";
+ const CATPPUCCIN_PINK_TOO_LONG: &str = "#f5c2e7f5c2e7f5";
+ const INVALID_COLOR: &str = "#ca🐈";
+
+ // Test if deserializing and serializing a color works.
+ #[test]
+ fn test_working_color_round_trip() {
+ assert_tokens(
+ &CATPPUCCIN_PINK_DELEGATE,
+ &[Token::Str(CATPPUCCIN_PINK_HEX)],
+ );
+ }
+
+ // Test if deserializing and serializing a color with an alpha channel works.
+ #[test]
+ fn test_working_color_with_alpha_round_trip() {
+ assert_tokens(
+ &CATPPUCCIN_PINK_ALPHA_DELEGATE,
+ &[Token::Str(CATPPUCCIN_PINK_HEX_ALPHA)],
+ );
+ }
+
+ // Missing octothorpe should fail.
+ #[test]
+ fn test_no_octothrope_color_rt() {
+ assert_de_tokens_error::(
+ &[Token::Str(CATPPUCCIN_PINK_NO_OCTO)],
+ "invalid value: character `%`, expected #",
+ );
+ }
+
+ // A hex color that is missing components should panic.
+ #[test]
+ fn test_len_too_small_color_de() {
+ assert_de_tokens_error::(
+ &[Token::Str(CATPPUCCIN_PINK_TRUNCATED)],
+ "invalid length 5, expected 7 or 9",
+ );
+ }
+
+ // A hex string that is too long shouldn't deserialize
+ #[test]
+ fn test_len_too_large_color_de() {
+ assert_de_tokens_error::(
+ &[Token::Str(CATPPUCCIN_PINK_TOO_LONG)],
+ "invalid length 15, expected 7 or 9",
+ );
+ }
+
+ // Invalid hexadecimal should panic
+ #[test]
+ fn test_invalid_hex_color_de() {
+ assert_de_tokens_error::(
+ &[Token::Str(INVALID_COLOR)],
+ "invalid value: string \"#ca🐈\", expected valid hexadecimal",
+ );
+ }
+}
diff --git a/src/gui/styles/types/custom_palette.rs b/src/gui/styles/types/custom_palette.rs
index 6a2d7f0a..f3ea4fb5 100644
--- a/src/gui/styles/types/custom_palette.rs
+++ b/src/gui/styles/types/custom_palette.rs
@@ -1,22 +1,42 @@
use std::fmt;
+use std::fs::File;
+use std::hash::{Hash, Hasher};
+use std::io::{BufReader, Read};
+use std::path::Path;
use iced::Color;
-use serde::{Deserialize, Serialize};
+use serde::{de::Error as DeErrorTrait, Deserialize, Serialize};
use crate::gui::styles::custom_themes::{dracula, gruvbox, nord, solarized};
use crate::gui::styles::types::palette::Palette;
+use super::color_remote::{color_hash, deserialize_color, serialize_color};
+
+const FLOAT_PRECISION: f32 = 10000.0;
+
/// Custom style with any relevant metadata
+// NOTE: This is flattened for ergonomics. With flatten, both [Palette] and [PaletteExtension] can be
+// defined in the TOML as a single entity rather than two separate tables. This is intentional because
+// the separation between palette and its extension is an implementation detail that shouldn't be exposed
+// to custom theme designers.
+#[derive(Debug, Clone, Copy, PartialEq, Deserialize, Serialize)]
pub struct CustomPalette {
- /// Color scheme's palette
+ /// Base colors for the theme
+ #[serde(flatten)]
pub(crate) palette: Palette,
/// Extra colors such as the favorites star
+ #[serde(flatten)]
pub(crate) extension: PaletteExtension,
}
/// Extension color for themes.
+#[derive(Debug, Clone, Copy, PartialEq, Deserialize, Serialize)]
pub struct PaletteExtension {
/// Color of favorites star
+ #[serde(
+ deserialize_with = "deserialize_color",
+ serialize_with = "serialize_color"
+ )]
pub starred: Color,
/// Badge/logo alpha
pub chart_badge_alpha: f32,
@@ -24,10 +44,80 @@ pub struct PaletteExtension {
pub round_borders_alpha: f32,
/// Round containers alpha
pub round_containers_alpha: f32,
+ /// Nightly (dark) style
+ pub nightly: bool,
+}
+
+impl CustomPalette {
+ /// Deserialize [`CustomPalette`] from `path`.
+ ///
+ /// # Arguments
+ /// * `path` - Path to a UTF-8 encoded file containing a custom style as TOML.
+ pub fn from_file(path: P) -> Result
+ where
+ P: AsRef,
+ {
+ // Try to open the file at `path`
+ let mut toml_reader = File::open(path)
+ .map_err(DeErrorTrait::custom)
+ .map(BufReader::new)?;
+
+ // Read the ostensible TOML
+ let mut style_toml = String::new();
+ toml_reader
+ .read_to_string(&mut style_toml)
+ .map_err(DeErrorTrait::custom)?;
+
+ toml::de::from_str(&style_toml)
+ }
+}
+
+impl Hash for CustomPalette {
+ fn hash(&self, state: &mut H) {
+ let Self { palette, extension } = self;
+
+ let Palette {
+ primary,
+ secondary,
+ outgoing,
+ buttons,
+ text_headers,
+ text_body,
+ } = palette;
+
+ color_hash(*primary, state);
+ color_hash(*secondary, state);
+ color_hash(*outgoing, state);
+ color_hash(*buttons, state);
+ color_hash(*text_headers, state);
+ color_hash(*text_body, state);
+
+ extension.hash(state);
+ }
+}
+
+impl Hash for PaletteExtension {
+ fn hash(&self, state: &mut H) {
+ let Self {
+ starred,
+ chart_badge_alpha,
+ round_borders_alpha,
+ round_containers_alpha,
+ ..
+ } = self;
+
+ color_hash(*starred, state);
+ #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
+ ((*chart_badge_alpha * FLOAT_PRECISION).trunc() as u32).hash(state);
+ #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
+ ((*round_borders_alpha * FLOAT_PRECISION).trunc() as u32).hash(state);
+ #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
+ ((*round_containers_alpha * FLOAT_PRECISION).trunc() as u32).hash(state);
+ }
}
/// Built in extra styles
-#[derive(Clone, Copy, Serialize, Deserialize, Debug, Hash, PartialEq)]
+#[derive(Clone, Copy, Debug, Hash, PartialEq, Serialize, Deserialize)]
#[serde(tag = "custom")]
pub enum ExtraStyles {
DraculaDark,
@@ -38,6 +128,7 @@ pub enum ExtraStyles {
NordLight,
SolarizedDark,
SolarizedLight,
+ CustomToml(CustomPalette),
}
impl ExtraStyles {
@@ -52,6 +143,7 @@ impl ExtraStyles {
ExtraStyles::NordDark => nord::nord_dark().palette,
ExtraStyles::SolarizedDark => solarized::solarized_dark().palette,
ExtraStyles::SolarizedLight => solarized::solarized_light().palette,
+ ExtraStyles::CustomToml(user) => user.palette,
}
}
@@ -66,21 +158,13 @@ impl ExtraStyles {
ExtraStyles::NordDark => nord::nord_dark().extension,
ExtraStyles::SolarizedDark => solarized::solarized_dark().extension,
ExtraStyles::SolarizedLight => solarized::solarized_light().extension,
+ ExtraStyles::CustomToml(user) => user.extension,
}
}
/// Theme is a night/dark style
- pub const fn is_nightly(self) -> bool {
- match self {
- ExtraStyles::DraculaDark
- | ExtraStyles::GruvboxDark
- | ExtraStyles::NordDark
- | ExtraStyles::SolarizedDark => true,
- ExtraStyles::DraculaLight
- | ExtraStyles::GruvboxLight
- | ExtraStyles::NordLight
- | ExtraStyles::SolarizedLight => false,
- }
+ pub fn is_nightly(self) -> bool {
+ self.to_ext().nightly
}
/// Slice of all implemented custom styles
@@ -109,6 +193,53 @@ impl fmt::Display for ExtraStyles {
ExtraStyles::NordDark => write!(f, "Nord (Night)"),
ExtraStyles::SolarizedLight => write!(f, "Solarized (Day)"),
ExtraStyles::SolarizedDark => write!(f, "Solarized (Night)"),
+ // Custom style names aren't used anywhere so this shouldn't be reached
+ ExtraStyles::CustomToml(_) => unreachable!(),
}
}
}
+
+#[cfg(test)]
+mod tests {
+ use iced::color;
+
+ use super::{CustomPalette, Palette, PaletteExtension};
+
+ fn style_path(name: &str) -> String {
+ format!(
+ "{}/resources/themes/{}.toml",
+ env!("CARGO_MANIFEST_DIR"),
+ name
+ )
+ }
+
+ // NOTE: This has to be updated if `resources/themes/catppuccin_mocha.toml` changes
+ fn catppuccin_style() -> CustomPalette {
+ CustomPalette {
+ palette: Palette {
+ primary: color!(30, 30, 46),
+ secondary: color!(137, 180, 250),
+ buttons: color!(49, 50, 68),
+ outgoing: color!(245, 194, 231),
+ text_headers: color!(17, 17, 27),
+ text_body: color!(205, 214, 244),
+ },
+ extension: PaletteExtension {
+ starred: color!(249, 226, 175, 0.6666667),
+ round_borders_alpha: 0.3,
+ round_containers_alpha: 0.15,
+ chart_badge_alpha: 0.2,
+ nightly: true,
+ },
+ }
+ }
+
+ #[test]
+ fn custompalette_from_file_de() -> Result<(), toml::de::Error> {
+ let style = catppuccin_style();
+ let style_de = CustomPalette::from_file(style_path("catppuccin_mocha"))?;
+
+ assert_eq!(style, style_de);
+ Ok(())
+ }
+}
diff --git a/src/gui/styles/types/mod.rs b/src/gui/styles/types/mod.rs
index 4613b2da..87437804 100644
--- a/src/gui/styles/types/mod.rs
+++ b/src/gui/styles/types/mod.rs
@@ -1,3 +1,4 @@
+pub(super) mod color_remote;
pub mod custom_palette;
pub mod gradient_type;
pub mod palette;
diff --git a/src/gui/styles/types/palette.rs b/src/gui/styles/types/palette.rs
index 5328a1a8..f78ba677 100644
--- a/src/gui/styles/types/palette.rs
+++ b/src/gui/styles/types/palette.rs
@@ -2,12 +2,15 @@
use iced::Color;
use plotters::style::RGBColor;
+use serde::{Deserialize, Serialize};
use crate::gui::styles::style_constants::{
DAY_STYLE, DEEP_SEA_STYLE, MON_AMOUR_STYLE, NIGHT_STYLE,
};
use crate::StyleType;
+use super::color_remote::{deserialize_color, serialize_color};
+
/// Set of colors to apply to GUI
///
/// Best practices:
@@ -16,18 +19,43 @@ use crate::StyleType;
/// - `secondary` and `outgoing` should be complementary colors if possible
/// - `text_headers` should be black or white and must have a strong contrast with `secondary`
/// - `text_body` should be black or white and must have a strong contrast with `primary`
+#[derive(Debug, Clone, Copy, PartialEq, Deserialize, Serialize)]
pub struct Palette {
/// Main color of the GUI (background, hovered buttons, active tab)
+ #[serde(
+ deserialize_with = "deserialize_color",
+ serialize_with = "serialize_color"
+ )]
pub primary: Color,
/// Secondary color of the GUI (incoming connections, header, footer, buttons' borders, radio selection)
+ #[serde(
+ deserialize_with = "deserialize_color",
+ serialize_with = "serialize_color"
+ )]
pub secondary: Color,
/// Color of outgoing connections
+ #[serde(
+ deserialize_with = "deserialize_color",
+ serialize_with = "serialize_color"
+ )]
pub outgoing: Color,
/// Color of active buttons (when not hovered) and inactive tabs
+ #[serde(
+ deserialize_with = "deserialize_color",
+ serialize_with = "serialize_color"
+ )]
pub buttons: Color,
/// Color of header and footer text
+ #[serde(
+ deserialize_with = "deserialize_color",
+ serialize_with = "serialize_color"
+ )]
pub text_headers: Color,
/// Color of body and buttons text
+ #[serde(
+ deserialize_with = "deserialize_color",
+ serialize_with = "serialize_color"
+ )]
pub text_body: Color,
}
diff --git a/src/gui/types/message.rs b/src/gui/types/message.rs
index b18d5493..1f575578 100644
--- a/src/gui/types/message.rs
+++ b/src/gui/types/message.rs
@@ -43,6 +43,8 @@ pub enum Message {
Reset,
/// Change application style
Style(StyleType),
+ /// Deserialize a style from a path
+ LoadStyle(String),
/// Manage waiting time
Waiting,
/// Displays a modal
diff --git a/src/gui/types/sniffer.rs b/src/gui/types/sniffer.rs
index 89752890..5711a221 100644
--- a/src/gui/types/sniffer.rs
+++ b/src/gui/types/sniffer.rs
@@ -16,6 +16,7 @@ use crate::countries::country_utils::COUNTRY_MMDB;
use crate::gui::components::types::my_modal::MyModal;
use crate::gui::pages::types::running_page::RunningPage;
use crate::gui::pages::types::settings_page::SettingsPage;
+use crate::gui::styles::types::custom_palette::{CustomPalette, ExtraStyles};
use crate::gui::styles::types::gradient_type::GradientType;
use crate::gui::types::message::Message;
use crate::gui::types::status::Status;
@@ -177,6 +178,13 @@ impl Sniffer {
self.style = style;
self.traffic_chart.change_style(self.style);
}
+ Message::LoadStyle(path) => {
+ self.advanced_settings.style_path = path.clone().into();
+ if let Ok(palette) = CustomPalette::from_file(path) {
+ self.style = StyleType::Custom(ExtraStyles::CustomToml(palette));
+ self.traffic_chart.change_style(self.style);
+ }
+ }
Message::Waiting => self.update_waiting_dots(),
Message::AddOrRemoveFavorite(host, add) => self.add_or_remove_favorite(&host, add),
Message::ShowModal(modal) => {
diff --git a/src/main.rs b/src/main.rs
index 2cb8bd53..37aff457 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -9,9 +9,6 @@ use std::{panic, process, thread};
use iced::window::PlatformSpecific;
use iced::{window, Application, Font, Settings};
-use crate::configs::types::config_advanced_settings::ConfigAdvancedSettings;
-use crate::configs::types::config_window::{ConfigWindow, ToPosition};
-use crate::configs::types::configs::Configs;
use chart::types::chart_type::ChartType;
use chart::types::traffic_chart::TrafficChart;
use cli::parse_cli_args;
@@ -33,6 +30,9 @@ use report::types::report_sort_type::ReportSortType;
use translations::types::language::Language;
use utils::formatted_strings::print_cli_welcome_message;
+use crate::configs::types::config_advanced_settings::ConfigAdvancedSettings;
+use crate::configs::types::config_window::{ConfigWindow, ToPosition};
+use crate::configs::types::configs::Configs;
use crate::secondary_threads::check_updates::set_newer_release_status;
mod chart;
diff --git a/src/networking/manage_packets.rs b/src/networking/manage_packets.rs
index 5fdf002a..20cc5a95 100644
--- a/src/networking/manage_packets.rs
+++ b/src/networking/manage_packets.rs
@@ -9,7 +9,6 @@ use pcap::{Active, Address, Capture, Device};
use crate::countries::country_utils::get_country;
use crate::networking::types::address_port_pair::AddressPortPair;
use crate::networking::types::app_protocol::from_port_to_application_protocol;
-use crate::networking::types::data_info::DataInfo;
use crate::networking::types::data_info_host::DataInfoHost;
use crate::networking::types::filters::Filters;
use crate::networking::types::host::Host;
@@ -284,7 +283,7 @@ pub fn reverse_dns_lookup(
let other_data = info_traffic_lock
.addresses_waiting_resolution
.remove(&address_to_lookup)
- .unwrap_or(DataInfo::default());
+ .unwrap_or_default();
// insert the newly resolved host in the collections, with the data it exchanged so far
info_traffic_lock
.addresses_resolved
diff --git a/src/notifications/types/notifications.rs b/src/notifications/types/notifications.rs
index e3eff629..6d6f6b41 100644
--- a/src/notifications/types/notifications.rs
+++ b/src/notifications/types/notifications.rs
@@ -58,7 +58,7 @@ impl Default for PacketsNotification {
impl PacketsNotification {
/// Arbitrary string constructor. Will fallback values to existing notification if set, default() otherwise
pub fn from(value: &str, existing: Option) -> Self {
- let default = existing.unwrap_or(Self::default());
+ let default = existing.unwrap_or_default();
let new_threshold = if value.is_empty() {
0
@@ -99,7 +99,7 @@ impl Default for BytesNotification {
impl BytesNotification {
/// Arbitrary string constructor. Will fallback values to existing notification if set, default() otherwise
pub fn from(value: &str, existing: Option) -> Self {
- let default = existing.unwrap_or(Self::default());
+ let default = existing.unwrap_or_default();
let mut byte_multiple_inserted = ByteMultiple::B;
let new_threshold = if value.is_empty() {
diff --git a/src/secondary_threads/write_report_file.rs b/src/secondary_threads/write_report_file.rs
index ad23e979..e65f9bbf 100644
--- a/src/secondary_threads/write_report_file.rs
+++ b/src/secondary_threads/write_report_file.rs
@@ -1,77 +1,77 @@
-//! Module containing functions executed by the thread in charge of updating the output report every 1 second
-
-use std::collections::HashSet;
-use std::fs::File;
-use std::io::{BufWriter, Seek, SeekFrom, Write};
-use std::sync::{Arc, Condvar, Mutex};
-use std::thread;
-use std::time::Duration;
-
-use crate::gui::types::status::Status;
-use crate::utils::formatted_strings::get_default_report_directory;
-use crate::InfoTraffic;
-
-/// The calling thread enters in a loop in which it sleeps for 1 second and then
-/// updates the output report containing detailed traffic information
-pub fn sleep_and_write_report_loop(
- current_capture_id: &Arc>,
- info_traffic_mutex: &Arc>,
- status_pair: &Arc<(Mutex, Condvar)>,
-) {
- let cvar = &status_pair.1;
-
- let path_report = get_default_report_directory();
-
- let mut capture_id = *current_capture_id.lock().unwrap();
-
- let mut output =
- BufWriter::new(File::create(path_report.clone()).expect("Error creating output file\n\r"));
- writeln!(output, "---------------------------------------------------------------------------------------------------------------------------------------------------------------------").expect("Error writing output file\n\r");
- writeln!(output, "| Src IP address | Src port | Dst IP address | Dst port | Layer 4 | Layer 7 | Packets | Bytes | Initial timestamp | Final timestamp |").expect("Error writing output file\n\r");
- writeln!(output, "---------------------------------------------------------------------------------------------------------------------------------------------------------------------").expect("Error writing output file\n\r");
-
- loop {
- // sleep 1 second
- thread::sleep(Duration::from_secs(1));
-
- let current_capture_id_lock = current_capture_id.lock().unwrap();
- if *current_capture_id_lock != capture_id {
- capture_id = *current_capture_id_lock;
- output = BufWriter::new(
- File::create(path_report.clone()).expect("Error creating output file\n\r"),
- );
- writeln!(output, "---------------------------------------------------------------------------------------------------------------------------------------------------------------------").expect("Error writing output file\n\r");
- writeln!(output, "| Src IP address | Src port | Dst IP address | Dst port | Layer 4 | Layer 7 | Packets | Bytes | Initial timestamp | Final timestamp |").expect("Error writing output file\n\r");
- writeln!(output, "---------------------------------------------------------------------------------------------------------------------------------------------------------------------").expect("Error writing output file\n\r");
- }
- drop(current_capture_id_lock);
-
- let mut status = status_pair.0.lock().expect("Error acquiring mutex\n\r");
-
- if *status == Status::Running {
- drop(status);
-
- let mut info_traffic = info_traffic_mutex
- .lock()
- .expect("Error acquiring mutex\n\r");
-
- for index in &info_traffic.addresses_last_interval {
- let key_val = info_traffic.map.get_index(*index).unwrap();
- let seek_pos = 166 * 3 + 206 * (*index) as u64;
- output.seek(SeekFrom::Start(seek_pos)).unwrap();
- writeln!(output, "{}{}", key_val.0, key_val.1)
- .expect("Error writing output file\n\r");
- }
- info_traffic.addresses_last_interval = HashSet::new(); // empty set
-
- drop(info_traffic);
-
- output.flush().expect("Error writing output file\n\r");
- } else {
- //status is Init
- while *status == Status::Init {
- status = cvar.wait(status).expect("Error acquiring mutex\n\r");
- }
- }
- }
-}
+// //! Module containing functions executed by the thread in charge of updating the output report every 1 second
+//
+// use std::collections::HashSet;
+// use std::fs::File;
+// use std::io::{BufWriter, Seek, SeekFrom, Write};
+// use std::sync::{Arc, Condvar, Mutex};
+// use std::thread;
+// use std::time::Duration;
+//
+// use crate::gui::types::status::Status;
+// use crate::utils::formatted_strings::get_default_report_directory;
+// use crate::InfoTraffic;
+//
+// /// The calling thread enters in a loop in which it sleeps for 1 second and then
+// /// updates the output report containing detailed traffic information
+// pub fn sleep_and_write_report_loop(
+// current_capture_id: &Arc>,
+// info_traffic_mutex: &Arc>,
+// status_pair: &Arc<(Mutex, Condvar)>,
+// ) {
+// let cvar = &status_pair.1;
+//
+// let path_report = get_default_report_directory();
+//
+// let mut capture_id = *current_capture_id.lock().unwrap();
+//
+// let mut output =
+// BufWriter::new(File::create(path_report.clone()).expect("Error creating output file\n\r"));
+// writeln!(output, "---------------------------------------------------------------------------------------------------------------------------------------------------------------------").expect("Error writing output file\n\r");
+// writeln!(output, "| Src IP address | Src port | Dst IP address | Dst port | Layer 4 | Layer 7 | Packets | Bytes | Initial timestamp | Final timestamp |").expect("Error writing output file\n\r");
+// writeln!(output, "---------------------------------------------------------------------------------------------------------------------------------------------------------------------").expect("Error writing output file\n\r");
+//
+// loop {
+// // sleep 1 second
+// thread::sleep(Duration::from_secs(1));
+//
+// let current_capture_id_lock = current_capture_id.lock().unwrap();
+// if *current_capture_id_lock != capture_id {
+// capture_id = *current_capture_id_lock;
+// output = BufWriter::new(
+// File::create(path_report.clone()).expect("Error creating output file\n\r"),
+// );
+// writeln!(output, "---------------------------------------------------------------------------------------------------------------------------------------------------------------------").expect("Error writing output file\n\r");
+// writeln!(output, "| Src IP address | Src port | Dst IP address | Dst port | Layer 4 | Layer 7 | Packets | Bytes | Initial timestamp | Final timestamp |").expect("Error writing output file\n\r");
+// writeln!(output, "---------------------------------------------------------------------------------------------------------------------------------------------------------------------").expect("Error writing output file\n\r");
+// }
+// drop(current_capture_id_lock);
+//
+// let mut status = status_pair.0.lock().expect("Error acquiring mutex\n\r");
+//
+// if *status == Status::Running {
+// drop(status);
+//
+// let mut info_traffic = info_traffic_mutex
+// .lock()
+// .expect("Error acquiring mutex\n\r");
+//
+// for index in &info_traffic.addresses_last_interval {
+// let key_val = info_traffic.map.get_index(*index).unwrap();
+// let seek_pos = 166 * 3 + 206 * (*index) as u64;
+// output.seek(SeekFrom::Start(seek_pos)).unwrap();
+// writeln!(output, "{}{}", key_val.0, key_val.1)
+// .expect("Error writing output file\n\r");
+// }
+// info_traffic.addresses_last_interval = HashSet::new(); // empty set
+//
+// drop(info_traffic);
+//
+// output.flush().expect("Error writing output file\n\r");
+// } else {
+// //status is Init
+// while *status == Status::Init {
+// status = cvar.wait(status).expect("Error acquiring mutex\n\r");
+// }
+// }
+// }
+// }
diff --git a/src/translations/translations_3.rs b/src/translations/translations_3.rs
index 870444a6..99e753fc 100644
--- a/src/translations/translations_3.rs
+++ b/src/translations/translations_3.rs
@@ -66,3 +66,11 @@ pub fn file_path_translation(language: Language) -> &'static str {
_ => "File path",
}
}
+
+pub fn custom_style_translation(language: Language) -> &'static str {
+ match language {
+ Language::EN => "Custom style",
+ Language::IT => "Stile personalizzato",
+ _ => "Custom style",
+ }
+}