capsules_extra/
touch.rs

1// Licensed under the Apache License, Version 2.0 or the MIT License.
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3// Copyright Tock Contributors 2022.
4
5//! Provides userspace with access to the touch panel.
6//!
7//! Usage
8//! -----
9//!
10//! You need a touch that provides the `hil::touch::Touch` trait.
11//! An optional gesture client and a screen can be connected to it.
12//!
13//! ```rust,ignore
14//! let touch =
15//!     components::touch::TouchComponent::new(board_kernel, ts, Some(ts), Some(screen)).finalize(());
16//! ```
17
18use core::cell::Cell;
19use core::mem;
20
21use kernel::grant::{AllowRoCount, AllowRwCount, Grant, UpcallCount};
22use kernel::hil;
23use kernel::hil::screen::ScreenRotation;
24use kernel::hil::touch::{GestureEvent, TouchClient, TouchEvent, TouchStatus};
25use kernel::processbuffer::WriteableProcessBuffer;
26use kernel::syscall::{CommandReturn, SyscallDriver};
27use kernel::{ErrorCode, ProcessId};
28
29/// Syscall driver number.
30use capsules_core::driver;
31pub const DRIVER_NUM: usize = driver::NUM::Touch as usize;
32
33/// Ids for read-write allow buffers
34mod rw_allow {
35    /// Allow a buffer for the multi touch. See header for format
36    // Buffer data format
37    //  0         1           2              4              6           7             8          ...
38    // +---------+-----------+--------------+--------------+-----------+---------------+-------- ...
39    // | id (u8) | type (u8) | x (u16)      | y (u16)      | size (u8) | pressure (u8) |         ...
40    // +---------+-----------+--------------+--------------+-----------+---------------+-------- ...
41    // | Touch 0                                                                       | Touch 1 ...
42    pub const EVENTS: usize = 2;
43    /// The number of allow buffers the kernel stores for this grant
44    pub const COUNT: u8 = 3;
45}
46
47fn touch_status_to_number(status: &TouchStatus) -> usize {
48    match status {
49        TouchStatus::Released => 0,
50        TouchStatus::Pressed => 1,
51        TouchStatus::Moved => 2,
52        TouchStatus::Unstarted => 3,
53    }
54}
55
56pub struct App {
57    ack: bool,
58    dropped_events: usize,
59    x: u16,
60    y: u16,
61    status: usize,
62    touch_enable: bool,
63    multi_touch_enable: bool,
64}
65
66impl Default for App {
67    fn default() -> App {
68        App {
69            ack: true,
70            dropped_events: 0,
71            x: 0,
72            y: 0,
73            status: touch_status_to_number(&TouchStatus::Unstarted),
74            touch_enable: false,
75            multi_touch_enable: false,
76        }
77    }
78}
79
80pub struct Touch<'a> {
81    touch: Option<&'a dyn hil::touch::Touch<'a>>,
82    multi_touch: Option<&'a dyn hil::touch::MultiTouch<'a>>,
83    /// Screen under the touch panel
84    /// Most of the touch panels have a screen that can be rotated
85    /// 90 deg (clockwise), 180 deg (upside-down), 270 deg(clockwise).
86    /// The touch gets the rotation from the screen and
87    /// updates the touch (x, y) position
88    screen: Option<&'a dyn hil::screen::Screen<'a>>,
89    apps: Grant<App, UpcallCount<3>, AllowRoCount<0>, AllowRwCount<{ rw_allow::COUNT }>>,
90    screen_rotation_offset: Cell<ScreenRotation>,
91}
92
93impl<'a> Touch<'a> {
94    pub fn new(
95        touch: Option<&'a dyn hil::touch::Touch<'a>>,
96        multi_touch: Option<&'a dyn hil::touch::MultiTouch<'a>>,
97        screen: Option<&'a dyn hil::screen::Screen<'a>>,
98        grant: Grant<App, UpcallCount<3>, AllowRoCount<0>, AllowRwCount<{ rw_allow::COUNT }>>,
99    ) -> Touch<'a> {
100        Touch {
101            touch,
102            multi_touch,
103            screen,
104            screen_rotation_offset: Cell::new(ScreenRotation::Normal),
105            apps: grant,
106        }
107    }
108
109    pub fn set_screen_rotation_offset(&self, screen_rotation_offset: ScreenRotation) {
110        self.screen_rotation_offset.set(screen_rotation_offset);
111    }
112
113    fn touch_enable(&self) -> Result<(), ErrorCode> {
114        let mut enabled = false;
115        for app in self.apps.iter() {
116            if app.enter(|app, _| app.touch_enable) {
117                enabled = true;
118                break;
119            }
120        }
121        self.touch.map_or_else(
122            || {
123                // if there is no single touch device
124                // try to use the multi touch device and
125                // report only a single touch
126                self.multi_touch
127                    .map_or(Err(ErrorCode::NODEVICE), |multi_touch| {
128                        if enabled {
129                            multi_touch.enable()
130                        } else {
131                            multi_touch.disable()
132                        }
133                    })
134            },
135            |touch| {
136                if enabled {
137                    touch.enable()
138                } else {
139                    touch.disable()
140                }
141            },
142        )
143    }
144
145    fn multi_touch_enable(&self) -> Result<(), ErrorCode> {
146        let mut enabled = false;
147        for app in self.apps.iter() {
148            if app.enter(|app, _| app.multi_touch_enable) {
149                enabled = true;
150                break;
151            }
152        }
153        self.multi_touch
154            .map_or(Err(ErrorCode::NODEVICE), |multi_touch| {
155                if enabled {
156                    multi_touch.enable()
157                } else {
158                    multi_touch.disable()
159                }
160            })
161    }
162
163    /// Updates the (x, y) pf the touch event based on the
164    /// screen rotation (if there si a screen)
165    fn update_rotation(&self, touch_event: &mut TouchEvent) {
166        if let Some(screen) = self.screen {
167            let rotation = screen.get_rotation() + self.screen_rotation_offset.get();
168            let (mut width, mut height) = screen.get_resolution();
169
170            let (x, y) = match rotation {
171                ScreenRotation::Rotated90 => {
172                    mem::swap(&mut width, &mut height);
173                    (touch_event.y, height as u16 - touch_event.x)
174                }
175                ScreenRotation::Rotated180 => {
176                    (width as u16 - touch_event.x, height as u16 - touch_event.y)
177                }
178                ScreenRotation::Rotated270 => {
179                    mem::swap(&mut width, &mut height);
180                    (width as u16 - touch_event.y, touch_event.x)
181                }
182                _ => (touch_event.x, touch_event.y),
183            };
184
185            touch_event.x = x;
186            touch_event.y = y;
187        }
188    }
189}
190
191impl hil::touch::TouchClient for Touch<'_> {
192    fn touch_event(&self, mut event: TouchEvent) {
193        // update rotation if there is a screen attached
194        self.update_rotation(&mut event);
195        // debug!(
196        //     "touch {:?} x {} y {} size {:?} pressure {:?}",
197        //     event.status, event.x, event.y, event.size, event.pressure
198        // );
199        for app in self.apps.iter() {
200            app.enter(|app, kernel_data| {
201                let event_status = touch_status_to_number(&event.status);
202                if app.x != event.x || app.y != event.y || app.status != event_status {
203                    app.x = event.x;
204                    app.y = event.y;
205                    app.status = event_status;
206
207                    let pressure_size = match event.pressure {
208                        Some(pressure) => (pressure as usize) << 16,
209                        None => 0,
210                    } | match event.size {
211                        Some(size) => size as usize,
212                        None => 0,
213                    };
214                    let _ = kernel_data.schedule_upcall(
215                        0,
216                        (
217                            event_status,
218                            (event.x as usize) << 16 | event.y as usize,
219                            pressure_size,
220                        ),
221                    );
222                }
223            });
224        }
225    }
226}
227
228impl hil::touch::MultiTouchClient for Touch<'_> {
229    fn touch_events(&self, touch_events: &[TouchEvent], num_events: usize) {
230        let len = if touch_events.len() < num_events {
231            touch_events.len()
232        } else {
233            num_events
234        };
235        if len > 0 {
236            // report the first touch as single touch
237            // for applications that only use single touches
238            self.touch_event(touch_events[0]);
239            // debug!("{} touch(es)", len);
240            for app in self.apps.iter() {
241                app.enter(|app, kernel_data| {
242                    if app.ack {
243                        app.dropped_events = 0;
244
245                        let num = kernel_data
246                            .get_readwrite_processbuffer(rw_allow::EVENTS)
247                            .and_then(|events| {
248                                events.mut_enter(|buffer| {
249                                    let num = if buffer.len() / 8 < len {
250                                        buffer.len() / 8
251                                    } else {
252                                        len
253                                    };
254
255                                    for event_index in 0..num {
256                                        let mut event = touch_events[event_index];
257                                        self.update_rotation(&mut event);
258                                        let event_status = touch_status_to_number(&event.status);
259                                        // debug!(
260                                        //     " multitouch {:?} x {} y {} size {:?} pressure {:?}",
261                                        //     event.status, event.x, event.y, event.size, event.pressure
262                                        // );
263                                        // one touch entry is 8 bytes long
264                                        let offset = event_index * 8;
265                                        if buffer.len() > event_index + 8 {
266                                            buffer[offset].set(event.id as u8);
267                                            buffer[offset + 1].set(event_status as u8);
268                                            buffer[offset + 2].set((event.x & 0xFF) as u8);
269                                            buffer[offset + 3].set(((event.x & 0xFFFF) >> 8) as u8);
270                                            buffer[offset + 4].set((event.y & 0xFF) as u8);
271                                            buffer[offset + 5].set(((event.y & 0xFFFF) >> 8) as u8);
272                                            buffer[offset + 6].set(
273                                                if let Some(size) = event.size {
274                                                    size as u8
275                                                } else {
276                                                    0
277                                                },
278                                            );
279                                            buffer[offset + 7].set(
280                                                if let Some(pressure) = event.pressure {
281                                                    pressure as u8
282                                                } else {
283                                                    0
284                                                },
285                                            );
286                                        } else {
287                                            break;
288                                        }
289                                    }
290                                    num
291                                })
292                            })
293                            .unwrap_or(0);
294                        let dropped_events = app.dropped_events;
295                        if num > 0 {
296                            app.ack = false;
297                            let _ = kernel_data
298                                .schedule_upcall(2, (num, dropped_events, len.saturating_sub(num)));
299                        }
300                    } else {
301                        app.dropped_events += 1;
302                    }
303                });
304            }
305        }
306    }
307}
308
309impl hil::touch::GestureClient for Touch<'_> {
310    fn gesture_event(&self, event: GestureEvent) {
311        for app in self.apps.iter() {
312            app.enter(|_app, kernel_data| {
313                let gesture_id = match event {
314                    GestureEvent::SwipeUp => 1,
315                    GestureEvent::SwipeDown => 2,
316                    GestureEvent::SwipeLeft => 3,
317                    GestureEvent::SwipeRight => 4,
318                    GestureEvent::ZoomIn => 5,
319                    GestureEvent::ZoomOut => 6,
320                };
321                let _ = kernel_data.schedule_upcall(1, (gesture_id, 0, 0));
322            });
323        }
324    }
325}
326
327impl SyscallDriver for Touch<'_> {
328    fn command(
329        &self,
330        command_num: usize,
331        _data1: usize,
332        _data2: usize,
333        processid: ProcessId,
334    ) -> CommandReturn {
335        match command_num {
336            // driver existence check
337            0 => CommandReturn::success(),
338
339            // touch enable
340            1 => {
341                self.apps
342                    .enter(processid, |app, _| {
343                        app.touch_enable = true;
344                    })
345                    .unwrap_or(());
346                let _ = self.touch_enable();
347                CommandReturn::success()
348            }
349
350            // touch disable
351            2 => {
352                self.apps
353                    .enter(processid, |app, _| {
354                        app.touch_enable = false;
355                    })
356                    .unwrap_or(());
357                let _ = self.touch_enable();
358                CommandReturn::success()
359            }
360
361            // multi touch ack
362            10 => {
363                self.apps
364                    .enter(processid, |app, _| {
365                        app.ack = true;
366                    })
367                    .unwrap_or(());
368                CommandReturn::success()
369            }
370
371            // multi touch enable
372            11 => {
373                self.apps
374                    .enter(processid, |app, _| {
375                        app.multi_touch_enable = true;
376                    })
377                    .unwrap_or(());
378                let _ = self.multi_touch_enable();
379                CommandReturn::success()
380            }
381
382            // multi touch disable
383            12 => {
384                self.apps
385                    .enter(processid, |app, _| {
386                        app.multi_touch_enable = false;
387                    })
388                    .unwrap_or(());
389                let _ = self.multi_touch_enable();
390                CommandReturn::success()
391            }
392
393            // number of touches
394            100 => {
395                let num_touches = if let Some(multi_touch) = self.multi_touch {
396                    multi_touch.get_num_touches()
397                } else {
398                    usize::from(self.touch.is_some())
399                };
400                CommandReturn::success_u32(num_touches as u32)
401            }
402
403            _ => CommandReturn::failure(ErrorCode::NOSUPPORT),
404        }
405    }
406
407    fn allocate_grant(&self, processid: ProcessId) -> Result<(), kernel::process::Error> {
408        self.apps.enter(processid, |_, _| {})
409    }
410}