-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmain.c
More file actions
2122 lines (1889 loc) · 89.2 KB
/
main.c
File metadata and controls
2122 lines (1889 loc) · 89.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <time.h>
#include <math.h>
#include <stdint.h>
#include <fcntl.h>
#include <unistd.h>
#include <poll.h>
#include <sys/ioctl.h>
#include <linux/input.h>
#include <errno.h>
#include <linux/input-event-codes.h>
#include <wayland-client.h>
#include <wayland-client-protocol.h>
#include <wayland-egl.h>
#include <EGL/egl.h>
#include <GL/gl.h>
#include "protocol/wlr-layer-shell-unstable-v1-client-protocol.h"
#include <linux/uinput.h>
// --- Macros and Basic Defines ---
#define INVALID_FINGER_ID -1
#ifndef M_PI
#define M_PI 3.14159265358979323846f
#endif
// Define MIN/MAX macros
#ifndef MAX
#define MAX(a,b) ((a) > (b) ? (a) : (b))
#endif
#ifndef MIN
#define MIN(a,b) ((a) < (b) ? (a) : (b))
#endif
// Debug macro
#define D(fmt, ...) fprintf(stderr, "[DEBUG] %s:%d: " fmt "\n", __FILE__, __LINE__, ##__VA_ARGS__)
// --- Core Data Structures and Enumerations ---
// Mappable Keys
typedef struct {
int keycode; // Linux input event code (e.g., KEY_A)
const char *label; // Human-readable label (e.g., "A", "Spc", "Ctrl")
} MappableKey;
// Widget System
#define WIDGET_TYPE_LIST \
X(JOYSTICK, joystick) \
X(DPAD, dpad) \
X(BUTTON, button)
typedef enum {
#define X(name, func) WIDGET_##name,
WIDGET_TYPE_LIST
#undef X
WIDGET_MAX // Sentinel value
} WidgetType;
typedef struct {
float x, y;
} Vec2;
typedef struct {
GLubyte r, g, b, a;
} Color;
typedef struct {
int id;
WidgetType type;
Vec2 normCenter; // Normalized center [0..1]
float normHalfSize; // Normalized half-size
// Calculated absolute values
Vec2 absCenter;
float absRadius; // Also used for button radius if needed
Vec2 absTopLeft;
float absSize;
// Interaction state
int controllingFinger; // Slot index controlling this widget, INVALID_FINGER_ID if none
// Type-specific data
union {
// Joystick/DPad key mapping and press state for 4 directions
struct {
int keycode[4]; // Up, Down, Left, Right
const char *mappedLabel[4];
} analog;
// Button data
struct {
int keycode;
const char *mappedLabel;
bool isPressed;
} button;
} data;
// Common output value (used by joystick/dpad for direction, potentially others)
Vec2 outputValue;
} Widget;
// Application State
typedef enum {
APP_STATE_RUNNING, // Normal operation or trackpad mode
APP_STATE_EDIT_MODE, // Edit mode active, no specific action
APP_STATE_MENU_ADD_WIDGET, // Add button pressed, showing selection menu
APP_STATE_MENU_WIDGET_PROPERTIES, // Editing properties of a selected widget
APP_STATE_MENU_REMAP_ACTION, // Selecting which direction/action to remap for DPad/Joystick
APP_STATE_MENU_REMAP_KEY // Remapping keys
} ApplicationState;
// Edit Mode State
typedef enum {
EDIT_NONE,
EDIT_MOVE,
EDIT_RESIZE
} EditAction;
typedef struct {
Widget* targetWidget;
EditAction action;
Vec2 startTouchPos;
Vec2 startWidgetCenter;
float startWidgetHalfSize;
float startTouchDistance;
} EditState;
// Input System
typedef enum {
EVT_KEY_DOWN,
EVT_KEY_UP
} EventType;
typedef struct {
int widget_id;
EventType type;
int keycode; // Linux KEY_ code
} InputEvent;
typedef enum {
DIR_UP = 0,
DIR_DOWN,
DIR_LEFT,
DIR_RIGHT
} Direction;
// Touch Input System
typedef struct {
bool active;
bool was_down;
double x;
double y;
} MTSlot;
typedef enum {
SLOT_IDLE = 0,
SLOT_WIDGET,
SLOT_TRACKPAD
} SlotMode;
// UI Menu System
typedef struct {
const char* label;
Color bgColor;
} MenuItem;
typedef enum {
PROP_ACTION_DELETE,
PROP_ACTION_REMAP
/*, PROP_ACTION_OPACITY ... */
} PropertyAction;
// --- Global Constants ---
// Mappable Keys Data
static const MappableKey gMappableKeys[] = {
{KEY_A, "A"}, {KEY_B, "B"}, {KEY_C, "C"}, {KEY_D, "D"},
{KEY_E, "E"}, {KEY_F, "F"}, {KEY_G, "G"}, {KEY_H, "H"},
{KEY_I, "I"}, {KEY_J, "J"}, {KEY_K, "K"}, {KEY_L, "L"},
{KEY_M, "M"}, {KEY_N, "N"}, {KEY_O, "O"}, {KEY_P, "P"},
{KEY_Q, "Q"}, {KEY_R, "R"}, {KEY_S, "S"}, {KEY_T, "T"},
{KEY_U, "U"}, {KEY_V, "V"}, {KEY_W, "W"}, {KEY_X, "X"},
{KEY_Y, "Y"}, {KEY_Z, "Z"},
{KEY_1, "1"}, {KEY_2, "2"}, {KEY_3, "3"}, {KEY_4, "4"},
{KEY_5, "5"}, {KEY_6, "6"}, {KEY_7, "7"}, {KEY_8, "8"},
{KEY_9, "9"}, {KEY_0, "0"},
{KEY_ESC, "Esc"},
{KEY_SPACE, "Spc"}, {KEY_ENTER, "Ent"}, {KEY_BACKSPACE, "Bk"}, {KEY_TAB, "Tab"},
{KEY_LEFTCTRL, "Ctrl"}, {KEY_LEFTSHIFT, "Shft"}, {KEY_LEFTALT, "Alt"},
{KEY_UP, "Up"}, {KEY_DOWN, "Dn"}, {KEY_LEFT, "Lt"}, {KEY_RIGHT, "Rt"},
{BTN_LEFT, "LMB"}, {BTN_RIGHT, "RMB"},
};
static const int gNumMappableKeys = sizeof(gMappableKeys) / sizeof(gMappableKeys[0]);
// UInput integration
static int uinput_fd = -1;
static bool uinput_init(void) {
uinput_fd = open("/dev/uinput", O_WRONLY | O_NONBLOCK);
if (uinput_fd < 0) { perror("Failed to open /dev/uinput"); return false; }
if (ioctl(uinput_fd, UI_SET_EVBIT, EV_REL) < 0) { perror("Failed to set EV_REL"); close(uinput_fd); return false; }
if (ioctl(uinput_fd, UI_SET_RELBIT, REL_X) < 0 || ioctl(uinput_fd, UI_SET_RELBIT, REL_Y) < 0) { perror("Failed to set REL_X/REL_Y"); close(uinput_fd); return false; }
if (ioctl(uinput_fd, UI_SET_EVBIT, EV_KEY) < 0) { perror("Failed to set EV_KEY"); close(uinput_fd); return false; }
const int extra_keys[] = {BTN_LEFT, BTN_RIGHT, KEY_VOLUMEDOWN, KEY_VOLUMEUP};
for (size_t i = 0; i < sizeof(extra_keys)/sizeof(extra_keys[0]); ++i) {
if (ioctl(uinput_fd, UI_SET_KEYBIT, extra_keys[i]) < 0) {
char err_msg[64];
snprintf(err_msg, sizeof(err_msg), "Failed to set keybit for extra key %d", extra_keys[i]);
perror(err_msg);
close(uinput_fd);
return false;
}
}
for (int i = 0; i < gNumMappableKeys; ++i) {
if (ioctl(uinput_fd, UI_SET_KEYBIT, gMappableKeys[i].keycode) < 0) {
char err_msg[64];
snprintf(err_msg, sizeof(err_msg), "Failed to set keybit for keycode %d", gMappableKeys[i].keycode);
perror(err_msg);
close(uinput_fd);
return false;
}
}
if (ioctl(uinput_fd, UI_SET_EVBIT, EV_SYN) < 0) { perror("Failed to set EV_SYN"); close(uinput_fd); return false; }
struct uinput_user_dev uidev;
memset(&uidev, 0, sizeof(uidev));
snprintf(uidev.name, UINPUT_MAX_NAME_SIZE, "wlr_gamepad_uinput");
uidev.id.bustype = BUS_USB;
uidev.id.vendor = 0x1234;
uidev.id.product = 0x5678;
uidev.id.version = 1;
if (write(uinput_fd, &uidev, sizeof(uidev)) < 0) { perror("Failed to write uinput_user_dev"); close(uinput_fd); return false; }
if (ioctl(uinput_fd, UI_DEV_CREATE) < 0) { perror("Failed to create uinput device"); close(uinput_fd); return false; }
fprintf(stderr, "[UINPUT] initialized: fd=%d\n", uinput_fd);
return true;
}
static void uinput_move(int dx, int dy) {
if (uinput_fd < 0) return;
struct input_event ev;
memset(&ev, 0, sizeof(ev));
ev.type = EV_REL;
ev.code = REL_X;
ev.value = dx;
write(uinput_fd, &ev, sizeof(ev));
ev.code = REL_Y;
ev.value = dy;
write(uinput_fd, &ev, sizeof(ev));
ev.type = EV_SYN;
ev.code = SYN_REPORT;
ev.value = 0;
write(uinput_fd, &ev, sizeof(ev));
}
static void uinput_key(int keycode, bool pressed) {
if (uinput_fd < 0) return;
struct input_event ev;
memset(&ev, 0, sizeof(ev));
ev.type = EV_KEY;
ev.code = keycode;
ev.value = pressed ? 1 : 0;
write(uinput_fd, &ev, sizeof(ev));
ev.type = EV_SYN;
ev.code = SYN_REPORT;
ev.value = 0;
write(uinput_fd, &ev, sizeof(ev));
}
static void uinput_destroy(void) {
if (uinput_fd < 0) return;
fprintf(stderr, "[UINPUT] destroying device fd=%d\n", uinput_fd);
ioctl(uinput_fd, UI_DEV_DESTROY);
close(uinput_fd);
uinput_fd = -1;
}
// Key Selection Menu Layout Constants
static const int kKeyGridCols = 8;
static const float kKeyButtonSize = 60.0f;
static const float kKeyButtonSpacing = 10.0f;
// Grid layout helper struct for arranging items in a grid
typedef struct {
int rows;
int cols;
float cellSize;
float cellSpacing;
float totalWidth;
float totalHeight;
float startX;
float startY;
} GridLayout;
// Calculate grid layout parameters for a uniform grid
static void CalculateGridLayout(
int screenW,
int screenH,
int itemCount,
int numCols,
float baseCellSize,
float baseCellSpacing,
float offsetTop,
GridLayout *out)
{
// Empty layout if no columns or items
if (numCols <= 0 || itemCount <= 0) {
out->rows = 0;
out->cols = numCols;
out->cellSize = baseCellSize;
out->cellSpacing = baseCellSpacing;
out->totalWidth = 0;
out->totalHeight = 0;
out->startX = 0;
out->startY = offsetTop;
return;
}
// Compute rows and required grid size
int rows = (itemCount + numCols - 1) / numCols;
float reqW = numCols * (baseCellSize + baseCellSpacing) - baseCellSpacing;
float reqH = rows * (baseCellSize + baseCellSpacing) - baseCellSpacing;
// Available space
float availW = (float)screenW;
float availH = (float)screenH - offsetTop;
// Uniform scale to fit both dimensions
float scaleW = availW / reqW;
float scaleH = availH / reqH;
float scale = (scaleW < scaleH ? scaleW : scaleH);
if (scale > 1.0f) scale = 1.0f;
if (scale < 0.1f) scale = 0.1f;
// Populate layout
out->rows = rows;
out->cols = numCols;
out->cellSize = baseCellSize * scale;
out->cellSpacing = baseCellSpacing * scale;
out->totalWidth = numCols * (out->cellSize + out->cellSpacing) - out->cellSpacing;
out->totalHeight = rows * (out->cellSize + out->cellSpacing) - out->cellSpacing;
out->startX = (availW >= out->totalWidth
? (availW - out->totalWidth) * 0.5f
: 0.0f);
// Center grid vertically within available height (below offsetTop)
{
float availH = (float)screenH - offsetTop;
out->startY = offsetTop + MAX(0.0f, (availH - out->totalHeight) * 0.5f);
}
}
// Widget Selection Menu
static const WidgetType availableWidgetTypes[] = {WIDGET_JOYSTICK, WIDGET_DPAD, WIDGET_BUTTON};
static const char* availableWidgetNames[] = {"Joystick", "DPad", "Button"};
static const int numAvailableWidgetTypes = sizeof(availableWidgetTypes) / sizeof(availableWidgetTypes[0]);
// Analog Widget Remapping
static const char* availableAnalogActionNames[] = {"Up", "Down", "Left", "Right"};
static const int numAnalogActions = sizeof(availableAnalogActionNames) / sizeof(availableAnalogActionNames[0]);
// Widget Property Editing
static const PropertyAction availablePropertyActions[] = {PROP_ACTION_REMAP, PROP_ACTION_DELETE /*, ... */};
static const char* availablePropertyNames[] = {"Remap", "Delete" /*, ... */};
static const int numAvailablePropertyActions = sizeof(availablePropertyActions) / sizeof(availablePropertyActions[0]);
// Max Limits
#define MAX_WIDGETS 15
#define MAX_MT_SLOTS 10
#define MAX_INPUT_EVENTS 64
// UI Constants: Positioning and Sizes
static const float kEditButtonX = 10.0f, kEditButtonY = 10.0f;
static const float kEditButtonW = 80.0f, kEditButtonH = 40.0f;
static const float kAddButtonX = kEditButtonX + kEditButtonW + 10.0f;
static const float kAddButtonY = kEditButtonY;
static const float kAddButtonW = kEditButtonW;
static const float kAddButtonH = kEditButtonH;
static const float kPropsButtonX = kAddButtonX + kAddButtonW + 10.0f;
static const float kPropsButtonY = kAddButtonY;
static const float kPropsButtonW = kAddButtonW;
static const float kPropsButtonH = kAddButtonH;
static const float kHandleSize = 20.0f;
// Add constant for outline thickness
static const float kOutlineThickness = 2.0f;
// Widget Selection Menu Layout
static const float kMenuButtonW = 150.0f;
static const float kMenuButtonH = 50.0f;
static const float kMenuButtonSpacing = 10.0f;
// UI Colors
static const Color kMenuOverlayColor = {0, 0, 0, 150}; // Semi-transparent black
static const Color kColorIdle = {200, 200, 200, 255};
static const Color kColorActive = {100, 255, 100, 255};
static const Color kColorRed = {255, 100, 100, 255};
static const Color kColorEditMode = {100, 100, 255, 255};
static const Color kColorEditModeHandle = {100, 100, 255, 255};
static const Color kColorBlack = {0, 0, 0, 255};
static const Color kColorWhite = {255, 255, 255, 255};
static const Color kColorDisabled = {150, 150, 150, 255}; // Greyed out color for disabled buttons
// Master opacity for entire UI [0.0 .. 1.0]
static float gMasterOpacity = 0.5f;
// Helper macro to apply master opacity
#define SET_COLOR(c) glColor4ub((c).r, (c).g, (c).b, (GLubyte)((c).a * ((gAppState == APP_STATE_RUNNING) ? gMasterOpacity : 1.0f)))
// Input
static const float kTrackpadSensitivity = 1.0f;
// --- Global Variables ---
// Widget Management
static Widget gWidgets[MAX_WIDGETS];
static int gNumWidgets = 0;
static int FindWidgetIndexById(int widgetId);
static void enqueue_event(int widget_id, EventType type, int keycode);
// Application State
static ApplicationState gAppState = APP_STATE_RUNNING;
static EditState gEditState = {NULL, EDIT_NONE, {0,0}, {0,0}, 0.0f, 0.0f};
static int gSelectedWidgetId = 0; // ID of the widget selected for editing properties (0 = none)
static int gRemappingWidgetId = 0; // ID of the widget currently being remapped
static int gRemapAction = -1; // Which direction/action is being remapped for analog widgets
// UI Interaction State
static int gLastUIFinger = -1;
// Scaled UI Values (calculated at runtime)
static float gScaledKeyButtonSize = 60.0f;
static float gScaledKeyButtonSpacing = 10.0f;
// Cached layout for key selection grid (recomputed on resize)
static GridLayout gKeyGridLayout = {0};
// Wayland and EGL Globals
static struct wl_display *display = NULL;
static struct wl_registry *registry = NULL;
static struct wl_compositor *compositor = NULL;
static struct zwlr_layer_shell_v1 *layer_shell = NULL;
static struct wl_surface *surface = NULL;
static struct zwlr_layer_surface_v1 *layer_surface = NULL;
static struct wl_egl_window *egl_window = NULL;
static EGLDisplay egl_display = EGL_NO_DISPLAY;
static EGLContext egl_context = EGL_NO_CONTEXT;
static EGLSurface egl_surface = EGL_NO_SURFACE;
static EGLint egl_major, egl_minor;
static int width = 0, height = 0;
// Input Event Queue
static InputEvent gInputEvents[MAX_INPUT_EVENTS];
static int gInputEventCount = 0;
// Raw Touch Input (evdev)
static MTSlot mt_slots[MAX_MT_SLOTS] = {0};
static int gTouchDevFd = -1;
static int touch_min_x = 0, touch_max_x = 0;
static int touch_min_y = 0, touch_max_y = 0;
static int current_slot = 0; // Current slot being processed by evdev
static SlotMode slot_mode[MAX_MT_SLOTS] = {SLOT_IDLE};
static double track_last_x[MAX_MT_SLOTS];
static double track_last_y[MAX_MT_SLOTS];
static double track_accum_x[MAX_MT_SLOTS];
static double track_accum_y[MAX_MT_SLOTS];
static bool track_moved[MAX_MT_SLOTS];
static bool gLandscapeMode = false;
static bool gViewportChanged = true;
// Overlay toggle globals
static int gVolDevFd = -1;
static int gVolUpDevFd = -1;
static bool gOverlayActive = true;
// Volume-down long-press state
static bool gVolDown = false;
static struct timespec gVolTs;
#define LONG_PRESS_NS (250 * 1000000L)
static bool gVolToggled = false;
static bool gVolUpDown = false;
static struct timespec gVolUpTs;
// --- Forward Declarations ---
// Widget Specific Handlers (Generated by X-Macro)
#define X(name, func) \
void func##_draw (Widget *w); \
void func##_process(Widget *w);
WIDGET_TYPE_LIST
#undef X
// Input Handling
static void InputState_Update(void);
static void InputState_Flush(void);
static void init_touch_device(const char *device);
static void handle_evdev_event(const struct input_event *ev);
static const char* find_touchscreen_device(void);
// Helper to find a device with a given event type and code (e.g. volume-down key)
static int find_input_device(int ev_type, int ev_code) {
unsigned long bits[(KEY_MAX/(8*sizeof(long)))+1] = {0};
for (int i = 0; i < 32; i++) {
char path[32];
snprintf(path, sizeof(path), "/dev/input/event%d", i);
int fd = open(path, O_RDONLY | O_NONBLOCK);
if (fd < 0) continue;
if (ioctl(fd, EVIOCGBIT(ev_type, sizeof(bits)), bits) >= 0 &&
(bits[ev_code/(8*sizeof(long))] & (1UL << (ev_code%(8*sizeof(long))))) ) {
return fd;
}
close(fd);
}
return -1;
}
// Toggle overlay on/off by grabbing/ungrabbing the touch device
static void toggle_overlay(void) {
gOverlayActive = !gOverlayActive;
ioctl(gTouchDevFd, EVIOCGRAB, gOverlayActive);
// Reset all touch state to avoid stale slots blocking new input
for (int i = 0; i < MAX_MT_SLOTS; ++i) {
mt_slots[i].active = false;
mt_slots[i].was_down = false;
slot_mode[i] = SLOT_IDLE;
track_moved[i] = false;
}
gLastUIFinger = -1;
gEditState = (EditState){NULL, EDIT_NONE, {0,0}, {0,0}, 0.0f, 0.0f};
gSelectedWidgetId = 0;
gRemappingWidgetId = 0;
gRemapAction = -1;
if (!gOverlayActive) {
// Immediately clear screen when turned off
glClear(GL_COLOR_BUFFER_BIT);
eglSwapBuffers(egl_display, egl_surface);
// Release any pressed keys when overlay turned off
for (int i = 0; i < gNumMappableKeys; ++i) uinput_key(gMappableKeys[i].keycode, false);
}
}
// Drawing Primitives & Helpers
void DrawRect(float x, float y, float w, float h, Color col);
void DrawOutlinedRect(float x, float y, float w, float h, float thickness, Color col);
void DrawLine(float x1, float y1, float x2, float y2, float thickness, Color col);
void DrawCircle(float cx, float cy, float r, int segments, float thickness, Color col);
void DrawFilledCircle(float cx, float cy, float r, int segments, Color col);
void DrawTriangle(Vec2 a, Vec2 b, Vec2 c, Color col, float thickness);
void DrawTriangleFilled(Vec2 a, Vec2 b, Vec2 c, Color col);
void RenderText(const char *text, float x, float y, float pixelSize, Color col);
float CalculateFittingPixelSize(const char* text, float maxWidth, float maxHeight);
// UI Element Drawing
void DrawGenericButton(float x, float y, float w, float h, const char *label, Color bgColor, Color textColor);
void DrawMainButton(bool isActive);
void DrawAddButton(bool isActive, bool isDisabled);
void DrawPropertiesButton(bool isActive);
void DrawGenericMenu(int screenW, int screenH, const MenuItem items[], int numItems, float itemW, float itemH, float itemSpacing, Color overlayColor);
void DrawWidgetSelectionMenu(int screenW, int screenH);
void DrawWidgetPropertiesMenu(int screenW, int screenH);
void DrawKeySelectionMenu(int screenW, int screenH);
void DrawAnalogActionSelectionMenu(int screenW, int screenH);
void DrawAllWidgets(int screenW, int screenH, bool editMode);
void DrawUserInterface(bool editMode);
// Main Rendering
void RenderFrame(int w, int h, EGLDisplay display, EGLSurface surface);
// Wayland Callbacks
static void registry_handle_global(void *data, struct wl_registry *registry,
uint32_t name, const char *interface, uint32_t version);
static void layer_surface_handle_configure(void *data,
struct zwlr_layer_surface_v1 *layer_surface,
uint32_t serial,
uint32_t w, uint32_t h);
// --- Widget Dispatch Tables ---
// Initialized after widget handler forward declarations
static void (*widget_draw_tbl[WIDGET_MAX])(Widget*) = {
#define X(name, func) func##_draw,
WIDGET_TYPE_LIST
#undef X
};
static void (*widget_proc_tbl[WIDGET_MAX])(Widget*) = {
#define X(name, func) func##_process,
WIDGET_TYPE_LIST
#undef X
};
// --- Utility Functions ---
static float clampf(float v, float mn, float mx) {
return v < mn ? mn : (v > mx ? mx : v);
}
static float dist(Vec2 p1, Vec2 p2) {
float dx = p1.x - p2.x;
float dy = p1.y - p2.y;
return sqrtf(dx * dx + dy * dy);
}
// Helper to lookup label for a given keycode
static const char* GetMappableKeyLabel(int keycode) {
for (int i = 0; i < gNumMappableKeys; ++i) {
if (gMappableKeys[i].keycode == keycode) {
return gMappableKeys[i].label;
}
}
return "";
}
// Helper function to calculate text pixel size to fit within bounds
float CalculateFittingPixelSize(const char* text, float maxWidth, float maxHeight) {
if (!text || strlen(text) == 0) {
return 1.0f; // Default size if no text
}
// Initial estimate based on height (e.g., aim for 50% height)
float pixelSizeHeight = (maxHeight * 0.5f) / 8.0f; // Font is 8 pixels high
if (pixelSizeHeight <= 0) {
pixelSizeHeight = 1.0f; // Avoid zero/negative size
}
// Calculate width based on height estimate
int len = strlen(text);
float textWidth = len * 6.0f * pixelSizeHeight; // Font is 6 pixels wide
// If width exceeds bounds, recalculate based on width
float pixelSizeWidth = pixelSizeHeight;
float targetWidth = maxWidth * 0.9f; // Use 90% of width for padding
if (textWidth > targetWidth && len > 0) { // check len > 0 to avoid division by zero
pixelSizeWidth = targetWidth / (len * 6.0f);
}
if (pixelSizeWidth <= 0) {
pixelSizeWidth = 1.0f; // Avoid zero/negative size
}
// Return the smaller of the two calculated sizes
return MIN(pixelSizeHeight, pixelSizeWidth);
}
// --- Text Rendering ---
// Minimal 6x8 bitmap font for lowercase a–z, digits 0–9, and uppercase A-Z
static const uint8_t FONT6x8[62][6] = {
{0x20,0x54,0x54,0x54,0x78,0x00}, // a
{0x7F,0x28,0x44,0x44,0x38,0x00}, // b
{0x38,0x44,0x44,0x44,0x28,0x00}, // c
{0x38,0x44,0x44,0x28,0x7F,0x00}, // d
{0x38,0x54,0x54,0x54,0x18,0x00}, // e
{0x08,0x7E,0x09,0x01,0x02,0x00}, // f
{0x18,0xA4,0xA4,0xA8,0x7C,0x00}, // g
{0x7F,0x08,0x04,0x04,0x78,0x00}, // h
{0x00,0x44,0x7D,0x40,0x00,0x00}, // i
{0x00,0x40,0x80,0x84,0x7D,0x00}, // j
{0x00,0x7F,0x10,0x28,0x44,0x00}, // k
{0x00,0x41,0x7F,0x40,0x00,0x00}, // l
{0x7C,0x04,0x78,0x04,0x78,0x00}, // m
{0x7C,0x08,0x04,0x04,0x78,0x00}, // n
{0x38,0x44,0x44,0x44,0x38,0x00}, // o
{0xFC,0x28,0x44,0x44,0x38,0x00}, // p
{0x38,0x44,0x44,0x28,0xFC,0x00}, // q
{0x44,0x78,0x44,0x04,0x08,0x00}, // r
{0x48,0x54,0x54,0x54,0x24,0x00}, // s
{0x04,0x3F,0x44,0x40,0x20,0x00}, // t
{0x3C,0x40,0x40,0x20,0x7C,0x00}, // u
{0x1C,0x20,0x40,0x20,0x1C,0x00}, // v
{0x3C,0x40,0x30,0x40,0x3C,0x00}, // w
{0x44,0x28,0x10,0x28,0x44,0x00}, // x
{0x1C,0xA0,0xA0,0xA0,0x7C,0x00}, // y
{0x44,0x64,0x54,0x4C,0x44,0x00}, // z
{0x3E,0x51,0x49,0x45,0x3E,0x00}, // 0
{0x00,0x42,0x7F,0x40,0x00,0x00}, // 1
{0x62,0x51,0x49,0x49,0x46,0x00}, // 2
{0x22,0x49,0x49,0x49,0x36,0x00}, // 3
{0x18,0x14,0x52,0x7F,0x50,0x00}, // 4
{0x27,0x45,0x45,0x45,0x39,0x00}, // 5
{0x3C,0x4A,0x49,0x49,0x30,0x00}, // 6
{0x01,0x01,0x79,0x05,0x03,0x00}, // 7
{0x36,0x49,0x49,0x49,0x36,0x00}, // 8
{0x06,0x49,0x49,0x29,0x1E,0x00}, // 9
{0x7E,0x11,0x11,0x11,0x7E,0x00}, // A
{0x7F,0x49,0x49,0x49,0x36,0x00}, // B
{0x3E,0x41,0x41,0x41,0x22,0x00}, // C
{0x7F,0x41,0x41,0x22,0x1C,0x00}, // D
{0x7F,0x49,0x49,0x49,0x41,0x00}, // E
{0x7F,0x09,0x09,0x09,0x01,0x00}, // F
{0x3E,0x41,0x41,0x51,0x72,0x00}, // G
{0x7F,0x08,0x08,0x08,0x7F,0x00}, // H
{0x00,0x41,0x7F,0x41,0x00,0x00}, // I
{0x30,0x40,0x40,0x40,0x3F,0x00}, // J
{0x7F,0x08,0x14,0x22,0x41,0x00}, // K
{0x7F,0x40,0x40,0x40,0x40,0x00}, // L
{0x7F,0x06,0x18,0x06,0x7F,0x00}, // M
{0x7F,0x06,0x08,0x30,0x7F,0x00}, // N
{0x3E,0x41,0x41,0x41,0x3E,0x00}, // O
{0x7F,0x09,0x09,0x09,0x06,0x00}, // P
{0x3E,0x41,0x51,0x21,0x5E,0x00}, // Q
{0x7F,0x09,0x09,0x19,0x66,0x00}, // R
{0x26,0x49,0x49,0x49,0x32,0x00}, // S
{0x01,0x01,0x7F,0x01,0x01,0x00}, // T
{0x3F,0x40,0x40,0x40,0x3F,0x00}, // U
{0x07,0x18,0x60,0x18,0x07,0x00}, // V
{0x7F,0x20,0x18,0x20,0x7F,0x00}, // W
{0x63,0x14,0x08,0x14,0x63,0x00}, // X
{0x03,0x0C,0x78,0x0C,0x03,0x00}, // Y
{0x61,0x51,0x49,0x45,0x43,0x00}, // Z
};
// Map ASCII chars to FONT6x8 index: a–z → 0–25, 0–9 → 26–35, A-Z -> 36-61
static int map6x8(char c) {
if (c >= 'a' && c <= 'z') return c - 'a';
if (c >= '0' && c <= '9') return 26 + (c - '0');
if (c >= 'A' && c <= 'Z') return 36 + (c - 'A');
return -1; // Character not in font
}
// Lightweight bitmap blitter - Batched Immediate Mode
void RenderText(const char *text, float x, float y, float pixelSize, Color col) {
if (!text || *text == '\0') { // Early exit for empty or NULL string
return;
}
// Set color once for all pixels in this text string
SET_COLOR(col);
glBegin(GL_QUADS); // Start batching quads
float currentX = x;
for (; *text; ++text) {
int idx = map6x8(*text);
if (idx < 0) { // Handle characters not in font (e.g., space)
currentX += 6 * pixelSize; // Advance by character width
continue;
}
const uint8_t *glyph = FONT6x8[idx];
for (int cx = 0; cx < 6; ++cx) { // Character width
uint8_t bits = glyph[cx];
for (int ry = 0; ry < 8; ++ry) { // Character height
if (bits & (1 << ry)) {
// Directly emit vertices for this pixel's quad
float px = currentX + cx * pixelSize;
float py = y + ry * pixelSize;
glVertex2f(px, py);
glVertex2f(px + pixelSize, py);
glVertex2f(px + pixelSize, py + pixelSize);
glVertex2f(px, py + pixelSize);
}
}
}
currentX += 6 * pixelSize; // Advance to next character position
}
glEnd(); // End batching quads
}
// --- Drawing Primitives ---
void DrawRect(float x, float y, float w, float h, Color col) {
SET_COLOR(col);
glBegin(GL_QUADS);
glVertex2f(x, y);
glVertex2f(x + w, y);
glVertex2f(x + w, y + h);
glVertex2f(x, y + h);
glEnd();
}
void DrawOutlinedRect(float x, float y, float w, float h, float thickness, Color col) {
SET_COLOR(col);
glLineWidth(thickness);
glBegin(GL_LINE_LOOP);
glVertex2f(x, y);
glVertex2f(x + w, y);
glVertex2f(x + w, y + h);
glVertex2f(x, y + h);
glEnd();
}
void DrawLine(float x1, float y1, float x2, float y2, float thickness, Color col) {
SET_COLOR(col);
glLineWidth(thickness);
glBegin(GL_LINES);
glVertex2f(x1, y1);
glVertex2f(x2, y2);
glEnd();
}
void DrawCircle(float cx, float cy, float r, int segments, float thickness, Color col) {
SET_COLOR(col);
glLineWidth(thickness);
glBegin(GL_LINE_LOOP);
for (int i = 0; i < segments; i++) {
float a = (2.0f * M_PI * i) / segments;
glVertex2f(cx + cosf(a) * r, cy + sinf(a) * r);
}
glEnd();
}
void DrawFilledCircle(float cx, float cy, float r, int segments, Color col) {
SET_COLOR(col);
glBegin(GL_TRIANGLE_FAN);
glVertex2f(cx, cy); // Center point
for (int i = 0; i <= segments; i++) { // Loop to segments + 1 to close the circle
float a = (2.0f * M_PI * i) / segments;
glVertex2f(cx + cosf(a) * r, cy + sinf(a) * r);
}
glEnd();
}
void DrawTriangle(Vec2 a, Vec2 b, Vec2 c, Color col, float thickness) {
SET_COLOR(col);
glLineWidth(thickness);
glBegin(GL_LINE_LOOP);
glVertex2f(a.x, a.y);
glVertex2f(b.x, b.y);
glVertex2f(c.x, c.y);
glEnd();
}
void DrawTriangleFilled(Vec2 a, Vec2 b, Vec2 c, Color col) {
SET_COLOR(col);
glBegin(GL_TRIANGLES);
glVertex2f(a.x, a.y);
glVertex2f(b.x, b.y);
glVertex2f(c.x, c.y);
glEnd();
}
// --- UI Element Drawing Functions ---
static void DrawButton(float x, float y, float w, float h, Color col) { // Old static, keep for consistency if used
DrawOutlinedRect(x, y, w, h, kOutlineThickness, col);
}
void DrawGenericButton(float x, float y, float w, float h, const char *label, Color bgColor, Color textColor) {
DrawButton(x, y, w, h, bgColor); // Draw outlined background
if (label && strlen(label) > 0) {
float pixelSize = CalculateFittingPixelSize(label, w, h);
float textWidth = strlen(label) * 6.0f * pixelSize;
float textHeight = 8.0f * pixelSize;
float textX = x + (w - textWidth) * 0.5f;
float textY = y + (h - textHeight) * 0.5f;
RenderText(label, textX, textY, pixelSize, textColor);
}
}
void DrawMainButton(bool isActive) {
if (!isActive) {
// Transparent in running state: skip drawing
return;
}
Color btnCol = isActive ? kColorActive : kColorIdle;
DrawGenericButton(kEditButtonX, kEditButtonY, kEditButtonW, kEditButtonH, NULL, btnCol, kColorWhite);
}
void DrawAddButton(bool isActive, bool isDisabled) {
Color btnCol;
if (isDisabled) {
btnCol = kColorDisabled;
} else if (isActive) {
btnCol = kColorActive;
} else {
btnCol = kColorIdle;
}
DrawGenericButton(kAddButtonX, kAddButtonY, kAddButtonW, kAddButtonH, "Add", btnCol, kColorWhite);
}
void DrawPropertiesButton(bool isActive) {
Color btnCol = isActive ? kColorActive : kColorIdle;
DrawGenericButton(kPropsButtonX, kPropsButtonY, kPropsButtonW, kPropsButtonH, "Edit", btnCol, kColorWhite);
}
void DrawGenericMenu(int screenW, int screenH, const MenuItem items[], int numItems, float itemW, float itemH, float itemSpacing, Color overlayColor) {
DrawRect(0, 0, (float)screenW, (float)screenH, overlayColor);
float totalMenuHeight = (itemH + itemSpacing) * numItems - itemSpacing;
float startY = ((float)screenH - totalMenuHeight) * 0.5f;
float startX = ((float)screenW - itemW) * 0.5f;
for (int i = 0; i < numItems; ++i) {
float itemY = startY + i * (itemH + itemSpacing);
DrawGenericButton(startX, itemY, itemW, itemH, items[i].label, items[i].bgColor, kColorWhite);
}
}
void DrawWidgetSelectionMenu(int screenW, int screenH) {
MenuItem menuItems[numAvailableWidgetTypes];
for (int i = 0; i < numAvailableWidgetTypes; ++i) {
menuItems[i] = (MenuItem){availableWidgetNames[i], kColorIdle};
}
DrawGenericMenu(screenW, screenH, menuItems, numAvailableWidgetTypes,
kMenuButtonW, kMenuButtonH, kMenuButtonSpacing, kMenuOverlayColor);
}
void DrawWidgetPropertiesMenu(int screenW, int screenH) {
MenuItem menuItems[numAvailablePropertyActions];
for (int i = 0; i < numAvailablePropertyActions; ++i) {
Color btnColor = (availablePropertyActions[i] == PROP_ACTION_DELETE) ? kColorRed : kColorIdle;
menuItems[i] = (MenuItem){availablePropertyNames[i], btnColor};
}
DrawGenericMenu(screenW, screenH, menuItems, numAvailablePropertyActions,
kMenuButtonW, kMenuButtonH, kMenuButtonSpacing, kMenuOverlayColor);
}
void DrawKeySelectionMenu(int screenW, int screenH) {
DrawRect(0, 0, (float)screenW, (float)screenH, kMenuOverlayColor);
int widgetIndex = FindWidgetIndexById(gRemappingWidgetId);
Widget *targetWidget = (widgetIndex != -1) ? &gWidgets[widgetIndex] : NULL;
bool isAnalog = (targetWidget && (targetWidget->type == WIDGET_JOYSTICK || targetWidget->type == WIDGET_DPAD));
int currentKeycode = -1;
if (targetWidget) {
if (isAnalog && gRemapAction >= 0 && gRemapAction < numAnalogActions) {
currentKeycode = targetWidget->data.analog.keycode[gRemapAction];
} else if (targetWidget->type == WIDGET_BUTTON) {
currentKeycode = targetWidget->data.button.keycode;
}
}
// Prepare title text
char titleBuffer[128];
if (!isAnalog) {
snprintf(titleBuffer, sizeof(titleBuffer), "Select Key for Button %d", gRemappingWidgetId);
} else {
const char *wname = (targetWidget->type == WIDGET_JOYSTICK ? "Joystick" : "DPad");
if (gRemapAction >= 0 && gRemapAction < numAnalogActions) {
snprintf(titleBuffer, sizeof(titleBuffer), "Select Key for %s '%s'", wname, availableAnalogActionNames[gRemapAction]);
} else {
snprintf(titleBuffer, sizeof(titleBuffer), "Select Key for %s", wname);
}
}
// Title and grid group vertical centering
float titlePixelSize = 2.0f;
float titleTextRenderHeight = 8.0f * titlePixelSize;
float paddingBelowTitle = 10.0f;
// Compute overall group height (title + padding + grid)
float groupHeight = titleTextRenderHeight + paddingBelowTitle + gKeyGridLayout.totalHeight;
float groupStartY = ((float)screenH - groupHeight) * 0.5f;
float titleWidth = strlen(titleBuffer) * 6.0f * titlePixelSize;
float titleX = ((float)screenW - titleWidth) * 0.5f;
float titleY = groupStartY;
RenderText(titleBuffer, titleX, titleY, titlePixelSize, kColorWhite);
// Position grid below title
gKeyGridLayout.startY = titleY + titleTextRenderHeight + paddingBelowTitle;
// Draw key buttons using cached grid layout (horizontally centered, startY updated)
for (int i = 0; i < gNumMappableKeys; ++i) {
int row = i / gKeyGridLayout.cols;
int col = i % gKeyGridLayout.cols;
float buttonX = gKeyGridLayout.startX + col * (gKeyGridLayout.cellSize + gKeyGridLayout.cellSpacing);
float buttonY = gKeyGridLayout.startY + row * (gKeyGridLayout.cellSize + gKeyGridLayout.cellSpacing);
Color btnColor = (gMappableKeys[i].keycode == currentKeycode) ? kColorActive : kColorIdle;
DrawGenericButton(buttonX, buttonY, gKeyGridLayout.cellSize, gKeyGridLayout.cellSize,
gMappableKeys[i].label, btnColor, kColorWhite);
}
}
void DrawAnalogActionSelectionMenu(int screenW, int screenH) {
DrawRect(0, 0, (float)screenW, (float)screenH, kMenuOverlayColor);
MenuItem items[numAnalogActions];
for (int i = 0; i < numAnalogActions; ++i) {
items[i] = (MenuItem){availableAnalogActionNames[i], kColorIdle};
}
DrawGenericMenu(screenW, screenH, items, numAnalogActions,
kMenuButtonW, kMenuButtonH, kMenuButtonSpacing, kMenuOverlayColor);
}
// --- Widget Structure and Core Logic ---
void Widget_UpdateAbsCoords(Widget* w, int screenW, int screenH) {
w->absCenter.x = w->normCenter.x * screenW;
w->absCenter.y = w->normCenter.y * screenH;
float minDim = (float)MIN(screenW, screenH);
w->absRadius = w->normHalfSize * minDim;
w->absSize = w->absRadius * 2.0f;
w->absTopLeft.x = w->absCenter.x - w->absRadius;
w->absTopLeft.y = w->absCenter.y - w->absRadius;
}
bool Widget_IsInside(const Widget* w, Vec2 p) {
return p.x >= w->absTopLeft.x && p.x <= w->absTopLeft.x + w->absSize &&
p.y >= w->absTopLeft.y && p.y <= w->absTopLeft.y + w->absSize;
}
void Widget_ClampToScreen(Widget* w, int screenW, int screenH) {
w->normCenter.x = clampf(w->normCenter.x, 0.0f, 1.0f);
w->normCenter.y = clampf(w->normCenter.y, 0.0f, 1.0f);
Widget_UpdateAbsCoords(w, screenW, screenH); // Re-calculate absolute after clamping normalized
}
void CreateWidget(WidgetType type, Vec2 normCenter, float normHalfSize) {
if (gNumWidgets >= MAX_WIDGETS) {