From ac61f593d9934417931424f3abbd4b01b8f9108c Mon Sep 17 00:00:00 2001 From: SafeSmallPace <144520345+SafeSmallPace@users.noreply.github.com> Date: Tue, 16 Apr 2024 19:38:50 +0800 Subject: [PATCH] feat: add kde support for linux (#1) Co-authored-by: GyDi --- Cargo.toml | 3 + README.md | 2 +- src/lib.rs | 7 ++ src/linux.rs | 334 +++++++++++++++++++++++++++++++++++++++------------ 4 files changed, 267 insertions(+), 79 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 9e4283b..571a06a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,9 @@ log = "0.4" thiserror = "1" iptools = { version = "0.2.4", optional = true } +[target.'cfg(target_os = "linux")'.dependencies] +xdg = "^2.5" + [target.'cfg(target_os = "macos")'.dependencies] interfaces = "0.0.8" diff --git a/README.md b/README.md index 8fea173..ac7b3fb 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,3 @@ # sysproxy-rs -A library for set/get system proxy. Supports Windows, macOS and linux (via gsettings). +A library for set/get system proxy. Supports Windows, macOS and linux (via gsettings/kconfig). diff --git a/src/lib.rs b/src/lib.rs index 6c7431e..a4dbbae 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -29,6 +29,13 @@ pub enum Error { #[error("failed to get default network interface")] NetworkInterface, + #[error("failed to set proxy for this environment")] + NotSupport, + + #[cfg(target_os = "linux")] + #[error(transparent)] + Xdg(#[from] xdg::BaseDirectoriesError), + #[cfg(target_os = "windows")] #[error("system call failed")] SystemCall(#[from] windows::Win32Error), diff --git a/src/linux.rs b/src/linux.rs index 5c72a21..d7679fc 100644 --- a/src/linux.rs +++ b/src/linux.rs @@ -1,5 +1,6 @@ use crate::{Error, Result, Sysproxy}; -use std::{process::Command, str::from_utf8}; +use std::{env, process::Command, str::from_utf8}; +use xdg; const CMD_KEY: &str = "org.gnome.system.proxy"; @@ -42,31 +43,82 @@ impl Sysproxy { } pub fn get_enable() -> Result { - let mode = gsettings().args(["get", CMD_KEY, "mode"]).output()?; - let mode = from_utf8(&mode.stdout) - .or(Err(Error::ParseStr("mode".into())))? - .trim(); - Ok(mode == "'manual'") + match env::var("XDG_CURRENT_DESKTOP").unwrap_or_default().as_str() { + "GNOME" => { + let mode = gsettings().args(["get", CMD_KEY, "mode"]).output()?; + let mode = from_utf8(&mode.stdout).or(Err(Error::ParseStr("mode".into())))?.trim(); + Ok(mode == "'manual'") + } + "KDE" => { + let xdg_dir = xdg::BaseDirectories::new()?; + let config = xdg_dir.get_config_file("kioslaverc"); + let config = config.to_str().ok_or(Error::ParseStr("config".into()))?; + + let mode = kreadconfig() + .args([ + "--file", + config, + "--group", + "Proxy Settings", + "--key", + "ProxyType", + ]) + .output()?; + let mode = from_utf8(&mode.stdout).or(Err(Error::ParseStr("mode".into())))?.trim(); + Ok(mode == "1") + } + _ => Err(Error::NotSupport), + } } pub fn get_bypass() -> Result { - let bypass = gsettings() - .args(["get", CMD_KEY, "ignore-hosts"]) - .output()?; - let bypass = from_utf8(&bypass.stdout) - .or(Err(Error::ParseStr("bypass".into())))? - .trim(); - - let bypass = bypass.strip_prefix('[').unwrap_or(bypass); - let bypass = bypass.strip_suffix(']').unwrap_or(bypass); - - let bypass = bypass - .split(',') - .map(|h| strip_str(h.trim())) - .collect::>() - .join(","); - - Ok(bypass) + match env::var("XDG_CURRENT_DESKTOP").unwrap_or_default().as_str() { + "GNOME" => { + let bypass = gsettings() + .args(["get", CMD_KEY, "ignore-hosts"]) + .output()?; + let bypass = from_utf8(&bypass.stdout) + .or(Err(Error::ParseStr("bypass".into())))? + .trim(); + + let bypass = bypass.strip_prefix('[').unwrap_or(bypass); + let bypass = bypass.strip_suffix(']').unwrap_or(bypass); + + let bypass = bypass + .split(',') + .map(|h| strip_str(h.trim())) + .collect::>() + .join(","); + + Ok(bypass) + } + "KDE" => { + let xdg_dir = xdg::BaseDirectories::new()?; + let config = xdg_dir.get_config_file("kioslaverc"); + let config = config.to_str().ok_or(Error::ParseStr("config".into()))?; + + let bypass = kreadconfig() + .args([ + "--file", + config, + "--group", + "Proxy Settings", + "--key", + "NoProxyFor", + ]) + .output()?; + let bypass = from_utf8(&bypass.stdout).or(Err(Error::ParseStr("bypass".into())))?.trim(); + + let bypass = bypass + .split(',') + .map(|h| strip_str(h.trim())) + .collect::>() + .join(","); + + Ok(bypass) + } + _ => Err(Error::NotSupport), + } } pub fn get_http() -> Result { @@ -82,34 +134,80 @@ impl Sysproxy { } pub fn set_enable(&self) -> Result<()> { - let mode = if self.enable { "'manual'" } else { "'none'" }; - gsettings().args(["set", CMD_KEY, "mode", mode]).status()?; - Ok(()) + match env::var("XDG_CURRENT_DESKTOP").unwrap_or_default().as_str() { + "GNOME" => { + let mode = if self.enable { "'manual'" } else { "'none'" }; + gsettings().args(["set", CMD_KEY, "mode", mode]).status()?; + Ok(()) + } + "KDE" => { + let xdg_dir = xdg::BaseDirectories::new()?; + let config = xdg_dir.get_config_file("kioslaverc"); + let config = config.to_str().ok_or(Error::ParseStr)?; + let mode = if self.enable { "1" } else { "0" }; + kwriteconfig() + .args([ + "--file", + config, + "--group", + "Proxy Settings", + "--key", + "ProxyType", + mode, + ]) + .status()?; + Ok(()) + } + _ => Err(Error::NotSupport), + } } pub fn set_bypass(&self) -> Result<()> { - let bypass = self - .bypass - .split(',') - .map(|h| { - let mut host = String::from(h.trim()); - if !host.starts_with('\'') && !host.starts_with('"') { - host = String::from("'") + &host; - } - if !host.ends_with('\'') && !host.ends_with('"') { - host = host + "'"; - } - host - }) - .collect::>() - .join(", "); - - let bypass = format!("[{bypass}]"); - - gsettings() - .args(["set", CMD_KEY, "ignore-hosts", bypass.as_str()]) - .status()?; - Ok(()) + match env::var("XDG_CURRENT_DESKTOP").unwrap_or_default().as_str() { + "GNOME" => { + let bypass = self + .bypass + .split(',') + .map(|h| { + let mut host = String::from(h.trim()); + if !host.starts_with('\'') && !host.starts_with('"') { + host = String::from("'") + &host; + } + if !host.ends_with('\'') && !host.ends_with('"') { + host = host + "'"; + } + host + }) + .collect::>() + .join(", "); + + let bypass = format!("[{bypass}]"); + + gsettings() + .args(["set", CMD_KEY, "ignore-hosts", bypass.as_str()]) + .status()?; + Ok(()) + } + "KDE" => { + let xdg_dir = xdg::BaseDirectories::new()?; + let config = xdg_dir.get_config_file("kioslaverc"); + let config = config.to_str().ok_or(Error::ParseStr)?; + + kwriteconfig() + .args([ + "--file", + config, + "--group", + "Proxy Settings", + "--key", + "NoProxyFor", + self.bypass.as_str(), + ]) + .status()?; + Ok(()) + } + _ => Err(Error::NotSupport), + } } pub fn set_http(&self) -> Result<()> { @@ -129,43 +227,123 @@ fn gsettings() -> Command { Command::new("gsettings") } +fn kreadconfig() -> Command { + Command::new("kreadconfig5") +} + +fn kwriteconfig() -> Command { + Command::new("kwriteconfig5") +} + fn set_proxy(proxy: &Sysproxy, service: &str) -> Result<()> { - let schema = format!("{CMD_KEY}.{service}"); - let schema = schema.as_str(); + match env::var("XDG_CURRENT_DESKTOP").unwrap_or_default().as_str() { + "GNOME" => { + let schema = format!("{CMD_KEY}.{service}"); + let schema = schema.as_str(); - let host = format!("'{}'", proxy.host); - let host = host.as_str(); - let port = format!("{}", proxy.port); - let port = port.as_str(); + let host = format!("'{}'", proxy.host); + let host = host.as_str(); + let port = format!("{}", proxy.port); + let port = port.as_str(); - gsettings().args(["set", schema, "host", host]).status()?; - gsettings().args(["set", schema, "port", port]).status()?; + gsettings().args(["set", schema, "host", host]).status()?; + gsettings().args(["set", schema, "port", port]).status()?; - Ok(()) + Ok(()) + } + "KDE" => { + let xdg_dir = xdg::BaseDirectories::new()?; + let config = xdg_dir.get_config_file("kioslaverc"); + let config = config.to_str().ok_or(Error::ParseStr)?; + + let key = format!("{service}Proxy"); + let key = key.as_str(); + + let service = match service { + "socks" => "socks", + _ => "http", + }; + + let host = format!("{}", proxy.host); + let host = host.as_str(); + let port = format!("{}", proxy.port); + let port = port.as_str(); + + let schema = format!("{service}://{host} {port}"); + let schema = schema.as_str(); + + kwriteconfig() + .args([ + "--file", + config, + "--group", + "Proxy Settings", + "--key", + key, + schema, + ]) + .status()?; + + Ok(()) + } + _ => Err(Error::NotSupport), + } } fn get_proxy(service: &str) -> Result { - let schema = format!("{CMD_KEY}.{service}"); - let schema = schema.as_str(); - - let host = gsettings().args(["get", schema, "host"]).output()?; - let host = from_utf8(&host.stdout) - .or(Err(Error::ParseStr("host".into())))? - .trim(); - let host = strip_str(host); - - let port = gsettings().args(["get", schema, "port"]).output()?; - let port = from_utf8(&port.stdout) - .or(Err(Error::ParseStr("port".into())))? - .trim(); - let port = port.parse().unwrap_or(80u16); - - Ok(Sysproxy { - enable: false, - host: String::from(host), - port, - bypass: "".into(), - }) + match env::var("XDG_CURRENT_DESKTOP").unwrap_or_default().as_str() { + "GNOME" => { + let schema = format!("{CMD_KEY}.{service}"); + let schema = schema.as_str(); + + let host = gsettings().args(["get", schema, "host"]).output()?; + let host = from_utf8(&host.stdout) + .or(Err(Error::ParseStr("host".into())))? + .trim(); + let host = strip_str(host); + + let port = gsettings().args(["get", schema, "port"]).output()?; + let port = from_utf8(&port.stdout) + .or(Err(Error::ParseStr("port".into())))? + .trim(); + let port = port.parse().unwrap_or(80u16); + + Ok(Sysproxy { + enable: false, + host: String::from(host), + port, + bypass: "".into(), + }) + } + "KDE" => { + let xdg_dir = xdg::BaseDirectories::new()?; + let config = xdg_dir.get_config_file("kioslaverc"); + let config = config.to_str().ok_or(Error::ParseStr("config".into()))?; + + let key = format!("{service}Proxy"); + let key = key.as_str(); + + let schema = kreadconfig() + .args(["--file", config, "--group", "Proxy Settings", "--key", key]) + .output()?; + let schema = from_utf8(&schema.stdout).or(Err(Error::ParseStr("schema".into())))?.trim(); + let schema = schema + .trim_start_matches("http://") + .trim_start_matches("socks://"); + let schema = schema.split_once(' ').ok_or(Error::ParseStr("schema".into()))?; + + let host = strip_str(schema.0); + let port = schema.1.parse().unwrap_or(80u16); + + Ok(Sysproxy { + enable: false, + host: String::from(host), + port, + bypass: "".into(), + }) + } + _ => Err(Error::NotSupport), + } } fn strip_str<'a>(text: &'a str) -> &'a str {