1use anyhow::{Result, anyhow, bail};
2use hashbrown::HashMap;
3use serde::{Deserialize, Serialize};
4
5use crate::accessibility::reduce_motion_preference;
6use crate::status_line::StatusLineConfig;
7use crate::terminal_title::TerminalTitleConfig;
8use vtcode_commons::ui_protocol::AppIconPreference;
9use vtcode_commons::ui_protocol::BusyEnterBehavior;
10use vtcode_commons::ui_protocol::DiffPreviewMode;
11
12mod modes;
13
14pub use modes::{
15 LayoutModeOverride, NotificationBackend, NotificationDeliveryMode, ReasoningDisplayMode, ToolDisplayMode,
16 ToolOutputMode, UiDisplayMode,
17};
18
19#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
21#[derive(Debug, Clone, Deserialize, Serialize)]
22pub struct UiNotificationsConfig {
23 #[serde(default = "default_notifications_enabled")]
25 pub enabled: bool,
26
27 #[serde(default)]
29 pub delivery_mode: NotificationDeliveryMode,
30
31 #[serde(default)]
33 pub backend: NotificationBackend,
34
35 #[serde(default = "default_notifications_suppress_when_focused")]
37 pub suppress_when_focused: bool,
38
39 #[serde(default)]
42 pub command_failure: Option<bool>,
43
44 #[serde(default = "default_notifications_tool_failure")]
46 pub tool_failure: bool,
47
48 #[serde(default = "default_notifications_error")]
50 pub error: bool,
51
52 #[serde(default = "default_notifications_completion")]
55 pub completion: bool,
56
57 #[serde(default)]
60 pub completion_success: Option<bool>,
61
62 #[serde(default)]
65 pub completion_failure: Option<bool>,
66
67 #[serde(default = "default_notifications_hitl")]
69 pub hitl: bool,
70
71 #[serde(default)]
74 pub policy_approval: Option<bool>,
75
76 #[serde(default)]
79 pub request: Option<bool>,
80
81 #[serde(default = "default_notifications_tool_success")]
83 pub tool_success: bool,
84
85 #[serde(default = "default_notifications_repeat_window_seconds")]
87 pub repeat_window_seconds: u64,
88
89 #[serde(default = "default_notifications_max_identical_in_window")]
91 pub max_identical_in_window: u32,
92}
93
94impl Default for UiNotificationsConfig {
95 fn default() -> Self {
96 Self {
97 enabled: default_notifications_enabled(),
98 delivery_mode: NotificationDeliveryMode::default(),
99 backend: NotificationBackend::default(),
100 suppress_when_focused: default_notifications_suppress_when_focused(),
101 command_failure: Some(default_notifications_command_failure()),
102 tool_failure: default_notifications_tool_failure(),
103 error: default_notifications_error(),
104 completion: default_notifications_completion(),
105 completion_success: Some(default_notifications_completion_success()),
106 completion_failure: Some(default_notifications_completion_failure()),
107 hitl: default_notifications_hitl(),
108 policy_approval: Some(default_notifications_policy_approval()),
109 request: Some(default_notifications_request()),
110 tool_success: default_notifications_tool_success(),
111 repeat_window_seconds: default_notifications_repeat_window_seconds(),
112 max_identical_in_window: default_notifications_max_identical_in_window(),
113 }
114 }
115}
116
117#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
118#[derive(Debug, Clone, Deserialize, Serialize)]
119pub struct UiFullscreenConfig {
120 #[serde(default = "default_fullscreen_mouse_capture")]
123 pub mouse_capture: bool,
124
125 #[serde(default = "default_fullscreen_copy_on_select")]
131 pub copy_on_select: bool,
132
133 #[serde(default = "default_fullscreen_scroll_speed")]
137 pub scroll_speed: u8,
138}
139
140impl Default for UiFullscreenConfig {
141 fn default() -> Self {
142 Self {
143 mouse_capture: default_fullscreen_mouse_capture(),
144 copy_on_select: default_fullscreen_copy_on_select(),
145 scroll_speed: default_fullscreen_scroll_speed(),
146 }
147 }
148}
149
150#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
154#[derive(Debug, Clone, Deserialize, Serialize)]
155pub struct UiTranscriptReviewConfig {
156 #[serde(default = "default_transcript_review_control")]
158 pub show_hints: bool,
159
160 #[serde(default = "default_transcript_review_control")]
162 pub show_shortcut_guide: bool,
163
164 #[serde(default = "default_transcript_review_control")]
166 pub show_close_button: bool,
167}
168
169impl Default for UiTranscriptReviewConfig {
170 fn default() -> Self {
171 Self {
172 show_hints: default_transcript_review_control(),
173 show_shortcut_guide: default_transcript_review_control(),
174 show_close_button: default_transcript_review_control(),
175 }
176 }
177}
178
179#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
180#[derive(Debug, Clone, Deserialize, Serialize)]
181pub struct UiConfig {
182 #[serde(default = "default_tool_output_mode")]
184 pub tool_output_mode: ToolOutputMode,
185
186 #[serde(default = "default_tool_display_mode")]
189 pub tool_display_mode: ToolDisplayMode,
190
191 #[serde(default, alias = "key_bindings")]
194 #[cfg_attr(
195 feature = "schema",
196 schemars(with = "std::collections::BTreeMap<String, Vec<String>>")
197 )]
198 pub keybindings: HashMap<String, Vec<String>>,
199
200 #[serde(default = "default_tool_output_max_lines")]
202 pub tool_output_max_lines: usize,
203
204 #[serde(default = "default_tool_output_spool_bytes")]
206 pub tool_output_spool_bytes: usize,
207
208 #[serde(default)]
210 pub tool_output_spool_dir: Option<String>,
211
212 #[serde(default = "default_allow_tool_ansi")]
214 pub allow_tool_ansi: bool,
215
216 #[serde(default = "default_inline_viewport_rows")]
218 pub inline_viewport_rows: u16,
219
220 #[serde(default = "default_reasoning_display_mode")]
222 pub reasoning_display_mode: ReasoningDisplayMode,
223
224 #[serde(default = "default_reasoning_visible_default")]
226 pub reasoning_visible_default: bool,
227
228 #[serde(default = "default_thinking_display")]
230 pub thinking_display: vtcode_commons::ui_protocol::ThinkingBlockState,
231
232 #[serde(default = "default_vim_mode")]
234 pub vim_mode: bool,
235
236 #[serde(default = "default_busy_enter_behavior")]
241 pub busy_enter_behavior: BusyEnterBehavior,
242
243 #[serde(default)]
245 pub status_line: StatusLineConfig,
246
247 #[serde(default)]
249 pub terminal_title: TerminalTitleConfig,
250
251 #[serde(default)]
253 pub keyboard_protocol: KeyboardProtocolConfig,
254
255 #[serde(default)]
257 pub layout_mode: LayoutModeOverride,
258
259 #[serde(default)]
261 pub display_mode: UiDisplayMode,
262
263 #[serde(default = "default_show_sidebar")]
265 pub show_sidebar: bool,
266
267 #[serde(default = "default_dim_completed_todos")]
269 pub dim_completed_todos: bool,
270
271 #[serde(default = "default_show_task_panel")]
275 pub show_task_panel: bool,
276
277 #[serde(default = "default_message_block_spacing")]
279 pub message_block_spacing: bool,
280
281 #[serde(default = "default_show_turn_timer")]
283 pub show_turn_timer: bool,
284
285 #[serde(default = "default_show_diagnostics_in_transcript")]
289 pub show_diagnostics_in_transcript: bool,
290
291 #[serde(default = "default_show_message_timestamps")]
293 pub show_message_timestamps: bool,
294
295 #[serde(default = "default_minimum_contrast")]
303 pub minimum_contrast: f32,
304
305 #[serde(default = "default_bold_is_bright")]
309 pub bold_is_bright: bool,
310
311 #[serde(default = "default_safe_colors_only")]
317 pub safe_colors_only: bool,
318
319 #[serde(default = "default_color_scheme_mode")]
324 pub color_scheme_mode: ColorSchemeMode,
325
326 #[serde(default = "default_app_icon")]
333 pub app_icon: AppIconPreference,
334
335 #[serde(default)]
337 pub program_status: UiProgramStatusConfig,
338
339 #[serde(default)]
341 pub notifications: UiNotificationsConfig,
342
343 #[serde(default)]
345 pub fullscreen: UiFullscreenConfig,
346
347 #[serde(default = "default_screen_reader_mode")]
352 pub screen_reader_mode: bool,
353
354 #[serde(default = "default_reduce_motion_mode")]
358 pub reduce_motion_mode: bool,
359
360 #[serde(default = "default_reduce_motion_keep_progress_animation")]
363 pub reduce_motion_keep_progress_animation: bool,
364
365 #[serde(default = "default_hide_header")]
367 pub hide_header: bool,
368
369 #[serde(default = "default_diff_preview_mode")]
372 pub diff_preview_mode: DiffPreviewMode,
373
374 #[serde(default)]
376 pub transcript_review: UiTranscriptReviewConfig,
377}
378
379#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
381#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq, Default)]
382#[serde(rename_all = "snake_case")]
383pub enum ColorSchemeMode {
384 #[default]
386 Auto,
387 Light,
389 Dark,
391 #[serde(other)]
393 Unknown,
394}
395
396fn default_minimum_contrast() -> f32 {
397 crate::constants::ui::THEME_MIN_CONTRAST_RATIO
398}
399
400fn default_bold_is_bright() -> bool {
401 false
402}
403
404fn default_safe_colors_only() -> bool {
405 false
406}
407
408fn default_color_scheme_mode() -> ColorSchemeMode {
409 ColorSchemeMode::Auto
410}
411
412fn default_app_icon() -> AppIconPreference {
413 AppIconPreference::Dynamic
414}
415
416fn default_show_sidebar() -> bool {
417 true
418}
419
420fn default_dim_completed_todos() -> bool {
421 true
422}
423
424fn default_message_block_spacing() -> bool {
425 true
426}
427
428fn default_program_status_enabled() -> bool {
429 true
430}
431
432fn default_show_turn_timer() -> bool {
433 false
434}
435
436fn default_show_diagnostics_in_transcript() -> bool {
437 false
438}
439
440fn default_show_message_timestamps() -> bool {
441 true
442}
443
444fn default_vim_mode() -> bool {
445 false
446}
447
448fn default_busy_enter_behavior() -> BusyEnterBehavior {
449 BusyEnterBehavior::Queue
450}
451
452fn default_notifications_enabled() -> bool {
453 true
454}
455
456fn default_notifications_suppress_when_focused() -> bool {
457 true
458}
459
460fn default_notifications_command_failure() -> bool {
461 false
462}
463
464fn default_notifications_tool_failure() -> bool {
465 false
466}
467
468fn default_notifications_error() -> bool {
469 true
470}
471
472fn default_notifications_completion() -> bool {
473 true
474}
475
476fn default_notifications_completion_success() -> bool {
477 false
478}
479
480fn default_notifications_completion_failure() -> bool {
481 true
482}
483
484fn default_notifications_hitl() -> bool {
485 true
486}
487
488fn default_notifications_policy_approval() -> bool {
489 true
490}
491
492fn default_notifications_request() -> bool {
493 false
494}
495
496fn default_notifications_tool_success() -> bool {
497 false
498}
499
500fn default_notifications_repeat_window_seconds() -> u64 {
501 30
502}
503
504fn default_notifications_max_identical_in_window() -> u32 {
505 1
506}
507
508fn env_bool_var(name: &str) -> Option<bool> {
509 crate::env_helpers::read_env_var(name).and_then(|v| vtcode_commons::utils::parse_bool_env_value(&v))
510}
511
512fn env_u8_var(name: &str) -> Option<u8> {
513 crate::env_helpers::read_env_var(name)
514 .and_then(|value| value.trim().parse::<u8>().ok())
515 .map(clamp_fullscreen_scroll_speed)
516}
517
518fn clamp_fullscreen_scroll_speed(value: u8) -> u8 {
519 value.clamp(1, 20)
520}
521
522fn default_fullscreen_mouse_capture() -> bool {
523 env_bool_var("VTCODE_FULLSCREEN_MOUSE_CAPTURE").unwrap_or(true)
524}
525
526fn default_fullscreen_copy_on_select() -> bool {
527 env_bool_var("VTCODE_FULLSCREEN_COPY_ON_SELECT").unwrap_or(true)
528}
529
530fn default_fullscreen_scroll_speed() -> u8 {
531 env_u8_var("VTCODE_FULLSCREEN_SCROLL_SPEED").unwrap_or(3)
532}
533
534fn default_screen_reader_mode() -> bool {
535 env_bool_var("VTCODE_SCREEN_READER").unwrap_or(false)
536}
537
538fn default_reduce_motion_mode() -> bool {
539 env_bool_var("VTCODE_REDUCE_MOTION")
540 .or_else(reduce_motion_preference)
541 .unwrap_or(false)
542}
543
544fn default_reduce_motion_keep_progress_animation() -> bool {
545 false
546}
547
548fn default_hide_header() -> bool {
549 true
550}
551
552fn default_diff_preview_mode() -> DiffPreviewMode {
553 DiffPreviewMode::Inline
554}
555
556fn default_transcript_review_control() -> bool {
557 true
558}
559
560fn default_show_task_panel() -> bool {
561 false
562}
563
564fn default_ask_questions_enabled() -> bool {
565 true
566}
567
568impl Default for UiConfig {
569 fn default() -> Self {
570 Self {
571 tool_output_mode: default_tool_output_mode(),
572 tool_display_mode: default_tool_display_mode(),
573 keybindings: HashMap::new(),
574 tool_output_max_lines: default_tool_output_max_lines(),
575 tool_output_spool_bytes: default_tool_output_spool_bytes(),
576 tool_output_spool_dir: None,
577 allow_tool_ansi: default_allow_tool_ansi(),
578 inline_viewport_rows: default_inline_viewport_rows(),
579 reasoning_display_mode: default_reasoning_display_mode(),
580 reasoning_visible_default: default_reasoning_visible_default(),
581 thinking_display: default_thinking_display(),
582 vim_mode: default_vim_mode(),
583 busy_enter_behavior: default_busy_enter_behavior(),
584 status_line: StatusLineConfig::default(),
585 terminal_title: TerminalTitleConfig::default(),
586 keyboard_protocol: KeyboardProtocolConfig::default(),
587 layout_mode: LayoutModeOverride::default(),
588 display_mode: UiDisplayMode::default(),
589 show_sidebar: default_show_sidebar(),
590 dim_completed_todos: default_dim_completed_todos(),
591 show_task_panel: default_show_task_panel(),
592 message_block_spacing: default_message_block_spacing(),
593 show_turn_timer: default_show_turn_timer(),
594 show_diagnostics_in_transcript: default_show_diagnostics_in_transcript(),
595 show_message_timestamps: default_show_message_timestamps(),
596 minimum_contrast: default_minimum_contrast(),
598 bold_is_bright: default_bold_is_bright(),
599 safe_colors_only: default_safe_colors_only(),
600 color_scheme_mode: default_color_scheme_mode(),
601 app_icon: default_app_icon(),
602 program_status: UiProgramStatusConfig::default(),
603 notifications: UiNotificationsConfig::default(),
604 fullscreen: UiFullscreenConfig::default(),
605 screen_reader_mode: default_screen_reader_mode(),
606 reduce_motion_mode: default_reduce_motion_mode(),
607 reduce_motion_keep_progress_animation: default_reduce_motion_keep_progress_animation(),
608 hide_header: default_hide_header(),
609 diff_preview_mode: default_diff_preview_mode(),
610 transcript_review: UiTranscriptReviewConfig::default(),
611 }
612 }
613}
614
615#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
617#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq)]
618#[serde(default)]
619pub struct UiProgramStatusConfig {
620 #[serde(default = "default_program_status_enabled")]
622 pub enabled: bool,
623}
624
625impl Default for UiProgramStatusConfig {
626 fn default() -> Self {
627 Self { enabled: default_program_status_enabled() }
628 }
629}
630
631#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
633#[derive(Debug, Clone, Deserialize, Serialize, Default)]
634pub struct ChatConfig {
635 #[serde(default, rename = "askQuestions", alias = "ask_questions")]
637 pub ask_questions: AskQuestionsConfig,
638}
639
640#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
642#[derive(Debug, Clone, Deserialize, Serialize)]
643pub struct AskQuestionsConfig {
644 #[serde(default = "default_ask_questions_enabled")]
646 pub enabled: bool,
647}
648
649impl Default for AskQuestionsConfig {
650 fn default() -> Self {
651 Self { enabled: default_ask_questions_enabled() }
652 }
653}
654
655#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
657#[derive(Debug, Clone, Deserialize, Serialize)]
658pub struct PtyConfig {
659 #[serde(default = "default_pty_enabled")]
661 pub enabled: bool,
662
663 #[serde(default = "default_pty_rows")]
665 pub default_rows: u16,
666
667 #[serde(default = "default_pty_cols")]
669 pub default_cols: u16,
670
671 #[serde(default = "default_max_pty_sessions")]
673 pub max_sessions: usize,
674
675 #[serde(default = "default_pty_timeout")]
681 pub command_timeout_seconds: u64,
682
683 #[serde(default = "default_stdout_tail_lines")]
685 pub stdout_tail_lines: usize,
686
687 #[serde(default = "default_scrollback_lines")]
689 pub scrollback_lines: usize,
690
691 #[serde(default = "default_max_scrollback_bytes")]
693 pub max_scrollback_bytes: usize,
694
695 #[serde(default = "default_large_output_threshold_kb")]
697 pub large_output_threshold_kb: usize,
698
699 #[serde(default)]
701 pub preferred_shell: Option<String>,
702
703 #[serde(default = "default_shell_zsh_fork")]
705 pub shell_zsh_fork: bool,
706
707 #[serde(default)]
709 pub zsh_path: Option<String>,
710}
711
712impl Default for PtyConfig {
713 fn default() -> Self {
714 Self {
715 enabled: default_pty_enabled(),
716 default_rows: default_pty_rows(),
717 default_cols: default_pty_cols(),
718 max_sessions: default_max_pty_sessions(),
719 command_timeout_seconds: default_pty_timeout(),
720 stdout_tail_lines: default_stdout_tail_lines(),
721 scrollback_lines: default_scrollback_lines(),
722 max_scrollback_bytes: default_max_scrollback_bytes(),
723 large_output_threshold_kb: default_large_output_threshold_kb(),
724 preferred_shell: None,
725 shell_zsh_fork: default_shell_zsh_fork(),
726 zsh_path: None,
727 }
728 }
729}
730
731impl PtyConfig {
732 pub fn validate(&self) -> Result<()> {
733 self.zsh_fork_shell_path()?;
734 Ok(())
735 }
736
737 pub fn zsh_fork_shell_path(&self) -> Result<Option<&str>> {
738 if !self.shell_zsh_fork {
739 return Ok(None);
740 }
741
742 let zsh_path = self
743 .zsh_path
744 .as_deref()
745 .map(str::trim)
746 .filter(|path| !path.is_empty())
747 .ok_or_else(|| {
748 anyhow!(
749 "pty.shell_zsh_fork is enabled, but pty.zsh_path is not configured. \
750 Set pty.zsh_path to an absolute path to patched zsh."
751 )
752 })?;
753
754 #[cfg(not(unix))]
755 {
756 let _ = zsh_path;
757 bail!("pty.shell_zsh_fork is only supported on Unix platforms");
758 }
759
760 #[cfg(unix)]
761 {
762 let path = std::path::Path::new(zsh_path);
763 if !path.is_absolute() {
764 bail!("pty.zsh_path '{zsh_path}' must be an absolute path when pty.shell_zsh_fork is enabled");
765 }
766 if !path.exists() {
767 bail!("pty.zsh_path '{zsh_path}' does not exist (required when pty.shell_zsh_fork is enabled)");
768 }
769 if !path.is_file() {
770 bail!("pty.zsh_path '{zsh_path}' is not a file (required when pty.shell_zsh_fork is enabled)");
771 }
772
773 Ok(Some(zsh_path))
774 }
775 }
776}
777
778fn default_pty_enabled() -> bool {
779 true
780}
781
782fn default_pty_rows() -> u16 {
783 24
784}
785
786fn default_pty_cols() -> u16 {
787 80
788}
789
790fn default_max_pty_sessions() -> usize {
791 10
792}
793
794fn default_pty_timeout() -> u64 {
795 300
796}
797
798fn default_shell_zsh_fork() -> bool {
799 false
800}
801
802fn default_stdout_tail_lines() -> usize {
803 crate::constants::defaults::DEFAULT_PTY_STDOUT_TAIL_LINES
804}
805
806fn default_scrollback_lines() -> usize {
807 crate::constants::defaults::DEFAULT_PTY_SCROLLBACK_LINES
808}
809
810fn default_max_scrollback_bytes() -> usize {
811 25_000_000 }
815
816fn default_large_output_threshold_kb() -> usize {
817 5_000 }
819
820fn default_tool_output_mode() -> ToolOutputMode {
821 ToolOutputMode::Compact
822}
823
824fn default_tool_display_mode() -> ToolDisplayMode {
825 ToolDisplayMode::Compact
826}
827
828fn default_tool_output_max_lines() -> usize {
829 30
830}
831
832fn default_tool_output_spool_bytes() -> usize {
833 80_000
834}
835
836fn default_allow_tool_ansi() -> bool {
837 false
838}
839
840fn default_inline_viewport_rows() -> u16 {
841 crate::constants::ui::DEFAULT_INLINE_VIEWPORT_ROWS
842}
843
844fn default_reasoning_display_mode() -> ReasoningDisplayMode {
845 ReasoningDisplayMode::Toggle
846}
847
848fn default_reasoning_visible_default() -> bool {
849 crate::constants::ui::DEFAULT_REASONING_VISIBLE
850}
851
852fn default_thinking_display() -> vtcode_commons::ui_protocol::ThinkingBlockState {
853 vtcode_commons::ui_protocol::ThinkingBlockState::Collapsed
854}
855
856#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
860#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Deserialize, Serialize)]
861#[serde(rename_all = "snake_case")]
862pub enum KeyboardProtocolMode {
863 #[default]
865 Default,
866 Full,
868 Minimal,
870 Custom,
872}
873
874impl KeyboardProtocolMode {
875 pub fn as_str(&self) -> &'static str {
877 match self {
878 Self::Default => "default",
879 Self::Full => "full",
880 Self::Minimal => "minimal",
881 Self::Custom => "custom",
882 }
883 }
884}
885
886#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
887#[derive(Debug, Clone, Deserialize, Serialize)]
888pub struct KeyboardProtocolConfig {
889 #[serde(default = "default_keyboard_protocol_enabled")]
891 pub enabled: bool,
892
893 #[serde(default = "default_keyboard_protocol_mode")]
895 pub mode: KeyboardProtocolMode,
896
897 #[serde(default = "default_disambiguate_escape_codes")]
899 pub disambiguate_escape_codes: bool,
900
901 #[serde(default = "default_report_event_types")]
903 pub report_event_types: bool,
904
905 #[serde(default = "default_report_alternate_keys")]
907 pub report_alternate_keys: bool,
908
909 #[serde(default = "default_report_all_keys")]
911 pub report_all_keys: bool,
912}
913
914impl Default for KeyboardProtocolConfig {
915 fn default() -> Self {
916 Self {
917 enabled: default_keyboard_protocol_enabled(),
918 mode: default_keyboard_protocol_mode(),
919 disambiguate_escape_codes: default_disambiguate_escape_codes(),
920 report_event_types: default_report_event_types(),
921 report_alternate_keys: default_report_alternate_keys(),
922 report_all_keys: default_report_all_keys(),
923 }
924 }
925}
926
927impl KeyboardProtocolConfig {
928 pub fn validate(&self) -> Result<()> {
929 let _ = self.mode;
931 Ok(())
932 }
933}
934
935fn default_keyboard_protocol_enabled() -> bool {
936 std::env::var("VTCODE_KEYBOARD_PROTOCOL_ENABLED")
937 .ok()
938 .and_then(|v| v.parse().ok())
939 .unwrap_or(true)
940}
941
942fn default_keyboard_protocol_mode() -> KeyboardProtocolMode {
943 std::env::var("VTCODE_KEYBOARD_PROTOCOL_MODE")
944 .ok()
945 .and_then(|v| match v.to_ascii_lowercase().as_str() {
946 "default" => Some(KeyboardProtocolMode::Default),
947 "full" => Some(KeyboardProtocolMode::Full),
948 "minimal" => Some(KeyboardProtocolMode::Minimal),
949 "custom" => Some(KeyboardProtocolMode::Custom),
950 _ => None,
951 })
952 .unwrap_or_default()
953}
954
955fn default_disambiguate_escape_codes() -> bool {
956 true
957}
958
959fn default_report_event_types() -> bool {
960 true
961}
962
963fn default_report_alternate_keys() -> bool {
964 true
965}
966
967fn default_report_all_keys() -> bool {
968 false
969}
970#[cfg(test)]
971mod tests;