2222
2323#[ cfg( windows) ]
2424mod windows {
25+ use std:: collections:: HashMap ;
2526 use std:: io;
2627 use std:: time:: Duration ;
2728
2829 use crossterm:: event:: {
29- DisableBracketedPaste , EnableBracketedPaste , Event , KeyCode , KeyEventKind , poll, read,
30+ DisableBracketedPaste , DisableMouseCapture , EnableBracketedPaste , EnableMouseCapture ,
31+ Event , KeyCode , KeyEventKind , KeyModifiers , MouseEventKind , poll, read,
3032 } ;
3133 use crossterm:: execute;
3234 use crossterm:: terminal:: { disable_raw_mode, enable_raw_mode} ;
3335 use crossterm_winapi:: { ConsoleMode , Handle } ;
3436
3537 const ENABLE_VIRTUAL_TERMINAL_INPUT : u32 = 0x0200 ;
38+ const ENABLE_MOUSE_INPUT : u32 = 0x0010 ;
39+ const ENABLE_WINDOW_INPUT : u32 = 0x0008 ;
40+
41+ #[ derive( Default ) ]
42+ struct SessionStats {
43+ mouse_moved : u64 ,
44+ mouse_clicks : u64 ,
45+ mouse_drags : u64 ,
46+ mouse_wheels : u64 ,
47+ key_presses : u64 ,
48+ key_releases : u64 ,
49+ press_balance : HashMap < ( KeyCode , KeyModifiers ) , u32 > ,
50+ duplicate_candidates : u64 ,
51+ last_alt_press : Option < KeyCode > ,
52+ duplicate_candidate_key : Option < KeyCode > ,
53+ }
54+
55+ impl SessionStats {
56+ fn record ( & mut self , event : & Event ) {
57+ match event {
58+ Event :: Mouse ( mouse) if matches ! ( mouse. kind, MouseEventKind :: Moved ) => {
59+ self . mouse_moved += 1 ;
60+ }
61+ Event :: Mouse ( mouse) => match mouse. kind {
62+ MouseEventKind :: Down ( _) | MouseEventKind :: Up ( _) => self . mouse_clicks += 1 ,
63+ MouseEventKind :: Drag ( _) => self . mouse_drags += 1 ,
64+ MouseEventKind :: ScrollUp
65+ | MouseEventKind :: ScrollDown
66+ | MouseEventKind :: ScrollLeft
67+ | MouseEventKind :: ScrollRight => self . mouse_wheels += 1 ,
68+ MouseEventKind :: Moved => unreachable ! ( ) ,
69+ } ,
70+ Event :: Key ( key) if key. code == KeyCode :: Esc => {
71+ // Escape terminates the diagnostic; do not report its intentional lone Press
72+ // as an unmatched key in the final summary.
73+ }
74+ Event :: Key ( key) => match key. kind {
75+ KeyEventKind :: Press => {
76+ self . key_presses += 1 ;
77+ let identity = ( key. code , key. modifiers ) ;
78+ if self . duplicate_candidate_key == Some ( key. code ) {
79+ self . duplicate_candidates += 1 ;
80+ self . duplicate_candidate_key = None ;
81+ }
82+ if key. modifiers . contains ( KeyModifiers :: ALT ) {
83+ self . last_alt_press = Some ( key. code ) ;
84+ } else {
85+ self . last_alt_press = None ;
86+ }
87+ * self . press_balance . entry ( identity) . or_default ( ) += 1 ;
88+ }
89+ KeyEventKind :: Release => {
90+ self . key_releases += 1 ;
91+ let identity = ( key. code , key. modifiers ) ;
92+ if let Some ( balance) = self . press_balance . get_mut ( & identity) {
93+ * balance = balance. saturating_sub ( 1 ) ;
94+ }
95+ self . duplicate_candidate_key = self
96+ . last_alt_press
97+ . filter ( |press_code| * press_code == key. code ) ;
98+ self . last_alt_press = None ;
99+ }
100+ KeyEventKind :: Repeat => { }
101+ } ,
102+ _ => { }
103+ }
104+ }
105+
106+ fn print_summary ( & self ) {
107+ let mismatches = self
108+ . press_balance
109+ . values ( )
110+ . filter ( |count| * * count != 0 )
111+ . count ( ) ;
112+ println ! ( "\n Summary:" ) ;
113+ println ! (
114+ " key Press={}, Release={}" ,
115+ self . key_presses, self . key_releases
116+ ) ;
117+ println ! (
118+ " mouse Moved={} (suppressed from log), click={}, drag={}, wheel={}" ,
119+ self . mouse_moved, self . mouse_clicks, self . mouse_drags, self . mouse_wheels
120+ ) ;
121+ println ! ( " Press/Release mismatches={mismatches}" ) ;
122+ println ! (
123+ " ALT Press -> Release -> same-code NONE Press candidates={} (manual check)" ,
124+ self . duplicate_candidates
125+ ) ;
126+ }
127+ }
36128
37129 fn current_console_mode ( ) -> io:: Result < u32 > {
38130 let handle = Handle :: current_in_handle ( ) ?;
@@ -47,6 +139,9 @@ mod windows {
47139 fn run_session ( ) -> io:: Result < ( ) > {
48140 let mut stdout = io:: stdout ( ) ;
49141 let mut raw_enabled = false ;
142+ let mut mouse_enabled = false ;
143+ let mut paste_enabled = false ;
144+ let mut stats = SessionStats :: default ( ) ;
50145
51146 let session_result = ( || {
52147 enable_raw_mode ( ) ?;
@@ -61,14 +156,28 @@ mod windows {
61156 "raw-mode VT_INPUT activation: legacy/unsupported fallback (not a failure)"
62157 ) ;
63158 }
159+ println ! (
160+ "mouse mode bits after raw mode: MOUSE_INPUT={}, WINDOW_INPUT={}" ,
161+ raw_mode & ENABLE_MOUSE_INPUT != 0 ,
162+ raw_mode & ENABLE_WINDOW_INPUT != 0
163+ ) ;
164+ execute ! ( stdout, EnableMouseCapture ) ?;
165+ mouse_enabled = true ;
166+ let mouse_mode = current_console_mode ( ) ?;
167+ print_mode ( "after EnableMouseCapture" , mouse_mode) ;
64168 execute ! ( stdout, EnableBracketedPaste ) ?;
169+ paste_enabled = true ;
65170 println ! (
66- "Reading events. Expected: a/F1 Press+Release (Repeat while held), Alt+numpad 0233 as a character, and multiline paste as one Paste event . Press Esc to exit."
171+ "Reading events. Try click, drag, wheel, paste, ordinary keys, Ctrl+Shift+letter, and Alt+numpad 0233. Moved events are counted but not logged . Press Esc to exit."
67172 ) ;
68173
69174 loop {
70175 let event = read ( ) ?;
71- println ! ( "Event: {event:?}" ) ;
176+ stats. record ( & event) ;
177+ if !matches ! ( & event, Event :: Mouse ( mouse) if matches!( mouse. kind, MouseEventKind :: Moved ) )
178+ {
179+ println ! ( "Event: {event:?}" ) ;
180+ }
72181
73182 if let Event :: Key ( key) = event
74183 && key. code == KeyCode :: Esc
@@ -83,18 +192,33 @@ mod windows {
83192
84193 // Cleanup is deliberately unconditional: a command or read can fail after raw mode or
85194 // bracketed paste has already been enabled.
86- let disable_paste_result = execute ! ( stdout, DisableBracketedPaste ) ;
195+ let mut cleanup_error = None ;
196+ if mouse_enabled {
197+ if let Err ( error) = execute ! ( stdout, DisableMouseCapture ) {
198+ cleanup_error = Some ( error) ;
199+ }
200+ }
201+ if paste_enabled {
202+ if let Err ( error) = execute ! ( stdout, DisableBracketedPaste ) {
203+ cleanup_error. get_or_insert ( error) ;
204+ }
205+ }
87206 let disable_raw_result = if raw_enabled {
88207 disable_raw_mode ( )
89208 } else {
90209 Ok ( ( ) )
91210 } ;
211+ if let Err ( error) = disable_raw_result {
212+ cleanup_error. get_or_insert ( error) ;
213+ }
214+ stats. print_summary ( ) ;
92215
93216 if let Err ( error) = session_result {
94217 return Err ( error) ;
95218 }
96- disable_paste_result?;
97- disable_raw_result?;
219+ if let Some ( error) = cleanup_error {
220+ return Err ( error) ;
221+ }
98222 Ok ( ( ) )
99223 }
100224
@@ -123,10 +247,11 @@ mod windows {
123247 match final_mode_result {
124248 Ok ( final_mode) => {
125249 print_mode ( "after cleanup" , final_mode) ;
126- let restored = final_mode & ENABLE_VIRTUAL_TERMINAL_INPUT
127- == original_mode & ENABLE_VIRTUAL_TERMINAL_INPUT ;
250+ let restore_mask =
251+ ENABLE_VIRTUAL_TERMINAL_INPUT | ENABLE_MOUSE_INPUT | ENABLE_WINDOW_INPUT ;
252+ let restored = final_mode & restore_mask == original_mode & restore_mask;
128253 println ! (
129- "original VT_INPUT bit restoration: {}" ,
254+ "original VT_INPUT/MOUSE_INPUT/WINDOW_INPUT restoration: {}" ,
130255 if restored { "PASS" } else { "FAIL" }
131256 ) ;
132257 if !restored && session_result. is_ok ( ) {
0 commit comments