JeVois  1.16
JeVois Smart Embedded Machine Vision Toolkit
Share this page:
ImGuiBackendMALI.C
Go to the documentation of this file.
1 // ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
2 //
3 // JeVois Smart Embedded Machine Vision Toolkit - Copyright (C) 2020 by Laurent Itti, the University of Southern
4 // California (USC), and iLab at USC. See http://iLab.usc.edu and http://jevois.org for information about this project.
5 //
6 // This file is part of the JeVois Smart Embedded Machine Vision Toolkit. This program is free software; you can
7 // redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software
8 // Foundation, version 2. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
9 // without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
10 // License for more details. You should have received a copy of the GNU General Public License along with this program;
11 // if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
12 //
13 // Contact information: Laurent Itti - 3641 Watt Way, HNB-07A - Los Angeles, CA 90089-2520 - USA.
14 // Tel: +1 213 740 3527 - itti@pollux.usc.edu - http://iLab.usc.edu - http://jevois.org
15 // ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
16 /*! \file */
17 
18 #ifdef JEVOIS_PLATFORM_PRO
19 
21 #include <jevois/Debug/Log.H>
22 #include <jevois/GPU/OpenGL.H>
23 #include <jevois/Util/Console.H>
24 #include <jevois/Util/Utils.H>
25 
26 #include <imgui_impl_opengl3.h>
27 #include <imgui.h>
28 
29 #include <linux/input.h>
30 #include <linux/kd.h>
31 #include <linux/keyboard.h>
32 #include <sys/types.h>
33 #include <sys/stat.h>
34 #include <fcntl.h>
35 #include <unistd.h>
36 #include <cctype>
37 #include <wchar.h>
38 #include <wctype.h>
39 
40 /* Mysteriously missing defines from <linux/input.h>. */
41 #define EV_MAKE 1
42 #define EV_BREAK 0
43 #define EV_REPEAT 2
44 
45 #define NELEMS(x) (sizeof(x) / sizeof((x)[0]))
46 
47 namespace
48 {
49  // ##############################################################################################################
50  static std::string clipboardText;
51 
52  char const * ImGui_ImplMALI_GetClipboardText(void *)
53  { return clipboardText.c_str(); }
54 
55  void ImGui_ImplMALI_SetClipboardText(void *, char const * text)
56  { clipboardText = text; }
57 
58  // Code from https://github.com/bingmann/evdev-keylogger/blob/master/keymap.c
59 
60  /* *******************************************************************************
61  * Copyright (C) 2012 Jason A. Donenfeld <Jason@zx2c4.com>
62  * Copyright (C) 2014 Timo Bingmann <tb@panthema.net>
63  *
64  * This program is free software; you can redistribute it and/or modify it
65  * under the terms of the GNU General Public License as published by the Free
66  * Software Foundation; either version 2 of the License, or (at your option)
67  * any later version.
68  *
69  * This program is distributed in the hope that it will be useful, but WITHOUT
70  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
71  * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
72  * more details.
73  *
74  * You should have received a copy of the GNU General Public License along with
75  * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
76  * Place, Suite 330, Boston, MA 02111-1307 USA
77  ******************************************************************************/
78 
79  /* c = character key
80  * f = function key
81  * _ = blank/error
82  *
83  * Source: KEY_* defines from <linux/input.h>
84  */
85  static char const char_or_func[] =
86  /* 0123456789ABCDEF */
87  "_fccccccccccccff" /* 0 */
88  "ccccccccccccfFcc" /* 1 */
89  "ccccccccccFccccc" /* 2 */
90  "ccccccFfFfFfffff" /* 3 */
91  "fffffFFfffffffff" /* 4 */
92  "ffff__cff_______" /* 5 */
93  "fFffFfffffffffff" /* 6 */
94  "_______f_____FFF"; /* 7 */
95 
96  int is_char_key(unsigned int code)
97  {
98  if (code >= sizeof(char_or_func)) throw std::range_error("is_char_key: invalid code " + std::to_string(code));
99  return char_or_func[code] == 'c';
100  }
101  /*
102  int is_func_key(unsigned int code)
103  {
104  if (code >= sizeof(char_or_func)) throw std::range_error("is_func_key: invalid code " + std::to_string(code));
105  return char_or_func[code] == 'f' || char_or_func[code] == 'F';
106  }
107  */
108  /*
109  int is_modfunc_key(unsigned int code)
110  {
111  if (code >= sizeof(char_or_func)) throw std::range_error("is_modfunc_key: invalid code " + std::to_string(code));
112  return char_or_func[code] == 'F';
113  }
114  */
115  /*
116  int is_used_key(unsigned int code)
117  {
118  if (code >= sizeof(char_or_func)) throw std::range_error("is_used_key: invalid code " + std::to_string(code));
119  return char_or_func[code] != '_';
120  }
121  */
122 
123  /* Translates character keycodes to continuous array indexes. */
124  int to_char_keys_index(unsigned int keycode)
125  {
126  // keycodes 2-13: US keyboard: 1, 2, ..., 0, -, =
127  if (keycode >= KEY_1 && keycode <= KEY_EQUAL) return keycode - KEY_1;
128  // keycodes 16-27: q, w, ..., [, ]
129  if (keycode >= KEY_Q && keycode <= KEY_RIGHTBRACE) return keycode - KEY_Q + 12;
130  // keycodes 30-41: a, s, ..., ', `
131  if (keycode >= KEY_A && keycode <= KEY_GRAVE) return keycode - KEY_A + 24;
132  // keycodes 43-53: \, z, ..., ., /
133  if (keycode >= KEY_BACKSLASH && keycode <= KEY_SLASH) return keycode - KEY_BACKSLASH + 36;
134  // key right to the left of 'Z' on US layout
135  if (keycode == KEY_102ND) return 47;
136  return -1; // not character keycode
137  }
138 
139  /* Translates function keys keycodes to continuous array indexes. */
140  int to_func_keys_index(unsigned int keycode)
141  {
142  // 1
143  if (keycode == KEY_ESC) return 0;
144  // 14-15
145  if (keycode >= KEY_BACKSPACE && keycode <= KEY_TAB) return keycode - 13;
146  // 28-29
147  if (keycode >= KEY_ENTER && keycode <= KEY_LEFTCTRL) return keycode - 25;
148  // 42
149  if (keycode == KEY_LEFTSHIFT) return keycode - 37;
150  // 54-83
151  if (keycode >= KEY_RIGHTSHIFT && keycode <= KEY_KPDOT) return keycode - 48;
152  // 87-88
153  if (keycode >= KEY_F11 && keycode <= KEY_F12) return keycode - 51;
154  // 96-111
155  if (keycode >= KEY_KPENTER && keycode <= KEY_DELETE) return keycode - 58;
156  // 119
157  if (keycode == KEY_PAUSE) return keycode - 65;
158  // 125-127
159  if (keycode >= KEY_LEFTMETA && keycode <= KEY_COMPOSE) return keycode - 70;
160 
161  return -1; // not function key keycode
162  }
163 
164  /* Determines the system keymap via the dump keys program and some disgusting parsing of it. */
165  void load_system_keymap(std::array<wchar_t, 49> & char_keys, std::array<wchar_t, 49> & shift_keys,
166  std::array<wchar_t, 49> & altgr_keys)
167  {
168  /* HACK: This is obscenely ugly, and we should really just do this in C... */
169  FILE * dumpkeys = popen("/usr/bin/dumpkeys -n | /bin/grep '^\\([[:space:]]shift[[:space:]]\\)*\\([[:space:]]altgr[[:space:]]\\)*keycode' | /bin/sed 's/U+/0x/g' 2>&1", "r");
170  if (!dumpkeys) { LERROR("Cannot run dumpkeys -- NOT UPDATING KEYMAP"); return; }
171 
172  // Here we can get strings like:
173  // keycode x = y
174  // shift keycode x = y
175  // altgr keycode x = y
176  // shift altgr keycode x = y: never happens on my keyboard
177 
178  char_keys.fill(0);
179  shift_keys.fill(0);
180  altgr_keys.fill(0);
181 
182  LINFO("Loading keymap...");
183  char buffer[256]; int n = 0;
184  while (fgets(buffer, sizeof(buffer), dumpkeys))
185  {
186  auto tok = jevois::split(buffer);
187  size_t const ntok = tok.size();
188 
189  if (ntok < 4) continue;
190  if (tok[ntok - 4] != "keycode") continue;
191  unsigned int const keycode = std::stoi(tok[ntok - 3]);
192  if (!is_char_key(keycode)) continue;
193  if (keycode >= sizeof(char_or_func)) continue;
194  bool const shift = (tok[1] == "shift");
195  bool const altgr = (tok[1] == "altgr" || tok[2] == "altgr");
196  std::string const & val = tok[ntok - 1];
197  if (val.empty()) { LERROR("Skipping invalid empty keycode value"); continue; }
198 
199  int index = to_char_keys_index(keycode);
200  wchar_t wch = (wchar_t)jevois::from_string<unsigned int>(val);
201  if (val[0] == '+' && (wch & 0xB00)) wch ^= 0xB00;
202 
203  if (tok[0] == "keycode") { char_keys[index] = wch; ++n; }
204  else
205  {
206  if (shift)
207  {
208  if (wch == L'\0') wch = towupper(char_keys[index]);
209  shift_keys[index] = wch;
210  ++n;
211  }
212  else if (altgr) { altgr_keys[index] = wch; ++n; }
213  }
214  }
215  pclose(dumpkeys);
216  LINFO("Loaded " << n << " keymap entries.");
217  }
218 
219  /* Translates struct input_event *event into the string of size buffer_length
220  * pointed to by buffer. Stores state originating from sequence of event
221  * structs in struc input_event_state *state. Returns the number of bytes
222  * written to buffer. */
223  size_t translate_eventw(input_event const *event, input_event_state *state,
224  wchar_t *wbuffer, size_t wbuffer_len, std::array<wchar_t, 49> const & char_keys,
225  std::array<wchar_t, 49> const & shift_keys, std::array<wchar_t, 49> const & altgr_keys,
226  wchar_t const func_keys[58][8])
227  {
228  wchar_t wch;
229  size_t len = 0;
230 
231  if (event->type != EV_KEY)
232  {
233  // not a keyboard event
234  }
235  else if (event->code >= sizeof(char_or_func))
236  {
237  len += swprintf(&wbuffer[len], wbuffer_len, L"<E-%x>", event->code);
238  }
239  else if (event->value == EV_MAKE || event->value == EV_REPEAT)
240  {
241  if (event->code == KEY_LEFTSHIFT || event->code == KEY_RIGHTSHIFT) state->shift = 1;
242  else if (event->code == KEY_RIGHTALT) state->altgr = 1;
243  else if (event->code == KEY_LEFTALT) state->alt = 1;
244  else if (event->code == KEY_LEFTCTRL || event->code == KEY_RIGHTCTRL) state->ctrl = 1;
245  else if (event->code == KEY_LEFTMETA || event->code == KEY_RIGHTMETA) state->meta = 1;
246 
247  if (is_char_key(event->code))
248  {
249  if (state->altgr)
250  {
251  wch = altgr_keys[to_char_keys_index(event->code)];
252  if (wch == L'\0')
253  {
254  if (state->shift) wch = shift_keys[to_char_keys_index(event->code)];
255  else wch = char_keys[to_char_keys_index(event->code)];
256  }
257  }
258  else if (state->shift)
259  {
260  wch = shift_keys[to_char_keys_index(event->code)];
261  if (wch == L'\0') wch = char_keys[to_char_keys_index(event->code)];
262  }
263  else wch = char_keys[to_char_keys_index(event->code)];
264 
265  if (wch != L'\0') len += swprintf(&wbuffer[len], wbuffer_len, L"%lc", wch);
266  }
267 
268  else if (event->code == 57) // special handling of space which is listed as a function key
269  len += swprintf(&wbuffer[len], wbuffer_len, L"%ls", func_keys[to_func_keys_index(event->code)]);
270  /*
271  else if (is_modfunc_key(event->code) && event->code == 57)
272  {
273  if (event->value == EV_MAKE)
274  len += swprintf(&wbuffer[len], wbuffer_len, L"%ls", func_keys[to_func_keys_index(event->code)]);
275  }
276  else if (is_func_key(event->code) && event->code == 57)
277  {
278  len += swprintf(&wbuffer[len], wbuffer_len, L"%ls", func_keys[to_func_keys_index(event->code)]);
279  }
280  else
281  {
282  //len += swprintf(&wbuffer[len], wbuffer_len, L"<E-%x>", event->code);
283  }
284  */
285 
286  }
287  else if (event->value == EV_BREAK)
288  {
289  if (event->code == KEY_LEFTSHIFT || event->code == KEY_RIGHTSHIFT) state->shift = 0;
290  else if (event->code == KEY_RIGHTALT) state->altgr = 0;
291  else if (event->code == KEY_LEFTALT) state->alt = 0;
292  else if (event->code == KEY_LEFTCTRL || event->code == KEY_RIGHTCTRL) state->ctrl = 0;
293  else if (event->code == KEY_LEFTMETA || event->code == KEY_RIGHTMETA) state->meta = 0;
294  }
295 
296  return len;
297  }
298 
299  /* Translates event into multi-byte string description. */
300  size_t translate_event(input_event const *event, input_event_state *state,
301  char *buffer, size_t buffer_len, std::array<wchar_t, 49> const & char_keys,
302  std::array<wchar_t, 49> const & shift_keys, std::array<wchar_t, 49> const & altgr_keys,
303  wchar_t const func_keys[58][8])
304  {
305  wchar_t *wbuffer;
306  size_t wbuffer_len, len;
307 
308  wbuffer = (wchar_t*)buffer;
309  wbuffer_len = buffer_len / sizeof(wchar_t);
310 
311  len = translate_eventw(event, state, wbuffer, wbuffer_len, char_keys, shift_keys, altgr_keys, func_keys);
312 
313  if (!len) *buffer = 0;
314  else wcstombs(buffer, wbuffer, buffer_len);
315  return len;
316  }
317 
318  // We could maybe use that in scanDevices()...
319  // from https://github.com/bingmann/evdev-keylogger/blob/master/logger.c
320  /*
321 #define MAX_PATH 256
322 #define MAX_EVDEV 16
323 
324  int find_default_keyboard_list(char event_device[MAX_EVDEV][MAX_PATH])
325  {
326  FILE *devices;
327  char events[128];
328  char handlers[128];
329  char *event;
330  int evnum = 0, i;
331 
332  devices = fopen("/proc/bus/input/devices", "r");
333  if (!devices) {
334  perror("fopen");
335  return evnum;
336  }
337  while (fgets(events, sizeof(events), devices))
338  {
339  if (strstr(events, "H: Handlers=") == events)
340  strcpy(handlers, events);
341  else if (!strcmp(events, "B: EV=120013\n") && (event = strstr(handlers, "event")))
342  {
343  for (i = 0, event += sizeof("event") - 1; *event && isdigit(*event); ++event, ++i)
344  handlers[i] = *event;
345  handlers[i] = '\0';
346 
347  snprintf(event_device[evnum], sizeof(event_device[evnum]),
348  "/dev/input/event%s", handlers);
349 
350  fprintf(stderr, "listening to keyboard: %s\n", event_device[evnum]);
351 
352  if (++evnum == MAX_EVDEV) break;
353  }
354  }
355  fclose(devices);
356  return evnum;
357  }
358  */
359 
360 } // anonymous namespace
361 
362 // ##############################################################################################################
363 jevois::ImGuiBackendMALI::ImGuiBackendMALI() :
364  jevois::VideoDisplayBackendMALI(), itsLastNewFrameTime(std::chrono::steady_clock::now()),
365  itsMouseX(0), itsMouseY(0), itsConsoleFd(-1), itsTTY(-1), itsKBmode(0)
366 {
367  for (size_t i = 0; i < NELEMS(itsFd); ++i) itsFd[i] = -1;
368  for (size_t i = 0; i < NELEMS(itsMouseButton); ++i) itsMouseButton[i] = false;
369 
370  load_system_keymap(itsCharKeys, itsShiftKeys, itsAltgrKeys);
371 }
372 
373 // ##############################################################################################################
374 jevois::ImGuiBackendMALI::~ImGuiBackendMALI()
375 {
376  ImGui_ImplOpenGL3_Shutdown();
377  ImGui::DestroyContext();
378 
379  // Close console:
380  if (itsConsoleFd >= 0) { ioctl(itsConsoleFd, KDSETMODE, KD_TEXT); close(itsConsoleFd); }
381 
382  // Unmute keyboard:
383  if (itsTTY >= 0)
384  {
385  try { jevois::unMuteKeyboard(itsTTY, itsKBmode); } catch (...) { jevois::warnAndIgnoreException(); }
386  close(itsTTY);
387  }
388 }
389 
390 // ##############################################################################################################
391 void jevois::ImGuiBackendMALI::init(unsigned short w, unsigned short h, bool fullscreen, float scale, bool conslock)
392 {
393  // Lock the console if desired:
394  if (conslock)
395  {
396  // Open console and tell OS to stop drawing on it:
397  try
398  {
399  itsConsoleFd = jevois::getConsoleFd();
400  ioctl(itsConsoleFd, KDSETMODE, KD_GRAPHICS);
401  }
402  catch (...) { jevois::warnAndIgnoreException(); }
403 
404  // https://unix.stackexchange.com/questions/173712/
405  // best-practice-for-hiding-virtual-console-while-rendering-video-to-framebuffer
406 
407  // Mute keyboard:
408  itsTTY = STDIN_FILENO;
409  try { jevois::muteKeyboard(itsTTY, itsKBmode); }
410  catch (...)
411  {
412  // stdin is not a tty, probably we were launched remotely, so we try to disable the active tty:
413  try { itsTTY = jevois::getActiveTTY(); jevois::muteKeyboard(itsTTY, itsKBmode); }
414  catch (...) { jevois::warnAndIgnoreException(); }
415  }
416  }
417 
418  // Init MALI and OpenGL:
419  jevois::VideoDisplayBackendMALI::init(w, h, fullscreen);
420 
421  // Init our internals:
422  itsWidth = w; itsHeight = h;
423  itsMouseX = w / 2; itsMouseY = h / 2;
424 
425  // Setup Dear ImGui context:
426  IMGUI_CHECKVERSION();
427  ImGui::CreateContext();
428  ImGuiIO & io = ImGui::GetIO();
429  // io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls
430  //io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controls
431 
432  // First setup the imgui context:
433  //io.BackendFlags |= ImGuiBackendFlags_HasMouseCursors; // We can honor GetMouseCursor() values (optional)
434  //io.BackendFlags |= ImGuiBackendFlags_HasSetMousePos; // We can honor io.WantSetMousePos requests (optional)
435  //io.BackendPlatformName = "imgui_impl_jevoispro";
436  io.BackendPlatformName = "imgui_impl_sdl";
437 
438  // Keyboard mapping. ImGui will use those indices to peek into the io.KeysDown[] array.
439  io.KeyMap[ImGuiKey_Tab] = KEY_TAB;
440  io.KeyMap[ImGuiKey_LeftArrow] = KEY_LEFT;
441  io.KeyMap[ImGuiKey_RightArrow] = KEY_RIGHT;
442  io.KeyMap[ImGuiKey_UpArrow] = KEY_UP;
443  io.KeyMap[ImGuiKey_DownArrow] = KEY_DOWN;
444  io.KeyMap[ImGuiKey_PageUp] = KEY_PAGEUP;
445  io.KeyMap[ImGuiKey_PageDown] = KEY_PAGEDOWN;
446  io.KeyMap[ImGuiKey_Home] = KEY_HOME;
447  io.KeyMap[ImGuiKey_End] = KEY_END;
448  io.KeyMap[ImGuiKey_Insert] = KEY_INSERT;
449  io.KeyMap[ImGuiKey_Delete] = KEY_DELETE;
450  io.KeyMap[ImGuiKey_Backspace] = KEY_BACKSPACE;
451  io.KeyMap[ImGuiKey_Space] = KEY_SPACE;
452  io.KeyMap[ImGuiKey_Enter] = KEY_ENTER;
453  io.KeyMap[ImGuiKey_Escape] = KEY_ESC;
454  io.KeyMap[ImGuiKey_KeyPadEnter] = KEY_KPENTER;
455  io.KeyMap[ImGuiKey_A] = KEY_A;
456  io.KeyMap[ImGuiKey_C] = KEY_C;
457  io.KeyMap[ImGuiKey_V] = KEY_V;
458  io.KeyMap[ImGuiKey_X] = KEY_X;
459  io.KeyMap[ImGuiKey_Y] = KEY_Y;
460  io.KeyMap[ImGuiKey_Z] = KEY_Z;
461 
462  io.SetClipboardTextFn = ImGui_ImplMALI_SetClipboardText;
463  io.GetClipboardTextFn = ImGui_ImplMALI_GetClipboardText;
464  io.ClipboardUserData = NULL;
465 
466  // Tell imgui to draw a mouse cursor (false at init time):
467  io.MouseDrawCursor = false;
468 
469  // Setup Dear ImGui style:
470  ImGui::StyleColorsDark();
471  //ImGui::StyleColorsClassic();
472 
473  io.FontGlobalScale = scale;
474  ImGui::GetStyle().ScaleAllSizes(scale);
475 
476  // Setup renderer backend:
477  ImGui_ImplOpenGL3_Init("#version 300 es");
478 
479  // Load Fonts
480  // - If no fonts are loaded, dear imgui will use the default font. You can also load multiple fonts and use ImGui::PushFont()/PopFont() to select them.
481  // - AddFontFromFileTTF() will return the ImFont* so you can store it if you need to select the font among multiple.
482  // - If the file cannot be loaded, the function will return NULL. Please handle those errors in your application (e.g. use an assertion, or display an error and quit).
483  // - The fonts will be rasterized at a given size (w/ oversampling) and stored into a texture when calling ImFontAtlas::Build()/GetTexDataAsXXXX(), which ImGui_ImplXXXX_NewFrame below will call.
484  // - Read 'docs/FONTS.md' for more instructions and details.
485  // - Remember that in C/C++ if you want to include a backslash \ in a string literal you need to write a double backslash \\ !
486  //io.Fonts->AddFontDefault();
487  //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Roboto-Medium.ttf", 16.0f);
488  //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Cousine-Regular.ttf", 15.0f);
489  //io.Fonts->AddFontFromFileTTF("../../misc/fonts/DroidSans.ttf", 16.0f);
490  //io.Fonts->AddFontFromFileTTF("../../misc/fonts/ProggyTiny.ttf", 10.0f);
491  //ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf", 18.0f, NULL, io.Fonts->GetGlyphRangesJapanese());
492  //IM_ASSERT(font != NULL);
493 
494  // Do an initial scan for input event devices:
495  //scanDevices();
496 }
497 
498 // ##############################################################################################################
499 bool jevois::ImGuiBackendMALI::pollEvents(bool & shouldclose)
500 {
501  ImGuiIO & io = ImGui::GetIO();
502  bool gotsome = false;
503 
504  // Rescan our devices once in a while; could use udev instead
505  static int count = 0;
506  if (++count == 100) { scanDevices(); count = 0; }
507 
508  // Mouse button detection: we want to make sure we do not miss click-then-release within a video frame, while at ths
509  // same time detecting releases from drag:
510  bool mouse_pressed[5] = { false, false, false, false, false };
511  bool mouse_released[5] = { false, false, false, false, false };
512 
513  // Check all our input devices in one select() call:
514  static fd_set rfds; // ready to read
515  static fd_set efds; // has error
516  static struct timeval tv; // timeout
517 
518  FD_ZERO(&rfds); FD_ZERO(&efds);
519  tv.tv_sec = 0; tv.tv_usec = 5;
520  int maxfd = -1;
521 
522  for (size_t i = 0; i < NELEMS(itsFd); ++i)
523  if (itsFd[i] != -1)
524  {
525  FD_SET(itsFd[i], &rfds);
526  FD_SET(itsFd[i], &efds);
527  maxfd = std::max(maxfd, itsFd[i]);
528  }
529  if (maxfd <= 0) return gotsome; // Nothing to check
530 
531  int ret = select(maxfd + 1, &rfds, nullptr, &efds, &tv);
532  if (ret == -1)
533  {
534  LERROR("Select error");
535  if (errno == EINTR) return gotsome; // interrupted; will try again next time...
536  }
537  else if (ret > 0)
538  {
539  for (size_t i = 0; i < NELEMS(itsFd); ++i)
540  {
541  // Only consider the fds that we monitor:
542  if (itsFd[i] == -1) continue;
543 
544  // If error flag is set, terminate this device:
545  if (FD_ISSET(itsFd[i], &efds)) { removeDevice(i); continue; }
546 
547  // If ready to read, let's read:
548  if (FD_ISSET(itsFd[i], &rfds))
549  {
550  static struct input_event events[32];
551  ssize_t len = read(itsFd[i], events, sizeof(events));
552 
553  if (len == -1)
554  {
555  // Drop this device (unplugged?) if true error:
556  if (errno != EAGAIN && errno != EWOULDBLOCK) removeDevice(i);
557  continue;
558  }
559  else if (len > 0)
560  {
561  len /= sizeof(events[0]);
562  gotsome = true; // we received some events, let caller know
563 
564  for (ssize_t j = 0; j < len; ++j)
565  {
566  unsigned int const code = events[j].code;
567  int const val = events[j].value;
568 
569  switch (events[j].type)
570  {
571  // --------------------------------------------------
572  case EV_KEY:
573  // A keyboard or mouse button event: First check for mouse button. To not miss click-then-release within
574  // one video frame, we here just keep track of whether a button press or release event occurred:
575  if (code >= BTN_MOUSE && code < BTN_MOUSE + NELEMS(itsMouseButton))
576  {
577  if (val) mouse_pressed[code - BTN_MOUSE] = true;
578  else mouse_released[code - BTN_MOUSE] = true;
579  continue;
580  }
581 
582  // Now check keyboard keys:
583  if (code == KEY_ESC)
584  {
585  shouldclose = true;
586 
587  // Terminate on ESC
588  /*
589  if (itsConsoleFd >= 0) jevois::unMuteKeyboard(itsTTY, itsKBmode);
590  if (itsTTY >= 0) ioctl(itsConsoleFd, KDSETMODE, KD_TEXT);
591  std::terminate();
592  */
593  }
594 
595  // Any other key: send to ImGui
596  io.KeysDown[code] = (val != 0);
597  if (translate_event(&events[j], &itsInputState, itsInputBuffer, sizeof(itsInputBuffer), itsCharKeys,
598  itsShiftKeys, itsAltgrKeys, itsFuncKeys) > 0)
599  io.AddInputCharactersUTF8(itsInputBuffer);
600 
601  //io.KeyAltgr = (itsInputState.altgr != 0); // not supported by ImGui?
602  io.KeyAlt = (itsInputState.alt != 0);
603  io.KeyShift = (itsInputState.shift != 0);
604  io.KeyCtrl = (itsInputState.ctrl != 0);
605  io.KeySuper = (itsInputState.meta != 0);
606 
607  break;
608 
609  // --------------------------------------------------
610  case EV_ABS:
611  // An absolute position event:
612  switch (code)
613  {
614  case ABS_X: itsMouseX = val; break;
615  case ABS_Y: itsMouseY = val; break;
616  }
617  break;
618 
619  // --------------------------------------------------
620  case EV_REL:
621  // Relative mouse movement:
622  switch (code)
623  {
624  case REL_X:
625  itsMouseX += val;
626  if (itsMouseX < 0) itsMouseX = 0; else if (itsMouseX >= int(itsWidth)) itsMouseX = itsWidth - 1;
627  break;
628 
629  case REL_Y:
630  itsMouseY += val;
631  if (itsMouseY < 0) itsMouseY = 0; else if (itsMouseY >= int(itsHeight)) itsMouseY = itsHeight - 1;
632  break;
633 
634  case REL_WHEEL:
635  io.MouseWheel += val;
636  break;
637 
638  case REL_HWHEEL:
639  io.MouseWheelH += val;
640  break;
641  }
642  break;
643  }
644  }
645  }
646  }
647  }
648  }
649 
650  // Tell imgui about mouse position and buttons now:
651  for (size_t i = 0; i < NELEMS(itsMouseButton); ++i)
652  {
653  // If a button was pressed, say so, unless a button was released and not also pressed:
654  if (mouse_pressed[i]) io.MouseDown[i] = true;
655  else if (mouse_released[i]) io.MouseDown[i] = false;
656  }
657 
658  io.MousePos = ImVec2(float(itsMouseX), float(itsMouseY));
659 
660  return gotsome;
661 }
662 
663 // ##############################################################################################################
664 void jevois::ImGuiBackendMALI::newFrame()
665 {
666  // OpenGL new frame:
667  ImGui_ImplOpenGL3_NewFrame(); // initializes ImGui objects on first frame
668  jevois::VideoDisplayBackendMALI::newFrame(); // Clear OpenGL display
669 
670  // Backend new frame:
671  ImGuiIO & io = ImGui::GetIO();
672  if (io.Fonts->IsBuilt() == false) LERROR("Font atlas not built -- IGNORED");
673 
674  // Update imgui displaysize and scale:
675  io.DisplaySize = ImVec2(float(itsWidth), float(itsHeight));
676  if (itsWidth > 0 && itsHeight > 0) io.DisplayFramebufferScale = ImVec2(1.0F, 1.0F);
677 
678  // Update io.DeltaTime with our rendering frame period:
679  auto const now = std::chrono::steady_clock::now();
680  std::chrono::duration<double> const dur = now - itsLastNewFrameTime;
681  io.DeltaTime = dur.count();
682  itsLastNewFrameTime = now;
683 
684  // Imgui new frame:
685  ImGui::NewFrame();
686 }
687 
688 // ##############################################################################################################
689 void jevois::ImGuiBackendMALI::render()
690 {
691  ImGui::Render(); // Draw ImGui stuff
692  ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); // Render to OpenGL
693  jevois::VideoDisplayBackendMALI::render(); // OpenGL swapbuffers
694 }
695 
696 // ##############################################################################################################
697 void jevois::ImGuiBackendMALI::addDevice(size_t num, int fd)
698 {
699  if (num >= NELEMS(itsFd)) LFATAL("Invalid device number " << num);
700  if (itsFd[num] != -1) { LERROR("Invalid device number " << num << ": already open -- IGNORED"); return; }
701  itsFd[num] = fd; LINFO("Registered new input device " << fd << ": /dev/input/event" << num);
702 }
703 
704 // ##############################################################################################################
705 void jevois::ImGuiBackendMALI::removeDevice(size_t num)
706 {
707  if (num >= NELEMS(itsFd)) LFATAL("Invalid device number " << num);
708  if (itsFd[num] == -1) { LERROR("Invalid device number " << num << ": not open -- IGNORED"); return; }
709  close(itsFd[num]);
710  LINFO("Un-registered input device " << itsFd[num]);
711  itsFd[num] = -1;
712 }
713 
714 // ##############################################################################################################
715 void jevois::ImGuiBackendMALI::scanDevices()
716 {
717  // FIXME: do not even try to open event devices 0 and 1 as those flow down our framerate...
718  for (size_t i = 2; i < NELEMS(itsFd); ++i)
719  {
720  if (itsFd[i] == -1)
721  {
722  // We do not have this device currently under our watch; see if a new keyboard/mouse was plugged in:
723  std::string const devname = "/dev/input/event" + std::to_string(i);
724 
725  int fd = open(devname.c_str(), O_RDONLY | O_NONBLOCK);
726  if (fd > 0) { if (jevois::isInputDevice(fd)) addDevice(i, fd); else close(fd); }
727  }
728  else if (jevois::isInputDevice(itsFd[i]) == false)
729  removeDevice(i); // We had this device, but it likely got unplugged:
730  }
731 }
732 
733 #endif // JEVOIS_PLATFORM_PRO
jevois::unMuteKeyboard
void unMuteKeyboard(int tty, int kb_mode)
Restore the keyboard mode for given tty.
Definition: Console.C:114
jevois::split
std::vector< std::string > split(std::string const &input, std::string const &regex="\\s+")
Split string into vector of tokens using a regex to specify what to split on; default regex splits by...
Definition: Utils.C:257
Console.H
LERROR
#define LERROR(msg)
Convenience macro for users to print out console or syslog messages, ERROR level.
Definition: Log.H:198
OpenGL.H
jevois::getActiveTTY
int getActiveTTY()
Get current active tty.
Definition: Console.C:121
jevois
Definition: Concepts.dox:1
F
float F
Definition: GUIhelper.C:2150
Log.H
jevois::isInputDevice
bool isInputDevice(int fd)
Indicate whether this fd (which should be from /dev/input/eventX) is keyboard, mouse,...
Definition: Console.C:36
jevois::warnAndIgnoreException
std::string warnAndIgnoreException(std::string const &prefix="")
Convenience function to catch an exception, issue some LERROR (depending on type),...
Definition: Log.C:224
jevois::muteKeyboard
void muteKeyboard(int tty, int &kb_mode)
Prevent keystrokes from reaching the tty.
Definition: Console.C:103
L
#define L(i, x, y, z)
Definition: GUIhelper.C:2151
LFATAL
#define LFATAL(msg)
Convenience macro for users to print out console or syslog messages, FATAL level.
Definition: Log.H:217
jevois::to_string
std::string to_string(T const &val)
Convert from type to string.
Utils.H
h
int h
Definition: GUIhelper.C:2150
jevois::getConsoleFd
int getConsoleFd()
Get a file descriptor to the console.
Definition: Console.C:85
ImGuiBackendMALI.H
LINFO
#define LINFO(msg)
Convenience macro for users to print out console or syslog messages, INFO level.
Definition: Log.H:181
NELEMS
#define NELEMS(x)
Definition: Console.C:33