FabGL
ESP32 Display Controller and Graphics Library
mouse.cpp
1/*
2 Created by Fabrizio Di Vittorio (fdivitto2013@gmail.com) - <http://www.fabgl.com>
3 Copyright (c) 2019-2022 Fabrizio Di Vittorio.
4 All rights reserved.
5
6
7* Please contact fdivitto2013@gmail.com if you need a commercial license.
8
9
10* This library and related software is available under GPL v3.
11
12 FabGL is free software: you can redistribute it and/or modify
13 it under the terms of the GNU General Public License as published by
14 the Free Software Foundation, either version 3 of the License, or
15 (at your option) any later version.
16
17 FabGL is distributed in the hope that it will be useful,
18 but WITHOUT ANY WARRANTY; without even the implied warranty of
19 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 GNU General Public License for more details.
21
22 You should have received a copy of the GNU General Public License
23 along with FabGL. If not, see <http://www.gnu.org/licenses/>.
24 */
25
26
27#include "freertos/FreeRTOS.h"
28
29#include "mouse.h"
31#include "displaycontroller.h"
32
33
34#pragma GCC optimize ("O2")
35
36
37
38namespace fabgl {
39
40
41bool Mouse::s_quickCheckHardware = false;
42
43
44Mouse::Mouse()
45 : m_mouseAvailable(false),
46 m_mouseType(LegacyMouse),
47 m_mouseUpdateTask(nullptr),
48 m_receivedPacket(nullptr),
49 m_absoluteUpdate(false),
50 m_prevDeltaTime(0),
51 m_movementAcceleration(180),
52 m_wheelAcceleration(60000),
53 m_absoluteQueue(nullptr),
54 m_updateDisplayController(nullptr),
55 m_uiApp(nullptr)
56{
57}
58
59
60Mouse::~Mouse()
61{
62 PS2DeviceLock lock(this);
63 terminateAbsolutePositioner();
64 if (m_mouseUpdateTask)
65 vTaskDelete(m_mouseUpdateTask);
66 if (m_receivedPacket)
67 vQueueDelete(m_receivedPacket);
68}
69
70
71void Mouse::begin(int PS2Port)
72{
73 if (s_quickCheckHardware)
74 PS2Device::quickCheckHardware();
75 PS2Device::begin(PS2Port);
76 reset();
77 m_receivedPacket = xQueueCreate(1, sizeof(MousePacket));
78 xTaskCreate(&mouseUpdateTask, "", 1600, this, 5, &m_mouseUpdateTask);
79 m_area = Size(0, 0);
80}
81
82
83void Mouse::begin(gpio_num_t clkGPIO, gpio_num_t dataGPIO)
84{
85 PS2Controller::begin(clkGPIO, dataGPIO);
86 PS2Controller::setMouse(this);
87 begin(0);
88}
89
90
91bool Mouse::reset()
92{
93 if (s_quickCheckHardware) {
94 m_mouseAvailable = send_cmdReset();
95 } else {
96 // tries up to three times for mouse reset
97 for (int i = 0; i < 3; ++i) {
98 m_mouseAvailable = send_cmdReset();
99 if (m_mouseAvailable)
100 break;
101 vTaskDelay(500 / portTICK_PERIOD_MS);
102 }
103 // give the time to the device to be fully initialized
104 vTaskDelay(200 / portTICK_PERIOD_MS);
105 }
106
107 // negotiate compatibility and default parameters
108 if (m_mouseAvailable) {
109 // try Intellimouse (three buttons + scroll wheel, 4 bytes packet)
110 if (send_cmdSetSampleRate(200) && send_cmdSetSampleRate(100) && send_cmdSetSampleRate(80) && identify() == PS2DeviceType::MouseWithScrollWheel) {
111 // Intellimouse ok!
112 m_mouseType = Intellimouse;
113 }
114
115 setSampleRate(60);
116 }
117
118 return m_mouseAvailable;
119}
120
121
122int Mouse::getPacketSize()
123{
124 return (m_mouseType == Intellimouse ? 4 : 3);
125}
126
127
128bool Mouse::packetAvailable()
129{
130 return uxQueueMessagesWaiting(m_receivedPacket) > 0;
131}
132
133
134bool Mouse::getNextPacket(MousePacket * packet, int timeOutMS, bool requestResendOnTimeOut)
135{
136 return xQueueReceive(m_receivedPacket, packet, msToTicks(timeOutMS));
137}
138
139
140bool Mouse::deltaAvailable()
141{
142 return packetAvailable();
143}
144
145
146// Mouse packet format:
147// byte 0:
148// bit 0 = Left Button
149// bit 1 = Right Button
150// bit 2 = Middle Button
151// bit 3 = Always 1
152// bit 4 = X sign bit
153// bit 5 = Y sign bit
154// bit 6 = X overflow
155// bit 7 = Y overflow
156// byte 1:
157// X movement
158// byte 2:
159// Y movement
160// byte 3:
161// Z movement
162bool Mouse::decodeMousePacket(MousePacket * mousePacket, MouseDelta * delta)
163{
164 // the bit 4 of first byte must be always 1
165 if ((mousePacket->data[0] & 8) == 0)
166 return false;
167
168 m_prevStatus = m_status;
169
170 // decode packet
171 m_status.buttons.left = (mousePacket->data[0] & 0x01 ? 1 : 0);
172 m_status.buttons.middle = (mousePacket->data[0] & 0x04 ? 1 : 0);
173 m_status.buttons.right = (mousePacket->data[0] & 0x02 ? 1 : 0);
174 if (delta) {
175 delta->deltaX = (int16_t)(mousePacket->data[0] & 0x10 ? 0xFF00 | mousePacket->data[1] : mousePacket->data[1]);
176 delta->deltaY = (int16_t)(mousePacket->data[0] & 0x20 ? 0xFF00 | mousePacket->data[2] : mousePacket->data[2]);
177 delta->deltaZ = (int8_t)(getPacketSize() > 3 ? mousePacket->data[3] : 0);
178 delta->overflowX = (mousePacket->data[0] & 0x40 ? 1 : 0);
179 delta->overflowY = (mousePacket->data[0] & 0x80 ? 1 : 0);
180 delta->buttons = m_status.buttons;
181 }
182
183 return true;
184}
185
186
187bool Mouse::getNextDelta(MouseDelta * delta, int timeOutMS, bool requestResendOnTimeOut)
188{
189 MousePacket mousePacket;
190 return getNextPacket(&mousePacket, timeOutMS, requestResendOnTimeOut) && decodeMousePacket(&mousePacket, delta);
191}
192
193
194void Mouse::setupAbsolutePositioner(int width, int height, bool createAbsolutePositionsQueue, BitmappedDisplayController * updateDisplayController, uiApp * app)
195{
196 if (m_area != Size(width, height)) {
197 m_area = Size(width, height);
198 m_status.X = width >> 1;
199 m_status.Y = height >> 1;
200 }
201 m_status.wheelDelta = 0;
202 m_status.buttons.left = 0;
203 m_status.buttons.middle = 0;
204 m_status.buttons.right = 0;
205 m_prevStatus = m_status;
206
207 m_updateDisplayController = updateDisplayController;
208
209 m_uiApp = app;
210
211 if (createAbsolutePositionsQueue && m_absoluteQueue == nullptr) {
212 m_absoluteQueue = xQueueCreate(FABGLIB_MOUSE_EVENTS_QUEUE_SIZE, sizeof(MouseStatus));
213 }
214
215 if (m_updateDisplayController) {
216 // setup initial position
217 m_updateDisplayController->setMouseCursorPos(m_status.X, m_status.Y);
218 }
219
220 m_absoluteUpdate = (m_updateDisplayController || createAbsolutePositionsQueue || m_uiApp);
221}
222
223
224void Mouse::terminateAbsolutePositioner()
225{
226 if (m_absoluteQueue) {
227 vQueueDelete(m_absoluteQueue);
228 m_absoluteQueue = nullptr;
229 }
230 m_absoluteUpdate = false;
231 m_updateDisplayController = nullptr;
232 m_uiApp = nullptr;
233}
234
235
236void Mouse::updateAbsolutePosition(MouseDelta * delta)
237{
238 const int maxDeltaTimeUS = 500000; // after 0.5s doesn't consider acceleration
239
240 int dx = delta->deltaX;
241 int dy = delta->deltaY;
242 int dz = delta->deltaZ;
243
244 int64_t now = esp_timer_get_time();
245 int deltaTime = now - m_prevDeltaTime; // time in microseconds
246
247 if (deltaTime < maxDeltaTimeUS) {
248
249 // calculate movement acceleration
250 if (dx != 0 || dy != 0) {
251 int deltaDist = isqrt(dx * dx + dy * dy); // distance in mouse points
252 float vel = (float)deltaDist / deltaTime; // velocity in mousepoints/microsecond
253 float newVel = vel + m_movementAcceleration * vel * vel; // new velocity
254 int newDeltaDist = newVel * deltaTime; // new distance
255 dx = dx * newDeltaDist / deltaDist;
256 dy = dy * newDeltaDist / deltaDist;
257 }
258
259 // calculate wheel acceleration
260 if (dz != 0) {
261 int deltaDist = abs(dz); // distance in wheel points
262 float vel = (float)deltaDist / deltaTime; // velocity in mousepoints/microsecond
263 float newVel = vel + m_wheelAcceleration * vel * vel; // new velocity
264 int newDeltaDist = newVel * deltaTime; // new distance
265 dz = dz * newDeltaDist / deltaDist;
266 }
267
268 }
269
270 m_status.X = tclamp((int)m_status.X + dx, 0, m_area.width - 1);
271 m_status.Y = tclamp((int)m_status.Y - dy, 0, m_area.height - 1);
272 m_status.wheelDelta = dz;
273 m_prevDeltaTime = now;
274}
275
276
277void Mouse::mouseUpdateTask(void * arg)
278{
279 constexpr int MAX_TIME_BETWEEN_DATA_US = 500000; // maximum time between data composing a delta frame
280
281 Mouse * mouse = (Mouse*) arg;
282
283 int64_t prevDataTime = 0;
284
285 while (true) {
286
287 MousePacket mousePacket;
288 int mousePacketLen = 0;
289 while (mousePacketLen < mouse->getPacketSize()) {
290 int r = mouse->getData(-1);
291 if (mouse->parityError() || mouse->syncError()) {
292 mousePacketLen = 0;
293 continue;
294 }
295 int64_t now = esp_timer_get_time();
296 if (mousePacketLen > 0 && prevDataTime > 0 && (now - prevDataTime) > MAX_TIME_BETWEEN_DATA_US) {
297 // too much time elapsed since last byte, start a new delta
298 mousePacketLen = 0;
299 }
300 if (r > -1) {
301 mousePacket.data[mousePacketLen++] = r;
302 prevDataTime = now;
303 }
304 }
305
306 if (mouse->m_absoluteUpdate) {
307 MouseDelta delta;
308 if (mouse->decodeMousePacket(&mousePacket, &delta)) {
309 mouse->updateAbsolutePosition(&delta);
310
311 // VGA Controller
312 if (mouse->m_updateDisplayController)
313 mouse->m_updateDisplayController->setMouseCursorPos(mouse->m_status.X, mouse->m_status.Y);
314
315 // queue (if you need availableStatus() or getNextStatus())
316 if (mouse->m_absoluteQueue) {
317 xQueueSend(mouse->m_absoluteQueue, &mouse->m_status, 0);
318 }
319
320 if (mouse->m_uiApp) {
321 // generate uiApp events
322 if (mouse->m_prevStatus.X != mouse->m_status.X || mouse->m_prevStatus.Y != mouse->m_status.Y) {
323 // X and Y movement: UIEVT_MOUSEMOVE
324 uiEvent evt = uiEvent(nullptr, UIEVT_MOUSEMOVE);
325 evt.params.mouse.status = mouse->m_status;
326 evt.params.mouse.changedButton = 0;
327 mouse->m_uiApp->postEvent(&evt);
328 }
329 if (mouse->m_status.wheelDelta != 0) {
330 // wheel movement: UIEVT_MOUSEWHEEL
331 uiEvent evt = uiEvent(nullptr, UIEVT_MOUSEWHEEL);
332 evt.params.mouse.status = mouse->m_status;
333 evt.params.mouse.changedButton = 0;
334 mouse->m_uiApp->postEvent(&evt);
335 }
336 if (mouse->m_prevStatus.buttons.left != mouse->m_status.buttons.left) {
337 // left button: UIEVT_MOUSEBUTTONDOWN, UIEVT_MOUSEBUTTONUP
338 uiEvent evt = uiEvent(nullptr, mouse->m_status.buttons.left ? UIEVT_MOUSEBUTTONDOWN : UIEVT_MOUSEBUTTONUP);
339 evt.params.mouse.status = mouse->m_status;
340 evt.params.mouse.changedButton = 1;
341 mouse->m_uiApp->postEvent(&evt);
342 }
343 if (mouse->m_prevStatus.buttons.middle != mouse->m_status.buttons.middle) {
344 // middle button: UIEVT_MOUSEBUTTONDOWN, UIEVT_MOUSEBUTTONUP
345 uiEvent evt = uiEvent(nullptr, mouse->m_status.buttons.middle ? UIEVT_MOUSEBUTTONDOWN : UIEVT_MOUSEBUTTONUP);
346 evt.params.mouse.status = mouse->m_status;
347 evt.params.mouse.changedButton = 2;
348 mouse->m_uiApp->postEvent(&evt);
349 }
350 if (mouse->m_prevStatus.buttons.right != mouse->m_status.buttons.right) {
351 // right button: UIEVT_MOUSEBUTTONDOWN, UIEVT_MOUSEBUTTONUP
352 uiEvent evt = uiEvent(nullptr, mouse->m_status.buttons.right ? UIEVT_MOUSEBUTTONDOWN : UIEVT_MOUSEBUTTONUP);
353 evt.params.mouse.status = mouse->m_status;
354 evt.params.mouse.changedButton = 3;
355 mouse->m_uiApp->postEvent(&evt);
356 }
357 }
358
359 }
360
361 } else {
362
363 xQueueOverwrite(mouse->m_receivedPacket, &mousePacket);
364
365 }
366
367 }
368}
369
370
371int Mouse::availableStatus()
372{
373 return m_absoluteQueue ? uxQueueMessagesWaiting(m_absoluteQueue) : 0;
374}
375
376
377MouseStatus Mouse::getNextStatus(int timeOutMS)
378{
379 MouseStatus status;
380 if (m_absoluteQueue)
381 xQueueReceive(m_absoluteQueue, &status, msToTicks(timeOutMS));
382 return status;
383}
384
385
386void Mouse::emptyQueue()
387{
388 while (getData(0) != -1)
389 ;
390 if (m_absoluteQueue)
391 xQueueReset(m_absoluteQueue);
392}
393
394
395
396} // end of namespace
void setMouseCursorPos(int X, int Y)
Sets mouse cursor position.
Represents the base abstract class for bitmapped display controllers.
void updateAbsolutePosition(MouseDelta *delta)
Updates absolute position from the specified mouse delta event.
Definition: mouse.cpp:236
The PS2 Mouse controller class.
Definition: mouse.h:111
bool postEvent(uiEvent const *event)
Places an event in the event queue and returns without waiting for the receiver to process the event.
Definition: fabui.cpp:609
Represents the whole application base class.
Definition: fabui.h:3105
uint8_t width
uint8_t height
This file contains fabgl::BitmappedDisplayController definition.
#define FABGLIB_MOUSE_EVENTS_QUEUE_SIZE
Definition: fabglconf.h:138
@ Intellimouse
Definition: mouse.h:78
@ LegacyMouse
Definition: mouse.h:77
This file contains fabgl::Mouse definition.
This file contains fabgl::PS2Controller definition.
int8_t deltaZ
Definition: mouse.h:58
MouseButtons buttons
Definition: mouse.h:59
uint8_t overflowY
Definition: mouse.h:61
uint8_t overflowX
Definition: mouse.h:60
int16_t deltaY
Definition: mouse.h:57
int16_t deltaX
Definition: mouse.h:56
Describes mouse movement and buttons status.
Definition: mouse.h:55
Contains raw data received from mouse.
Definition: mouse.h:68
MouseButtons buttons
Definition: fabutils.h:303
Describes mouse absolute position, scroll wheel delta and buttons status.
Definition: fabutils.h:299
Represents a bidimensional size.
Definition: fabutils.h:231