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-2021 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. Feel free to use FabGL in free software and hardware:
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 
38 namespace fabgl {
39 
40 
41 bool Mouse::s_quickCheckHardware = false;
42 
43 
44 Mouse::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 
60 Mouse::~Mouse()
61 {
62  PS2DeviceLock lock(this);
64  if (m_mouseUpdateTask)
65  vTaskDelete(m_mouseUpdateTask);
66  if (m_receivedPacket)
67  vQueueDelete(m_receivedPacket);
68 }
69 
70 
71 void 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 
83 void 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 
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 
123 {
124  return (m_mouseType == Intellimouse ? 4 : 3);
125 }
126 
127 
129 {
130  return uxQueueMessagesWaiting(m_receivedPacket) > 0;
131 }
132 
133 
134 bool Mouse::getNextPacket(MousePacket * packet, int timeOutMS, bool requestResendOnTimeOut)
135 {
136  return xQueueReceive(m_receivedPacket, packet, msToTicks(timeOutMS));
137 }
138 
139 
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
162 bool 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 
187 bool Mouse::getNextDelta(MouseDelta * delta, int timeOutMS, bool requestResendOnTimeOut)
188 {
189  MousePacket mousePacket;
190  return getNextPacket(&mousePacket, timeOutMS, requestResendOnTimeOut) && decodeMousePacket(&mousePacket, delta);
191 }
192 
193 
194 void 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 
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 
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 
277 void 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 
372 {
373  return m_absoluteQueue ? uxQueueMessagesWaiting(m_absoluteQueue) : 0;
374 }
375 
376 
378 {
380  if (m_absoluteQueue)
381  xQueueReceive(m_absoluteQueue, &status, msToTicks(timeOutMS));
382  return status;
383 }
384 
385 
387 {
388  while (getData(0) != -1)
389  ;
390  if (m_absoluteQueue)
391  xQueueReset(m_absoluteQueue);
392 }
393 
394 
395 
396 } // end of namespace
MouseButtons buttons
Definition: fabutils.h:280
uint8_t overflowX
Definition: mouse.h:60
This file contains fabgl::PS2Controller definition.
bool lock(int timeOutMS)
Gets exclusive access to the device.
Definition: ps2device.cpp:91
Represents the whole application base class.
Definition: fabui.h:2703
Contains raw data received from mouse.
Definition: mouse.h:68
uint8_t overflowY
Definition: mouse.h:61
bool setSampleRate(int value)
Sets the maximum rate of mouse movements reporting.
Definition: mouse.h:232
int getPacketSize()
Gets mouse packet size.
Definition: mouse.cpp:122
#define FABGLIB_MOUSE_EVENTS_QUEUE_SIZE
Definition: fabglconf.h:134
This file contains fabgl::BitmappedDisplayController definition.
Represents the base abstract class for bitmapped display controllers.
int16_t deltaY
Definition: mouse.h:57
Describes mouse movement and buttons status.
Definition: mouse.h:55
This file contains fabgl::Mouse definition.
int8_t deltaZ
Definition: mouse.h:58
Describes mouse absolute position, scroll wheel delta and buttons status.
Definition: fabutils.h:276
PS2DeviceType identify()
Identifies the device attached to the PS2 port.
Definition: ps2device.h:79
MouseStatus getNextStatus(int timeOutMS=-1)
Gets the next status from the status queue.
Definition: mouse.cpp:377
void emptyQueue()
Empties the mouse status and events queue.
Definition: mouse.cpp:386
Definition: canvas.cpp:36
bool deltaAvailable()
Determines if there is a mouse movement available in the queue.
Definition: mouse.cpp:140
bool packetAvailable()
Determines if there is a raw mouse movement available in the queue.
Definition: mouse.cpp:128
int16_t width
Definition: fabutils.h:209
MouseButtons buttons
Definition: mouse.h:59
void terminateAbsolutePositioner()
Terminates absolute position handler.
Definition: mouse.cpp:224
int16_t height
Definition: fabutils.h:210
bool getNextPacket(MousePacket *packet, int timeOutMS=-1, bool requestResendOnTimeOut=false)
Gets a mouse raw movement (packet) from the queue.
Definition: mouse.cpp:134
bool getNextDelta(MouseDelta *delta, int timeOutMS=-1, bool requestResendOnTimeOut=false)
Gets a mouse movement from the queue.
Definition: mouse.cpp:187
Represents a bidimensional size.
Definition: fabutils.h:208
void setMouseCursorPos(int X, int Y)
Sets mouse cursor position.
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:536
MouseStatus & status()
Gets or sets current mouse status.
Definition: mouse.h:313
int availableStatus()
Gets the number of available mouse status.
Definition: mouse.cpp:371
void begin(gpio_num_t clkGPIO, gpio_num_t dataGPIO)
Initializes Mouse specifying CLOCK and DATA GPIOs.
Definition: mouse.cpp:83
int16_t deltaX
Definition: mouse.h:56
void updateAbsolutePosition(MouseDelta *delta)
Updates absolute position from the specified mouse delta event.
Definition: mouse.cpp:236
uint8_t height
The PS2 Mouse controller class.
Definition: mouse.h:111
void setupAbsolutePositioner(int width, int height, bool createAbsolutePositionsQueue, BitmappedDisplayController *updateDisplayController=nullptr, uiApp *app=nullptr)
Initializes absolute position handler.
Definition: mouse.cpp:194
bool reset()
Sends a Reset command to the mouse.
Definition: mouse.cpp:91
uint8_t width
static void begin(gpio_num_t port0_clkGPIO, gpio_num_t port0_datGPIO, gpio_num_t port1_clkGPIO=GPIO_UNUSED, gpio_num_t port1_datGPIO=GPIO_UNUSED)
Initializes PS2 device controller.