win32_synthetic_pointer/
lib.rs

1use enumflags2::{bitflags, BitFlags};
2use thiserror::Error;
3use windows::Win32::{
4    Foundation::{GetLastError, POINT, RECT},
5    UI::{
6        Controls::{
7            CreateSyntheticPointerDevice, DestroySyntheticPointerDevice, HSYNTHETICPOINTERDEVICE,
8            POINTER_FEEDBACK_INDIRECT, POINTER_TYPE_INFO,
9        },
10        Input::Pointer::{
11            InitializeTouchInjection, InjectSyntheticPointerInput, POINTER_FLAG_DOWN,
12            POINTER_FLAG_FIRSTBUTTON, POINTER_FLAG_HASTRANSFORM, POINTER_FLAG_INCONTACT,
13            POINTER_FLAG_INRANGE, POINTER_FLAG_NEW, POINTER_FLAG_NONE, POINTER_FLAG_PRIMARY,
14            POINTER_FLAG_UP, POINTER_FLAG_UPDATE, POINTER_INFO, POINTER_PEN_INFO,
15            POINTER_TOUCH_INFO, TOUCH_FEEDBACK_DEFAULT,
16        },
17        WindowsAndMessaging::POINTER_INPUT_TYPE,
18    },
19};
20
21const MAX_POINTERS: u32 = 10;
22const MAX_POINTERS_USIZE: usize = 10;
23
24pub struct SyntheticPointer {
25    pub pointer_type: PointerType,
26    pub pointer_count: u32,
27    pointer_type_info: [POINTER_TYPE_INFO; MAX_POINTERS_USIZE],
28    synthetic_pointer_device: HSYNTHETICPOINTERDEVICE,
29    last_contact: [bool; MAX_POINTERS_USIZE],
30}
31
32impl SyntheticPointer {
33    /// Creates a new synthetic pointer of the type specified.
34    /// The maximum working number of pointers is [MAX_POINTERS]
35    pub fn new(
36        pointer_type: PointerType,
37        pointer_count: u32,
38    ) -> Result<Self, SyntheticPointerError> {
39        if pointer_count == 0 || pointer_count > MAX_POINTERS {
40            return Err(SyntheticPointerError::InvalidPointerCount {
41                count: pointer_count,
42            });
43        }
44
45        match pointer_type {
46            PointerType::Pointer => return Err(SyntheticPointerError::InvalidPointerType),
47            PointerType::Touchpad => {
48                // TODO: fail with an InvalidPointerType if windows < 8.1
49            }
50            _ => {}
51        }
52
53        let pointer_input_type = POINTER_INPUT_TYPE(pointer_type.get_value());
54
55        if let PointerType::Touch(_) = pointer_type {
56            unsafe {
57                if (!InitializeTouchInjection(pointer_count, TOUCH_FEEDBACK_DEFAULT)).into() {
58                    return Err(SyntheticPointerError::InputInjectionError(
59                        GetLastError().into(),
60                    ));
61                }
62            }
63        }
64
65        let device = unsafe {
66            CreateSyntheticPointerDevice(
67                pointer_input_type,
68                pointer_count,
69                POINTER_FEEDBACK_INDIRECT,
70            )
71        }?;
72
73        let mut s = Self {
74            pointer_type,
75            pointer_count,
76            synthetic_pointer_device: device,
77
78            // fills with zeroes
79            pointer_type_info: [POINTER_TYPE_INFO::default(); MAX_POINTERS_USIZE],
80            last_contact: [false; MAX_POINTERS_USIZE],
81        };
82
83        // initialize pointers
84
85        match pointer_type {
86            PointerType::Touch(properties) => {
87                for i in 0..pointer_count as usize {
88                    let mut touch_info = POINTER_TOUCH_INFO {
89                        pointerInfo: POINTER_INFO::default(),
90                        ..Default::default()
91                    };
92
93                    touch_info.pointerInfo.pointerType = pointer_input_type;
94                    touch_info.pointerInfo.pointerId = i as u32 + 1;
95                    touch_info.pointerInfo.pointerFlags = POINTER_FLAG_NEW;
96                    touch_info.touchMask = properties.bits();
97
98                    s.pointer_type_info[i].r#type = pointer_input_type;
99                    s.pointer_type_info[i].Anonymous.touchInfo = touch_info;
100                }
101            }
102            PointerType::Pen(properties) => {
103                let mut pen_info = POINTER_PEN_INFO {
104                    pointerInfo: POINTER_INFO::default(),
105                    ..Default::default()
106                };
107
108                pen_info.pointerInfo.pointerType = pointer_input_type;
109                pen_info.pointerInfo.pointerFlags = POINTER_FLAG_NEW;
110                pen_info.penMask = properties.bits();
111
112                s.pointer_type_info[0].r#type = pointer_input_type;
113                s.pointer_type_info[0].Anonymous.penInfo = pen_info;
114            }
115
116            _ => {
117                todo!()
118            }
119        }
120
121        // inject once so windows knows about the new pointers
122        s.inject()?;
123
124        Ok(s)
125    }
126
127    /// Schedules pen input for injection.
128    /// If None, the pen is outside the detection range.
129    /// After this, you need to call inject()
130    pub fn pen_input(&mut self, input: &Option<PenInput>) -> Result<(), SyntheticPointerError> {
131        match self.pointer_type {
132            PointerType::Pen(_) => {
133                // nothing
134            }
135            _ => {
136                return Err(SyntheticPointerError::InvalidPointerType);
137            }
138        }
139
140        let union = &mut self.pointer_type_info[0].Anonymous;
141
142        // if none, it's out of range
143        if let Some(data) = input {
144            let mut flags = POINTER_FLAG_INRANGE
145                | POINTER_FLAG_HASTRANSFORM
146                | POINTER_FLAG_PRIMARY
147                | POINTER_FLAG_UPDATE;
148
149            // set position
150
151            union.penInfo.pointerInfo.ptPixelLocation = POINT {
152                x: data.x,
153                y: data.y,
154            };
155
156            if let Some(tilt_x) = data.tilt_x {
157                union.penInfo.tiltX = tilt_x;
158            }
159
160            if let Some(tilt_y) = data.tilt_y {
161                union.penInfo.tiltY = tilt_y;
162            }
163
164            if let Some(rotation) = data.rotation {
165                union.penInfo.rotation = rotation;
166            }
167
168            union.penInfo.pressure = data.pressure;
169            let contact = data.pressure > 0;
170
171            if contact && !self.last_contact[0] {
172                flags |= POINTER_FLAG_DOWN;
173            } else if !contact && self.last_contact[0] {
174                flags |= POINTER_FLAG_UP;
175            }
176
177            if data.bind_active && contact {
178                flags |= POINTER_FLAG_INCONTACT | POINTER_FLAG_FIRSTBUTTON;
179            }
180
181            union.penInfo.pointerInfo.pointerFlags = flags;
182            self.last_contact[0] = data.pressure > 0;
183        } else {
184            // mark as out of range
185            union.penInfo.pointerInfo.pointerFlags = POINTER_FLAG_NONE;
186            self.last_contact[0] = false;
187        }
188
189        Ok(())
190    }
191
192    /// Schedules touch input for injection.
193    /// If None, the finger is outside of the detection range.
194    /// Set all the disabled fingers to None.
195    /// After this, you need to call inject()
196    pub fn touch_input(
197        &mut self,
198        input: &[Option<TouchInput>; MAX_POINTERS_USIZE],
199    ) -> Result<(), SyntheticPointerError> {
200        match self.pointer_type {
201            PointerType::Touch(_) => {
202                // nothing
203            }
204            _ => {
205                return Err(SyntheticPointerError::InvalidPointerType);
206            }
207        }
208
209        for (i, pt_info) in self.pointer_type_info[0..self.pointer_count as usize]
210            .iter_mut()
211            .enumerate()
212        {
213            let union = &mut pt_info.Anonymous;
214
215            // if none, it's out of range
216            if let Some(data) = &input[i] {
217                let mut flags =
218                    POINTER_FLAG_INRANGE | POINTER_FLAG_HASTRANSFORM | POINTER_FLAG_UPDATE;
219
220                // the first touch is always the primary one
221                if i == 0 {
222                    flags |= POINTER_FLAG_PRIMARY;
223                }
224
225                // set position
226                union.touchInfo.pointerInfo.ptPixelLocation = POINT {
227                    x: data.x,
228                    y: data.y,
229                };
230
231                if let Some(area) = data.contact_area {
232                    union.touchInfo.rcContact = RECT {
233                        top: area.top,
234                        bottom: area.bottom,
235                        left: area.left,
236                        right: area.right,
237                    };
238                }
239
240                if let Some(orientation) = data.orientation {
241                    union.touchInfo.orientation = orientation;
242                }
243
244                union.touchInfo.pressure = data.pressure;
245                let contact = data.pressure > 0;
246
247                if contact && !self.last_contact[i] {
248                    flags |= POINTER_FLAG_DOWN;
249                } else if !contact && self.last_contact[i] {
250                    flags |= POINTER_FLAG_UP;
251                }
252
253                if data.bind_active && contact {
254                    flags |= POINTER_FLAG_INCONTACT | POINTER_FLAG_FIRSTBUTTON;
255                }
256
257                union.touchInfo.pointerInfo.pointerFlags = flags;
258                self.last_contact[i] = data.pressure > 0;
259            } else {
260                // mark as out of range
261                union.touchInfo.pointerInfo.pointerFlags = POINTER_FLAG_NONE;
262                self.last_contact[i] = false;
263            }
264        }
265
266        Ok(())
267    }
268
269    /// Sends the scheduled input data to windows.
270    pub fn inject(&mut self) -> Result<(), SyntheticPointerError> {
271        unsafe {
272            if (!InjectSyntheticPointerInput(
273                self.synthetic_pointer_device,
274                &self.pointer_type_info[0..self.pointer_count as usize],
275            ))
276            .into()
277            {
278                return Err(SyntheticPointerError::InputInjectionError(
279                    GetLastError().into(),
280                ));
281            }
282        }
283        Ok(())
284    }
285}
286
287#[derive(Copy, Clone, Debug, PartialEq)]
288pub struct PenInput {
289    /// The X screen coordinate
290    pub x: i32,
291    /// The Y screen coordinate
292    pub y: i32,
293    /// The pressure value, if enabled
294    pub pressure: u32,
295    /// The tilt value on the X axis, if enabled
296    pub tilt_x: Option<i32>,
297    /// The tilt value on the Y axis, if enabled
298    pub tilt_y: Option<i32>,
299    /// The rotation value, if enabled
300    pub rotation: Option<u32>,
301    /// Whether or not to bind pressure to contact
302    pub bind_active: bool,
303}
304
305#[derive(Copy, Clone, Debug, PartialEq)]
306pub struct TouchInput {
307    /// The X screen coordinate
308    pub x: i32,
309    /// The Y screen coordinate
310    pub y: i32,
311    /// The pressure value, if enabled
312    pub pressure: u32,
313    /// The orientation of the touch, if enabled
314    pub orientation: Option<u32>,
315    /// The contact area of the touch, if enabled.
316    /// This is a rectangle with the X and Y coordinates as its center.
317    pub contact_area: Option<Rectangle>,
318    /// Whether or not to bind pressure to contact
319    pub bind_active: bool,
320}
321
322#[derive(Copy, Clone, Debug, PartialEq)]
323pub struct Rectangle {
324    top: i32,
325    bottom: i32,
326    left: i32,
327    right: i32,
328}
329
330#[bitflags]
331#[repr(u32)]
332#[derive(Copy, Clone, Debug, PartialEq)]
333pub enum PenProperties {
334    TiltX = 4,
335    TiltY = 8,
336    Pressure = 1,
337    Rotation = 2,
338}
339
340#[bitflags]
341#[repr(u32)]
342#[derive(Copy, Clone, Debug, PartialEq)]
343pub enum TouchProperties {
344    Pressure = 4,
345    ContactArea = 1,
346    Orientation = 2,
347}
348
349impl Drop for SyntheticPointer {
350    fn drop(&mut self) {
351        unsafe { DestroySyntheticPointerDevice(self.synthetic_pointer_device) };
352    }
353}
354
355#[derive(Debug, PartialEq, Copy, Clone)]
356pub enum PointerType {
357    /// Generic pointer. This never appears in messages or pointer data.
358    /// Don't use this.
359    Pointer,
360    Touch(BitFlags<TouchProperties>),
361    Pen(BitFlags<PenProperties>),
362    Mouse,
363
364    /// Windows 8.1 and later
365    Touchpad,
366}
367
368impl PointerType {
369    pub fn get_value(&self) -> i32 {
370        match self {
371            PointerType::Pointer => 1,
372            PointerType::Touch(_) => 2,
373            PointerType::Pen(_) => 3,
374            PointerType::Mouse => 4,
375            PointerType::Touchpad => 5,
376        }
377    }
378}
379
380#[derive(Error, Debug)]
381pub enum SyntheticPointerError {
382    #[error("The selected pointer type is invalid or not supported on the current OS version.")]
383    InvalidPointerType,
384
385    #[error("The number of pointers must be between 0 and {MAX_POINTERS}, but it was {count}.")]
386    InvalidPointerCount { count: u32 },
387
388    #[error("Failed to inject input.")]
389    InputInjectionError(#[from] windows::core::Error),
390}