fbgl  0.1.0
fbgl.h
1 // fbgl.h - v0.1.0 - public domain Levent Kaya 2024
2 // FBGL - Framebuffer Graphics Library
3 //
4 // This file provides both the interface and the implementation.
5 // To instantiate the implementation,
6 // #define FBGL_IMPLEMENTATION
7 // in *ONE* source file, before #including this file.
8 //
9 //
10 // History:
11 // - 0.1.0 First public release
12 //
13 // Status:
14 // 24/11/2024 texture rendering implemented
15 //
16 // Contributors:
17 // @lvntky
18 // @dario-loi
19 //
20 // LICENSE
21 //
22 // See end of file for license information.
23 
24 #ifndef __FBGL_H__
25 #define __FBGL_H__
26 
27 #define VERSION "0.1.0"
28 #define NAME "FBGL"
29 #define DEFAULT_FB "/dev/fb0"
30 
31 #include <fcntl.h>
32 #include <linux/fb.h>
33 #include <math.h>
34 #include <signal.h>
35 #include <stdbool.h>
36 #include <stddef.h>
37 #include <stdint.h>
38 #include <stdio.h>
39 #include <stdlib.h>
40 #include <string.h>
41 #include <sys/ioctl.h>
42 #include <sys/mman.h>
43 #include <termios.h>
44 #include <time.h>
45 #include <unistd.h>
46 
50 typedef struct fbgl {
51  int32_t width;
52  int32_t height;
53  int32_t fd;
54  uint32_t screen_size;
55  uint32_t *pixels;
56  struct fb_var_screeninfo vinfo; // Variable screen information
57  struct fb_fix_screeninfo finfo; // Fixed screen information
58 } fbgl_t;
59 
60 typedef struct fbgl_window {
61  int32_t x; // Top-left x-coordinate of the window
62  int32_t y; // Top-left y-coordinate of the window
63  uint32_t width; // Width of the window
64  uint32_t height; // Height of the window
65  fbgl_t *fb; // Pointer to the framebuffer context
67 
68 typedef struct fbgl_point {
69  int32_t x;
70  int32_t y;
71 } fbgl_point_t;
72 
73 typedef struct fbgl_tga_texture {
74  uint16_t width;
75  uint16_t height;
76  uint32_t *data;
78 
79 typedef struct fbgl_psf1_font {
80  uint8_t magic[2]; // Magic number (0x36, 0x04 for PSF1)
81  uint8_t mode; // Mode (0 = 256 glyphs, 1 = 512 glyphs)
82  uint8_t char_height; // Character height in pixels
83  uint8_t *glyphs; // Pointer to glyph data
84  uint16_t glyph_count; // Number of glyphs (calculated from mode)
85  uint16_t char_width; // Character width in pixels (always 8 for PSF1)
87 
88 typedef enum fbgl_key {
89  FBGL_KEY_NONE = 0,
90  FBGL_KEY_UP,
91  FBGL_KEY_DOWN,
92  FBGL_KEY_LEFT,
93  FBGL_KEY_RIGHT,
94  FBGL_KEY_ESCAPE,
95  FBGL_KEY_ENTER,
96  FBGL_KEY_SPACE,
97 
98 } fbgl_key_t;
99 
100 typedef struct fbgl_keyboard_state {
101  bool is_key_down;
102  fbgl_key_t current_key;
103  bool special_key_pressed;
105 
110 static struct timespec previous_frame_time = { 0 };
111 static struct termios orig_termios;
112 static fbgl_keyboard_state_t g_keyboard_state = { 0 };
113 
114 #ifdef __cplusplus
115 extern "C" {
116 #endif
117 
121 char const *fbgl_name_info(void);
122 char const *fbgl_version_info(void);
123 float fbgl_get_fps(void);
124 
125 /*Create and destroy methods*/
126 int fbgl_init(const char *device, fbgl_t *fb);
127 void fbgl_destroy(fbgl_t *fb);
128 
132 void fbgl_clear(uint32_t color);
133 void fbgl_put_pixel(int x, int y, uint32_t color, fbgl_t *fb);
134 void fbgl_draw_line(fbgl_point_t x, fbgl_point_t y, uint32_t color, fbgl_t *fb);
135 
139 uint32_t *fb_get_data(fbgl_t const *fb);
140 uint32_t fb_get_width(fbgl_t const *fb);
141 uint32_t fb_get_height(fbgl_t const *fb);
142 
146 void fbgl_draw_rectangle_outline(fbgl_point_t top_left,
147  fbgl_point_t bottom_right, uint32_t color,
148  fbgl_t *fb);
149 void fbgl_draw_rectangle_filled(fbgl_point_t top_left,
150  fbgl_point_t bottom_right, uint32_t color,
151  fbgl_t *fb);
152 void fbgl_draw_circle_outline(int x, int y, int radius, uint32_t color,
153  fbgl_t *fb);
154 void fbgl_draw_circle_filled(int x, int y, int radius, uint32_t color,
155  fbgl_t *fb);
156 
160 fbgl_tga_texture_t *fbgl_load_tga_texture(const char *path);
161 void fbgl_destroy_texture(fbgl_tga_texture_t *texture);
162 void fbgl_draw_texture(fbgl_t *fb, fbgl_tga_texture_t const *texture, int32_t x,
163  int32_t y);
164 
168 fbgl_psf1_font_t *fbgl_load_psf1_font(const char *path);
169 void fbgl_destroy_psf1_font(fbgl_psf1_font_t *font);
170 void fbgl_render_psf1_text(fbgl_t *fb, fbgl_psf1_font_t *font, const char *text,
171  int x, int y, uint32_t color);
175 int fbgl_keyboard_init(void);
176 void fbgl_destroy_keyboard(void);
177 fbgl_key_t fbgl_get_key(void);
178 bool fbgl_is_key_pressed(fbgl_key_t key);
179 
184 #define FBGL_RGB(r, g, b) ((uint32_t)(((r) << 16) | ((g) << 8) | (b)))
185 #define FBGL_RGBA(r, g, b, a) \
186  ((uint32_t)(((a) << 24) | ((r) << 16) | ((g) << 8) | (b)))
187 #define FBGL_F32RGB_TO_U32(r, g, b) \
188  ((uint32_t)(((uint8_t)(r * 255) << 16) | ((uint8_t)(g * 255) << 8) | \
189  (uint8_t)(b * 255)))
190 #define FBGL_F32RGBA_TO_U32(r, g, b, a) ((uint32_t)(((uint8_t)(a * 255) << 24) | ((uint8_t)(r * 255) << 16) | ((uint8_t)(g * 255) << 8) | (uint8_t)(b * 255))
191 
192 // Inside functions
193 static void i_fbgl_die(const char *s);
194 static void i_fbgl_disable_raw_mode();
195 static void i_fbgl_enable_raw_mode();
196 
197 static void i_fbgl_die(const char *s)
198 {
199  perror(s);
200  exit(1);
201 }
202 
203 static void i_fbgl_enable_raw_mode()
204 {
205  if (tcgetattr(STDIN_FILENO, &orig_termios) == -1) {
206  i_fbgl_die("tcgetattr");
207  }
208  atexit(i_fbgl_disable_raw_mode);
209  struct termios raw = orig_termios;
210  raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
211  raw.c_oflag &= ~(OPOST);
212  raw.c_cflag |= (CS8);
213  raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG);
214  raw.c_cc[VMIN] = 0;
215  raw.c_cc[VTIME] = 1;
216  if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw) == -1) {
217  i_fbgl_die("tcsetattr");
218  }
219 }
220 
221 static void i_fbgl_disable_raw_mode()
222 {
223  if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &orig_termios) == -1) {
224  i_fbgl_die("tcesetattr");
225  }
226 }
227 
228 #ifdef FBGL_IMPLEMENTATION
229 
230 char const *fbgl_name_info(void)
231 {
232  return NAME;
233 }
234 
235 char const *fbgl_version_info(void)
236 {
237  return VERSION;
238 }
239 
240 int fbgl_init(const char *device, fbgl_t *fb)
241 {
242  if (!fb) {
243  fprintf(stderr, "Error: fbgl_t pointer is NULL.");
244  return -1;
245  }
246 
247  fb->fd = device == NULL ? open(DEFAULT_FB, O_RDWR) :
248  open(device, O_RDWR);
249  if (fb->fd == -1) {
250  perror("Error openning framebuffer device");
251  return -1;
252  }
253 
254  if (ioctl(fb->fd, FBIOGET_FSCREENINFO, &fb->finfo) == -1) {
255  perror("Error: Reading fixed information.");
256  close(fb->fd);
257  return -1;
258  }
259  if (ioctl(fb->fd, FBIOGET_VSCREENINFO, &fb->vinfo) == -1) {
260  perror("Error reading variable information");
261  close(fb->fd);
262  return -1;
263  }
264 
265  fb->width = fb->vinfo.xres;
266  fb->height = fb->vinfo.yres;
267  fb->screen_size = fb->finfo.smem_len;
268 
269  // Map framebuffer to memory
270  fb->pixels = (uint32_t *)mmap(NULL, fb->screen_size,
271  PROT_READ | PROT_WRITE, MAP_SHARED,
272  fb->fd, 0);
273  if (fb->pixels == MAP_FAILED) {
274  perror("Error mapping framebuffer device to memory");
275  close(fb->fd);
276  return -1;
277  }
278 
279  return 0;
280 }
281 
282 void fbgl_destroy(fbgl_t *fb)
283 {
284  if (!fb || fb->fd == -1) {
285  fprintf(stderr,
286  "Error: framebuffer not initialized or already destroyed.\n");
287  return;
288  }
289 
290  if (fb->pixels && fb->pixels != MAP_FAILED) {
291  munmap(fb->pixels, fb->screen_size);
292  }
293 
294  close(fb->fd);
295  fb->fd = -1;
296 }
297 
298 void fbgl_set_bg(fbgl_t *fb, uint32_t color)
299 {
300 #ifdef DEBUG
301  if (!fb || fb->fd == -1) {
302  fprintf(stderr, "Error: framebuffer not initialized.\n");
303  return;
304  }
305 #endif // DEBUG
306 
307  // Fill the entire framebuffer with the specified color
308  for (int32_t i = 0; i < fb->width * fb->height; i++) {
309  fb->pixels[i] = color;
310  }
311 }
312 
313 void fbgl_put_pixel(int x, int y, uint32_t color, fbgl_t *fb)
314 {
315 #ifdef FBGL_VALIDATE_PUT_PIXEL
316  if (!fb || !fb->pixels) {
317  fprintf(stderr, "Error: framebuffer not initialized.\n");
318  return;
319  }
320 
321  if (x < 0 || x >= fb->width || y < 0 || y >= fb->height) {
322  return; // Ignore out-of-bound coordinates
323  }
324 #endif // FBGL_VALIDATE_PUT_PIXEL
325 
326  const size_t index = y * fb->width + x;
327  fb->pixels[index] = color;
328 }
329 
330 void fbgl_draw_line(fbgl_point_t x, fbgl_point_t y, uint32_t color,
331  fbgl_t *buffer)
332 {
333  const int32_t dx = abs(y.x - x.x);
334  const int32_t dy = abs(y.y - x.y);
335 
336  const int32_t sx = (x.x < y.x) ? 1 : -1;
337  const int32_t sy = (x.y < y.y) ? 1 : -1;
338 
339  int32_t err = dx - dy;
340 
341  while (1) {
342  // Set the pixel at the current position
343  fbgl_put_pixel(x.x, x.y, color, buffer);
344 
345  // If we've reached the end point, break
346  if (x.x >= y.x && x.y >= y.y)
347  break;
348 
349  const int32_t e2 = 2 * err;
350 
351  if (e2 > -dy) {
352  err -= dy;
353  x.x += sx;
354  }
355 
356  if (e2 < dx) {
357  err += dx;
358  x.y += sy;
359  }
360  }
361 }
362 void fbgl_draw_rectangle_outline(fbgl_point_t top_left,
363  fbgl_point_t bottom_right, uint32_t color,
364  fbgl_t *fb)
365 {
366  // Top horizontal line
367  for (int x = top_left.x; x < bottom_right.x; x++) {
368  fbgl_put_pixel(x, top_left.y, color, fb);
369  }
370 
371  // Bottom horizontal line
372  for (int x = top_left.x; x < bottom_right.x; x++) {
373  fbgl_put_pixel(x, bottom_right.y - 1, color, fb);
374  }
375 
376  // Left vertical line
377  for (int y = top_left.y; y < bottom_right.y; y++) {
378  fbgl_put_pixel(top_left.x, y, color, fb);
379  }
380 
381  // Right vertical line
382  for (int y = top_left.y; y < bottom_right.y; y++) {
383  fbgl_put_pixel(bottom_right.x - 1, y, color, fb);
384  }
385 }
386 
387 void fbgl_draw_rectangle_filled(fbgl_point_t top_left,
388  fbgl_point_t bottom_right, uint32_t color,
389  fbgl_t *fb)
390 {
391  for (int32_t y = top_left.y; y < bottom_right.y; y++) {
392  // Manually set each pixel in the row
393  for (int32_t x = top_left.x; x < bottom_right.x; x++) {
394  fbgl_put_pixel(x, y, color, fb);
395  }
396  }
397 }
398 
399 void fbgl_draw_circle_outline(int x, int y, int radius, uint32_t color,
400  fbgl_t *fb)
401 {
402  int f = 1 - radius;
403  int ddF_x = 1;
404  int ddF_y = -2 * radius;
405  int xx = 0;
406  int yy = radius;
407 
408  fbgl_put_pixel(x, y + radius, color, fb);
409  fbgl_put_pixel(x, y - radius, color, fb);
410  fbgl_put_pixel(x + radius, y, color, fb);
411  fbgl_put_pixel(x - radius, y, color, fb);
412 
413  while (xx < yy) {
414  if (f >= 0) {
415  yy--;
416  ddF_y += 2;
417  f += ddF_y;
418  }
419  xx++;
420  ddF_x += 2;
421  f += ddF_x;
422 
423  fbgl_put_pixel(x + xx, y + yy, color, fb);
424  fbgl_put_pixel(x - xx, y + yy, color, fb);
425  fbgl_put_pixel(x + xx, y - yy, color, fb);
426  fbgl_put_pixel(x - xx, y - yy, color, fb);
427  fbgl_put_pixel(x + yy, y + xx, color, fb);
428  fbgl_put_pixel(x - yy, y + xx, color, fb);
429  fbgl_put_pixel(x + yy, y - xx, color, fb);
430  fbgl_put_pixel(x - yy, y - xx, color, fb);
431  }
432 }
433 
434 void fbgl_draw_circle_filled(int x, int y, int radius, uint32_t color,
435  fbgl_t *fb)
436 {
437  for (int yy = -radius; yy <= radius; ++yy) {
438  int half_width = (int)sqrt(radius * radius - yy * yy);
439 
440  int row_start = x - half_width;
441  int row_end = x + half_width;
442 
443  if (y + yy < 0 || y + yy >= fb->height)
444  continue;
445  if (row_start < 0)
446  row_start = 0;
447  if (row_end >= fb->width)
448  row_end = fb->width - 1;
449 
450  int pixel_offset = (y + yy) * fb->width + row_start;
451  int num_pixels = row_end - row_start + 1;
452 
453  uint32_t *row_start_ptr = fb->pixels + pixel_offset;
454  for (int i = 0; i < num_pixels; ++i) {
455  row_start_ptr[i] = color;
456  }
457  }
458 }
459 
460 fbgl_tga_texture_t *fbgl_load_tga_texture(const char *path)
461 {
462  FILE *file = fopen(path, "rb");
463  if (!file) {
464  perror("Unable to open texture file");
465  return NULL;
466  }
467 
468  // TGA header structure
469  uint8_t header[18];
470  if (fread(header, 1, sizeof(header), file) != sizeof(header)) {
471  perror("Error reading TGA header");
472  fclose(file);
473  return NULL;
474  }
475 
476  // Allocate texture structure
477  fbgl_tga_texture_t *texture =
478  (fbgl_tga_texture_t *)malloc(sizeof(fbgl_tga_texture_t));
479  if (!texture) {
480  perror("Failed to allocate texture structure");
481  fclose(file);
482  return NULL;
483  }
484 
485  // Extract dimensions from header
486  texture->width = header[12] | (header[13] << 8);
487  texture->height = header[14] | (header[15] << 8);
488  uint8_t bits_per_pixel = header[16];
489  uint8_t image_descriptor = header[17];
490 
491  // Verify format support
492  if (bits_per_pixel != 24 && bits_per_pixel != 32) {
493  fprintf(stderr,
494  "Unsupported TGA bit depth: %d (only 24 and 32-bit supported)\n",
495  bits_per_pixel);
496  free(texture);
497  fclose(file);
498  return NULL;
499  }
500 
501  // Skip image ID field
502  if (header[0]) {
503  fseek(file, header[0], SEEK_CUR);
504  }
505 
506  // Allocate pixel data
507  size_t pixel_count = texture->width * texture->height;
508  texture->data = (uint32_t *)malloc(pixel_count * sizeof(uint32_t));
509  if (!texture->data) {
510  perror("Failed to allocate pixel data");
511  free(texture);
512  fclose(file);
513  return NULL;
514  }
515 
516  // Read pixel data
517  uint8_t *pixel_buffer = (uint8_t *)malloc(bits_per_pixel / 8);
518  if (!pixel_buffer) {
519  perror("Failed to allocate pixel buffer");
520  free(texture->data);
521  free(texture);
522  fclose(file);
523  return NULL;
524  }
525 
526  // Determine if image is flipped (based on image descriptor)
527  bool bottom_up = !(image_descriptor & 0x20);
528 
529  for (size_t i = 0; i < pixel_count; i++) {
530  size_t pixel_index =
531  bottom_up ?
532  (texture->height - 1 - (i / texture->width)) *
533  texture->width +
534  (i % texture->width) :
535  i;
536 
537  if (fread(pixel_buffer, 1, bits_per_pixel / 8, file) !=
538  bits_per_pixel / 8) {
539  perror("Error reading pixel data");
540  free(pixel_buffer);
541  free(texture->data);
542  free(texture);
543  fclose(file);
544  return NULL;
545  }
546 
547  // Convert BGR(A) to RGBA
548  uint32_t pixel = 0xFF000000; // Default alpha to 255
549  pixel |= pixel_buffer[2] << 16; // R
550  pixel |= pixel_buffer[1] << 8; // G
551  pixel |= pixel_buffer[0]; // B
552  if (bits_per_pixel == 32) {
553  pixel = (pixel & 0x00FFFFFF) |
554  (pixel_buffer[3] << 24); // A
555  }
556 
557  texture->data[pixel_index] = pixel;
558  }
559 
560  free(pixel_buffer);
561  fclose(file);
562  return texture;
563 }
564 
565 void fbgl_destroy_texture(fbgl_tga_texture_t *texture)
566 {
567  if (texture) {
568  free(texture->data);
569  free(texture);
570  }
571 }
572 
573 void fbgl_draw_texture(fbgl_t *fb, fbgl_tga_texture_t const *texture, int32_t x,
574  int32_t y)
575 {
576  if (!fb || !texture || !texture->data) {
577  return;
578  }
579 
580  for (int ty = 0; ty < texture->height; ty++) {
581  for (int tx = 0; tx < texture->width; tx++) {
582  int screen_x = x + tx;
583  int screen_y = y + ty;
584 
585  // Skip if outside screen bounds
586  if (screen_x < 0 || screen_x >= fb->width ||
587  screen_y < 0 || screen_y >= fb->height) {
588  continue;
589  }
590 
591  uint32_t pixel =
592  texture->data[ty * texture->width + tx];
593  // Only draw if pixel is not fully transparent
594  if ((pixel & 0xFF000000) != 0) {
595  fbgl_put_pixel(screen_x, screen_y, pixel, fb);
596  }
597  }
598  }
599 }
600 
601 uint32_t fb_get_width(fbgl_t const *fb)
602 {
603  return fb->width;
604 }
605 
606 uint32_t fb_get_height(fbgl_t const *fb)
607 {
608  return fb->height;
609 }
610 
611 uint32_t *fb_get_data(fbgl_t const *fb)
612 {
613  return fb->pixels;
614 }
615 
616 float fbgl_get_fps(void)
617 {
618  struct timespec current_time;
619  clock_gettime(CLOCK_MONOTONIC, &current_time);
620 
621  if (previous_frame_time.tv_sec == 0 &&
622  previous_frame_time.tv_nsec == 0) {
623  previous_frame_time = current_time;
624  return 0.0f;
625  }
626 
627  double time_diff =
628  (current_time.tv_sec - previous_frame_time.tv_sec) +
629  (current_time.tv_nsec - previous_frame_time.tv_nsec) / 1e9;
630 
631  previous_frame_time = current_time;
632 
633  if (time_diff > 0.0) {
634  return 1.0 / time_diff;
635  } else {
636  return 0.0f; // Avoid division by zero
637  }
638 }
639 
640 fbgl_psf1_font_t *fbgl_load_psf1_font(const char *path)
641 {
642  FILE *file = fopen(path, "rb");
643  if (!file) {
644  perror("Failed to open font file");
645  return NULL;
646  }
647 
648  // Allocate memory for the font structure
649  fbgl_psf1_font_t *font = malloc(sizeof(fbgl_psf1_font_t));
650  if (!font) {
651  perror("Failed to allocate memory for font");
652  fclose(file);
653  return NULL;
654  }
655 
656  // Read the header (4 bytes)
657  uint8_t header[4];
658  if (fread(header, 1, sizeof(header), file) != sizeof(header)) {
659  perror("Failed to read font header");
660  free(font);
661  fclose(file);
662  return NULL;
663  }
664 
665  // Verify magic number
666  if (header[0] != 0x36 || header[1] != 0x04) {
667  fprintf(stderr, "Invalid PSF1 magic number\n");
668  free(font);
669  fclose(file);
670  return NULL;
671  }
672 
673  // Populate the font structure
674  font->magic[0] = header[0];
675  font->magic[1] = header[1];
676  font->mode = header[2];
677  font->char_height = header[3];
678  font->glyph_count = (font->mode & 0x01) ? 512 :
679  256; // Determine glyph count
680  font->char_width = 8; // PSF1 glyphs are always 8 pixels wide
681 
682  // Allocate memory for glyphs
683  size_t glyph_data_size = font->glyph_count * font->char_height;
684  font->glyphs = malloc(glyph_data_size);
685  if (!font->glyphs) {
686  perror("Failed to allocate memory for glyphs");
687  free(font);
688  fclose(file);
689  return NULL;
690  }
691 
692  // Read glyph data
693  if (fread(font->glyphs, 1, glyph_data_size, file) != glyph_data_size) {
694  perror("Failed to read glyph data");
695  free(font->glyphs);
696  free(font);
697  fclose(file);
698  return NULL;
699  }
700 
701  fclose(file);
702  return font;
703 }
704 
705 void fbgl_destroy_psf1_font(fbgl_psf1_font_t *font)
706 {
707  if (font) {
708  free(font->glyphs);
709  free(font);
710  }
711 }
712 
713 void fbgl_render_psf1_text(fbgl_t *fb, fbgl_psf1_font_t *font, const char *text,
714  int x, int y, uint32_t color)
715 {
716  if (!fb || !font || !text)
717  return;
718 
719  int cursor_x = x;
720  int cursor_y = y;
721 
722  for (const char *c = text; *c; c++) {
723  uint8_t glyph_index = (uint8_t)*c;
724 
725  // Ensure glyph index is within range
726  if (glyph_index >= font->glyph_count)
727  glyph_index = 0; // Default to space or undefined glyph
728 
729  // Locate the glyph in the glyph table
730  uint8_t *glyph = font->glyphs + glyph_index * font->char_height;
731 
732  // Render the glyph
733  for (int row = 0; row < font->char_height; row++) {
734  for (int col = 0; col < font->char_width; col++) {
735  // Check if the bit is set in the glyph
736  if (glyph[row] & (0x80 >> col)) {
737  fbgl_put_pixel(cursor_x + col,
738  cursor_y + row, color,
739  fb);
740  }
741  }
742  }
743 
744  // Move to the next character position
745  cursor_x += font->char_width;
746  }
747 }
748 
749 int fbgl_keyboard_init(void)
750 {
751  i_fbgl_enable_raw_mode();
752 
753  // Initialize keyboard state
754  g_keyboard_state.is_key_down = false;
755  g_keyboard_state.current_key = FBGL_KEY_NONE;
756  g_keyboard_state.special_key_pressed = false;
757 
758  return 0;
759 }
760 
761 void fbgl_destroy_keyboard(void)
762 {
763  i_fbgl_disable_raw_mode();
764 }
765 fbgl_key_t fbgl_get_key(void)
766 {
767  char c;
768  ssize_t bytes_read = read(STDIN_FILENO, &c, 1);
769 
770  if (bytes_read <= 0) {
771  return FBGL_KEY_NONE;
772  }
773 
774  // Handle escape sequences for special keys
775  if (c == 27) {
776  char seq[3];
777  if (read(STDIN_FILENO, &seq[0], 1) != 1)
778  return FBGL_KEY_ESCAPE;
779  if (read(STDIN_FILENO, &seq[1], 1) != 1)
780  return FBGL_KEY_ESCAPE;
781 
782  if (seq[0] == '[') {
783  switch (seq[1]) {
784  case 'A':
785  return FBGL_KEY_UP;
786  case 'B':
787  return FBGL_KEY_DOWN;
788  case 'C':
789  return FBGL_KEY_RIGHT;
790  case 'D':
791  return FBGL_KEY_LEFT;
792  }
793  }
794 
795  return FBGL_KEY_NONE;
796  }
797 
798  // Handle direct key presses
799  switch (c) {
800  case 10: // Enter key
801  return FBGL_KEY_ENTER;
802  case 32: // Space key
803  return FBGL_KEY_SPACE;
804  case 'w':
805  case 'W':
806  return FBGL_KEY_UP;
807  case 's':
808  case 'S':
809  return FBGL_KEY_DOWN;
810  case 'a':
811  case 'A':
812  return FBGL_KEY_LEFT;
813  case 'd':
814  case 'D':
815  return FBGL_KEY_RIGHT;
816  case 27: // Escape key
817  return FBGL_KEY_ESCAPE;
818  }
819 
820  return FBGL_KEY_NONE;
821 }
822 
823 bool fbgl_is_key_pressed(fbgl_key_t key)
824 {
825  // Use select() for non-blocking input check
826  fd_set read_fds;
827  struct timeval timeout;
828 
829  FD_ZERO(&read_fds);
830  FD_SET(STDIN_FILENO, &read_fds);
831 
832  // Set a very short timeout
833  timeout.tv_sec = 0;
834  timeout.tv_usec = 0;
835 
836  // Check if there's input available
837  if (select(STDIN_FILENO + 1, &read_fds, NULL, NULL, &timeout) > 0) {
838  fbgl_key_t pressed_key = fbgl_get_key();
839  return pressed_key == key;
840  }
841 
842  return false;
843 }
844 
845 #endif // FBGL_IMPLEMENTATION
846 
847 #ifdef __cplusplus
848 } // extern "C"
849 #endif
850 #endif // __FBGL_H__
851 
852 /*
853 ------------------------------------------------------------------------------
854 This software is available under 2 licenses -- choose whichever you prefer.
855 ------------------------------------------------------------------------------
856 ALTERNATIVE A - MIT License
857 Copyright (c) 2024 Levent Kaya
858 Permission is hereby granted, free of charge, to any person obtaining a copy of
859 this software and associated documentation files (the "Software"), to deal in
860 the Software without restriction, including without limitation the rights to
861 use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
862 of the Software, and to permit persons to whom the Software is furnished to do
863 so, subject to the following conditions:
864 The above copyright notice and this permission notice shall be included in all
865 copies or substantial portions of the Software.
866 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
867 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
868 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
869 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
870 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
871 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
872 SOFTWARE.
873 ------------------------------------------------------------------------------
874 ALTERNATIVE B - Public Domain (www.unlicense.org)
875 This is free and unencumbered software released into the public domain.
876 Anyone is free to copy, modify, publish, use, compile, sell, or distribute this
877 software, either in source code form or as a compiled binary, for any purpose,
878 commercial or non-commercial, and by any means.
879 In jurisdictions that recognize copyright laws, the author or authors of this
880 software dedicate any and all copyright interest in the software to the public
881 domain. We make this dedication for the benefit of the public at large and to
882 the detriment of our heirs and successors. We intend this dedication to be an
883 overt act of relinquishment in perpetuity of all present and future rights to
884 this software under copyright law.
885 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
886 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
887 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
888 AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
889 ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
890 WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
891 ------------------------------------------------------------------------------
892 */
Definition: fbgl.h:100
Definition: fbgl.h:68
Definition: fbgl.h:79
Definition: fbgl.h:73
Definition: fbgl.h:60
Definition: fbgl.h:50