1use std::{io, path::PathBuf};
2
3use directories::ProjectDirs;
4use serde::{Deserialize, Deserializer, Serialize};
5use snafu::{ResultExt, Snafu};
6use tracing::warn;
7
8use crate::timestamp::TimestampFormat;
9
10use super::options::{ActivePane, AppLayout, SeccompBpf};
11
12#[derive(Debug, Default, Clone, Deserialize, Serialize)]
13pub struct Config {
14 pub log: Option<LogModeConfig>,
15 pub tui: Option<TuiModeConfig>,
16 pub modifier: Option<ModifierConfig>,
17 pub ptrace: Option<PtraceConfig>,
18 pub debugger: Option<DebuggerConfig>,
19}
20
21#[derive(Debug, Snafu)]
22pub enum ConfigLoadError {
23 #[snafu(display("Config file not found."))]
24 NotFound,
25 #[snafu(display("Failed to load config file."))]
26 IoError { source: io::Error },
27 #[snafu(display("Failed to parse config file."))]
28 TomlError { source: toml::de::Error },
29}
30
31impl Config {
32 pub fn load(path: Option<PathBuf>) -> Result<Self, ConfigLoadError> {
33 let config_text = match path {
34 Some(path) => std::fs::read_to_string(path).context(IoSnafu)?, None => {
36 let Some(project_dirs) = project_directory() else {
37 warn!("No valid home directory found! Not loading config.toml.");
38 return Err(ConfigLoadError::NotFound);
39 };
40 let config_path = project_dirs.config_dir().join("config.toml");
42
43 std::fs::read_to_string(config_path).map_err(|e| match e.kind() {
44 io::ErrorKind::NotFound => ConfigLoadError::NotFound,
45 _ => ConfigLoadError::IoError { source: e },
46 })?
47 }
48 };
49
50 let config: Self = toml::from_str(&config_text).context(TomlSnafu)?;
51 Ok(config)
52 }
53}
54
55#[derive(Debug, Default, Clone, Deserialize, Serialize)]
56pub struct ModifierConfig {
57 pub seccomp_bpf: Option<SeccompBpf>,
58 pub successful_only: Option<bool>,
59 pub fd_in_cmdline: Option<bool>,
60 pub stdio_in_cmdline: Option<bool>,
61 pub resolve_proc_self_exe: Option<bool>,
62 pub hide_cloexec_fds: Option<bool>,
63 pub timestamp: Option<TimestampConfig>,
64}
65
66#[derive(Debug, Default, Clone, Deserialize, Serialize)]
67pub struct TimestampConfig {
68 pub enable: bool,
69 pub inline_format: Option<TimestampFormat>,
70}
71
72#[derive(Debug, Default, Clone, Deserialize, Serialize)]
73pub struct PtraceConfig {
74 pub seccomp_bpf: Option<SeccompBpf>,
75}
76
77#[derive(Debug, Default, Clone, Deserialize, Serialize)]
78pub struct TuiModeConfig {
79 pub follow: Option<bool>,
80 pub exit_handling: Option<ExitHandling>,
81 pub active_pane: Option<ActivePane>,
82 pub layout: Option<AppLayout>,
83 #[serde(default, deserialize_with = "deserialize_frame_rate")]
84 pub frame_rate: Option<f64>,
85 pub max_events: Option<u64>,
86}
87
88#[derive(Debug, Default, Clone, Deserialize, Serialize)]
89pub struct DebuggerConfig {
90 pub default_external_command: Option<String>,
91}
92
93fn deserialize_frame_rate<'de, D>(deserializer: D) -> Result<Option<f64>, D::Error>
94where
95 D: Deserializer<'de>,
96{
97 let value = Option::<f64>::deserialize(deserializer)?;
98 if value.is_some_and(|v| v.is_nan() || v <= 0. || v.is_infinite()) {
99 return Err(serde::de::Error::invalid_value(
100 serde::de::Unexpected::Float(value.unwrap()),
101 &"a positive floating-point number",
102 ));
103 }
104 Ok(value)
105}
106
107#[derive(Debug, Default, Clone, Deserialize, Serialize)]
108pub struct LogModeConfig {
109 pub show_interpreter: Option<bool>,
110 pub color_level: Option<ColorLevel>,
111 pub foreground: Option<bool>,
112 pub fd_display: Option<FileDescriptorDisplay>,
113 pub env_display: Option<EnvDisplay>,
114 pub show_comm: Option<bool>,
115 pub show_argv: Option<bool>,
116 pub show_filename: Option<bool>,
117 pub show_cwd: Option<bool>,
118 pub show_cmdline: Option<bool>,
119 pub decode_errno: Option<bool>,
120}
121
122#[derive(Debug, Default, Clone, Deserialize, Serialize)]
123pub enum ColorLevel {
124 Less,
125 #[default]
126 Normal,
127 More,
128}
129
130#[derive(Debug, Default, Clone, Deserialize, Serialize)]
131pub enum FileDescriptorDisplay {
132 Hide,
133 Show,
134 #[default]
135 Diff,
136}
137
138#[derive(Debug, Default, Clone, Deserialize, Serialize)]
139pub enum EnvDisplay {
140 Hide,
141 Show,
142 #[default]
143 Diff,
144}
145
146#[derive(Debug, Default, Clone, Deserialize, Serialize)]
147pub enum ExitHandling {
148 #[default]
149 Wait,
150 Kill,
151 Terminate,
152}
153
154pub fn project_directory() -> Option<ProjectDirs> {
155 ProjectDirs::from("dev", "kxxt", env!("CARGO_PKG_NAME"))
156}