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}