@@ -34,7 +34,7 @@ impl Backend for FakeBackend {
3434 Ok ( RawDeviceInfo {
3535 name : "Fake Device" . into ( ) ,
3636 capacity : 16 ,
37- features : vec ! [ u64 :: MAX ] ,
37+ features : vec ! [ u64 :: MAX , u64 :: MAX ] ,
3838 } )
3939 }
4040
@@ -76,6 +76,11 @@ impl Backend for FakeBackend {
7676 }
7777 }
7878
79+ fn is_playing ( handle : & Self :: Handle , id : i32 ) -> ShakeResult < bool > {
80+ let lock = handle. effects . lock ( ) . unwrap ( ) ;
81+ Ok ( lock. get ( id as usize ) . and_then ( |e| e. as_ref ( ) ) . is_some ( ) )
82+ }
83+
7984 fn set_gain ( _handle : & Self :: Handle , _value : u16 ) -> ShakeResult < ( ) > {
8085 Ok ( ( ) )
8186 }
@@ -157,15 +162,15 @@ fn backend_capabilities_match_raw_features() {
157162 let handle = open_first ( ) . unwrap ( ) ;
158163 let caps = FakeBackend :: capabilities ( & handle) . unwrap ( ) ;
159164
160- // RawDeviceInfo.features = [u64::MAX], so all bits are set.
161165 assert ! ( caps. rumble) ;
162166 assert ! ( caps. periodic) ;
167+ assert ! ( caps. constant) ;
168+ assert ! ( caps. ramp) ;
163169 assert ! ( caps. spring) ;
164170 assert ! ( caps. friction) ;
165171 assert ! ( caps. damper) ;
166172 assert ! ( caps. inertia) ;
167173
168- // max_effects must match RawDeviceInfo.capacity
169174 assert_eq ! ( caps. max_effects, 16 ) ;
170175}
171176
@@ -176,9 +181,10 @@ fn backend_capabilities_are_stable() {
176181 let c1 = FakeBackend :: capabilities ( & handle) . unwrap ( ) ;
177182 let c2 = FakeBackend :: capabilities ( & handle) . unwrap ( ) ;
178183
179- // Values must match exactly
180184 assert_eq ! ( c1. rumble, c2. rumble) ;
181185 assert_eq ! ( c1. periodic, c2. periodic) ;
186+ assert_eq ! ( c1. constant, c2. constant) ;
187+ assert_eq ! ( c1. ramp, c2. ramp) ;
182188 assert_eq ! ( c1. spring, c2. spring) ;
183189 assert_eq ! ( c1. friction, c2. friction) ;
184190 assert_eq ! ( c1. damper, c2. damper) ;
@@ -188,32 +194,26 @@ fn backend_capabilities_are_stable() {
188194
189195#[ test]
190196fn backend_capabilities_handle_empty_feature_vector ( ) {
191- // Manually create a handle with no effects
192197 let handle = FakeHandle {
193198 effects : std:: sync:: Mutex :: new ( Vec :: new ( ) ) ,
194199 } ;
195200
196- // capabilities() must not panic even if the handle was not opened via scan/open
197201 let caps = FakeBackend :: capabilities ( & handle) . unwrap ( ) ;
198202
199- // FakeBackend::query() always returns features = [u64::MAX]
200- // so all capability bits must be true
201203 assert ! ( caps. rumble) ;
202204 assert ! ( caps. periodic) ;
203205 assert ! ( caps. spring) ;
204206 assert ! ( caps. friction) ;
205207 assert ! ( caps. damper) ;
206208 assert ! ( caps. inertia) ;
207209
208- // max_effects must match RawDeviceInfo.capacity
209210 assert_eq ! ( caps. max_effects, 16 ) ;
210211}
211212
212213#[ test]
213214fn backend_capabilities_does_not_modify_effect_slots ( ) {
214215 let handle = open_first ( ) . unwrap ( ) ;
215216
216- // Upload an effect
217217 let effect = Effect :: Rumble ( RumbleEffect {
218218 strong_magnitude : 1000 ,
219219 weak_magnitude : 500 ,
@@ -223,11 +223,7 @@ fn backend_capabilities_does_not_modify_effect_slots() {
223223 } ) ;
224224
225225 let id = FakeBackend :: upload ( & handle, & effect) . unwrap ( ) ;
226-
227- // Calling capabilities() must not erase or modify effects
228226 let _ = FakeBackend :: capabilities ( & handle) . unwrap ( ) ;
229-
230- // Effect must still be playable
231227 FakeBackend :: play ( & handle, id) . unwrap ( ) ;
232228}
233229
@@ -297,8 +293,6 @@ fn backend_update_overwrites_effect() {
297293
298294 let id = FakeBackend :: upload ( & handle, & e1) . unwrap ( ) ;
299295 FakeBackend :: update ( & handle, id, & e2) . unwrap ( ) ;
300-
301- // play() must succeed, meaning the slot still exists
302296 FakeBackend :: play ( & handle, id) . unwrap ( ) ;
303297}
304298
@@ -318,20 +312,116 @@ fn backend_erase_does_not_shift_ids() {
318312 let id2 = FakeBackend :: upload ( & handle, & e) . unwrap ( ) ;
319313
320314 FakeBackend :: erase ( & handle, id1) . unwrap ( ) ;
321-
322- // id2 must still be valid
323315 FakeBackend :: play ( & handle, id2) . unwrap ( ) ;
324316}
325317
326318#[ test]
327319fn backend_close_is_noop_and_safe ( ) {
328320 let handle = open_first ( ) . unwrap ( ) ;
329321
330- // close must not panic
331322 FakeBackend :: close ( handle) ;
332323
333- // open again must still work
334324 let handle2 = open_first ( ) . unwrap ( ) ;
335325 let info = FakeBackend :: query ( & handle2) . unwrap ( ) ;
336326 assert_eq ! ( info. capacity, 16 ) ;
337327}
328+
329+ #[ test]
330+ fn backend_is_playing_reports_status_correctly ( ) {
331+ let handle = open_first ( ) . unwrap ( ) ;
332+
333+ let e = Effect :: Rumble ( RumbleEffect {
334+ strong_magnitude : 1000 ,
335+ weak_magnitude : 500 ,
336+ duration : 200 ,
337+ delay : 0 ,
338+ direction : 0 ,
339+ } ) ;
340+
341+ let id = FakeBackend :: upload ( & handle, & e) . unwrap ( ) ;
342+ assert ! ( FakeBackend :: is_playing( & handle, id) . unwrap( ) ) ;
343+
344+ FakeBackend :: erase ( & handle, id) . unwrap ( ) ;
345+ assert ! ( !FakeBackend :: is_playing( & handle, id) . unwrap( ) ) ;
346+ }
347+
348+ #[ test]
349+ fn backend_capabilities_includes_constant_and_ramp ( ) {
350+ let handle = open_first ( ) . unwrap ( ) ;
351+ let caps = FakeBackend :: capabilities ( & handle) . unwrap ( ) ;
352+
353+ assert ! ( caps. constant) ;
354+ assert ! ( caps. ramp) ;
355+ }
356+
357+ #[ test]
358+ fn backend_capabilities_parses_partial_feature_masks ( ) {
359+ pub struct RestrictedBackend ;
360+
361+ impl Backend for RestrictedBackend {
362+ type Handle = FakeHandle ;
363+
364+ fn scan ( ) -> ShakeResult < Vec < PathBuf > > { Ok ( vec ! [ PathBuf :: from( "/dev/fake_restricted" ) ] ) }
365+ fn open ( _path : & Path ) -> ShakeResult < Self :: Handle > { Ok ( FakeHandle { effects : std:: sync:: Mutex :: new ( Vec :: new ( ) ) } ) }
366+ fn close ( _handle : Self :: Handle ) { }
367+
368+ fn query ( _handle : & Self :: Handle ) -> ShakeResult < RawDeviceInfo > {
369+ let mut features = vec ! [ 0u64 , 0u64 ] ;
370+ features[ 1 ] |= 1 << ( 80 % 64 ) ; // FF_RUMBLE
371+
372+ Ok ( RawDeviceInfo {
373+ name : "Restricted Device" . into ( ) ,
374+ capacity : 4 ,
375+ features,
376+ } )
377+ }
378+
379+ fn upload ( _: & Self :: Handle , _: & Effect ) -> ShakeResult < i32 > { Ok ( 0 ) }
380+ fn update ( _: & Self :: Handle , _: i32 , _: & Effect ) -> ShakeResult < ( ) > { Ok ( ( ) ) }
381+ fn play ( _: & Self :: Handle , _: i32 ) -> ShakeResult < ( ) > { Ok ( ( ) ) }
382+ fn stop ( _: & Self :: Handle , _: i32 ) -> ShakeResult < ( ) > { Ok ( ( ) ) }
383+ fn erase ( _: & Self :: Handle , _: i32 ) -> ShakeResult < ( ) > { Ok ( ( ) ) }
384+ fn is_playing ( _: & Self :: Handle , _: i32 ) -> ShakeResult < bool > { Ok ( false ) }
385+ fn set_gain ( _: & Self :: Handle , _: u16 ) -> ShakeResult < ( ) > { Ok ( ( ) ) }
386+ fn set_autocenter ( _: & Self :: Handle , _: u16 ) -> ShakeResult < ( ) > { Ok ( ( ) ) }
387+ }
388+
389+ let handle = RestrictedBackend :: open ( Path :: new ( "/dev/fake_restricted" ) ) . unwrap ( ) ;
390+ let caps = RestrictedBackend :: capabilities ( & handle) . unwrap ( ) ;
391+
392+ assert ! ( caps. rumble) ;
393+ assert ! ( !caps. periodic) ;
394+ assert ! ( !caps. constant) ;
395+ assert ! ( !caps. spring) ;
396+ }
397+
398+ #[ test]
399+ fn backend_capabilities_handles_empty_or_short_feature_vectors ( ) {
400+ struct EmptyBackend ;
401+ impl Backend for EmptyBackend {
402+ type Handle = FakeHandle ;
403+ fn scan ( ) -> ShakeResult < Vec < PathBuf > > { Ok ( vec ! [ PathBuf :: from( "/dev/empty" ) ] ) }
404+ fn open ( _: & Path ) -> ShakeResult < Self :: Handle > { Ok ( FakeHandle { effects : std:: sync:: Mutex :: new ( Vec :: new ( ) ) } ) }
405+ fn close ( _: Self :: Handle ) { }
406+ fn query ( _: & Self :: Handle ) -> ShakeResult < RawDeviceInfo > {
407+ Ok ( RawDeviceInfo { name : "Empty" . into ( ) , capacity : 0 , features : vec ! [ ] } )
408+ }
409+ fn upload ( _: & Self :: Handle , _: & Effect ) -> ShakeResult < i32 > { Ok ( 0 ) }
410+ fn update ( _: & Self :: Handle , _: i32 , _: & Effect ) -> ShakeResult < ( ) > { Ok ( ( ) ) }
411+ fn play ( _: & Self :: Handle , _: i32 ) -> ShakeResult < ( ) > { Ok ( ( ) ) }
412+ fn stop ( _: & Self :: Handle , _: i32 ) -> ShakeResult < ( ) > { Ok ( ( ) ) }
413+ fn erase ( _: & Self :: Handle , _: i32 ) -> ShakeResult < ( ) > { Ok ( ( ) ) }
414+ fn is_playing ( _: & Self :: Handle , _: i32 ) -> ShakeResult < bool > { Ok ( false ) }
415+ fn set_gain ( _: & Self :: Handle , _: u16 ) -> ShakeResult < ( ) > { Ok ( ( ) ) }
416+ fn set_autocenter ( _: & Self :: Handle , _: u16 ) -> ShakeResult < ( ) > { Ok ( ( ) ) }
417+ }
418+
419+ let handle = EmptyBackend :: open ( Path :: new ( "/dev/empty" ) ) . unwrap ( ) ;
420+ let caps = EmptyBackend :: capabilities ( & handle) . unwrap ( ) ;
421+
422+ assert ! ( !caps. rumble) ;
423+ assert ! ( !caps. periodic) ;
424+ assert ! ( !caps. constant) ;
425+ assert ! ( !caps. ramp) ;
426+ assert_eq ! ( caps. max_effects, 0 ) ;
427+ }
0 commit comments