863 lines
37 KiB
C
863 lines
37 KiB
C
// stdlib
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <stdbool.h>
|
|
#include <unistd.h>
|
|
// wayland
|
|
#include <wayland-client.h>
|
|
#include <wayland-egl.h>
|
|
// OGL
|
|
#include <EGL/egl.h>
|
|
#include <GL/gl.h>
|
|
// X11
|
|
#include <xkbcommon/xkbcommon.h>
|
|
// linux
|
|
#include <sys/mman.h>
|
|
#include <linux/input-event-codes.h>
|
|
#include <fcntl.h>
|
|
#include <errno.h>
|
|
#include <dlfcn.h>
|
|
// libevdev
|
|
#include <libevdev-1.0/libevdev/libevdev.h>
|
|
// generated header using wayland-scanner
|
|
#include "xdg-shell-client-protocol.h"
|
|
#include "zwp-text-input-unstable-v3-protocol.h"
|
|
#include "xdg-decoration-unstable-v1.h"
|
|
// common headers for platform layer and application layer
|
|
#include "input.h"
|
|
#include "arena.h"
|
|
#include "debug.h"
|
|
#include "game.h"
|
|
|
|
struct wl_compositor *compositor = NULL;
|
|
struct wl_registry *registry = NULL;
|
|
struct wl_surface *surface = NULL;
|
|
struct xdg_wm_base *wm_base = NULL;
|
|
struct xdg_surface *xdg_surface = NULL;
|
|
struct xdg_toplevel *xdg_toplevel = NULL;
|
|
struct wl_egl_window *window = NULL;
|
|
|
|
/* Input API */
|
|
struct wl_seat *wl_seat = NULL;
|
|
struct wl_keyboard *wl_keyboard = NULL;
|
|
struct wl_pointer *wl_pointer = NULL;
|
|
|
|
/* ZWP text input extension */
|
|
struct zwp_text_input_v3 *zwp_text_input_v3 = NULL;
|
|
struct zwp_text_input_manager_v3 *zwp_text_input_manager_v3 = NULL;
|
|
|
|
/* XDG decoration manager extension */
|
|
struct zxdg_decoration_manager_v1* zxdg_decoration_manager_v1 = NULL;
|
|
|
|
|
|
/* XKB library */
|
|
struct xkb_context *xkb_context = NULL;
|
|
struct xkb_state *xkb_state = NULL;
|
|
struct xkb_keymap *xkb_keymap = NULL;
|
|
|
|
/* libevdev */
|
|
struct libevdev *libevdev = NULL;
|
|
|
|
/* Misc. global state */
|
|
game_state_t game_state = {};
|
|
EGLBoolean errcode = 0;
|
|
bool running = true;
|
|
input_t user_input = {};
|
|
arena_t main_arena = {};
|
|
console_verbosity_t console_verbosity = { MODULE_ALL };
|
|
error_level_t error_verbosity = { ERROR_LEVEL_DEBUG };
|
|
|
|
typedef void (*simulate_frame_fn)(game_state_t*);
|
|
|
|
/* Game layer */
|
|
typedef struct {
|
|
simulate_frame_fn simulate_frame;
|
|
} game_api_t;
|
|
|
|
game_api_t game_api = {};
|
|
|
|
char *egl_s(int code)
|
|
{
|
|
switch (code)
|
|
{
|
|
case (intptr_t)EGL_NO_SURFACE: return "EGL_NO_SURFACE";
|
|
case EGL_BAD_CONTEXT: return "EGL_BAD_CONTEXT";
|
|
case EGL_BAD_SURFACE: return "EGL_BAD_SURFACE";
|
|
case EGL_BAD_MATCH: return "EGL_BAD_MATCH";
|
|
case EGL_BAD_NATIVE_WINDOW: return "EGL_BAD_NATIVE_WINDOW";
|
|
case EGL_BAD_CURRENT_SURFACE: return "EGL_BAD_CURRENT_SURFACE";
|
|
case EGL_BAD_ALLOC: return "EGL_BAD_ALLOC";
|
|
case EGL_CONTEXT_LOST: return "EGL_CONTEXT_LOST";
|
|
case EGL_NOT_INITIALIZED: return "EGL_NOT_INITIALIZED";
|
|
case EGL_BAD_DISPLAY: return "EGL_BAD_DISPLAY";
|
|
case EGL_BAD_CONFIG: return "EGL_BAD_CONFIG";
|
|
case EGL_BAD_ATTRIBUTE: return "EGL_BAD_ATTRIBUTE";
|
|
default: return "UNKNOWN";
|
|
}
|
|
}
|
|
|
|
void registry_handle_global( void *data, struct wl_registry *registry,
|
|
uint32_t name, const char *interface, uint32_t version)
|
|
{
|
|
console_log_ss(MODULE_WAYLAND, "Wayland interface: '%s', version: %d, name: %d\n", interface, version, name);
|
|
if (!strcmp(interface, wl_compositor_interface.name))
|
|
{
|
|
console_log_ss(MODULE_WAYLAND, "Registering compositor...\n");
|
|
compositor = wl_registry_bind(registry, name, &wl_compositor_interface, 1);
|
|
}
|
|
if (!strcmp(interface, xdg_wm_base_interface.name))
|
|
{
|
|
console_log_ss(MODULE_WAYLAND, "Registering XDG WM base interface...\n");
|
|
wm_base = wl_registry_bind(registry, name, &xdg_wm_base_interface, 1);
|
|
}
|
|
/* NOTE: Since wl_seat v5, pointer events are grouped into "frames", which do not appear to be useful for non-touch
|
|
applications and also create more events which are more complicated to dispatch. */
|
|
if (!strcmp(interface, wl_seat_interface.name))
|
|
{
|
|
console_log_ss(MODULE_WAYLAND, "Registering WL seat interface...\n");
|
|
wl_seat = wl_registry_bind(registry, name, &wl_seat_interface, 8);
|
|
}
|
|
|
|
if (!strcmp(interface, zwp_text_input_manager_v3_interface.name))
|
|
{
|
|
console_log_ss(MODULE_WAYLAND, "Registering WL input manager v3 interface...\n");
|
|
zwp_text_input_manager_v3 = wl_registry_bind(registry, name, &zwp_text_input_manager_v3_interface, 1);
|
|
}
|
|
|
|
if (!strcmp(interface, zxdg_decoration_manager_v1_interface.name))
|
|
{
|
|
console_log_ss(MODULE_WAYLAND, "Registering WL decoration manager v1 interface...\n");
|
|
zxdg_decoration_manager_v1 = wl_registry_bind(registry, name, &zxdg_decoration_manager_v1_interface, 1);
|
|
}
|
|
}
|
|
|
|
void zxdg_toplevel_decoration_configure_callback(void *data, struct zxdg_toplevel_decoration_v1 *zxdg_toplevel_decoration_v1, uint32_t mode)
|
|
{
|
|
// TODO: We are supposed to call xdg_surface_ack_configure here
|
|
console_log_ss(MODULE_WAYLAND, "Setting XDG toplevel to use %s decorations\n",
|
|
(ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE == mode ? "server" : "client"));
|
|
}
|
|
|
|
void xdg_surface_configure_callback(void *data, struct xdg_surface *xdg_surface, uint32_t serial)
|
|
{
|
|
// confirm that you exist to the compositor
|
|
console_log_ss(MODULE_WAYLAND, "ACKing XDG configuration with serial %d\n", serial);
|
|
xdg_surface_ack_configure(xdg_surface, serial);
|
|
}
|
|
|
|
void zwp_text_input_v3_enter_callback(void *data, struct zwp_text_input_v3 *text_input, struct wl_surface *surface)
|
|
{
|
|
console_debug_ss(MODULE_KEYBOARD, "ZWP text input v3 enter callback\n");
|
|
zwp_text_input_v3 = text_input;
|
|
zwp_text_input_v3_enable(zwp_text_input_v3);
|
|
zwp_text_input_v3_commit(zwp_text_input_v3);
|
|
}
|
|
|
|
void zwp_text_input_v3_leave_callback(void *data, struct zwp_text_input_v3 *text_input, struct wl_surface *surface)
|
|
{
|
|
console_debug_ss(MODULE_KEYBOARD, "ZWP text input v3 leave callback\n");
|
|
zwp_text_input_v3_disable(zwp_text_input_v3);
|
|
zwp_text_input_v3_commit(zwp_text_input_v3);
|
|
}
|
|
|
|
void zwp_text_input_v3_preedit_string_callback(void *data, struct zwp_text_input_v3 *text_input, const char *text, int32_t cursor_begin, int32_t cursor_end)
|
|
{
|
|
console_debug_ss(MODULE_KEYBOARD, "ZWP text input v3 preedit string callback with text %s\n", text);
|
|
user_input.keyboard_state &= KEYBOARD_STATE_IME;
|
|
}
|
|
|
|
void zwp_text_input_v3_commit_string_callback(void *data, struct zwp_text_input_v3 *text_input, const char *text)
|
|
{
|
|
console_debug_ss(MODULE_KEYBOARD, "ZWP text input v3 commit string callback with text %s\n", text);
|
|
user_input.keyboard_state &= ~KEYBOARD_STATE_IME;
|
|
|
|
const char *s = text;
|
|
int i = 0;
|
|
for (; i < IME_STR_MAX - 1 && s; ++i)
|
|
user_input.ime_str[i] = *s++;
|
|
|
|
user_input.ime_str[i] = '\0';
|
|
console_debug_ss(MODULE_KEYBOARD, "Copied string %s\n", text);
|
|
}
|
|
|
|
void zwp_text_input_v3_delete_surrounding_text_callback(void *data, struct zwp_text_input_v3 *text_input, uint32_t before_length, uint32_t after_length)
|
|
{
|
|
console_debug_ss(MODULE_KEYBOARD, "ZWP text input v3 delete surrounding text callback\n");
|
|
}
|
|
|
|
void zwp_text_input_v3_done_callback(void *data, struct zwp_text_input_v3 *text_input, uint32_t surface)
|
|
{
|
|
console_debug_ss(MODULE_KEYBOARD, "ZWP text input v3 done callback\n");
|
|
}
|
|
|
|
void registry_handle_global_remove(void *data, struct wl_registry *registry,
|
|
uint32_t name)
|
|
{
|
|
// This space deliberately left blank
|
|
}
|
|
|
|
static const struct wl_registry_listener
|
|
registry_listener = {
|
|
.global = registry_handle_global,
|
|
.global_remove = registry_handle_global_remove,
|
|
};
|
|
|
|
/* Window size change callbacks */
|
|
void xdg_toplevel_configure(void *data, struct xdg_toplevel *t, int32_t width, int32_t height, struct wl_array *states)
|
|
{
|
|
if (!width || !height)
|
|
return; // compositor is deferring to us
|
|
|
|
console_debug_ss(MODULE_WAYLAND, "Trying to resize window to %d W %d H\n", width, height);
|
|
wl_egl_window_resize(window, width, height, 0, 0);
|
|
glViewport(0, 0, width, height);
|
|
}
|
|
|
|
/* Window close callback */
|
|
void xdg_toplevel_close(void *data, struct xdg_toplevel *xdg_toplevel)
|
|
{
|
|
running = false;
|
|
}
|
|
|
|
/* Basic wayland method to check if our program is responsive still */
|
|
void wayland_ping(void *data, struct xdg_wm_base *base, uint32_t serial)
|
|
{
|
|
console_debug_ss(MODULE_WAYLAND, "Responding to WL ping\n");
|
|
xdg_wm_base_pong(base, serial);
|
|
}
|
|
|
|
void wl_seat_capabilities_callback(void *data, struct wl_seat *wl_seat, uint capabilities)
|
|
{
|
|
console_debug_ss(MODULE_WAYLAND, "Capabilities changed for WL seat\n");
|
|
}
|
|
|
|
void keymap_callback(void *data, struct wl_keyboard *kb, uint format, int fd, uint size)
|
|
{
|
|
// TODO: Check for XKB compatibility and use xkbcommon to extract
|
|
console_debug_ss(MODULE_KEYBOARD, "KBMAP of size %d detected\n", size);
|
|
|
|
char *mem = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0);
|
|
if (mem == MAP_FAILED)
|
|
console_err_ss(MODULE_KEYBOARD, "Could not mmap keyboard: %s\n", strerror(errno));
|
|
console_debug_ss(MODULE_KEYBOARD, "XKBMAP desc:\n\t%s\n", mem);
|
|
xkb_keymap = xkb_keymap_new_from_string(xkb_context, mem, XKB_KEYMAP_FORMAT_TEXT_V1, XKB_KEYMAP_COMPILE_NO_FLAGS);
|
|
munmap(mem, size);
|
|
close(fd);
|
|
|
|
if (!(xkb_state = xkb_state_new(xkb_keymap)))
|
|
console_debug_ss(MODULE_KEYBOARD, "Could not initialize XKB state machine\n");
|
|
}
|
|
|
|
void enter_callback(void *data, struct wl_keyboard* kb, uint serial, struct wl_surface* surface, struct wl_array *arr)
|
|
{
|
|
console_debug_ss(MODULE_KEYBOARD, "KBMAP with serial %d entered surface\n", serial);
|
|
user_input.keyboard_state = KEYBOARD_STATE_FOCUSED;
|
|
|
|
// NOTE: wl_array is a dynamic array as specified in wayland-util.h
|
|
int32_t *pos = arr->data;
|
|
// wl_keyboard is giving us all the currenly pressed keys anyways
|
|
for (int i = 0; i < KEYBOARD_KEY_TOTAL; ++i)
|
|
user_input.keys[i] = KEY_STATE_UNPRESSED;
|
|
|
|
wl_array_for_each(pos, arr) {
|
|
// TODO: For some reason, we can get here with a perfectly correct keymap but with a single bad key that libxkbcommon resolves as 65293
|
|
xkb_keysym_t sym = xkb_state_key_get_one_sym(xkb_state, (*pos) + 8);
|
|
// TODO: Decide if we should also set KEY_STATE_HELD
|
|
if (sym < KEYBOARD_KEY_TOTAL)
|
|
user_input.keys[sym] = KEY_STATE_PRESSED;
|
|
}
|
|
}
|
|
|
|
void leave_callback(void *data, struct wl_keyboard* kb, uint serial, struct wl_surface* surface)
|
|
{
|
|
console_debug_ss(MODULE_KEYBOARD, "KBMAP with serial %d left surface\n", serial);
|
|
user_input.keyboard_state = KEYBOARD_STATE_UNFOCUSED;
|
|
}
|
|
|
|
void key_callback(void *data, struct wl_keyboard *kb, uint serial, uint time, uint key, uint state)
|
|
{
|
|
/* NOTE: libxkbcommon provides the correct glyphs if:
|
|
1. The user is using the system layout without an IME, or
|
|
2. The user is using an IME, but the current key is the same for both languages.
|
|
|
|
If none of them are true, wayback will either use text-input-v3's callbacks instead, or
|
|
call both functions.
|
|
|
|
For example, if the user is using spanish IME on US keymap and inputs the letter A, key_callback
|
|
will be called normally. However trying to write ñ or à won't call this function at all.
|
|
*/
|
|
key += 8;
|
|
uint32_t layout = xkb_state_key_get_layout(xkb_state, key);
|
|
if (layout >= xkb_keymap_num_layouts_for_key(xkb_keymap, key))
|
|
console_debug_ss(MODULE_KEYBOARD, "Layout %d exceeds number of available key layouts\n", layout);
|
|
|
|
xkb_keysym_t sym = xkb_state_key_get_one_sym(xkb_state, key);
|
|
char keyname[128];
|
|
xkb_keysym_get_name(sym, keyname, sizeof(keyname));
|
|
char keyutf8[128];
|
|
xkb_state_key_get_utf8(xkb_state, key, keyutf8, sizeof(keyutf8));
|
|
console_debug_ss(MODULE_KEYBOARD, "%s key %d layout %d (KEYSYM %s) (UTF8 %s)\n", (state == WL_KEYBOARD_KEY_STATE_PRESSED ? "Pressed" : "Released"), key, layout, keyname, keyutf8);
|
|
// TODO: Test this
|
|
user_input.keys[key] = (state == WL_KEYBOARD_KEY_STATE_PRESSED ? KEY_STATE_PRESSED : KEY_STATE_RELEASED);
|
|
}
|
|
|
|
void modifier_callback(void *data, struct wl_keyboard *kb, uint serial, uint mods_depressed, uint mods_latched, uint mods_locked, uint group)
|
|
{
|
|
console_debug_ss(MODULE_KEYBOARD, "Keyboard modifier keys changed\n");
|
|
xkb_state_update_mask(xkb_state, mods_depressed, mods_latched, mods_locked, 0, 0, group);
|
|
user_input.modifiers = (mods_depressed | mods_latched | mods_locked);
|
|
}
|
|
|
|
void repeat_info_callback(void *data, struct wl_keyboard *kb, int rate, int delay)
|
|
{
|
|
console_debug_ss(MODULE_KEYBOARD, "Keyboard repeat/delay rate changed\n");
|
|
}
|
|
|
|
void pointer_enter_callback(void *data, struct wl_pointer *pointer, uint serial, struct wl_surface *surface, wl_fixed_t x, wl_fixed_t y)
|
|
{
|
|
console_debug_ss(MODULE_MOUSE, "Pointer entered WL surface on coordinates %d %d\n", x, y);
|
|
user_input.mouse_state = MOUSE_STATE_FOCUSED;
|
|
}
|
|
|
|
void pointer_leave_callback(void *data, struct wl_pointer *pointer, uint serial, struct wl_surface *surface)
|
|
{
|
|
console_debug_ss(MODULE_MOUSE, "Pointer left WL surface\n");
|
|
user_input.mouse_state = MOUSE_STATE_UNFOCUSED;
|
|
}
|
|
|
|
void pointer_motion_callback(void *data, struct wl_pointer *pointer, uint time, wl_fixed_t x, wl_fixed_t y)
|
|
{
|
|
console_debug_ss(MODULE_MOUSE, "Pointer moved to %d %d\n", x, y);
|
|
user_input.mouse_x = x;
|
|
user_input.mouse_y = y;
|
|
}
|
|
|
|
void pointer_button_callback(void *data, struct wl_pointer *pointer, uint serial, uint time, uint button, uint state)
|
|
{
|
|
console_debug_ss(MODULE_MOUSE, "Pointer button %d changed state\n", button);
|
|
mouse_keysym_t idx = 0;
|
|
switch (button)
|
|
{
|
|
case BTN_LEFT: idx = MOUSE_KEY_LEFT; break;
|
|
case BTN_RIGHT: idx = MOUSE_KEY_RIGHT; break;
|
|
case BTN_MIDDLE: idx = MOUSE_KEY_MIDDLE; break;
|
|
case BTN_SIDE: idx = MOUSE_KEY_SIDE; break;
|
|
case BTN_EXTRA: idx = MOUSE_KEY_EXTRA; break;
|
|
case BTN_FORWARD: idx = MOUSE_KEY_FORWARD; break;
|
|
case BTN_TASK: idx = MOUSE_KEY_TASK; break;
|
|
case BTN_0: idx = MOUSE_KEY_EXTRA_0; break;
|
|
case BTN_1: idx = MOUSE_KEY_EXTRA_1; break;
|
|
case BTN_2: idx = MOUSE_KEY_EXTRA_2; break;
|
|
case BTN_3: idx = MOUSE_KEY_EXTRA_3; break;
|
|
case BTN_4: idx = MOUSE_KEY_EXTRA_4; break;
|
|
case BTN_5: idx = MOUSE_KEY_EXTRA_5; break;
|
|
case BTN_6: idx = MOUSE_KEY_EXTRA_6; break;
|
|
case BTN_7: idx = MOUSE_KEY_EXTRA_7; break;
|
|
case BTN_8: idx = MOUSE_KEY_EXTRA_8; break;
|
|
case BTN_9: idx = MOUSE_KEY_EXTRA_9; break;
|
|
default:
|
|
fprintf(stderr, "Unknown wayland button identifier: 0x%x\n", button);
|
|
}
|
|
user_input.mouse_keys[idx] = (state == WL_POINTER_BUTTON_STATE_PRESSED) ? KEY_STATE_PRESSED : KEY_STATE_RELEASED;
|
|
}
|
|
|
|
void pointer_axis_callback(void *data, struct wl_pointer *pointer, uint time, uint axis, wl_fixed_t value)
|
|
{
|
|
console_debug_ss(MODULE_MOUSE, "Pointer scroll on axis %d value %d\n", axis, value);
|
|
}
|
|
|
|
/* NOTE: *some* events are logically grouped together. The frame event marks the boundary between groups of them. More info: https://wayland.freedesktop.org/docs/html/apa.html#protocol-spec-wl_pointer */
|
|
|
|
void pointer_frame_callback(void *data, struct wl_pointer *frame)
|
|
{
|
|
console_debug_ss(MODULE_MOUSE, "Pointer frame ended\n");
|
|
}
|
|
|
|
/* NOTE: This event is also used to group pointer events together. It specifies info about all events in its frame */
|
|
void pointer_axis_source_callback(void *data, struct wl_pointer *pointer, uint axis_source)
|
|
{
|
|
console_debug_ss(MODULE_MOUSE, "Pointer axis source: %d\n", axis_source);
|
|
}
|
|
|
|
/* Optional event to implement kinetic scrolling */
|
|
void pointer_axis_stop_callback(void *data, struct wl_pointer *pointer, uint time, uint axis)
|
|
{
|
|
console_debug_ss(MODULE_MOUSE, "Pointer axis stopped: %d\n", axis);
|
|
}
|
|
|
|
/* Optional event. Carries discrete scroll info after an 'axis' event */
|
|
void pointer_axis_discrete_callback(void *data, struct wl_pointer *pointer, uint axis, int discrete)
|
|
{
|
|
console_debug_ss(MODULE_MOUSE, "Pointer axis discrete step: %d\n", discrete);
|
|
}
|
|
|
|
void pointer_axis_value120_callback(void *data, struct wl_pointer *pointer, uint axis, int value120)
|
|
{
|
|
console_debug_ss(MODULE_MOUSE, "Pointer axis value120: %d\n", value120);
|
|
}
|
|
|
|
void pointer_axis_relative_direction_callback(void *data, struct wl_pointer *pointer, uint axis, uint direction)
|
|
{
|
|
console_debug_ss(MODULE_MOUSE, "Pointer axis direction: %d\n", direction);
|
|
}
|
|
|
|
int main()
|
|
{
|
|
console_log("Starting platform layer...\n");
|
|
|
|
libevdev = libevdev_new();
|
|
if (!libevdev)
|
|
console_err_ss(MODULE_CONTROLLER, "Could not initialize libevdev...\n");
|
|
|
|
console_log_ss(MODULE_CONTROLLER, "Querying input devices...\n");
|
|
int32_t i = 0;
|
|
int32_t controller_idx = 0;
|
|
do
|
|
{
|
|
char path[17+33] = "/dev/input/event";
|
|
char idx[33] = {};
|
|
// I'd like to use itoa, but it's not in stdlib.h ??
|
|
sprintf(idx, "%d", i);
|
|
strcat(path, idx);
|
|
console_debug_ss(MODULE_CONTROLLER, "Opening %s\n", path);
|
|
int fd = open(path, O_RDONLY | O_NONBLOCK);
|
|
|
|
if (fd == -1 && errno == EACCES) continue;
|
|
|
|
if (libevdev_new_from_fd(fd, &libevdev))
|
|
{
|
|
console_warn_ss(MODULE_CONTROLLER, "Could not open %s !\n", path);
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
console_debug_ss(MODULE_CONTROLLER, "\tName: %s\n", libevdev_get_name(libevdev));
|
|
console_debug_ss(MODULE_CONTROLLER, "\tPhysical location: %s\n", libevdev_get_phys(libevdev));
|
|
console_debug_ss(MODULE_CONTROLLER, "\tUID: %s\n", libevdev_get_uniq(libevdev));
|
|
console_debug_ss(MODULE_CONTROLLER, "\tVendor: %d\n", libevdev_get_id_vendor(libevdev));
|
|
console_debug_ss(MODULE_CONTROLLER, "\tProduct: %d\n", libevdev_get_id_product(libevdev));
|
|
console_debug_ss(MODULE_CONTROLLER, "\tBus type: %d\n", libevdev_get_id_bustype(libevdev));
|
|
console_debug_ss(MODULE_CONTROLLER, "\tFirmware version: %d\n", libevdev_get_id_version(libevdev));
|
|
console_debug_ss(MODULE_CONTROLLER, "\tDriver version: %d\n", libevdev_get_driver_version(libevdev));
|
|
console_debug_ss(MODULE_CONTROLLER, "\tSupported event types:\n\t\t");
|
|
for (int j = 0; j < EV_MAX; ++j)
|
|
if (libevdev_has_event_type(libevdev, j))
|
|
console_debug_ss(MODULE_CONTROLLER, "%s ", libevdev_event_type_get_name(j));
|
|
console_debug_ss(MODULE_CONTROLLER, "\n");
|
|
if (libevdev_has_event_type(libevdev, EV_KEY && libevdev_has_event_code(libevdev, EV_KEY, BTN_NORTH)) &&
|
|
libevdev_has_event_type(libevdev, EV_ABS))
|
|
{
|
|
console_debug_ss(MODULE_CONTROLLER, "\tThis device looks like a controller!\n");
|
|
controller_idx = i;
|
|
}
|
|
libevdev_free(libevdev);
|
|
close(fd);
|
|
}
|
|
}
|
|
while (++i);
|
|
|
|
char controller_path[17+33] = "/dev/input/event";
|
|
int controller_fd;
|
|
sprintf(controller_path, "/dev/input/event%d", controller_idx);
|
|
controller_fd = open(controller_path, O_RDONLY | O_NONBLOCK);
|
|
libevdev_new_from_fd(controller_fd, &libevdev);
|
|
|
|
main_arena = arena_create(1*GB);
|
|
if (!main_arena.begin)
|
|
console_err("Could not reserve memory for main arena!\n");
|
|
|
|
struct wl_display *display = wl_display_connect(NULL);
|
|
if (!display)
|
|
{
|
|
console_err_ss(MODULE_WAYLAND, "Cannot retrieve wayland display!\n");
|
|
}
|
|
|
|
registry = wl_display_get_registry(display);
|
|
if (!registry)
|
|
{
|
|
console_err_ss(MODULE_WAYLAND, "Cannot retrieve wayland registry!\n");
|
|
}
|
|
|
|
fprintf(stdout, "Linking registry listener...\n");
|
|
wl_registry_add_listener(registry, ®istry_listener, NULL);
|
|
|
|
wl_display_dispatch(display);
|
|
wl_display_roundtrip(display);
|
|
|
|
surface = wl_compositor_create_surface(compositor);
|
|
if (!surface)
|
|
{
|
|
console_err_ss(MODULE_WAYLAND, "Can't create WL surface!\n");
|
|
}
|
|
|
|
xdg_surface = xdg_wm_base_get_xdg_surface(wm_base, surface);
|
|
xdg_toplevel = xdg_surface_get_toplevel(xdg_surface);
|
|
xdg_toplevel_set_title(xdg_toplevel, "Hi");
|
|
|
|
const struct xdg_surface_listener xdg_surface_listener = {
|
|
.configure = xdg_surface_configure_callback
|
|
};
|
|
|
|
xdg_surface_add_listener(xdg_surface, &xdg_surface_listener, NULL);
|
|
|
|
if (!zxdg_decoration_manager_v1)
|
|
console_warn_ss(MODULE_WAYLAND, "Could not get XDG decoration manager handle\n");
|
|
|
|
struct zxdg_toplevel_decoration_v1 *zxdg_toplevel_decoration_v1 = zxdg_decoration_manager_v1_get_toplevel_decoration(zxdg_decoration_manager_v1, xdg_toplevel);
|
|
|
|
struct zxdg_toplevel_decoration_v1_listener zxdg_toplevel_decoration_v1_listener = {
|
|
.configure = zxdg_toplevel_decoration_configure_callback
|
|
};
|
|
|
|
if (zxdg_toplevel_decoration_v1_add_listener(zxdg_toplevel_decoration_v1, &zxdg_toplevel_decoration_v1_listener, NULL))
|
|
console_warn_ss(MODULE_WAYLAND, "Could not set ZXDG toplevel decoration listener\n");
|
|
zxdg_toplevel_decoration_v1_set_mode(zxdg_toplevel_decoration_v1, ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE);
|
|
|
|
/* XKB library initialization */
|
|
xkb_context = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
|
|
|
|
|
|
const struct xdg_toplevel_listener xdg_toplevel_listener = {
|
|
.configure = xdg_toplevel_configure,
|
|
.close = xdg_toplevel_close
|
|
};
|
|
|
|
const struct xdg_wm_base_listener xdg_wm_base_listener = {
|
|
.ping = wayland_ping
|
|
};
|
|
|
|
if (xdg_toplevel_add_listener(xdg_toplevel, &xdg_toplevel_listener, NULL))
|
|
fprintf(stderr, "Could not set XDG toplevel listener\n");
|
|
if (xdg_wm_base_add_listener(wm_base, &xdg_wm_base_listener, NULL))
|
|
fprintf(stderr, "Could not set XDG WM base listener\n");
|
|
if (!(wl_keyboard = wl_seat_get_keyboard(wl_seat)))
|
|
{
|
|
fprintf(stderr, "Could not get XKB keyboard\n");
|
|
user_input.keyboard_state = KEYBOARD_STATE_UNPLUGGED;
|
|
}
|
|
if (!(wl_pointer = wl_seat_get_pointer(wl_seat)))
|
|
fprintf(stderr, "Could not get WL pointer\n");
|
|
|
|
if (!zwp_text_input_manager_v3)
|
|
fprintf(stderr, "Could not get ZWP text input manager\n");
|
|
else if (!(zwp_text_input_v3 = zwp_text_input_manager_v3_get_text_input(zwp_text_input_manager_v3, wl_seat)))
|
|
fprintf(stderr, "Could not get ZWP text input handle\n");
|
|
|
|
const struct zwp_text_input_v3_listener zwp_text_input_v3_listener = {
|
|
.enter = zwp_text_input_v3_enter_callback,
|
|
.leave = zwp_text_input_v3_leave_callback,
|
|
.preedit_string = zwp_text_input_v3_preedit_string_callback,
|
|
.commit_string = zwp_text_input_v3_commit_string_callback,
|
|
.delete_surrounding_text = zwp_text_input_v3_delete_surrounding_text_callback,
|
|
.done = zwp_text_input_v3_done_callback
|
|
};
|
|
|
|
if (zwp_text_input_v3_add_listener(zwp_text_input_v3, &zwp_text_input_v3_listener, NULL))
|
|
fprintf(stderr, "Could not add ZWP text input callback\n");
|
|
|
|
const struct wl_seat_listener wl_seat_listener = {
|
|
.capabilities = wl_seat_capabilities_callback
|
|
};
|
|
|
|
if (wl_seat_add_listener(wl_seat, &wl_seat_listener, NULL))
|
|
fprintf(stderr, "Could not set WL seat listener\n");
|
|
|
|
/* NOTE: We have to define all available callbacks for the keyboard listener even if we are not interested in them:
|
|
https://gitlab.freedesktop.org/wayland/wayland/-/issues/160 */
|
|
const struct wl_keyboard_listener wl_keyboard_listener = {
|
|
.keymap = keymap_callback,
|
|
.enter = enter_callback,
|
|
.leave = leave_callback,
|
|
.key = key_callback,
|
|
.modifiers = modifier_callback,
|
|
.repeat_info = repeat_info_callback,
|
|
};
|
|
if (wl_keyboard_add_listener(wl_keyboard, &wl_keyboard_listener, NULL))
|
|
fprintf(stderr, "Could not set XKB keyboard listener\n");
|
|
|
|
/* NOTE: Apparently wl_pointer added 'axis_value120' with v8 and 'axis_relative_direction' events with v9.
|
|
Failing to provide callbacks for them crashes the client in runtime */
|
|
const struct wl_pointer_listener wl_pointer_listener = {
|
|
.enter = pointer_enter_callback,
|
|
.leave = pointer_leave_callback,
|
|
.motion = pointer_motion_callback,
|
|
.button = pointer_button_callback,
|
|
.axis = pointer_axis_callback,
|
|
.frame = pointer_frame_callback,
|
|
.axis_source = pointer_axis_source_callback,
|
|
.axis_stop = pointer_axis_stop_callback,
|
|
.axis_discrete = pointer_axis_discrete_callback,
|
|
.axis_value120 = pointer_axis_value120_callback,
|
|
.axis_relative_direction = pointer_axis_relative_direction_callback,
|
|
};
|
|
|
|
if (wl_pointer_add_listener(wl_pointer, &wl_pointer_listener, NULL))
|
|
fprintf(stderr, "Could not set WL pointer listener\n");
|
|
|
|
/* WL EGL initialization */
|
|
window = wl_egl_window_create(surface, 480, 460);
|
|
|
|
EGLDisplay egl_display = eglGetDisplay((EGLNativeDisplayType) display);
|
|
if (egl_display == EGL_NO_DISPLAY)
|
|
{
|
|
fprintf(stderr, "Could not retrieve EGL display from wayland display\n");
|
|
}
|
|
errcode = eglInitialize(egl_display, NULL, NULL);
|
|
if (errcode != EGL_TRUE)
|
|
fprintf(stderr, "ERR %s: Could not initialize EGL display\n", egl_s(errcode));
|
|
|
|
int config_count;
|
|
errcode = eglGetConfigs(egl_display, NULL, 0, &config_count);
|
|
|
|
if (errcode != EGL_TRUE)
|
|
fprintf(stderr, "ERR %s: Could not retrieve EGL config number\n", egl_s(errcode));
|
|
|
|
//EGLConfig *configs = calloc(config_count, sizeof(EGLConfig));
|
|
EGLConfig *configs = arena_reserve(&main_arena, EGLConfig, config_count, ARENA_ZERO);
|
|
|
|
EGLint attr_list[] = {
|
|
EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
|
|
EGL_RED_SIZE, 8,
|
|
EGL_GREEN_SIZE, 8,
|
|
EGL_BLUE_SIZE, 8,
|
|
EGL_RENDERABLE_TYPE, (EGL_OPENGL_BIT | EGL_OPENGL_ES_BIT | EGL_OPENGL_ES2_BIT), // better be safe than sorry with our compatibility bitmask
|
|
EGL_NONE
|
|
};
|
|
int chosen_config_count;
|
|
errcode = eglChooseConfig(egl_display, attr_list, configs, config_count * sizeof(EGLConfig), &chosen_config_count);
|
|
|
|
if (errcode != EGL_TRUE)
|
|
console_err_ss(MODULE_EGL, "ERR %s: Could not choose EGL config parameters\n", egl_s(errcode));
|
|
|
|
int chosen_config_id = 0;
|
|
EGLConfig *chosen_config = configs[0];
|
|
|
|
console_log_ss(MODULE_EGL, "Found %d possible configurations:\n", chosen_config_count);
|
|
for (int i = 0; i < chosen_config_count; ++i)
|
|
{
|
|
//fprintf(stdout, "\tCONFIG %d:\n", i);
|
|
int attr;
|
|
#define GET_ATTR(attr_name) \
|
|
errcode = eglGetConfigAttrib(egl_display, configs[i], attr_name, &attr); \
|
|
int attr_##attr_name = -1; \
|
|
do { \
|
|
if (errcode == EGL_TRUE) { \
|
|
console_debug_ss(MODULE_EGL, "\t\t" #attr_name ": %d (%#0x)\n", attr, attr); \
|
|
attr_##attr_name = attr; \
|
|
} \
|
|
} while (0)
|
|
|
|
GET_ATTR(EGL_ALPHA_SIZE);
|
|
GET_ATTR(EGL_ALPHA_MASK_SIZE);
|
|
GET_ATTR(EGL_BIND_TO_TEXTURE_RGB);
|
|
GET_ATTR(EGL_BIND_TO_TEXTURE_RGBA);
|
|
GET_ATTR(EGL_BLUE_SIZE);
|
|
GET_ATTR(EGL_BUFFER_SIZE);
|
|
GET_ATTR(EGL_COLOR_BUFFER_TYPE);
|
|
GET_ATTR(EGL_CONFIG_CAVEAT);
|
|
GET_ATTR(EGL_CONFIG_ID);
|
|
GET_ATTR(EGL_CONFORMANT);
|
|
GET_ATTR(EGL_DEPTH_SIZE);
|
|
GET_ATTR(EGL_GREEN_SIZE);
|
|
GET_ATTR(EGL_LEVEL);
|
|
GET_ATTR(EGL_LUMINANCE_SIZE);
|
|
GET_ATTR(EGL_MAX_PBUFFER_WIDTH);
|
|
GET_ATTR(EGL_MAX_PBUFFER_HEIGHT);
|
|
GET_ATTR(EGL_MAX_PBUFFER_PIXELS);
|
|
GET_ATTR(EGL_MAX_SWAP_INTERVAL);
|
|
GET_ATTR(EGL_NATIVE_RENDERABLE);
|
|
GET_ATTR(EGL_NATIVE_VISUAL_ID);
|
|
GET_ATTR(EGL_NATIVE_VISUAL_TYPE);
|
|
GET_ATTR(EGL_RED_SIZE);
|
|
GET_ATTR(EGL_RENDERABLE_TYPE);
|
|
GET_ATTR(EGL_SAMPLE_BUFFERS);
|
|
GET_ATTR(EGL_SAMPLES);
|
|
GET_ATTR(EGL_STENCIL_SIZE);
|
|
GET_ATTR(EGL_SURFACE_TYPE);
|
|
GET_ATTR(EGL_TRANSPARENT_TYPE);
|
|
GET_ATTR(EGL_TRANSPARENT_RED_VALUE);
|
|
GET_ATTR(EGL_TRANSPARENT_GREEN_VALUE);
|
|
GET_ATTR(EGL_TRANSPARENT_BLUE_VALUE);
|
|
|
|
// Try to at least choose an RGBA 32bit config
|
|
if (attr_EGL_ALPHA_SIZE == 8 &&
|
|
attr_EGL_BLUE_SIZE == 8 &&
|
|
attr_EGL_RED_SIZE == 8 &&
|
|
attr_EGL_GREEN_SIZE == 8 &&
|
|
attr_EGL_BUFFER_SIZE == 32 &&
|
|
attr_EGL_COLOR_BUFFER_TYPE == EGL_RGB_BUFFER &&
|
|
attr_EGL_SAMPLE_BUFFERS == 0) // with no multisampling
|
|
{
|
|
chosen_config_id = i;
|
|
}
|
|
}
|
|
#undef GET_ATTR
|
|
|
|
chosen_config = configs[chosen_config_id];
|
|
console_log_ss(MODULE_EGL, "Chosen config #%d\n", chosen_config_id);
|
|
EGLSurface egl_surface = eglCreateWindowSurface(egl_display, chosen_config, (EGLNativeWindowType)window, NULL);
|
|
|
|
switch ((intptr_t)egl_surface)
|
|
{
|
|
case (intptr_t)EGL_NO_SURFACE:
|
|
case EGL_BAD_DISPLAY:
|
|
case EGL_NOT_INITIALIZED:
|
|
case EGL_BAD_CONFIG:
|
|
case EGL_BAD_NATIVE_WINDOW:
|
|
case EGL_BAD_ATTRIBUTE:
|
|
case EGL_BAD_ALLOC:
|
|
case EGL_BAD_MATCH:
|
|
console_err_ss(MODULE_EGL, "ERR %s: Could not create EGL window surface\n", egl_s((intptr_t)egl_surface));
|
|
break;
|
|
}
|
|
|
|
/* Even if we ask for a particular OGL version, the driver is likely to choose the latest one, so we do not even bother */
|
|
static const EGLint context_attribs[] = {
|
|
#ifdef DEBUG
|
|
EGL_CONTEXT_OPENGL_DEBUG, EGL_TRUE,
|
|
#endif
|
|
EGL_NONE
|
|
};
|
|
|
|
/* Change from OpenGL ES to regular OpenGL */
|
|
errcode = eglBindAPI(EGL_OPENGL_API);
|
|
if (errcode != EGL_TRUE)
|
|
console_err_ss(MODULE_EGL, "ERR %s: Could not bind OpenGL API to EGL\n", egl_s(errcode));
|
|
|
|
// Since we already bound desktop OGL, this is actually making a OGL context instead of an OGL ES one
|
|
EGLContext egl_context = eglCreateContext(egl_display, chosen_config, EGL_NO_CONTEXT, context_attribs);
|
|
|
|
errcode = eglMakeCurrent(egl_display, egl_surface, egl_surface, egl_context);
|
|
if (errcode != EGL_TRUE)
|
|
console_err_ss(MODULE_EGL, "ERR %s: could not make EGL context current\n", egl_s(errcode));
|
|
|
|
/* NOTE: It would seem that having a non-zero swap interval can hang the Mesa driver forever, according to this SDL GH issue:
|
|
https://github.com/libsdl-org/SDL/issues/4335#issuecomment-829789881
|
|
*/
|
|
if (eglSwapInterval(egl_display, 0) != EGL_TRUE)
|
|
console_err_ss(MODULE_EGL, "Could not set EGL swap interval\n");
|
|
|
|
// necessary before our first eglSwapBuffers
|
|
wl_surface_commit(surface);
|
|
|
|
// Trying to link with game layer
|
|
void *dlhandle = dlopen("./game.so", RTLD_NOW);
|
|
if (!dlhandle)
|
|
console_err_ss(MODULE_HOTRELOAD, "Could not load game layer\n");
|
|
dlerror();
|
|
|
|
game_api.simulate_frame = (simulate_frame_fn)dlsym(dlhandle, "simulate_frame");
|
|
|
|
char *dlerr = NULL;
|
|
if ((dlerr = dlerror()))
|
|
console_err_ss(MODULE_HOTRELOAD, "Could not link game layer: %s\n", dlerr);
|
|
|
|
// Main event loop
|
|
int framecount = 0;
|
|
while (running)
|
|
{
|
|
wl_display_dispatch_pending(display);
|
|
|
|
/* Controller input handling */
|
|
if (libevdev_get_fd(libevdev) != -1) // check if we actually found a controller to query
|
|
{
|
|
struct input_event iev;
|
|
int errcode = libevdev_next_event(libevdev, LIBEVDEV_READ_FLAG_NORMAL | LIBEVDEV_READ_FLAG_BLOCKING, &iev);
|
|
do {
|
|
if (errcode == LIBEVDEV_READ_STATUS_SYNC)
|
|
{
|
|
// TODO: Debug printing, deal with lost controller
|
|
while ((errcode = libevdev_next_event(libevdev, LIBEVDEV_READ_FLAG_SYNC, &iev)) == LIBEVDEV_READ_STATUS_SYNC);
|
|
}
|
|
else if (errcode == LIBEVDEV_READ_STATUS_SUCCESS)
|
|
{
|
|
// TODO: Debug printing
|
|
if (iev.type == EV_ABS || iev.type == EV_KEY)
|
|
console_debug_ss(MODULE_CONTROLLER, "Controller event\n\tTime: %ld.%06ld\n\tType: %s\n\tCode: %s\n\tValue: %d\n", iev.input_event_sec, iev.input_event_usec, libevdev_event_type_get_name(iev.type), libevdev_event_code_get_name(iev.type, iev.code), iev.value);
|
|
|
|
if (iev.type == EV_ABS)
|
|
{
|
|
int max_val = libevdev_event_type_get_max(EV_ABS);
|
|
float normalized_val = (2 * iev.value / (float)max_val) - 1.0f;
|
|
|
|
if (!strcmp(libevdev_event_code_get_name(iev.type, iev.code), "ABS_X"))
|
|
user_input.lx_axis = normalized_val;
|
|
if (!strcmp(libevdev_event_code_get_name(iev.type, iev.code), "ABS_Y"))
|
|
user_input.ly_axis = normalized_val;
|
|
if (!strcmp(libevdev_event_code_get_name(iev.type, iev.code), "ABS_Z"))
|
|
user_input.lz_axis = normalized_val;
|
|
if (!strcmp(libevdev_event_code_get_name(iev.type, iev.code), "ABS_RX"))
|
|
user_input.rx_axis = normalized_val;
|
|
if (!strcmp(libevdev_event_code_get_name(iev.type, iev.code), "ABS_RY"))
|
|
user_input.ry_axis = normalized_val;
|
|
if (!strcmp(libevdev_event_code_get_name(iev.type, iev.code), "ABS_RZ"))
|
|
user_input.rz_axis = normalized_val;
|
|
if (!strcmp(libevdev_event_code_get_name(iev.type, iev.code), "ABS_HAT0X"))
|
|
{
|
|
user_input.controller_buttons[CONT_LEFT] = iev.value == -1 ? 1 : 0;
|
|
user_input.controller_buttons[CONT_RIGHT] = iev.value == 1 ? 1 : 0;
|
|
}
|
|
if (!strcmp(libevdev_event_code_get_name(iev.type, iev.code), "ABS_HAT0Y"))
|
|
{
|
|
user_input.controller_buttons[CONT_UP] = iev.value == -1 ? 1 : 0;
|
|
user_input.controller_buttons[CONT_DOWN] = iev.value == 1 ? 1 : 0;
|
|
}
|
|
|
|
}
|
|
if (iev.type == EV_KEY)
|
|
{
|
|
if (!strcmp(libevdev_event_code_get_name(iev.type, iev.code), "BTN_TL"))
|
|
user_input.controller_buttons[CONT_L] = iev.value;
|
|
if (!strcmp(libevdev_event_code_get_name(iev.type, iev.code), "BTN_TR"))
|
|
user_input.controller_buttons[CONT_R] = iev.value;
|
|
if (!strcmp(libevdev_event_code_get_name(iev.type, iev.code), "BTN_SELECT"))
|
|
user_input.controller_buttons[CONT_SELECT] = iev.value;
|
|
if (!strcmp(libevdev_event_code_get_name(iev.type, iev.code), "BTN_START"))
|
|
user_input.controller_buttons[CONT_START] = iev.value;
|
|
if (!strcmp(libevdev_event_code_get_name(iev.type, iev.code), "BTN_MODE"))
|
|
user_input.controller_buttons[CONT_MODE] = iev.value;
|
|
if (!strcmp(libevdev_event_code_get_name(iev.type, iev.code), "BTN_THUMBR"))
|
|
user_input.controller_buttons[CONT_RTHUMB] = iev.value;
|
|
if (!strcmp(libevdev_event_code_get_name(iev.type, iev.code), "BTN_THUMBL"))
|
|
user_input.controller_buttons[CONT_RTHUMB] = iev.value;
|
|
if (!strcmp(libevdev_event_code_get_name(iev.type, iev.code), "BTN_NORTH"))
|
|
user_input.controller_buttons[CONT_NORTH] = iev.value;
|
|
if (!strcmp(libevdev_event_code_get_name(iev.type, iev.code), "BTN_EAST"))
|
|
user_input.controller_buttons[CONT_EAST] = iev.value;
|
|
if (!strcmp(libevdev_event_code_get_name(iev.type, iev.code), "BTN_SOUTH"))
|
|
user_input.controller_buttons[CONT_SOUTH] = iev.value;
|
|
if (!strcmp(libevdev_event_code_get_name(iev.type, iev.code), "BTN_WEST"))
|
|
user_input.controller_buttons[CONT_WEST] = iev.value;
|
|
if (!strcmp(libevdev_event_code_get_name(iev.type, iev.code), "BTN_MODE"))
|
|
user_input.controller_buttons[CONT_MODE] = iev.value;
|
|
}
|
|
}
|
|
} while (errcode != LIBEVDEV_READ_STATUS_SYNC && errcode != LIBEVDEV_READ_STATUS_SUCCESS && errcode != -EAGAIN);
|
|
|
|
if (errcode != LIBEVDEV_READ_STATUS_SUCCESS && errcode != -EAGAIN)
|
|
console_warn_ss(MODULE_CONTROLLER, "Failed to handle controller event: %s\n", strerror(-errcode));
|
|
} // end of input handling
|
|
|
|
game_state.game_input = user_input;
|
|
game_state.main_arena = main_arena;
|
|
int32_t w, h;
|
|
wl_egl_window_get_attached_size(window, &w, &h);
|
|
game_state.window_w = w;
|
|
game_state.window_h = h;
|
|
game_state.frame = framecount;
|
|
// TODO: Get delta time since last frame
|
|
game_state.dt = 0;
|
|
|
|
game_api.simulate_frame(&game_state);
|
|
//console_debug_ss(MODULE_WAYLAND, "Frame %d\n", framecount);
|
|
glClearColor(0.5, 0.3, 0.0, 1.0);
|
|
glClear(GL_COLOR_BUFFER_BIT);
|
|
glFlush();
|
|
eglSwapBuffers(egl_display, egl_surface);
|
|
++framecount;
|
|
}
|
|
|
|
wl_display_disconnect(display);
|
|
return 0;
|
|
}
|