minecraft-pi-reborn/media-layer/core/src/media.c

542 lines
15 KiB
C
Raw Normal View History

2021-06-17 21:32:24 +00:00
#include <unistd.h>
#include <SDL/SDL.h>
2022-06-11 01:59:57 +00:00
#include <libreborn/libreborn.h>
2021-06-17 21:32:24 +00:00
2021-08-27 02:52:18 +00:00
#ifndef MCPI_HEADLESS_MODE
2021-06-17 21:32:24 +00:00
#define GLFW_INCLUDE_NONE
#include <GLFW/glfw3.h>
2022-03-14 23:09:25 +00:00
#endif
2021-06-17 21:32:24 +00:00
2021-09-12 03:18:12 +00:00
#include <media-layer/core.h>
#include <media-layer/internal.h>
2021-06-17 21:32:24 +00:00
2021-09-14 22:10:32 +00:00
#ifndef MCPI_HEADLESS_MODE
2021-09-12 03:18:12 +00:00
#include "audio/engine.h"
2022-03-14 23:09:25 +00:00
#endif
2021-09-12 03:18:12 +00:00
2021-11-14 04:29:48 +00:00
// Allow Disabling Interaction
static void update_cursor();
static int is_interactable = 1;
void media_set_interactable(int toggle) {
is_interactable = toggle;
update_cursor();
}
2021-09-12 03:18:12 +00:00
// GLFW Code Not Needed In Headless Mode
2021-08-27 02:52:18 +00:00
#ifndef MCPI_HEADLESS_MODE
2021-06-17 21:32:24 +00:00
2022-03-07 01:07:49 +00:00
static GLFWwindow *glfw_window = NULL;
2021-06-17 21:32:24 +00:00
// Handle GLFW Error
static void glfw_error(__attribute__((unused)) int error, const char *description) {
WARN("GLFW Error: %s", description);
}
2021-07-04 23:02:45 +00:00
// Pass Character Event
static void character_event(char c) {
// SDL_UserEvent Is Never Used In MCPI, So It Is Repurposed For Character Events
SDL_Event event;
event.type = SDL_USEREVENT;
event.user.code = (int) c;
SDL_PushEvent(&event);
}
2021-06-17 21:32:24 +00:00
// Convert GLFW Key To SDL Key
static SDLKey glfw_key_to_sdl_key(int key) {
switch (key) {
// Movement
case GLFW_KEY_W:
return SDLK_w;
case GLFW_KEY_A:
return SDLK_a;
case GLFW_KEY_S:
return SDLK_s;
case GLFW_KEY_D:
return SDLK_d;
case GLFW_KEY_SPACE:
return SDLK_SPACE;
case GLFW_KEY_LEFT_SHIFT:
return SDLK_LSHIFT;
case GLFW_KEY_RIGHT_SHIFT:
return SDLK_RSHIFT;
// Inventory
case GLFW_KEY_E:
return SDLK_e;
2021-07-04 23:02:45 +00:00
// Drop Item
case GLFW_KEY_Q:
return SDLK_q;
2021-06-17 21:32:24 +00:00
// Hotbar
case GLFW_KEY_1:
return SDLK_1;
case GLFW_KEY_2:
return SDLK_2;
case GLFW_KEY_3:
return SDLK_3;
case GLFW_KEY_4:
return SDLK_4;
case GLFW_KEY_5:
return SDLK_5;
case GLFW_KEY_6:
return SDLK_6;
case GLFW_KEY_7:
return SDLK_7;
case GLFW_KEY_8:
return SDLK_8;
2021-10-27 22:14:30 +00:00
case GLFW_KEY_9:
return SDLK_9;
case GLFW_KEY_0:
return SDLK_0;
2021-06-17 21:32:24 +00:00
// UI Control
case GLFW_KEY_ESCAPE:
return SDLK_ESCAPE;
case GLFW_KEY_UP:
return SDLK_UP;
case GLFW_KEY_DOWN:
return SDLK_DOWN;
case GLFW_KEY_LEFT:
return SDLK_LEFT;
case GLFW_KEY_RIGHT:
return SDLK_RIGHT;
case GLFW_KEY_TAB:
return SDLK_TAB;
case GLFW_KEY_ENTER:
return SDLK_RETURN;
case GLFW_KEY_BACKSPACE:
return SDLK_BACKSPACE;
// Fullscreen
case GLFW_KEY_F11:
return SDLK_F11;
// Screenshot
case GLFW_KEY_F2:
return SDLK_F2;
// Hide GUI
case GLFW_KEY_F1:
return SDLK_F1;
// Third Person
case GLFW_KEY_F5:
return SDLK_F5;
// Chat
case GLFW_KEY_T:
return SDLK_t;
// Unknown
default:
return SDLK_UNKNOWN;
}
}
2021-07-04 23:02:45 +00:00
// Convert GLFW Key Modifier To SDL Key Modifier
static SDLMod glfw_modifier_to_sdl_modifier(int mods) {
SDLMod ret = KMOD_NONE;
// Control
if ((mods & GLFW_MOD_CONTROL) != 0) {
ret |= KMOD_CTRL;
}
// Shift
if ((mods & GLFW_MOD_SHIFT) != 0) {
ret |= KMOD_SHIFT;
}
// Alt
if ((mods & GLFW_MOD_ALT) != 0) {
ret |= KMOD_ALT;
}
// Return
return ret;
2021-06-17 21:32:24 +00:00
}
// Pass Key Presses To SDL
static void glfw_key(__attribute__((unused)) GLFWwindow *window, int key, int scancode, int action, int mods) {
2021-11-14 04:29:48 +00:00
if (is_interactable) {
SDL_Event event;
int up = action == GLFW_RELEASE;
event.type = up ? SDL_KEYUP : SDL_KEYDOWN;
event.key.state = up ? SDL_RELEASED : SDL_PRESSED;
event.key.keysym.scancode = scancode;
event.key.keysym.mod = glfw_modifier_to_sdl_modifier(mods);
event.key.keysym.sym = glfw_key_to_sdl_key(key);
SDL_PushEvent(&event);
2021-06-17 21:32:24 +00:00
}
}
// Pass Text To Minecraft
2022-07-31 03:52:50 +00:00
static void codepoint_to_utf8(unsigned char *const buffer, const unsigned int code) {
// https://stackoverflow.com/a/42013433/16198887
if (code <= 0x7f) {
buffer[0] = code;
} else if (code <= 0x7ff) {
buffer[0] = 0xc0 | (code >> 6); // 110xxxxx
buffer[1] = 0x80 | (code & 0x3f); // 10xxxxxx
} else if (code <= 0xffff) {
buffer[0] = 0xe0 | (code >> 12); // 1110xxxx
buffer[1] = 0x80 | ((code >> 6) & 0x3f); // 10xxxxxx
buffer[2] = 0x80 | (code & 0x3f); // 10xxxxxx
} else if (code <= 0x10ffff) {
buffer[0] = 0xf0 | (code >> 18); // 11110xxx
buffer[1] = 0x80 | ((code >> 12) & 0x3f); // 10xxxxxx
buffer[2] = 0x80 | ((code >> 6) & 0x3f); // 10xxxxxx
buffer[3] = 0x80 | (code & 0x3f); // 10xxxxxx
}
}
2021-06-17 21:32:24 +00:00
static void glfw_char(__attribute__((unused)) GLFWwindow *window, unsigned int codepoint) {
2021-11-14 04:29:48 +00:00
if (is_interactable) {
2022-07-31 03:52:50 +00:00
// Convert
size_t str_size = 4 /* Maximum UTF-8 character size */ + 1 /* NULL-terminator */;
char str[str_size];
memset(str, 0, str_size);
codepoint_to_utf8((unsigned char *) str, codepoint);
char *cp437_str = to_cp437(str);
// Send Event·
for (int i = 0; cp437_str[i] != '\0'; i++) {
character_event(cp437_str[i]);
2022-07-20 06:58:14 +00:00
}
// Free
2022-07-31 03:52:50 +00:00
free(cp437_str);
2021-11-14 04:29:48 +00:00
}
2021-06-17 21:32:24 +00:00
}
2021-11-14 04:29:48 +00:00
// Last Mouse Location
2021-06-17 21:32:24 +00:00
static double last_mouse_x = 0;
static double last_mouse_y = 0;
2021-11-14 04:29:48 +00:00
// Ignore Relative Cursor Motion
static int ignore_relative_motion = 0;
2021-06-17 21:32:24 +00:00
// Pass Mouse Movement To SDL
static void glfw_motion(__attribute__((unused)) GLFWwindow *window, double xpos, double ypos) {
2021-11-14 04:29:48 +00:00
if (is_interactable) {
SDL_Event event;
event.type = SDL_MOUSEMOTION;
event.motion.x = xpos;
event.motion.y = ypos;
event.motion.xrel = !ignore_relative_motion ? (xpos - last_mouse_x) : 0;
event.motion.yrel = !ignore_relative_motion ? (ypos - last_mouse_y) : 0;
SDL_PushEvent(&event);
}
ignore_relative_motion = 0;
2021-06-17 21:32:24 +00:00
last_mouse_x = xpos;
last_mouse_y = ypos;
}
// Create And Push SDL Mouse Click Event
static void click_event(int button, int up) {
2021-11-14 04:29:48 +00:00
if (is_interactable) {
SDL_Event event;
event.type = up ? SDL_MOUSEBUTTONUP : SDL_MOUSEBUTTONDOWN;
event.button.x = last_mouse_x;
event.button.y = last_mouse_y;
event.button.state = up ? SDL_RELEASED : SDL_PRESSED;
event.button.button = button;
SDL_PushEvent(&event);
}
2021-06-17 21:32:24 +00:00
}
// Pass Mouse Click To SDL
static void glfw_click(__attribute__((unused)) GLFWwindow *window, int button, int action, __attribute__((unused)) int mods) {
2021-11-14 04:29:48 +00:00
if (is_interactable) {
int up = action == GLFW_RELEASE;
int sdl_button = button == GLFW_MOUSE_BUTTON_RIGHT ? SDL_BUTTON_RIGHT : (button == GLFW_MOUSE_BUTTON_LEFT ? SDL_BUTTON_LEFT : SDL_BUTTON_MIDDLE);
click_event(sdl_button, up);
}
2021-06-17 21:32:24 +00:00
}
// Pass Mouse Scroll To SDL
static void glfw_scroll(__attribute__((unused)) GLFWwindow *window, __attribute__((unused)) double xoffset, double yoffset) {
2021-11-14 04:29:48 +00:00
if (is_interactable && yoffset != 0) {
2021-06-17 21:32:24 +00:00
int sdl_button = yoffset > 0 ? SDL_BUTTON_WHEELUP : SDL_BUTTON_WHEELDOWN;
click_event(sdl_button, 0);
click_event(sdl_button, 1);
}
}
2022-03-14 23:09:25 +00:00
#endif
2021-06-17 21:32:24 +00:00
2021-09-12 03:18:12 +00:00
// Track Media Layer State
static volatile int is_running = 0;
2021-09-12 03:18:12 +00:00
2022-03-07 01:07:49 +00:00
// Track If Raw Mouse Motion Is Enabled
static int raw_mouse_motion_enabled = 1;
void media_set_raw_mouse_motion_enabled(int enabled) {
raw_mouse_motion_enabled = enabled;
#ifndef MCPI_HEADLESS_MODE
if (is_running) {
glfwSetInputMode(glfw_window, GLFW_RAW_MOUSE_MOTION, GLFW_FALSE);
}
2022-03-14 23:09:25 +00:00
#endif
2022-03-07 01:07:49 +00:00
if (!raw_mouse_motion_enabled) {
2022-04-15 01:12:42 +00:00
WARN("Raw mouse motion has been DISABLED, this IS NOT recommended, and should only ever be used on systems that don't support or have broken raw mouse motion.");
2022-03-07 01:07:49 +00:00
}
}
2021-11-14 04:29:48 +00:00
// Disable V-Sync
static int disable_vsync = 0;
void media_disable_vsync() {
disable_vsync = 1;
#ifndef MCPI_HEADLESS_MODE
if (is_running) {
glfwSwapInterval(0);
}
2022-03-14 23:09:25 +00:00
#endif
2021-11-14 04:29:48 +00:00
}
2022-07-16 02:08:12 +00:00
// Force EGL
static int force_egl = 0;
void media_force_egl() {
if (force_egl == -1) {
IMPOSSIBLE();
}
force_egl = 1;
}
2021-09-12 03:18:12 +00:00
// Init Media Layer
#define GL_VERSION 0x1f02
typedef const unsigned char *(*glGetString_t)(unsigned int name);
2021-06-17 21:32:24 +00:00
void SDL_WM_SetCaption(const char *title, __attribute__((unused)) const char *icon) {
2021-09-12 03:18:12 +00:00
// Don't Enable GLFW In Headless Mode
2021-08-27 02:52:18 +00:00
#ifndef MCPI_HEADLESS_MODE
2021-09-12 03:18:12 +00:00
// Init GLFW
2021-06-17 21:32:24 +00:00
glfwSetErrorCallback(glfw_error);
if (!glfwInit()) {
2022-04-15 01:12:42 +00:00
ERR("Unable To Initialize GLFW");
2021-06-17 21:32:24 +00:00
}
// Create OpenGL ES Context
2021-06-17 21:32:24 +00:00
glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_ES_API);
#ifdef MCPI_USE_GLES1_COMPATIBILITY_LAYER
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 2);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0);
#else
2021-06-17 21:32:24 +00:00
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 1);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 1);
#endif
2022-07-16 02:08:12 +00:00
// Use EGL
if (force_egl) {
glfwWindowHint(GLFW_CONTEXT_CREATION_API, GLFW_EGL_CONTEXT_API);
}
force_egl = -1;
2021-06-17 21:32:24 +00:00
// Extra Settings
glfwWindowHint(GLFW_AUTO_ICONIFY, GLFW_FALSE);
glfwWindowHint(GLFW_ALPHA_BITS, 0); // Fix Transparent Window On Wayland
2022-07-30 02:13:03 +00:00
// App ID
glfwWindowHintString(GLFW_X11_CLASS_NAME, MCPI_APP_ID);
glfwWindowHintString(GLFW_WAYLAND_APP_ID, MCPI_APP_ID);
2021-06-17 21:32:24 +00:00
2022-05-14 02:36:12 +00:00
// Create Window
2021-06-17 21:32:24 +00:00
glfw_window = glfwCreateWindow(DEFAULT_WIDTH, DEFAULT_HEIGHT, title, NULL, NULL);
if (!glfw_window) {
2022-04-15 01:12:42 +00:00
ERR("Unable To Create GLFW Window");
2021-06-17 21:32:24 +00:00
}
2022-05-14 02:36:12 +00:00
// Event Handlers
2021-06-17 21:32:24 +00:00
glfwSetKeyCallback(glfw_window, glfw_key);
glfwSetCharCallback(glfw_window, glfw_char);
glfwSetCursorPosCallback(glfw_window, glfw_motion);
glfwSetMouseButtonCallback(glfw_window, glfw_click);
glfwSetScrollCallback(glfw_window, glfw_scroll);
2022-05-14 02:36:12 +00:00
// Make Window Context Current
2021-06-17 21:32:24 +00:00
glfwMakeContextCurrent(glfw_window);
2021-09-12 03:18:12 +00:00
// Debug
glGetString_t glGetString = (glGetString_t) glfwGetProcAddress("glGetString");
DEBUG("Using %s", (*glGetString)(GL_VERSION));
2021-09-12 03:18:12 +00:00
// Init OpenAL
_media_audio_init();
2022-03-14 23:09:25 +00:00
#else
2021-06-17 21:32:24 +00:00
(void) title; // Mark As Used
2022-03-14 23:09:25 +00:00
#endif
2021-09-12 03:18:12 +00:00
// Set State
is_running = 1;
2021-11-14 04:29:48 +00:00
// Update State
update_cursor();
if (disable_vsync) {
media_disable_vsync();
}
// Always Cleanup Media Layer
atexit(media_cleanup);
2021-06-17 21:32:24 +00:00
}
void media_swap_buffers() {
2021-08-27 02:52:18 +00:00
#ifndef MCPI_HEADLESS_MODE
2021-06-17 21:32:24 +00:00
// Don't Swap Buffers In A Context-Less Window
glfwSwapBuffers(glfw_window);
2022-03-14 23:09:25 +00:00
#endif
2021-06-17 21:32:24 +00:00
}
2021-09-12 03:18:12 +00:00
// Fullscreen Not Needed In Headless Mode
2021-08-27 02:52:18 +00:00
#ifndef MCPI_HEADLESS_MODE
2021-06-17 21:32:24 +00:00
static int is_fullscreen = 0;
// Old Size And Position To Use When Exiting Fullscreen
static int old_width = -1;
static int old_height = -1;
static int old_x = -1;
static int old_y = -1;
// Toggle Fullscreen
void media_toggle_fullscreen() {
if (is_fullscreen) {
glfwSetWindowMonitor(glfw_window, NULL, old_x, old_y, old_width, old_height, GLFW_DONT_CARE);
old_width = -1;
old_height = -1;
old_x = -1;
old_y = -1;
} else {
glfwGetWindowSize(glfw_window, &old_width, &old_height);
glfwGetWindowPos(glfw_window, &old_x, &old_y);
GLFWmonitor *monitor = glfwGetPrimaryMonitor();
const GLFWvidmode *mode = glfwGetVideoMode(monitor);
glfwSetWindowMonitor(glfw_window, monitor, 0, 0, mode->width, mode->height, GLFW_DONT_CARE);
}
is_fullscreen = !is_fullscreen;
}
2022-03-14 23:09:25 +00:00
#else
2021-06-17 21:32:24 +00:00
void media_toggle_fullscreen() {
}
2022-03-14 23:09:25 +00:00
#endif
2021-06-17 21:32:24 +00:00
// Intercept SDL Events
void _media_handle_SDL_PollEvent() {
2021-09-12 03:18:12 +00:00
// GLFW And Audio Are Disabled Disabled In Headless Mode
2021-08-27 02:52:18 +00:00
#ifndef MCPI_HEADLESS_MODE
2021-06-17 21:32:24 +00:00
// Process GLFW Events
glfwPollEvents();
// Close Window
if (glfwWindowShouldClose(glfw_window)) {
SDL_Event event;
event.type = SDL_QUIT;
SDL_PushEvent(&event);
glfwSetWindowShouldClose(glfw_window, GLFW_FALSE);
}
2022-03-14 23:09:25 +00:00
#endif
2021-06-17 21:32:24 +00:00
}
2021-09-12 03:18:12 +00:00
// Cleanup Media Layer
2021-06-17 21:32:24 +00:00
void media_cleanup() {
2021-09-12 03:18:12 +00:00
if (is_running) {
// GLFW And Audio Are Disabled In Headless Mode
2021-08-27 02:52:18 +00:00
#ifndef MCPI_HEADLESS_MODE
// Ignore GLFW Errors During Termination
glfwSetErrorCallback(NULL);
2021-09-12 03:18:12 +00:00
// Terminate GLFW
glfwDestroyWindow(glfw_window);
glfwTerminate();
// Cleanup OpenAL
_media_audio_cleanup();
2022-03-14 23:09:25 +00:00
#endif
2021-09-12 03:18:12 +00:00
// Update State
is_running = 0;
}
2021-06-17 21:32:24 +00:00
}
2021-06-19 23:07:09 +00:00
// Store Cursor State
static int cursor_grabbed = 0;
static int cursor_visible = 1;
// Update GLFW Cursor State (Client Only)
2021-11-14 04:29:48 +00:00
static void update_cursor() {
2021-08-27 02:52:18 +00:00
#ifndef MCPI_HEADLESS_MODE
2021-11-14 04:29:48 +00:00
if (is_running) {
// Get New State
int new_cursor_visible = is_interactable ? cursor_visible : 1;
int new_cursor_grabbed = is_interactable ? cursor_grabbed : 0;
// Store Old Mode
int old_mode = glfwGetInputMode(glfw_window, GLFW_CURSOR);
// Handle Cursor Visibility
int new_mode;
if (!new_cursor_visible) {
if (new_cursor_grabbed) {
new_mode = GLFW_CURSOR_DISABLED;
} else {
new_mode = GLFW_CURSOR_HIDDEN;
}
2021-06-19 23:07:09 +00:00
} else {
2021-11-14 04:29:48 +00:00
new_mode = GLFW_CURSOR_NORMAL;
2021-06-19 23:07:09 +00:00
}
2021-11-14 04:29:48 +00:00
if (new_mode != old_mode) {
// Ignore Relative Cursor Motion When Locking
if (new_mode == GLFW_CURSOR_DISABLED && old_mode != GLFW_CURSOR_DISABLED) {
ignore_relative_motion = 1;
}
// Set New Mode
glfwSetInputMode(glfw_window, GLFW_CURSOR, new_mode);
// Handle Cursor Lock/Unlock
2022-03-07 01:07:49 +00:00
if (raw_mouse_motion_enabled && ((new_mode == GLFW_CURSOR_DISABLED && old_mode != GLFW_CURSOR_DISABLED) || (new_mode != GLFW_CURSOR_DISABLED && old_mode == GLFW_CURSOR_DISABLED))) {
2021-11-14 04:29:48 +00:00
// Use Raw Mouse Motion
glfwSetInputMode(glfw_window, GLFW_RAW_MOUSE_MOTION, new_mode == GLFW_CURSOR_DISABLED ? GLFW_TRUE : GLFW_FALSE);
}
// Reset Mouse Position When Unlocking
if (new_mode != GLFW_CURSOR_DISABLED && old_mode == GLFW_CURSOR_DISABLED) {
double cursor_x;
double cursor_y;
glfwGetCursorPos(glfw_window, &cursor_x, &cursor_y);
glfw_motion(glfw_window, cursor_x, cursor_y);
}
2021-06-19 23:07:09 +00:00
}
}
2022-03-14 23:09:25 +00:00
#endif
2021-06-19 23:07:09 +00:00
}
2021-06-17 21:32:24 +00:00
// Fix SDL Cursor Visibility/Grabbing
SDL_GrabMode SDL_WM_GrabInput(SDL_GrabMode mode) {
2021-06-19 23:07:09 +00:00
if (mode == SDL_GRAB_QUERY) {
// Query
return cursor_grabbed ? SDL_GRAB_ON : SDL_GRAB_OFF;
} else if (mode == SDL_GRAB_ON) {
// Store State
cursor_grabbed = 1;
} else if (mode == SDL_GRAB_OFF) {
// Store State
cursor_grabbed = 0;
2021-06-17 21:32:24 +00:00
}
2021-06-19 23:07:09 +00:00
// Update Cursor GLFW State (Client Only)
2021-11-14 04:29:48 +00:00
update_cursor();
2021-06-19 23:07:09 +00:00
// Return
return mode;
2021-06-17 21:32:24 +00:00
}
// Stub SDL Cursor Visibility
int SDL_ShowCursor(int toggle) {
2021-06-19 23:07:09 +00:00
if (toggle == SDL_QUERY) {
// Query
return cursor_visible ? SDL_ENABLE : SDL_DISABLE;
} else if (toggle == SDL_ENABLE) {
// Store State
cursor_visible = 1;
} else if (toggle == SDL_DISABLE) {
// Store State
cursor_visible = 0;
}
// Update Cursor GLFW State (Client Only)
2021-11-14 04:29:48 +00:00
update_cursor();
2021-06-19 23:07:09 +00:00
// Return
return toggle;
2021-06-17 21:32:24 +00:00
}
// Get Framebuffer Size
void media_get_framebuffer_size(int *width, int *height) {
2021-08-27 02:52:18 +00:00
#ifndef MCPI_HEADLESS_MODE
2021-06-17 21:32:24 +00:00
if (glfw_window != NULL) {
glfwGetFramebufferSize(glfw_window, width, height);
return;
}
2022-03-14 23:09:25 +00:00
#endif
2021-06-17 21:32:24 +00:00
*width = DEFAULT_WIDTH;
*height = DEFAULT_HEIGHT;
}