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                    kernel_data
215                        .schedule_upcall(
216                            0,
217                            (
218                                event_status,
219                                (event.x as usize) << 16 | event.y as usize,
220                                pressure_size,
221                            ),
222                        )
223                        .ok();
224                }
225            });
226        }
227    }
228}
229
230impl hil::touch::MultiTouchClient for Touch<'_> {
231    fn touch_events(&self, touch_events: &[TouchEvent], num_events: usize) {
232        let len = if touch_events.len() < num_events {
233            touch_events.len()
234        } else {
235            num_events
236        };
237        if len > 0 {
238            // report the first touch as single touch
239            // for applications that only use single touches
240            self.touch_event(touch_events[0]);
241            // debug!("{} touch(es)", len);
242            for app in self.apps.iter() {
243                app.enter(|app, kernel_data| {
244                    if app.ack {
245                        app.dropped_events = 0;
246
247                        let num = kernel_data
248                            .get_readwrite_processbuffer(rw_allow::EVENTS)
249                            .and_then(|events| {
250                                events.mut_enter(|buffer| {
251                                    let num = if buffer.len() / 8 < len {
252                                        buffer.len() / 8
253                                    } else {
254                                        len
255                                    };
256
257                                    for event_index in 0..num {
258                                        let mut event = touch_events[event_index];
259                                        self.update_rotation(&mut event);
260                                        let event_status = touch_status_to_number(&event.status);
261                                        // debug!(
262                                        //     " multitouch {:?} x {} y {} size {:?} pressure {:?}",
263                                        //     event.status, event.x, event.y, event.size, event.pressure
264                                        // );
265                                        // one touch entry is 8 bytes long
266                                        let offset = event_index * 8;
267                                        if buffer.len() > event_index + 8 {
268                                            buffer[offset].set(event.id as u8);
269                                            buffer[offset + 1].set(event_status as u8);
270                                            buffer[offset + 2].set((event.x & 0xFF) as u8);
271                                            buffer[offset + 3].set(((event.x & 0xFFFF) >> 8) as u8);
272                                            buffer[offset + 4].set((event.y & 0xFF) as u8);
273                                            buffer[offset + 5].set(((event.y & 0xFFFF) >> 8) as u8);
274                                            buffer[offset + 6].set(
275                                                if let Some(size) = event.size {
276                                                    size as u8
277                                                } else {
278                                                    0
279                                                },
280                                            );
281                                            buffer[offset + 7].set(
282                                                if let Some(pressure) = event.pressure {
283                                                    pressure as u8
284                                                } else {
285                                                    0
286                                                },
287                                            );
288                                        } else {
289                                            break;
290                                        }
291                                    }
292                                    num
293                                })
294                            })
295                            .unwrap_or(0);
296                        let dropped_events = app.dropped_events;
297                        if num > 0 {
298                            app.ack = false;
299                            kernel_data
300                                .schedule_upcall(2, (num, dropped_events, len.saturating_sub(num)))
301                                .ok();
302                        }
303                    } else {
304                        app.dropped_events += 1;
305                    }
306                });
307            }
308        }
309    }
310}
311
312impl hil::touch::GestureClient for Touch<'_> {
313    fn gesture_event(&self, event: GestureEvent) {
314        for app in self.apps.iter() {
315            app.enter(|_app, kernel_data| {
316                let gesture_id = match event {
317                    GestureEvent::SwipeUp => 1,
318                    GestureEvent::SwipeDown => 2,
319                    GestureEvent::SwipeLeft => 3,
320                    GestureEvent::SwipeRight => 4,
321                    GestureEvent::ZoomIn => 5,
322                    GestureEvent::ZoomOut => 6,
323                };
324                kernel_data.schedule_upcall(1, (gesture_id, 0, 0)).ok();
325            });
326        }
327    }
328}
329
330impl SyscallDriver for Touch<'_> {
331    fn command(
332        &self,
333        command_num: usize,
334        _data1: usize,
335        _data2: usize,
336        processid: ProcessId,
337    ) -> CommandReturn {
338        match command_num {
339            // driver existence check
340            0 => CommandReturn::success(),
341
342            // touch enable
343            1 => {
344                self.apps
345                    .enter(processid, |app, _| {
346                        app.touch_enable = true;
347                    })
348                    .unwrap_or(());
349                let _ = self.touch_enable();
350                CommandReturn::success()
351            }
352
353            // touch disable
354            2 => {
355                self.apps
356                    .enter(processid, |app, _| {
357                        app.touch_enable = false;
358                    })
359                    .unwrap_or(());
360                let _ = self.touch_enable();
361                CommandReturn::success()
362            }
363
364            // multi touch ack
365            10 => {
366                self.apps
367                    .enter(processid, |app, _| {
368                        app.ack = true;
369                    })
370                    .unwrap_or(());
371                CommandReturn::success()
372            }
373
374            // multi touch enable
375            11 => {
376                self.apps
377                    .enter(processid, |app, _| {
378                        app.multi_touch_enable = true;
379                    })
380                    .unwrap_or(());
381                let _ = self.multi_touch_enable();
382                CommandReturn::success()
383            }
384
385            // multi touch disable
386            12 => {
387                self.apps
388                    .enter(processid, |app, _| {
389                        app.multi_touch_enable = false;
390                    })
391                    .unwrap_or(());
392                let _ = self.multi_touch_enable();
393                CommandReturn::success()
394            }
395
396            // number of touches
397            100 => {
398                let num_touches = if let Some(multi_touch) = self.multi_touch {
399                    multi_touch.get_num_touches()
400                } else {
401                    usize::from(self.touch.is_some())
402                };
403                CommandReturn::success_u32(num_touches as u32)
404            }
405
406            _ => CommandReturn::failure(ErrorCode::NOSUPPORT),
407        }
408    }
409
410    fn allocate_grant(&self, processid: ProcessId) -> Result<(), kernel::process::Error> {
411        self.apps.enter(processid, |_, _| {})
412    }
413}