r_extcap/
config.rs

1//! Module for implementing extcap config (also known as `arg`), which are UI
2//! elements shown in Wireshark that allows the user to customize the capture.
3//!
4//! Each interface can have custom options that are valid for this interface
5//! only. Those config options are specified on the command line when running
6//! the actual capture.
7
8use std::any::Any;
9use std::fmt::Debug;
10use std::ops::RangeInclusive;
11use typed_builder::TypedBuilder;
12
13pub use crate::{ExtcapFormatter, PrintSentence};
14
15macro_rules! generate_config_ext {
16    ($config_type:ty) => {
17        impl ConfigTrait for $config_type {
18            fn call(&self) -> &str {
19                &self.call
20            }
21
22            fn as_any(&self) -> &dyn Any {
23                self
24            }
25        }
26    };
27}
28
29/// Defines a reload operation for [`SelectorConfig`].
30pub struct Reload {
31    /// The label for the reload button displayed next to the selector config.
32    pub label: String,
33    /// The reload function executed when the reload button is pressed. Note
34    /// that this reload operation is run in a separate invocation of the
35    /// program, meaning it should not rely on any in-memory state.
36    pub reload_fn: fn() -> Vec<ConfigOptionValue>,
37}
38
39impl std::fmt::Debug for Reload {
40    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
41        write!(f, "Reload(label={})", self.label)
42    }
43}
44
45/// A selector config UI element that allows the user to select an option from a
46/// drop-down list. The list of options should have default=true on exactly one
47/// item.
48///
49/// Typically, these configs are created in a `lazy_static`, and passed to
50/// [`ConfigStep::list_configs`][crate::ConfigStep::list_configs].
51///
52/// ## Example
53/// ```
54/// use r_extcap::config::*;
55///
56/// let selector = SelectorConfig::builder()
57///     .config_number(3)
58///     .call("remote")
59///     .display("Remote Channel")
60///     .tooltip("Remote Channel Selector")
61///     .default_options([
62///         ConfigOptionValue::builder().value("if1").display("Remote1").default(true).build(),
63///         ConfigOptionValue::builder().value("if2").display("Remote2").build(),
64///     ])
65///     .build();
66/// assert_eq!(
67///     format!("{}", ExtcapFormatter(&selector)),
68///     concat!(
69///         "arg {number=3}{call=--remote}{display=Remote Channel}{tooltip=Remote Channel Selector}{type=selector}\n",
70///         "value {arg=3}{value=if1}{display=Remote1}{default=true}\n",
71///         "value {arg=3}{value=if2}{display=Remote2}{default=false}\n"
72///     )
73/// );
74/// ```
75#[derive(Debug, TypedBuilder)]
76pub struct SelectorConfig {
77    /// The config number, a unique identifier for this config.
78    pub config_number: u8,
79    /// The command line option that will be sent to this extcap program. For
80    /// example, if this field is `foobar`, and the corresponding value is `42`,
81    /// then `--foobar 42` will be sent to this program during the extcap
82    /// capture.
83    #[builder(setter(into))]
84    pub call: String,
85    /// The user-friendly label for the selector.
86    #[builder(setter(into))]
87    pub display: String,
88    /// The tooltip shown on when hovering over the UI element.
89    #[builder(default, setter(strip_option, into))]
90    pub tooltip: Option<String>,
91    /// If this is `Some`, a refresh button will be shown next to the selector,
92    /// allowing the user to refresh the list of available options to the return
93    /// value of this function. The first element of the pair is the label of
94    /// the button, and the second element is the function that will be invoked
95    /// on click.
96    ///
97    /// Note: In extcap, the key for the button label is called `placeholder`,
98    /// for some reason.
99    #[builder(default, setter(strip_option))]
100    pub reload: Option<Reload>,
101    /// The (user-visible) name of the tab which this config belongs to. If this
102    /// is `None`, the config will be placed in a tab called "Default".
103    #[builder(default, setter(strip_option, into))]
104    pub group: Option<String>,
105    /// The default list of options presented by this selector.
106    #[builder(setter(into))]
107    pub default_options: Vec<ConfigOptionValue>,
108}
109
110impl PrintSentence for SelectorConfig {
111    fn format_sentence(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
112        write!(f, "arg {{number={}}}", self.config_number)?;
113        write!(f, "{{call=--{}}}", self.call)?;
114        write!(f, "{{display={}}}", self.display)?;
115        if let Some(tooltip) = &self.tooltip {
116            write!(f, "{{tooltip={tooltip}}}")?;
117        }
118        write!(f, "{{type=selector}}")?;
119        if let Some(Reload { label, .. }) = &self.reload {
120            write!(f, "{{reload=true}}")?;
121            write!(f, "{{placeholder={label}}}")?;
122        }
123        if let Some(group) = &self.group {
124            write!(f, "{{group={group}}}")?;
125        }
126        writeln!(f)?;
127        for opt in self.default_options.iter() {
128            write!(f, "{}", ExtcapFormatter(&(opt, self.config_number)))?;
129        }
130        Ok(())
131    }
132}
133
134generate_config_ext!(SelectorConfig);
135
136/// A list of radio buttons for the user to choose one value from. The list of
137/// options should have exactly one item with default=true.
138///
139/// Typically, these configs are created in a `lazy_static`, and passed to
140/// [`ConfigStep::list_configs`][crate::ConfigStep::list_configs].
141///
142/// ## Example
143/// ```
144/// use r_extcap::config::*;
145///
146/// let radio = RadioConfig::builder()
147///     .config_number(3)
148///     .call("remote")
149///     .display("Remote Channel")
150///     .tooltip("Remote Channel Selector")
151///     .options([
152///         ConfigOptionValue::builder().value("if1").display("Remote1").default(true).build(),
153///         ConfigOptionValue::builder().value("if2").display("Remote2").build(),
154///     ])
155///     .build();
156/// assert_eq!(
157///     format!("{}", ExtcapFormatter(&radio)),
158///     concat!(
159///         "arg {number=3}{call=--remote}{display=Remote Channel}{tooltip=Remote Channel Selector}{type=radio}\n",
160///         "value {arg=3}{value=if1}{display=Remote1}{default=true}\n",
161///         "value {arg=3}{value=if2}{display=Remote2}{default=false}\n"
162///     )
163/// );
164/// ```
165#[derive(Debug, TypedBuilder)]
166pub struct RadioConfig {
167    /// The config number, a unique identifier for this config.
168    pub config_number: u8,
169    /// The command line option that will be sent to this extcap program. For
170    /// example, if this field is `foobar`, and the corresponding value is `42`,
171    /// then `--foobar 42` will be sent to this program during the extcap
172    /// capture.
173    #[builder(setter(into))]
174    pub call: String,
175    /// The user-friendly label for the radio button.
176    #[builder(setter(into))]
177    pub display: String,
178    /// The tooltip shown on when hovering over the UI element.
179    #[builder(default, setter(strip_option, into))]
180    pub tooltip: Option<String>,
181    /// The (user-visible) name of the tab which this config belongs to. If this
182    /// is `None`, the config will be placed in a tab called "Default".
183    #[builder(default, setter(strip_option, into))]
184    pub group: Option<String>,
185    /// The default list of options presented by this config.
186    #[builder(setter(into))]
187    pub options: Vec<ConfigOptionValue>,
188}
189
190impl PrintSentence for RadioConfig {
191    fn format_sentence(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
192        write!(f, "arg {{number={}}}", self.config_number)?;
193        write!(f, "{{call=--{}}}", self.call)?;
194        write!(f, "{{display={}}}", self.display)?;
195        if let Some(tooltip) = &self.tooltip {
196            write!(f, "{{tooltip={tooltip}}}")?;
197        }
198        if let Some(group) = &self.group {
199            write!(f, "{{group={}}}", group)?;
200        }
201        write!(f, "{{type=radio}}")?;
202        writeln!(f)?;
203        for opt in self.options.iter() {
204            write!(f, "{}", ExtcapFormatter(&(opt, self.config_number)))?;
205        }
206        Ok(())
207    }
208}
209
210generate_config_ext!(RadioConfig);
211
212/// A tree of hierarchical check boxes that the user can select.
213///
214/// The values are passed comma-separated into the extcap command line. For
215/// example, if the check boxes for `if1`, `if2a`, and `if2b` are checked in the
216/// example below, then `--multi if1,if2a,if2b` will be passed in the command
217/// line.
218///
219/// Typically, these configs are created in a `lazy_static`, and passed to
220/// [`ConfigStep::list_configs`][crate::ConfigStep::list_configs].
221///
222/// ## Example
223/// ```
224/// use r_extcap::config::*;
225///
226/// let config = MultiCheckConfig::builder()
227///     .config_number(3)
228///     .call("multi")
229///     .display("Remote Channel")
230///     .tooltip("Remote Channel Selector")
231///     .options([
232///         MultiCheckValue::builder().value("if1").display("Remote1").default_value(true).build(),
233///         MultiCheckValue::builder().value("if2").display("Remote2").children([
234///             MultiCheckValue::builder().value("if2a").display("Remote2A").default_value(true).build(),
235///             MultiCheckValue::builder().value("if2b").display("Remote2B").default_value(true).build(),
236///         ]).build(),
237///     ])
238///     .build();
239/// assert_eq!(
240///     format!("{}", ExtcapFormatter(&config)),
241///     concat!(
242///         "arg {number=3}{call=--multi}{display=Remote Channel}{tooltip=Remote Channel Selector}{type=multicheck}\n",
243///         "value {arg=3}{value=if1}{display=Remote1}{default=true}{enabled=true}\n",
244///         "value {arg=3}{value=if2}{display=Remote2}{default=false}{enabled=true}\n",
245///         "value {arg=3}{value=if2a}{display=Remote2A}{default=true}{enabled=true}{parent=if2}\n",
246///         "value {arg=3}{value=if2b}{display=Remote2B}{default=true}{enabled=true}{parent=if2}\n"
247///     )
248/// );
249/// ```
250///
251/// To parse those values as a `vec`, you can use the `value_delimiter` option
252/// in `clap`.
253///
254/// ```ignore
255/// #[arg(long, value_delimiter = ',')]
256/// multi: Vec<String>,
257/// ```
258#[derive(Debug, TypedBuilder)]
259pub struct MultiCheckConfig {
260    /// The config number, a unique identifier for this config.
261    pub config_number: u8,
262    /// The command line option that will be sent to this extcap program. For
263    /// example, if this field is `foobar`, and the corresponding value is `42`,
264    /// then `--foobar 42` will be sent to this program during the extcap
265    /// capture.
266    #[builder(setter(into))]
267    pub call: String,
268    /// The user-friendly label for the tree of checkboxes.
269    #[builder(setter(into))]
270    pub display: String,
271    /// The tooltip shown on when hovering over the UI element.
272    #[builder(default, setter(strip_option, into))]
273    pub tooltip: Option<String>,
274    /// The (user-visible) name of the tab which this config belongs to. If this
275    /// is `None`, the config will be placed in a tab called "Default".
276    #[builder(default, setter(strip_option, into))]
277    pub group: Option<String>,
278    /// The default list of options presented by this config. This can be refreshed by the user using via the `reload` field.
279    #[builder(setter(into))]
280    pub options: Vec<MultiCheckValue>,
281}
282
283impl PrintSentence for MultiCheckConfig {
284    fn format_sentence(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
285        write!(f, "arg {{number={}}}", self.config_number)?;
286        write!(f, "{{call=--{}}}", self.call)?;
287        write!(f, "{{display={}}}", self.display)?;
288        if let Some(tooltip) = &self.tooltip {
289            write!(f, "{{tooltip={tooltip}}}")?;
290        }
291        if let Some(group) = &self.group {
292            write!(f, "{{group={}}}", group)?;
293        }
294        write!(f, "{{type=multicheck}}")?;
295        writeln!(f)?;
296        for opt in self.options.iter() {
297            write!(f, "{}", ExtcapFormatter(&(opt, self.config_number, None)))?;
298        }
299        Ok(())
300    }
301}
302
303generate_config_ext!(MultiCheckConfig);
304
305/// Represents a checkbox in a [`MultiCheckConfig`]. Each value is a checkbox in
306/// the UI that can be nested into a hierarchy using the `children` field. See
307/// the docs for [`MultiCheckConfig`] for usage details.
308#[derive(Debug, Clone, TypedBuilder)]
309pub struct MultiCheckValue {
310    /// The value for this option, which is the value that will be passed to the
311    /// extcap command line. For example, if `MultiCheckConfig.call` is `foo`,
312    /// and this field is `bar`, then `--foo bar` will be passed to this extcap
313    /// program during capturing.
314    #[builder(setter(into))]
315    pub value: String,
316    /// The user-friendly label for this check box.
317    #[builder(setter(into))]
318    pub display: String,
319    /// The default value for this check box, whether it is checked or not.
320    #[builder(default = false)]
321    pub default_value: bool,
322    /// Whether this checkbox is enabled or not.
323    #[builder(default = true)]
324    pub enabled: bool,
325    /// The list of children checkboxes. Children check boxes will be indented
326    /// under this check box in the UI, but does not change how the value gets
327    /// sent to the extcap program.
328    #[builder(default, setter(into))]
329    pub children: Vec<MultiCheckValue>,
330}
331
332impl PrintSentence for (&MultiCheckValue, u8, Option<&MultiCheckValue>) {
333    fn format_sentence(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
334        let (config, config_number, parent) = self;
335        write!(f, "value {{arg={}}}", config_number)?;
336        write!(f, "{{value={}}}", config.value)?;
337        write!(f, "{{display={}}}", config.display)?;
338        write!(f, "{{default={}}}", config.default_value)?;
339        write!(f, "{{enabled={}}}", config.enabled)?;
340        if let Some(parent) = parent {
341            write!(f, "{{parent={}}}", parent.value)?;
342        }
343        writeln!(f)?;
344        for c in config.children.iter() {
345            write!(
346                f,
347                "{}",
348                ExtcapFormatter(&(c, *config_number, Some(*config)))
349            )?;
350        }
351        Ok(())
352    }
353}
354
355/// This provides a field for entering a numeric value of the given data type. A
356/// default value may be provided, as well as a range.
357///
358/// Typically, these configs are created in a `lazy_static`, and passed to
359/// [`ConfigStep::list_configs`][crate::ConfigStep::list_configs].
360///
361/// ## Example
362/// ```
363/// use r_extcap::config::*;
364///
365/// let config = LongConfig::builder()
366///     .config_number(0)
367///     .call("delay")
368///     .display("Time delay")
369///     .tooltip("Time delay between packages")
370///     .range(-2..=15)
371///     .default_value(0)
372///     .build();
373/// assert_eq!(
374///     format!("{}", ExtcapFormatter(&config)),
375///     "arg {number=0}{call=--delay}{display=Time delay}{tooltip=Time delay between packages}{range=-2,15}{default=0}{type=long}\n"
376/// );
377/// ```
378#[derive(Debug, TypedBuilder)]
379pub struct LongConfig {
380    /// The config number, a unique identifier for this config.
381    pub config_number: u8,
382    /// The command line option that will be sent to this extcap program. For
383    /// example, if this field is `foobar`, and the corresponding value is `42`,
384    /// then `--foobar 42` will be sent to this program during the extcap
385    /// capture.
386    #[builder(setter(into))]
387    pub call: String,
388    /// The user-friendly label for the numeric field.
389    #[builder(setter(into))]
390    pub display: String,
391    /// The tooltip shown on when hovering over the UI element.
392    #[builder(default, setter(strip_option, into))]
393    pub tooltip: Option<String>,
394    /// The valid range of values for this config.
395    #[builder(default, setter(strip_option))]
396    pub range: Option<RangeInclusive<i64>>,
397    /// The default value for this config.
398    pub default_value: i64,
399    /// The (user-visible) name of the tab which this config belongs to. If this
400    /// is `None`, the config will be placed in a tab called "Default".
401    #[builder(default, setter(strip_option, into))]
402    pub group: Option<String>,
403}
404
405impl PrintSentence for LongConfig {
406    fn format_sentence(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
407        write!(f, "arg {{number={}}}", self.config_number)?;
408        write!(f, "{{call=--{}}}", self.call)?;
409        write!(f, "{{display={}}}", self.display)?;
410        if let Some(tooltip) = &self.tooltip {
411            write!(f, "{{tooltip={tooltip}}}")?;
412        }
413        if let Some(range) = &self.range {
414            write!(f, "{{range={},{}}}", range.start(), range.end())?;
415        }
416        write!(f, "{{default={}}}", self.default_value)?;
417        write!(f, "{{type=long}}")?;
418        if let Some(group) = &self.group {
419            write!(f, "{{group={group}}}")?;
420        }
421        writeln!(f)?;
422        Ok(())
423    }
424}
425
426generate_config_ext!(LongConfig);
427
428/// This provides a field for entering a numeric value of the given data type. A
429/// default value may be provided, as well as a range.
430///
431/// Typically, these configs are created in a `lazy_static`, and passed to
432/// [`ConfigStep::list_configs`][crate::ConfigStep::list_configs].
433///
434/// ## Example
435/// ```
436/// use r_extcap::config::*;
437///
438/// let config = IntegerConfig::builder()
439///     .config_number(0)
440///     .call("delay")
441///     .display("Time delay")
442///     .tooltip("Time delay between packages")
443///     .range(-10..=15)
444///     .default_value(0)
445///     .build();
446/// assert_eq!(
447///     format!("{}", ExtcapFormatter(&config)),
448///     "arg {number=0}{call=--delay}{display=Time delay}{tooltip=Time delay between packages}{range=-10,15}{default=0}{type=integer}\n"
449/// );
450/// ```
451#[derive(Debug, TypedBuilder)]
452pub struct IntegerConfig {
453    /// The config number, a unique identifier for this config.
454    pub config_number: u8,
455    /// The command line option that will be sent to this extcap program. For
456    /// example, if this field is `foobar`, and the corresponding value is `42`,
457    /// then `--foobar 42` will be sent to this program during the extcap
458    /// capture.
459    #[builder(setter(into))]
460    pub call: String,
461    /// The user-friendly label for the numeric field.
462    #[builder(setter(into))]
463    pub display: String,
464    /// The tooltip shown on when hovering over the UI element.
465    #[builder(default, setter(strip_option, into))]
466    pub tooltip: Option<String>,
467    /// The valid range of values for this config.
468    #[builder(default, setter(strip_option))]
469    pub range: Option<RangeInclusive<i32>>,
470    /// The default value for this config.
471    pub default_value: i32,
472    /// The (user-visible) name of the tab which this config belongs to. If this
473    /// is `None`, the config will be placed in a tab called "Default".
474    #[builder(default, setter(strip_option, into))]
475    pub group: Option<String>,
476}
477
478impl PrintSentence for IntegerConfig {
479    fn format_sentence(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
480        write!(f, "arg {{number={}}}", self.config_number)?;
481        write!(f, "{{call=--{}}}", self.call)?;
482        write!(f, "{{display={}}}", self.display)?;
483        if let Some(tooltip) = &self.tooltip {
484            write!(f, "{{tooltip={tooltip}}}")?;
485        }
486        if let Some(range) = &self.range {
487            write!(f, "{{range={},{}}}", range.start(), range.end())?;
488        }
489        write!(f, "{{default={}}}", self.default_value)?;
490        write!(f, "{{type=integer}}")?;
491        if let Some(group) = &self.group {
492            write!(f, "{{group={group}}}")?;
493        }
494        writeln!(f)?;
495        Ok(())
496    }
497}
498
499generate_config_ext!(IntegerConfig);
500
501/// This provides a field for entering a numeric value of the given data type. A
502/// default value may be provided, as well as a range.
503///
504/// Typically, these configs are created in a `lazy_static`, and passed to
505/// [`ConfigStep::list_configs`][crate::ConfigStep::list_configs].
506///
507/// ## Example
508/// ```
509/// use r_extcap::config::*;
510///
511/// let config = UnsignedConfig::builder()
512///     .config_number(0)
513///     .call("delay")
514///     .display("Time delay")
515///     .tooltip("Time delay between packages")
516///     .range(1..=15)
517///     .default_value(0)
518///     .build();
519/// assert_eq!(
520///     format!("{}", ExtcapFormatter(&config)),
521///     "arg {number=0}{call=--delay}{display=Time delay}{tooltip=Time delay between packages}{range=1,15}{default=0}{type=unsigned}\n"
522/// );
523/// ```
524#[derive(Debug, TypedBuilder)]
525pub struct UnsignedConfig {
526    /// The config number, a unique identifier for this config.
527    pub config_number: u8,
528    /// The command line option that will be sent to this extcap program. For
529    /// example, if this field is `foobar`, and the corresponding value is `42`,
530    /// then `--foobar 42` will be sent to this program during the extcap
531    /// capture.
532    #[builder(setter(into))]
533    pub call: String,
534    /// The user-friendly label for the numeric field.
535    #[builder(setter(into))]
536    pub display: String,
537    /// The tooltip shown on when hovering over the UI element.
538    #[builder(default, setter(strip_option, into))]
539    pub tooltip: Option<String>,
540    /// The valid range of values for this config.
541    #[builder(default, setter(strip_option, into))]
542    pub range: Option<RangeInclusive<u32>>,
543    /// The default value for this config.
544    pub default_value: u32,
545    /// The (user-visible) name of the tab which this config belongs to. If this
546    /// is `None`, the config will be placed in a tab called "Default".
547    #[builder(default, setter(strip_option, into))]
548    pub group: Option<String>,
549}
550
551impl PrintSentence for UnsignedConfig {
552    fn format_sentence(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
553        write!(f, "arg {{number={}}}", self.config_number)?;
554        write!(f, "{{call=--{}}}", self.call)?;
555        write!(f, "{{display={}}}", self.display)?;
556        if let Some(tooltip) = &self.tooltip {
557            write!(f, "{{tooltip={tooltip}}}")?;
558        }
559        if let Some(range) = &self.range {
560            write!(f, "{{range={},{}}}", range.start(), range.end())?;
561        }
562        write!(f, "{{default={}}}", self.default_value)?;
563        write!(f, "{{type=unsigned}}")?;
564        if let Some(group) = &self.group {
565            write!(f, "{{group={group}}}")?;
566        }
567        writeln!(f)?;
568        Ok(())
569    }
570}
571
572generate_config_ext!(UnsignedConfig);
573
574/// This provides a field for entering a numeric value of the given data type. A
575/// default value may be provided, as well as a range.
576///
577/// Typically, these configs are created in a `lazy_static`, and passed to
578/// [`ConfigStep::list_configs`][crate::ConfigStep::list_configs].
579///
580/// ## Example
581/// ```
582/// use r_extcap::config::*;
583///
584/// let config = DoubleConfig::builder()
585///     .config_number(0)
586///     .call("delay")
587///     .display("Time delay")
588///     .tooltip("Time delay between packages")
589///     .range(-2.6..=8.2)
590///     .default_value(3.3)
591///     .build();
592/// assert_eq!(
593///     format!("{}", ExtcapFormatter(&config)),
594///     "arg {number=0}{call=--delay}{display=Time delay}{tooltip=Time delay between packages}{range=-2.6,8.2}{default=3.3}{type=double}\n"
595/// );
596/// ```
597#[derive(Debug, TypedBuilder)]
598pub struct DoubleConfig {
599    /// The config number, a unique identifier for this config.
600    pub config_number: u8,
601    /// The command line option that will be sent to this extcap program. For
602    /// example, if this field is `foobar`, and the corresponding value is `42`,
603    /// then `--foobar 42` will be sent to this program during the extcap
604    /// capture.
605    #[builder(setter(into))]
606    pub call: String,
607    /// The user-friendly label for the numeric field.
608    #[builder(setter(into))]
609    pub display: String,
610    /// The tooltip shown on when hovering over the UI element.
611    #[builder(default, setter(strip_option, into))]
612    pub tooltip: Option<String>,
613    /// The valid range of values for this config.
614    #[builder(default, setter(strip_option))]
615    pub range: Option<RangeInclusive<f64>>,
616    /// The default value for this config.
617    pub default_value: f64,
618    /// The (user-visible) name of the tab which this config belongs to. If this
619    /// is `None`, the config will be placed in a tab called "Default".
620    #[builder(default, setter(strip_option, into))]
621    pub group: Option<String>,
622}
623
624impl PrintSentence for DoubleConfig {
625    fn format_sentence(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
626        write!(f, "arg {{number={}}}", self.config_number)?;
627        write!(f, "{{call=--{}}}", self.call)?;
628        write!(f, "{{display={}}}", self.display)?;
629        if let Some(tooltip) = &self.tooltip {
630            write!(f, "{{tooltip={tooltip}}}")?;
631        }
632        if let Some(range) = &self.range {
633            write!(f, "{{range={},{}}}", range.start(), range.end())?;
634        }
635        write!(f, "{{default={}}}", self.default_value)?;
636        write!(f, "{{type=double}}")?;
637        if let Some(group) = &self.group {
638            write!(f, "{{group={group}}}")?;
639        }
640        writeln!(f)?;
641        Ok(())
642    }
643}
644
645generate_config_ext!(DoubleConfig);
646
647/// A field for entering a text value.
648///
649/// Typically, these configs are created in a `lazy_static`, and passed to
650/// [`ConfigStep::list_configs`][crate::ConfigStep::list_configs].
651///
652/// ## Example
653/// ```
654/// use r_extcap::config::*;
655///
656/// let config = StringConfig::builder()
657///     .config_number(1)
658///     .call("server")
659///     .display("IP Address")
660///     .tooltip("IP Address for log server")
661///     .validation(r"\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\b")
662///     .build();
663/// assert_eq!(
664///     format!("{}", ExtcapFormatter(&config)),
665///     concat!(
666///         r"arg {number=1}{call=--server}{display=IP Address}{tooltip=IP Address for log server}{validation=\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\b}{type=string}",
667///         "\n"
668///     )
669/// );
670/// ```
671#[allow(deprecated)]
672#[derive(Debug, TypedBuilder)]
673pub struct StringConfig {
674    /// The config number, a unique identifier for this config.
675    pub config_number: u8,
676    /// The command line option that will be sent to this extcap program. For
677    /// example, if this field is `foobar`, and the corresponding value is `42`,
678    /// then `--foobar 42` will be sent to this program during the extcap
679    /// capture.
680    #[builder(setter(into))]
681    pub call: String,
682    /// The user-friendly label for the text field.
683    #[builder(setter(into))]
684    pub display: String,
685    /// The tooltip shown on when hovering over the UI element.
686    #[builder(default, setter(strip_option, into))]
687    pub tooltip: Option<String>,
688    /// The placeholder string displayed if there is no value in the text field.
689    #[builder(default, setter(strip_option, into))]
690    pub placeholder: Option<String>,
691    /// Whether a value is required for this config.
692    #[builder(default = false)]
693    pub required: bool,
694    /// The (user-visible) name of the tab which this config belongs to. If this
695    /// is `None`, the config will be placed in a tab called "Default".
696    #[builder(default, setter(strip_option, into))]
697    pub group: Option<String>,
698    /// A regular expression string used to check the user input for validity.
699    /// Despite what the Wireshark documentation says, back-slashes in this
700    /// string do not need to be escaped. Just remember to use a Rust raw string
701    /// (e.g. `r"\d\d\d\d"`).
702    #[builder(default, setter(strip_option, into))]
703    pub validation: Option<String>,
704    /// Whether to save the value of this config. If true, the value will be
705    /// saved by Wireshark, and will be automatically populated next time that
706    /// interface is selected by the user.
707    ///
708    /// Note: This option is undocumented in the Wireshark documentation, but
709    /// the functionality was added in
710    /// <https://gitlab.com/wireshark/wireshark/-/commit/97a1a50e200a6c50e0014dde7e8ec932c30190a1>.
711    ///
712    /// It does not behave correctly in some versions of Wireshark, with the
713    /// same symptoms described in
714    /// <https://gitlab.com/wireshark/wireshark/-/issues/18487>.
715    #[builder(default = true)]
716    pub save: bool,
717}
718
719impl PrintSentence for StringConfig {
720    #[allow(deprecated)]
721    fn format_sentence(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
722        write!(f, "arg {{number={}}}", self.config_number)?;
723        write!(f, "{{call=--{}}}", self.call)?;
724        write!(f, "{{display={}}}", self.display)?;
725        if let Some(tooltip) = &self.tooltip {
726            write!(f, "{{tooltip={tooltip}}}")?;
727        }
728        if let Some(placeholder) = &self.placeholder {
729            write!(f, "{{placeholder={}}}", placeholder)?;
730        }
731        if self.required {
732            write!(f, "{{required=true}}")?;
733        }
734        if let Some(validation) = &self.validation {
735            write!(f, "{{validation={}}}", validation)?;
736        }
737        if let Some(group) = &self.group {
738            write!(f, "{{group={group}}}")?;
739        }
740        if !self.save {
741            write!(f, "{{save=false}}")?;
742        }
743        write!(f, "{{type=string}}")?;
744        writeln!(f)?;
745        Ok(())
746    }
747}
748
749generate_config_ext!(StringConfig);
750
751/// A field for entering text value, but with its value masked in the user
752/// interface. The value of a password field is not saved by Wireshark.
753///
754/// Typically, these configs are created in a `lazy_static`, and passed to
755/// [`ConfigStep::list_configs`][crate::ConfigStep::list_configs].
756///
757/// ## Example
758/// ```
759/// use r_extcap::config::*;
760///
761/// let config = PasswordConfig::builder()
762///     .config_number(0)
763///     .call("password")
764///     .display("The user password")
765///     .tooltip("The password for the connection")
766///     .build();
767/// assert_eq!(
768///     format!("{}", ExtcapFormatter(&config)),
769///     "arg {number=0}{call=--password}{display=The user password}{tooltip=The password for the connection}{type=password}\n"
770/// );
771/// ```
772#[derive(Debug, TypedBuilder)]
773pub struct PasswordConfig {
774    /// The config number, a unique identifier for this config.
775    pub config_number: u8,
776    /// The command line option that will be sent to this extcap program. For
777    /// example, if this field is `foobar`, and the corresponding value is `42`,
778    /// then `--foobar 42` will be sent to this program during the extcap
779    /// capture.
780    #[builder(setter(into))]
781    pub call: String,
782    /// The user-friendly label for the password field.
783    #[builder(setter(into))]
784    pub display: String,
785    /// The tooltip shown on when hovering over the UI element.
786    #[builder(default, setter(strip_option, into))]
787    pub tooltip: Option<String>,
788    /// The placeholder string displayed if there is no value in the text field.
789    #[builder(default, setter(strip_option, into))]
790    pub placeholder: Option<String>,
791    /// Whether a value is required for this config.
792    #[builder(default = false)]
793    pub required: bool,
794    /// A regular expression string used to check the user input for validity.
795    /// Despite what the Wireshark documentation says, back-slashes in this
796    /// string do not need to be escaped. Just remember to use a Rust raw string
797    /// (e.g. `r"\d\d\d\d"`).
798    #[builder(default, setter(strip_option, into))]
799    pub validation: Option<String>,
800    /// The (user-visible) name of the tab which this config belongs to. If this
801    /// is `None`, the config will be placed in a tab called "Default".
802    #[builder(default, setter(strip_option, into))]
803    pub group: Option<String>,
804}
805
806impl PrintSentence for PasswordConfig {
807    fn format_sentence(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
808        write!(f, "arg {{number={}}}", self.config_number)?;
809        write!(f, "{{call=--{}}}", self.call)?;
810        write!(f, "{{display={}}}", self.display)?;
811        if let Some(tooltip) = &self.tooltip {
812            write!(f, "{{tooltip={tooltip}}}")?;
813        }
814        if let Some(placeholder) = &self.placeholder {
815            write!(f, "{{placeholder={}}}", placeholder)?;
816        }
817        if self.required {
818            write!(f, "{{required=true}}")?;
819        }
820        if let Some(validation) = &self.validation {
821            write!(f, "{{validation={}}}", validation)?;
822        }
823        if let Some(group) = &self.group {
824            write!(f, "{{group={group}}}")?;
825        }
826        write!(f, "{{type=password}}")?;
827        writeln!(f)?;
828        Ok(())
829    }
830}
831
832generate_config_ext!(PasswordConfig);
833
834/// A config that is displayed as a date/time editor.
835///
836/// Typically, these configs are created in a `lazy_static`, and passed to
837/// [`ConfigStep::list_configs`][crate::ConfigStep::list_configs].
838///
839/// ## Example
840/// ```
841/// use r_extcap::config::*;
842///
843/// let config = TimestampConfig::builder()
844///     .config_number(9)
845///     .call("ts")
846///     .display("Start Time")
847///     .tooltip("Capture start time")
848///     .group("Time / Log")
849///     .build();
850/// assert_eq!(
851///     format!("{}", ExtcapFormatter(&config)),
852///     "arg {number=9}{call=--ts}{display=Start Time}{tooltip=Capture start time}{group=Time / Log}{type=timestamp}\n"
853/// );
854/// ```
855#[derive(Debug, TypedBuilder)]
856pub struct TimestampConfig {
857    /// The config number, a unique identifier for this config.
858    pub config_number: u8,
859    /// The command line option that will be sent to this extcap program. For
860    /// example, if this field is `foobar`, and the corresponding value is `42`,
861    /// then `--foobar 42` will be sent to this program during the extcap
862    /// capture.
863    #[builder(setter(into))]
864    pub call: String,
865    /// The user-friendly label for the config.
866    #[builder(setter(into))]
867    pub display: String,
868    /// The tooltip shown on when hovering over the UI element.
869    #[builder(default, setter(strip_option, into))]
870    pub tooltip: Option<String>,
871    /// The (user-visible) name of the tab which this config belongs to. If this
872    /// is `None`, the config will be placed in a tab called "Default".
873    #[builder(default, setter(strip_option, into))]
874    pub group: Option<String>,
875}
876
877impl PrintSentence for TimestampConfig {
878    fn format_sentence(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
879        write!(f, "arg {{number={}}}", self.config_number)?;
880        write!(f, "{{call=--{}}}", self.call)?;
881        write!(f, "{{display={}}}", self.display)?;
882        if let Some(tooltip) = &self.tooltip {
883            write!(f, "{{tooltip={tooltip}}}")?;
884        }
885        if let Some(group) = &self.group {
886            write!(f, "{{group={group}}}")?;
887        }
888        write!(f, "{{type=timestamp}}")?;
889        writeln!(f)?;
890        Ok(())
891    }
892}
893
894generate_config_ext!(TimestampConfig);
895
896/// Lets the user provide a file path.
897///
898/// Typically, these configs are created in a `lazy_static`, and passed to
899/// [`ConfigStep::list_configs`][crate::ConfigStep::list_configs].
900///
901/// ## Example
902/// ```
903/// use r_extcap::config::*;
904///
905/// let config = FileSelectConfig::builder()
906///     .config_number(3)
907///     .call("logfile")
908///     .display("Logfile")
909///     .tooltip("A file for log messages")
910///     .must_exist(false)
911///     .build();
912/// assert_eq!(
913///     format!("{}", ExtcapFormatter(&config)),
914///     "arg {number=3}{call=--logfile}{display=Logfile}{tooltip=A file for log messages}{type=fileselect}{mustexist=false}\n"
915/// );
916/// ```
917#[derive(Debug, TypedBuilder)]
918pub struct FileSelectConfig {
919    /// The config number, a unique identifier for this config.
920    pub config_number: u8,
921    /// The command line option that will be sent to this extcap program. For
922    /// example, if this field is `foobar`, and the corresponding value is `42`,
923    /// then `--foobar 42` will be sent to this program during the extcap
924    /// capture.
925    #[builder(setter(into))]
926    pub call: String,
927    /// The user-friendly label for the file selector.
928    #[builder(setter(into))]
929    pub display: String,
930    /// The tooltip shown on when hovering over the UI element.
931    #[builder(default, setter(strip_option, into))]
932    pub tooltip: Option<String>,
933    /// The (user-visible) name of the tab which this config belongs to. If this
934    /// is `None`, the config will be placed in a tab called "Default".
935    #[builder(default, setter(strip_option, into))]
936    pub group: Option<String>,
937    /// If true is provided, the GUI shows the user a dialog for selecting an
938    /// existing file. If false, the GUI shows a file dialog for saving a file.
939    #[builder(default = true)]
940    pub must_exist: bool,
941    /// If set, provide a filter for the file extension selectable by this
942    /// config. The format of the filter string is the same as qt's
943    /// [`QFileDialog`](https://doc.qt.io/qt-6/qfiledialog.html).
944    ///
945    /// For example, the filter `Text files (*.txt);;XML files (*.xml)` will
946    /// limit to `.txt` and `.xml` files:
947    ///
948    /// If `None`, any file can be selected (equivalent to `All Files (*)`).
949    ///
950    /// This feature is currnetly not documented in the Wireshark docs, but a
951    /// high level detail can be found in this commit:
952    /// <https://gitlab.com/wireshark/wireshark/-/commit/0d47113ddc53714ecd6d3c1b58b694321649d89e>
953    #[builder(default, setter(into, strip_option))]
954    pub file_extension_filter: Option<String>,
955}
956
957impl PrintSentence for FileSelectConfig {
958    fn format_sentence(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
959        write!(f, "arg {{number={}}}", self.config_number)?;
960        write!(f, "{{call=--{}}}", self.call)?;
961        write!(f, "{{display={}}}", self.display)?;
962        if let Some(tooltip) = &self.tooltip {
963            write!(f, "{{tooltip={tooltip}}}")?;
964        }
965        if let Some(group) = &self.group {
966            write!(f, "{{group={group}}}")?;
967        }
968        write!(f, "{{type=fileselect}}")?;
969        write!(f, "{{mustexist={}}}", self.must_exist)?;
970        if let Some(file_extension_filter) = &self.file_extension_filter {
971            write!(f, "{{fileext={}}}", file_extension_filter)?;
972        }
973        writeln!(f)?;
974        Ok(())
975    }
976}
977
978generate_config_ext!(FileSelectConfig);
979
980/// A checkbox configuration with a true/false value.
981///
982/// Typically, these configs are created in a `lazy_static`, and passed to
983/// [`ConfigStep::list_configs`][crate::ConfigStep::list_configs].
984///
985/// ## Example
986/// ```
987/// use r_extcap::config::*;
988///
989/// let config = BooleanConfig::builder()
990///     .config_number(2)
991///     .call("verify")
992///     .display("Verify")
993///     .tooltip("Verify package content")
994///     .build();
995/// assert_eq!(
996///     format!("{}", ExtcapFormatter(&config)),
997///     "arg {number=2}{call=--verify}{display=Verify}{tooltip=Verify package content}{type=boolflag}\n"
998/// );
999/// ```
1000#[derive(Debug, TypedBuilder)]
1001pub struct BooleanConfig {
1002    /// The config number, a unique identifier for this config.
1003    pub config_number: u8,
1004    /// The command line option that will be sent to this extcap program. For
1005    /// example, if this field is `foobar`, and the corresponding value is `42`,
1006    /// then `--foobar 42` will be sent to this program during the extcap
1007    /// capture.
1008    #[builder(setter(into))]
1009    pub call: String,
1010    /// The user-friendly label for the check box.
1011    #[builder(setter(into))]
1012    pub display: String,
1013    /// The tooltip shown on when hovering over the UI element.
1014    #[builder(default, setter(strip_option, into))]
1015    pub tooltip: Option<String>,
1016    /// The default value for this config.
1017    #[builder(default = false)]
1018    pub default_value: bool,
1019    /// The (user-visible) name of the tab which this config belongs to. If this
1020    /// is `None`, the config will be placed in a tab called "Default".
1021    #[builder(default, setter(strip_option, into))]
1022    pub group: Option<String>,
1023    /// If true, always include the command line flag (e.g. either `--foo true`
1024    /// or `--foo false`). If false (the default), the flag is provided to the
1025    /// command without a value if this is checked (`--foo`), or omitted from
1026    /// the command line arguments if unchecked.
1027    #[builder(default = false)]
1028    pub always_include_option: bool,
1029}
1030
1031impl PrintSentence for BooleanConfig {
1032    fn format_sentence(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
1033        write!(f, "arg {{number={}}}", self.config_number)?;
1034        write!(f, "{{call=--{}}}", self.call)?;
1035        write!(f, "{{display={}}}", self.display)?;
1036        if let Some(tooltip) = &self.tooltip {
1037            write!(f, "{{tooltip={tooltip}}}")?;
1038        }
1039        if self.default_value {
1040            write!(f, "{{default=true}}")?;
1041        }
1042        if self.always_include_option {
1043            write!(f, "{{type=boolean}}")?;
1044        } else {
1045            write!(f, "{{type=boolflag}}")?;
1046        }
1047        if let Some(group) = &self.group {
1048            write!(f, "{{group={group}}}")?;
1049        }
1050        writeln!(f)?;
1051        Ok(())
1052    }
1053}
1054
1055generate_config_ext!(BooleanConfig);
1056
1057/// An option for [`SelectorConfig`] and [`RadioConfig`].
1058#[derive(Clone, Debug, TypedBuilder)]
1059pub struct ConfigOptionValue {
1060    /// The value of this option. If this option is selected, the value will be
1061    /// passed to the command line. For example, if [`SelectorConfig.call`] is
1062    /// `foo`, and this field is `bar`, then `--foo bar` will be passed to this
1063    /// extcap program.
1064    #[builder(setter(into))]
1065    value: String,
1066    /// The user-friendly label for this option.
1067    #[builder(setter(into))]
1068    display: String,
1069    /// Whether this option is selected as the default. For each config there
1070    /// should only be one selected default.
1071    #[builder(default = false)]
1072    default: bool,
1073}
1074
1075impl ConfigOptionValue {
1076    /// Prints out the extcap sentence to stdout for Wireshark's consumption.
1077    pub fn print_sentence(&self, number: u8) {
1078        (self, number).print_sentence()
1079    }
1080}
1081
1082impl PrintSentence for (&ConfigOptionValue, u8) {
1083    fn format_sentence(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
1084        let (config, arg_number) = self;
1085        write!(f, "value {{arg={}}}", arg_number)?;
1086        write!(f, "{{value={}}}", config.value)?;
1087        write!(f, "{{display={}}}", config.display)?;
1088        write!(f, "{{default={}}}", config.default)?;
1089        writeln!(f)?;
1090        Ok(())
1091    }
1092}
1093
1094/// Represents a config, also known as `arg` in an extcap sentence`, which is a
1095/// UI element shown in Wireshark that allows the user to customize the capture.
1096pub trait ConfigTrait: PrintSentence + Any {
1097    /// The command line option that will be sent to this extcap program. For
1098    /// example, if this field is `foobar`, and the corresponding value is `42`,
1099    /// then `--foobar 42` will be sent to this program during the extcap
1100    /// capture.
1101    fn call(&self) -> &str;
1102
1103    /// Returns this trait as an `Any` type.
1104    fn as_any(&self) -> &dyn Any;
1105}