summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.woodpecker.yaml4
-rw-r--r--Cargo.toml6
-rw-r--r--NamelessTeam-i2p-browser-pipeline-2-metadata.json73
-rw-r--r--README.md3
-rw-r--r--screenshots/first_build.pngbin18379 -> 0 bytes
-rw-r--r--src/image.pngbin0 -> 36839 bytes
-rw-r--r--src/main.rs96
-rw-r--r--src/redirecting_i2p.rs76
-rw-r--r--src/resources/itoope.pngbin0 -> 3938 bytes
-rw-r--r--src/resources/reload.pngbin0 -> 45649 bytes
-rw-r--r--src/resources/settings.pngbin0 -> 172525 bytes
-rw-r--r--src/settings_window.rs270
12 files changed, 468 insertions, 60 deletions
diff --git a/.woodpecker.yaml b/.woodpecker.yaml
index 597c346..b299d4b 100644
--- a/.woodpecker.yaml
+++ b/.woodpecker.yaml
@@ -5,8 +5,8 @@ steps:
RUST_BACKTRACE: 1
CARGO_TERM_COLOR: always
commands:
- - rustup default stable
- - apt install -y libwebkit2 gtk-4.0-dev
+# - rustup default stable
+ - apt install -y libgtk4-dev webkit6gtk4-dev
when:
branch: main
event: [ push, pull_request ]
diff --git a/Cargo.toml b/Cargo.toml
index 1a150db..9346600 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -13,3 +13,9 @@ gtk4 = "0.10.3"
relm4 = "0.10.0"
webkit6 = "0.5.0"
toml = { version = "0.9.8", features = ["serde"] }
+url = { version = "2.5.7", features = ["serde"] }
+serde_ini = "0.2.0"
+
+[target.x86_64-unknown-linux-gnu]
+rustflags = ["-C", "target-feature=+crt-static"]
+
diff --git a/NamelessTeam-i2p-browser-pipeline-2-metadata.json b/NamelessTeam-i2p-browser-pipeline-2-metadata.json
new file mode 100644
index 0000000..e20fe3f
--- /dev/null
+++ b/NamelessTeam-i2p-browser-pipeline-2-metadata.json
@@ -0,0 +1,73 @@
+{
+ "repo": {
+ "id": 15666,
+ "name": "i2p-browser",
+ "owner": "NamelessTeam",
+ "remote_id": "982440",
+ "forge_url": "https://codeberg.org/NamelessTeam/i2p-browser",
+ "clone_url": "https://codeberg.org/NamelessTeam/i2p-browser.git",
+ "clone_url_ssh": "ssh://git@codeberg.org/NamelessTeam/i2p-browser.git",
+ "default_branch": "main",
+ "trusted": {}
+ },
+ "curr": {
+ "number": 2,
+ "created": 1764856870,
+ "started": 1764856870,
+ "finished": 1764857052,
+ "status": "failure",
+ "event": "push",
+ "forge_url": "https://codeberg.org/NamelessTeam/i2p-browser/commit/d5abad30ba847fa0e09aa711d90db29a473e9bef",
+ "commit": {
+ "sha": "d5abad30ba847fa0e09aa711d90db29a473e9bef",
+ "ref": "refs/heads/main",
+ "branch": "main",
+ "message": "Updated README.md\n",
+ "author": {
+ "name": "namilsk",
+ "email": "namilsk@noreply.codeberg.org",
+ "avatar": "https://codeberg.org/avatars/361cdb139971835cba985df335b59c2acff3b722a2bf45475ea9d2eb9406d86a"
+ },
+ "changed_files": [
+ "screenshots/first_build.png",
+ "README.md"
+ ]
+ },
+ "author": "namilsk",
+ "avatar": "https://codeberg.org/avatars/361cdb139971835cba985df335b59c2acff3b722a2bf45475ea9d2eb9406d86a"
+ },
+ "prev": {
+ "number": 1,
+ "created": 1764856091,
+ "started": 1764856092,
+ "finished": 1764856232,
+ "status": "success",
+ "event": "manual",
+ "forge_url": "https://codeberg.org/NamelessTeam/i2p-browser/commit/b5cd950218d6deadd46bd3d1529a3cabeac2220f",
+ "commit": {
+ "sha": "b5cd950218d6deadd46bd3d1529a3cabeac2220f",
+ "ref": "main",
+ "branch": "main",
+ "message": "MANUAL PIPELINE @ main",
+ "author": {
+ "name": "namilsk",
+ "email": "alive6863@gmail.com",
+ "avatar": "https://codeberg.org/avatars/361cdb139971835cba985df335b59c2acff3b722a2bf45475ea9d2eb9406d86a"
+ }
+ },
+ "author": "namilsk",
+ "avatar": "https://codeberg.org/avatars/361cdb139971835cba985df335b59c2acff3b722a2bf45475ea9d2eb9406d86a"
+ },
+ "workflow": {},
+ "step": {},
+ "sys": {
+ "name": "woodpecker",
+ "host": "ci.codeberg.org",
+ "url": "https://ci.codeberg.org",
+ "version": "3.10.0"
+ },
+ "forge": {
+ "type": "forgejo",
+ "url": "https://codeberg.org"
+ }
+} \ No newline at end of file
diff --git a/README.md b/README.md
index 577f180..42a07e7 100644
--- a/README.md
+++ b/README.md
@@ -18,7 +18,6 @@
A gtk-based browser that uses primarily i2p functions rather than the global web. I2P outproxy is used to access the web and all its main functions are supported.
Based on the ideas of privacy and anonymity, a customizable whitelist of sites is planned in which JavaScript will be enabled, and all major technologies that support forced shutdown are also.
->[!WARNING]
-> **First build (GUI-Only)**
+Released!
<img src="./screenshots/first_build.png"> \ No newline at end of file
diff --git a/screenshots/first_build.png b/screenshots/first_build.png
deleted file mode 100644
index a5a05ef..0000000
--- a/screenshots/first_build.png
+++ /dev/null
Binary files differ
diff --git a/src/image.png b/src/image.png
new file mode 100644
index 0000000..506eaf2
--- /dev/null
+++ b/src/image.png
Binary files differ
diff --git a/src/main.rs b/src/main.rs
index a74f0bd..287ec42 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,15 +1,27 @@
+mod redirecting_i2p;
+mod settings_window;
+
use gtk4::prelude::*;
use gtk4::{
- Application, ApplicationWindow, Box, Button, Entry, HeaderBar, Orientation, ScrolledWindow,
+ Application, ApplicationWindow, Box, Button, CheckButton, Entry, HeaderBar, Image, Orientation,
+ ScrolledWindow,
};
-
-use webkit6::WebView;
use webkit6::prelude::*;
-
-mod settings_window;
+use webkit6::{NetworkProxyMode, NetworkProxySettings, NetworkSession, WebView};
const APP_ID: &str = "com.namilsk.i2p-browser";
+fn configure_i2p_proxy(enabled: bool) {
+ if let Some(session) = NetworkSession::default() {
+ if enabled {
+ let settings = NetworkProxySettings::new(Some("http://127.0.0.1:4444"), &[]);
+ session.set_proxy_settings(NetworkProxyMode::Custom, Some(&settings));
+ } else {
+ session.set_proxy_settings(NetworkProxyMode::Default, None);
+ }
+ }
+}
+
fn main() -> glib::ExitCode {
let app = Application::builder().application_id(APP_ID).build();
app.connect_activate(build_ui);
@@ -23,7 +35,6 @@ fn build_ui(app: &Application) {
.default_width(1024)
.default_height(768)
.build();
-
let header_bar = HeaderBar::new();
window.set_titlebar(Some(&header_bar));
@@ -31,42 +42,75 @@ fn build_ui(app: &Application) {
.placeholder_text("Enter I2P URL (e.g., http://example.i2p)")
.build();
+
let button_back = Button::with_label("<");
let button_forward = Button::with_label(">");
- let button_reload = Button::with_label("R");
- let button_home = Button::with_label("H");
+ let button_reload = Button::with_label("Restart");
+ let button_home = Button::with_label("Home");
+ let settings_button = Button::with_label("Settings");
+ let i2p_proxy = CheckButton::with_label("Enable i2p proxy");
+ let i2p_panel = Button::with_label("Panel");
header_bar.pack_start(&button_back);
header_bar.pack_start(&button_forward);
header_bar.pack_start(&button_reload);
header_bar.pack_start(&button_home);
- header_bar.set_title_widget(Some(&url_entry));
+ header_bar.pack_end(&settings_button);
+ header_bar.pack_end(&i2p_proxy);
+ header_bar.pack_end(&i2p_panel);
let main_box = Box::new(Orientation::Vertical, 0);
window.set_child(Some(&main_box));
- let web_view = WebView::new();
+ if let Some(enabled) = settings_window::load_i2p_proxy_enabled() {
+ i2p_proxy.set_active(enabled);
+ configure_i2p_proxy(enabled);
+ }
+
+ i2p_proxy.connect_toggled(|btn| {
+ let enabled = btn.is_active();
+ settings_window::save_i2p_proxy_enabled(enabled);
+ configure_i2p_proxy(enabled);
+ });
+ let web_view = WebView::new();
let scrolled_window = ScrolledWindow::builder().child(&web_view).build();
+ scrolled_window.set_vexpand(true);
+ scrolled_window.set_hexpand(true);
+ web_view.set_vexpand(true);
+ web_view.set_hexpand(true);
+
+ main_box.append(&url_entry);
+
+ if let Some(enable_js) = settings_window::load_js_enabled()
+ && let Some(web_settings) = webkit6::prelude::WebViewExt::settings(&web_view) {
+ web_settings.set_enable_javascript(enable_js);
+ }
+
main_box.append(&scrolled_window);
let web_view_for_entry = web_view.clone();
let url_entry_for_entry = url_entry.clone();
+
url_entry.connect_activate(move |entry| {
- let url = entry.text();
- let full_url = if url.starts_with("http://") || url.starts_with("https://") {
- url.to_string()
- } else {
+ let url = entry.text().to_string();
+
+ let url_to_use = if !url.starts_with("http://") && !url.starts_with("https://") {
format!("http://{}", url)
+ } else {
+ url
};
- if glib::Uri::parse(&full_url, glib::UriFlags::NONE).is_ok() {
- web_view_for_entry.load_uri(&full_url);
+ let final_url = url_to_use;
+
+ if glib::Uri::parse(&final_url, glib::UriFlags::NONE).is_ok() {
+ web_view_for_entry.load_uri(&final_url);
+ println!("Loading: {}", final_url);
} else {
- eprintln!("Invalid URI: {}", full_url);
+ eprintln!("Invalid URI: {}", final_url);
}
- url_entry_for_entry.set_text(&full_url);
+ url_entry_for_entry.set_text(&final_url);
});
let web_view_for_back = web_view.clone();
@@ -96,6 +140,10 @@ fn build_ui(app: &Application) {
url_entry_for_home.set_text(home_url);
});
+ settings_button.connect_clicked(move |_| {
+ settings_window::settings_window();
+ });
+
let url_entry_for_notify = url_entry.clone();
web_view.connect_notify_local(Some("uri"), move |web_view, _| {
if let Some(uri) = web_view.uri() {
@@ -103,7 +151,17 @@ fn build_ui(app: &Application) {
}
});
- web_view.load_uri("http://legwork.i2p/");
+ let web_view_for_panel = web_view.clone();
+ let _i2p_panel_for_open = i2p_panel.clone();
+ i2p_panel.connect_clicked(move |_| {
+ let panel_port = redirecting_i2p::I2ProxySettings::from_i2pd_config(None)
+ .ok()
+ .and_then(|cfg| cfg.panel_port)
+ .unwrap_or(7070);
+ let i2p_panel_url = format!("http://127.0.0.1:{}/", panel_port);
+ web_view_for_panel.load_uri(&i2p_panel_url);
+ });
+ web_view.load_uri("http://legwork.i2p/");
window.present();
}
diff --git a/src/redirecting_i2p.rs b/src/redirecting_i2p.rs
new file mode 100644
index 0000000..3feef83
--- /dev/null
+++ b/src/redirecting_i2p.rs
@@ -0,0 +1,76 @@
+use serde::Deserialize;
+use std::fs;
+
+#[derive(Debug, Deserialize)]
+pub struct I2ProxySettings {
+ pub i2p_port: Option<isize>,
+ pub panel_port: Option<isize>,
+}
+
+#[derive(Deserialize, Debug)]
+struct I2pdConfigFile {
+ httpproxy: Option<HttpProxySection>,
+ http: Option<HttpSection>,
+}
+
+#[derive(Deserialize, Debug)]
+struct HttpProxySection {
+ enabled: Option<bool>,
+ port: Option<isize>,
+ address: Option<String>,
+}
+
+#[derive(Deserialize, Debug)]
+struct HttpSection {
+ enabled: Option<bool>,
+ port: Option<isize>,
+ address: Option<String>,
+}
+
+impl I2ProxySettings {
+ pub fn default() -> I2ProxySettings {
+ I2ProxySettings {
+ i2p_port: Some(4444),
+ panel_port: Some(7070),
+ }
+ }
+
+ const DEFAULT_I2PD_CONFIG_PATH: &'static str = "/etc/i2pd/i2pd.conf";
+
+ pub fn from_i2pd_config(
+ config_path: Option<&str>,
+ ) -> Result<I2ProxySettings, Box<dyn std::error::Error>> {
+ let path = config_path.unwrap_or(Self::DEFAULT_I2PD_CONFIG_PATH);
+ let content = fs::read_to_string(path)?;
+
+ let parsed_config: I2pdConfigFile = serde_ini::from_str(&content)?;
+
+ let i2p_port = parsed_config.httpproxy.and_then(|proxy| proxy.port);
+ let panel_port = parsed_config.http.and_then(|http| http.port);
+
+ Ok(I2ProxySettings {
+ i2p_port,
+ panel_port,
+ })
+ }
+
+ fn parse_url(url: &str) -> url::Url {
+ url::Url::parse(url).expect("Failed to parse URL")
+ }
+
+ pub fn redirecting_i2p(&mut self, url: &str) -> String {
+ if self.i2p_port.is_none() {
+ self.i2p_port = Some(4444);
+ }
+
+ let parsed_url = Self::parse_url(url);
+ let host = parsed_url.host_str().unwrap_or("");
+ let path = parsed_url.path();
+ // Wrong logic, unused, now used the proxy, not webpath
+ let combined = format!("{}{}", host, path);
+ let i2p_url = format!("http://127.0.0.1:{}/{}", self.i2p_port.unwrap(), combined);
+ let i2p_url = I2ProxySettings::parse_url(&i2p_url);
+
+ i2p_url.to_string()
+ }
+}
diff --git a/src/resources/itoope.png b/src/resources/itoope.png
new file mode 100644
index 0000000..e5ce55f
--- /dev/null
+++ b/src/resources/itoope.png
Binary files differ
diff --git a/src/resources/reload.png b/src/resources/reload.png
new file mode 100644
index 0000000..a1a3a4f
--- /dev/null
+++ b/src/resources/reload.png
Binary files differ
diff --git a/src/resources/settings.png b/src/resources/settings.png
new file mode 100644
index 0000000..5b77579
--- /dev/null
+++ b/src/resources/settings.png
Binary files differ
diff --git a/src/settings_window.rs b/src/settings_window.rs
index fbab489..105ff22 100644
--- a/src/settings_window.rs
+++ b/src/settings_window.rs
@@ -1,30 +1,23 @@
-
use gtk4::prelude::*;
-use gtk4::{
- ApplicationWindow,
- Box,
- Entry,
- Label,
- Orientation,
- HeaderBar,
- CheckButton
-};
-use toml;
-use serde::Serialize;
+use gtk4::{ApplicationWindow, Box, Button, CheckButton, Entry, HeaderBar, Label, Orientation};
+use serde::{Deserialize, Serialize};
+use std::fs;
+use std::path::PathBuf;
+use crate::redirecting_i2p::I2ProxySettings;
#[allow(dead_code)]
-#[derive(Debug, Serialize)]
+#[derive(Debug, Serialize, Deserialize)]
struct Settings {
- js: bool,
- outproxy: bool,
- i2p_proxy: bool,
+ js: bool,
+ outproxy: bool,
+ i2p_proxy: bool,
web_rtc: bool,
- home_addr: str,
-
-}
+ home_addr: String,
+ proxy_port: Option<isize>,
+ panel_port: Option<isize>,
+}
-#[allow(dead_code, unused_variables)]
-pub fn window() -> ApplicationWindow {
+pub fn settings_window() {
let app = ApplicationWindow::builder()
.title("Settings")
.default_width(400)
@@ -32,30 +25,233 @@ pub fn window() -> ApplicationWindow {
.build();
let header_bar = HeaderBar::new();
- window().set_titlebar(Some(&header_bar));
+ app.set_titlebar(Some(&header_bar));
let main_box = Box::new(Orientation::Vertical, 0);
- window().set_child(Some(&main_box));
-
- Label::new(Some("Settings")).set_markup("Settings");
-
- let javascript = CheckButton::new().set_label(Some("Enable JS"));
- let outproxy = CheckButton::new().set_label(Some("Enable outproxy"));
- let i2pproxy = CheckButton::new().set_label(Some("Enable i2p proxy"));
- let webrtc = CheckButton::new().set_label(Some("Enable WebRTC"));
+ app.set_child(Some(&main_box));
+
+ let label = Label::new(Some("Most wanted features"));
+ main_box.append(&label);
+
+ let javascript = CheckButton::with_label("Enable JS");
+ let outproxy = CheckButton::with_label("Enable outproxy");
+ let i2pproxy = CheckButton::with_label("Enable i2p proxy");
+ let webrtc = CheckButton::with_label("Enable WebRTC");
+ let load = Button::with_label("Load");
+ let load_i2pd_conf = Button::with_label("Load from i2pd conf");
+
+ main_box.append(&javascript);
+ main_box.append(&outproxy);
+ main_box.append(&i2pproxy);
+ main_box.append(&webrtc);
+
let homeaddr = Entry::new();
+ homeaddr.set_placeholder_text(Some("Enter home address"));
+ main_box.append(&homeaddr);
+
+ let proxy_port_entry = Entry::new();
+ proxy_port_entry.set_placeholder_text(Some("Proxy port"));
+ main_box.append(&proxy_port_entry);
+
+ let panel_port_entry = Entry::new();
+ panel_port_entry.set_placeholder_text(Some("Panel port"));
+ main_box.append(&panel_port_entry);
+
+ main_box.append(&load);
+ main_box.append(&load_i2pd_conf);
+ let save = Button::with_label("Save");
+ main_box.append(&save);
- app
+ if let Some(settings) = Settings::load_from_default_file() {
+ settings.apply_to_widgets(
+ &javascript,
+ &outproxy,
+ &i2pproxy,
+ &webrtc,
+ &homeaddr,
+ &proxy_port_entry,
+ &panel_port_entry,
+ );
+ }
+
+ let javascript_for_save = javascript.clone();
+ let outproxy_for_save = outproxy.clone();
+ let i2pproxy_for_save = i2pproxy.clone();
+ let webrtc_for_save = webrtc.clone();
+ let homeaddr_for_save = homeaddr.clone();
+ let proxy_port_for_save = proxy_port_entry.clone();
+ let panel_port_for_save = panel_port_entry.clone();
+ save.connect_clicked(move |_| {
+ let settings = Settings::from_widgets(
+ &javascript_for_save,
+ &outproxy_for_save,
+ &i2pproxy_for_save,
+ &webrtc_for_save,
+ &homeaddr_for_save,
+ &proxy_port_for_save,
+ &panel_port_for_save,
+ );
+
+ if let Err(err) = Settings::serialize_write(&Settings::default_config_path(), &settings) {
+ eprintln!("Failed to save settings: {err}");
+ }
+ });
+
+ let javascript_for_load = javascript.clone();
+ let outproxy_for_load = outproxy.clone();
+ let i2pproxy_for_load = i2pproxy.clone();
+ let webrtc_for_load = webrtc.clone();
+ let homeaddr_for_load = homeaddr.clone();
+ let proxy_port_for_load = proxy_port_entry.clone();
+ let panel_port_for_load = panel_port_entry.clone();
+ load.connect_clicked(move |_| {
+ if let Some(settings) = Settings::load_from_default_file() {
+ settings.apply_to_widgets(
+ &javascript_for_load,
+ &outproxy_for_load,
+ &i2pproxy_for_load,
+ &webrtc_for_load,
+ &homeaddr_for_load,
+ &proxy_port_for_load,
+ &panel_port_for_load,
+ );
+ }
+ });
+
+ let proxy_port_for_i2pd = proxy_port_entry.clone();
+ let panel_port_for_i2pd = panel_port_entry.clone();
+ let i2pproxy_for_i2pd = i2pproxy.clone();
+ load_i2pd_conf.connect_clicked(move |_| {
+ match I2ProxySettings::from_i2pd_config(None) {
+ Ok(cfg) => {
+ if let Some(port) = cfg.i2p_port {
+ proxy_port_for_i2pd.set_text(&port.to_string());
+ i2pproxy_for_i2pd.set_active(true);
+ }
+ if let Some(port) = cfg.panel_port {
+ panel_port_for_i2pd.set_text(&port.to_string());
+ }
+ }
+ Err(err) => {
+ eprintln!("Failed to load i2pd config: {err}");
+ }
+ }
+ });
+
+ app.present();
}
#[allow(dead_code)]
impl Settings {
- pub fn serialize_write(path: &std::path::Path,
- setts: &Settings) -> Result<(), toml::ser::Error> {
-
- let toml: String = toml::to_string(setts)?;
- std::fs::write(path, toml).expect("Some error occured, is permission granted to ~/.config?");
+ fn default_config_path() -> PathBuf {
+ let home = std::env::var("HOME").unwrap_or_else(|_| String::from("."));
+ let mut path = PathBuf::from(home);
+ path.push(".config");
+ path.push("i2p-browser");
+ // Best effort to ensure directory exists
+ let _ = fs::create_dir_all(&path);
+
+ path.push("settings.toml");
+ path
+ }
+
+ fn from_widgets(
+ javascript: &CheckButton,
+ outproxy: &CheckButton,
+ i2p_proxy: &CheckButton,
+ web_rtc: &CheckButton,
+ home_addr: &Entry,
+ proxy_port_entry: &Entry,
+ panel_port_entry: &Entry,
+ ) -> Settings {
+ let proxy_port = proxy_port_entry
+ .text()
+ .trim()
+ .parse::<isize>()
+ .ok();
+ let panel_port = panel_port_entry
+ .text()
+ .trim()
+ .parse::<isize>()
+ .ok();
+ Settings {
+ js: javascript.is_active(),
+ outproxy: outproxy.is_active(),
+ i2p_proxy: i2p_proxy.is_active(),
+ web_rtc: web_rtc.is_active(),
+ home_addr: home_addr.text().to_string(),
+ proxy_port,
+ panel_port,
+ }
+ }
+
+ fn apply_to_widgets(
+ &self,
+ javascript: &CheckButton,
+ outproxy: &CheckButton,
+ i2p_proxy: &CheckButton,
+ web_rtc: &CheckButton,
+ home_addr: &Entry,
+ proxy_port_entry: &Entry,
+ panel_port_entry: &Entry,
+ ) {
+ javascript.set_active(self.js);
+ outproxy.set_active(self.outproxy);
+ i2p_proxy.set_active(self.i2p_proxy);
+ web_rtc.set_active(self.web_rtc);
+ home_addr.set_text(&self.home_addr);
+ if let Some(port) = self.proxy_port {
+ proxy_port_entry.set_text(&port.to_string());
+ } else {
+ proxy_port_entry.set_text("");
+ }
+ if let Some(port) = self.panel_port {
+ panel_port_entry.set_text(&port.to_string());
+ } else {
+ panel_port_entry.set_text("");
+ }
+ }
+
+ fn load_from_default_file() -> Option<Settings> {
+ let path = Self::default_config_path();
+ let content = fs::read_to_string(path).ok()?;
+ toml::from_str(&content).ok()
+ }
+
+ pub fn serialize_write(
+ path: &std::path::Path,
+ setts: &Settings,
+ ) -> Result<(), toml::ser::Error> {
+ let toml: String = toml::to_string(setts)?;
+ std::fs::write(path, toml)
+ .expect("Some error occurred, is permission granted to ~/.config?");
Ok(())
}
-} \ No newline at end of file
+}
+
+pub fn load_js_enabled() -> Option<bool> {
+ Settings::load_from_default_file().map(|s| s.js)
+}
+
+pub fn load_i2p_proxy_enabled() -> Option<bool> {
+ Settings::load_from_default_file().map(|s| s.i2p_proxy)
+}
+
+pub fn save_i2p_proxy_enabled(enabled: bool) {
+ let mut settings = Settings::load_from_default_file().unwrap_or(Settings {
+ js: true,
+ outproxy: false,
+ i2p_proxy: enabled,
+ web_rtc: false,
+ home_addr: String::new(),
+ panel_port: Some(7070),
+ proxy_port: Some(4444),
+ });
+
+ settings.i2p_proxy = enabled;
+
+ if let Err(err) = Settings::serialize_write(&Settings::default_config_path(), &settings) {
+ eprintln!("Failed to save i2p proxy setting: {err}");
+ }
+}