gilrs_core/platform/linux/
ff.rs

1// Copyright 2016-2018 Mateusz Sieczko and other GilRs Developers
2//
3// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
4// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
5// http://opensource.org/licenses/MIT>, at your option. This file may not be
6// copied, modified, or distributed except according to those terms.
7
8use std::fs::File;
9use std::io::{Error as IoError, ErrorKind, Result as IoResult, Write};
10use std::os::unix::io::AsRawFd;
11use std::{mem, slice};
12
13use super::ioctl::{self, ff_effect, ff_replay, ff_rumble_effect, input_event};
14use nix::errno::Errno;
15use std::time::Duration;
16
17#[derive(Debug)]
18pub struct Device {
19    effect: i16,
20    file: File,
21}
22
23impl Device {
24    pub(crate) fn new(path: &str) -> IoResult<Self> {
25        let file = File::create(path)?;
26        let mut effect = ff_effect {
27            type_: FF_RUMBLE,
28            id: -1,
29            direction: 0,
30            trigger: Default::default(),
31            replay: Default::default(),
32            u: Default::default(),
33        };
34
35        #[allow(clippy::unnecessary_mut_passed)]
36        let res = unsafe { ioctl::eviocsff(file.as_raw_fd(), &mut effect) };
37
38        if res.is_err() {
39            Err(IoError::new(ErrorKind::Other, "Failed to create effect"))
40        } else {
41            Ok(Device {
42                effect: effect.id,
43                file,
44            })
45        }
46    }
47
48    pub fn set_ff_state(&mut self, strong: u16, weak: u16, min_duration: Duration) {
49        let duration = min_duration.as_secs() * 1000 + u64::from(min_duration.subsec_millis());
50        let duration = if duration > u64::from(u16::MAX) {
51            u16::MAX
52        } else {
53            duration as u16
54        };
55
56        let mut effect = ff_effect {
57            type_: FF_RUMBLE,
58            id: self.effect,
59            direction: 0,
60            trigger: Default::default(),
61            replay: ff_replay {
62                delay: 0,
63                length: duration,
64            },
65            u: Default::default(),
66        };
67
68        unsafe {
69            let rumble = &mut effect.u as *mut _ as *mut ff_rumble_effect;
70            (*rumble).strong_magnitude = strong;
71            (*rumble).weak_magnitude = weak;
72
73            if let Err(err) = ioctl::eviocsff(self.file.as_raw_fd(), &effect) {
74                error!(
75                    "Failed to modify effect of gamepad {:?}, error: {}",
76                    self.file, err
77                );
78
79                return;
80            }
81        };
82
83        let time = libc::timeval {
84            tv_sec: 0,
85            tv_usec: 0,
86        };
87        let ev = input_event {
88            type_: EV_FF,
89            code: self.effect as u16,
90            value: 1,
91            time,
92        };
93
94        let size = mem::size_of::<input_event>();
95        let s = unsafe { slice::from_raw_parts(&ev as *const _ as *const u8, size) };
96
97        match self.file.write(s) {
98            Ok(s) if s == size => (),
99            Ok(_) => unreachable!(),
100            Err(e) => error!("Failed to set ff state: {}", e),
101        }
102    }
103}
104
105impl Drop for Device {
106    fn drop(&mut self) {
107        #[cfg(target_os = "linux")]
108        let effect = self.effect as ::libc::c_ulong;
109        #[cfg(not(target_os = "linux"))]
110        let effect = self.effect as ::libc::c_int;
111
112        if let Err(err) = unsafe { ioctl::eviocrmff(self.file.as_raw_fd(), effect) } {
113            if err != Errno::ENODEV {
114                error!(
115                    "Failed to remove effect of gamepad {:?}: {}",
116                    self.file, err
117                )
118            }
119        };
120    }
121}
122
123const EV_FF: u16 = 0x15;
124const FF_RUMBLE: u16 = 0x50;