1+ #![ feature( default_field_values) ]
2+
3+ use evdev:: { EventType , InputEvent , RelativeAxisCode } ;
4+ use std:: time:: SystemTime ;
5+
6+ /// Parameters for the anxious scroll algorithm
7+ #[ derive( Debug , Clone ) ]
8+ pub struct AnxiousParams {
9+ /// Base sensitivity to start at
10+ pub base_sens : f32 ,
11+ /// Max sensitivity to taper off towards
12+ pub max_sens : f32 ,
13+ /// How fast to ramp up the logistic function
14+ pub ramp_up_rate : f32 ,
15+ }
16+
17+ impl Default for AnxiousParams {
18+ fn default ( ) -> Self {
19+ Self {
20+ base_sens : 1.0 ,
21+ max_sens : 15.0 ,
22+ ramp_up_rate : 0.3 ,
23+ }
24+ }
25+ }
26+
27+ /// State for tracking scroll velocity over time
28+ #[ derive( Debug ) ]
29+ #[ repr( transparent) ]
30+ pub struct AnxiousState {
31+ pub prev_time : SystemTime ,
32+ }
33+
34+ impl AnxiousState {
35+ pub fn new ( ) -> Self {
36+ Self {
37+ prev_time : SystemTime :: now ( ) ,
38+ }
39+ }
40+ }
41+
42+ #[ inline( always) ]
43+ /// We use a logistic function as the transformation function.
44+ /// f(vel) = max_sens / (1 + C * e^(-ramp_up_rate * vel)), where
45+ /// C = (max_sens / (base_sens) - 1
46+ /// Visualisation: https://www.desmos.com/calculator/grsgyudrch
47+ pub fn apply_anxious_scroll (
48+ value : f32 ,
49+ timestamp : SystemTime ,
50+ anxious_params : & AnxiousParams ,
51+ anxious_state : & mut AnxiousState ,
52+ ) -> i32 {
53+ let elapsed_time = timestamp. duration_since ( anxious_state. prev_time ) . unwrap ( ) ;
54+ anxious_state. prev_time = timestamp;
55+
56+ let vel = value. abs ( ) / elapsed_time. as_millis ( ) as f32 ;
57+ let c = ( anxious_params. max_sens / anxious_params. base_sens ) - 1.0 ;
58+ // TODO: Use fast approximation for the calculation
59+ let sens = anxious_params. max_sens
60+ / ( 1.0 + c * ( -1.0 * vel as f32 * anxious_params. ramp_up_rate ) . exp ( ) ) ;
61+ return ( value * sens) as i32 ;
62+ }
63+
64+ #[ inline( always) ]
65+ /// Process a batch of input events, applying anxious scroll transformation to wheel events
66+ /// This is a pure function with no I/O dependencies, making it easily testable and benchmarkable
67+ pub fn process_events (
68+ events : impl Iterator < Item = InputEvent > ,
69+ anxious_params : & AnxiousParams ,
70+ anxious_state : & mut AnxiousState ,
71+ ) -> Vec < InputEvent > {
72+ let mut event_batch = Vec :: new ( ) ;
73+
74+ for event in events {
75+ if event. event_type ( ) == EventType :: RELATIVE
76+ && event. code ( ) == RelativeAxisCode :: REL_WHEEL_HI_RES . 0
77+ {
78+ // Create a new event with modified value
79+ let modified_value = apply_anxious_scroll (
80+ event. value ( ) as f32 ,
81+ event. timestamp ( ) ,
82+ anxious_params,
83+ anxious_state,
84+ ) ;
85+ // new_now() is not necessary here as the kernel will update the time field
86+ // when it emits the events to any programs reading the event "file".
87+ let modified_event = InputEvent :: new ( event. event_type ( ) . 0 , event. code ( ) , modified_value) ;
88+ event_batch. push ( modified_event) ;
89+ } else if event. event_type ( ) == EventType :: RELATIVE
90+ && event. code ( ) == RelativeAxisCode :: REL_WHEEL . 0
91+ {
92+ // Drop event
93+ continue ;
94+ } else {
95+ // Pass through all other events unchanged
96+ event_batch. push ( event) ;
97+ }
98+ }
99+
100+ event_batch
101+ }
102+
103+ #[ cfg( test) ]
104+ mod tests {
105+ use super :: * ;
106+ use std:: time:: { Duration , UNIX_EPOCH } ;
107+
108+ fn create_test_state_with_time ( prev_time : SystemTime ) -> AnxiousState {
109+ AnxiousState { prev_time }
110+ }
111+
112+ #[ test]
113+ fn test_zero_value ( ) {
114+ let params = AnxiousParams :: default ( ) ;
115+ let base_time = UNIX_EPOCH + Duration :: from_secs ( 1000000000 ) ;
116+ let mut state = create_test_state_with_time ( base_time) ;
117+
118+ let result = apply_anxious_scroll ( 0.0 , base_time + Duration :: from_millis ( 10 ) , & params, & mut state) ;
119+ assert_eq ! ( result, 0 ) ;
120+ }
121+
122+ #[ test]
123+ fn test_large_value ( ) {
124+ let params = AnxiousParams :: default ( ) ;
125+ let base_time = UNIX_EPOCH + Duration :: from_secs ( 1000000000 ) ;
126+ let mut state = create_test_state_with_time ( base_time) ;
127+
128+ let result = apply_anxious_scroll ( 1000.0 , base_time + Duration :: from_millis ( 1 ) , & params, & mut state) ;
129+ // Should not panic and should return a reasonable value
130+ assert ! ( result > 0 ) ;
131+ }
132+
133+ #[ test]
134+ fn test_negative_value ( ) {
135+ let params = AnxiousParams :: default ( ) ;
136+ let base_time = UNIX_EPOCH + Duration :: from_secs ( 1000000000 ) ;
137+ let mut state = create_test_state_with_time ( base_time) ;
138+
139+ let result = apply_anxious_scroll ( -10.0 , base_time + Duration :: from_millis ( 10 ) , & params, & mut state) ;
140+ assert ! ( result < 0 ) ;
141+ }
142+
143+ #[ test]
144+ fn test_very_small_elapsed_time ( ) {
145+ let params = AnxiousParams :: default ( ) ;
146+ let base_time = UNIX_EPOCH + Duration :: from_secs ( 1000000000 ) ;
147+ let mut state = create_test_state_with_time ( base_time) ;
148+
149+ // Test with very small elapsed time (1 microsecond)
150+ let result = apply_anxious_scroll ( 10.0 , base_time + Duration :: from_micros ( 1 ) , & params, & mut state) ;
151+ // Should not panic and should return a reasonable value
152+ assert ! ( result > 0 ) ;
153+ }
154+
155+ #[ test]
156+ fn test_parameter_configurations ( ) {
157+ let base_time = UNIX_EPOCH + Duration :: from_secs ( 1000000000 ) ;
158+ let mut state = create_test_state_with_time ( base_time) ;
159+
160+ // Test default parameters
161+ let default_params = AnxiousParams :: default ( ) ;
162+ let result1 = apply_anxious_scroll ( 10.0 , base_time + Duration :: from_millis ( 10 ) , & default_params, & mut state) ;
163+
164+ // Test high sensitivity
165+ let high_sens_params = AnxiousParams {
166+ base_sens : 1.0 ,
167+ max_sens : 30.0 ,
168+ ramp_up_rate : 0.5 ,
169+ } ;
170+ let result2 = apply_anxious_scroll ( 10.0 , base_time + Duration :: from_millis ( 10 ) , & high_sens_params, & mut state) ;
171+
172+ // Test low sensitivity
173+ let low_sens_params = AnxiousParams {
174+ base_sens : 0.5 ,
175+ max_sens : 5.0 ,
176+ ramp_up_rate : 0.1 ,
177+ } ;
178+ let result3 = apply_anxious_scroll ( 10.0 , base_time + Duration :: from_millis ( 10 ) , & low_sens_params, & mut state) ;
179+
180+ // All should return reasonable values
181+ assert ! ( result1 > 0 ) ;
182+ assert ! ( result2 > 0 ) ;
183+ assert ! ( result3 > 0 ) ;
184+
185+ // High sensitivity should generally produce higher values than low sensitivity
186+ assert ! ( result2 > result3) ;
187+ }
188+
189+ #[ test]
190+ fn test_process_events_basic ( ) {
191+ use evdev:: { EventType , InputEvent , RelativeAxisCode } ;
192+
193+ // Create events with proper timestamps to avoid SystemTime issues
194+ let base_time = UNIX_EPOCH + Duration :: from_secs ( 1000000000 ) ;
195+ let events = vec ! [
196+ InputEvent :: new_now( EventType :: RELATIVE . 0 , RelativeAxisCode :: REL_WHEEL_HI_RES . 0 , 120 ) ,
197+ InputEvent :: new_now( EventType :: RELATIVE . 0 , RelativeAxisCode :: REL_WHEEL . 0 , 1 ) , // Should be dropped
198+ InputEvent :: new_now( EventType :: RELATIVE . 0 , RelativeAxisCode :: REL_X . 0 , 10 ) , // Should pass through
199+ ] ;
200+
201+ let params = AnxiousParams :: default ( ) ;
202+ let mut state = create_test_state_with_time ( base_time) ;
203+
204+ let result = process_events ( events. iter ( ) . cloned ( ) , & params, & mut state) ;
205+
206+ // Should have 2 events: one processed wheel event and one pass-through event
207+ assert_eq ! ( result. len( ) , 2 ) ;
208+
209+ // First event should be the processed wheel event
210+ assert_eq ! ( result[ 0 ] . event_type( ) , EventType :: RELATIVE ) ;
211+ assert_eq ! ( result[ 0 ] . code( ) , RelativeAxisCode :: REL_WHEEL_HI_RES . 0 ) ;
212+
213+ // Second event should be the pass-through event
214+ assert_eq ! ( result[ 1 ] . event_type( ) , EventType :: RELATIVE ) ;
215+ assert_eq ! ( result[ 1 ] . code( ) , RelativeAxisCode :: REL_X . 0 ) ;
216+ assert_eq ! ( result[ 1 ] . value( ) , 10 ) ;
217+ }
218+ }
0 commit comments