minecraft-pi-reborn/mods/src/compat.c
2020-10-30 18:44:47 -04:00

482 lines
14 KiB
C

#define _GNU_SOURCE
#include <time.h>
#include <unistd.h>
#include <FreeImage.h>
#define GLFW_EXPOSE_NATIVE_X11
#define GLFW_INCLUDE_ES1
#include <GLFW/glfw3.h>
#include <GLFW/glfw3native.h>
#include <X11/extensions/Xfixes.h>
#include <SDL/SDL.h>
#include <SDL/SDL_syswm.h>
#include <GLES/gl.h>
#include <X11/Xlib.h>
#include <libcore/libcore.h>
#include "extra.h"
static GLFWwindow *glfw_window;
static Display *x11_display;
static Window x11_window;
static Window x11_root_window;
static int window_loaded = 0;
static int is_server = 0;
// Get Reference To X Window
static void store_x11_window() {
x11_display = glfwGetX11Display();
x11_window = glfwGetX11Window(glfw_window);
x11_root_window = RootWindow(x11_display, DefaultScreen(x11_display));
window_loaded = 1;
}
// Handle GLFW Error
static void glfw_error(__attribute__((unused)) int error, const char *description) {
ERR("GLFW Error: %s", description);
}
// 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;
// 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;
// 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;
// Unknown
default:
return SDLK_UNKNOWN;
}
}
// Pass Key Presses To SDL
static void glfw_key(__attribute__((unused)) GLFWwindow *window, int key, int scancode, int action, __attribute__((unused)) int mods) {
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 = KMOD_NONE;
event.key.keysym.sym = glfw_key_to_sdl_key(key);
SDL_PushEvent(&event);
if (key == GLFW_KEY_BACKSPACE && !up) {
extra_key_press((char) '\b');
}
}
// Pass Text To Minecraft
static void glfw_char(__attribute__((unused)) GLFWwindow *window, unsigned int codepoint) {
extra_key_press((char) codepoint);
}
static double last_mouse_x = 0;
static double last_mouse_y = 0;
// Pass Mouse Movement To SDL
static void glfw_motion(__attribute__((unused)) GLFWwindow *window, double xpos, double ypos) {
SDL_Event event;
event.type = SDL_MOUSEMOTION;
event.motion.x = xpos;
event.motion.y = ypos;
event.motion.xrel = (xpos - last_mouse_x);
event.motion.yrel = (ypos - last_mouse_y);
last_mouse_x = xpos;
last_mouse_y = ypos;
SDL_PushEvent(&event);
}
// Create And Push SDL Mouse Click Event
static void click(int button, int up) {
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);
if (button == SDL_BUTTON_RIGHT) {
extra_set_is_right_click(!up);
}
}
// Pass Mouse Click To SDL
static void glfw_click(__attribute__((unused)) GLFWwindow *window, int button, int action, __attribute__((unused)) int mods) {
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(sdl_button, up);
}
// Pass Mouse Scroll To SDL
static void glfw_scroll(__attribute__((unused)) GLFWwindow *window, __attribute__((unused)) double xoffset, double yoffset) {
if (yoffset != 0) {
int sdl_button = yoffset > 0 ? SDL_BUTTON_WHEELUP : SDL_BUTTON_WHEELDOWN;
click(sdl_button, 0);
click(sdl_button, 1);
}
}
// Default Window Size
#define DEFAULT_WIDTH 840
#define DEFAULT_HEIGHT 480
// Init GLFW
HOOK(SDL_WM_SetCaption, void, (const char *title, __attribute__((unused)) const char *icon)) {
FreeImage_Initialise(0);
// Don't Enable GLFW In Server Mode
if (!is_server) {
glfwSetErrorCallback(glfw_error);
if (!glfwInit()) {
fprintf(stderr, "Unable To Initialize GLFW\n");
exit(1);
}
// Create OpenGL ES 1.1 Context
glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_ES_API);
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 1);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 1);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
glfw_window = glfwCreateWindow(DEFAULT_WIDTH, DEFAULT_HEIGHT, title, NULL, NULL);
if (!glfw_window) {
fprintf(stderr, "Unable To Create GLFW Window\n");
exit(1);
}
// Don't Process Events In Server Mode
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);
store_x11_window();
glfwMakeContextCurrent(glfw_window);
}
}
#include <EGL/egl.h>
HOOK(eglSwapBuffers, EGLBoolean, (__attribute__((unused)) EGLDisplay display, __attribute__((unused)) EGLSurface surface)) {
if (!is_server) {
// Don't Swap Buffers In A Context-Less Window
glfwSwapBuffers(glfw_window);
}
return EGL_TRUE;
}
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
static void 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);
Screen *screen = DefaultScreenOfDisplay(x11_display);
glfwSetWindowMonitor(glfw_window, glfwGetPrimaryMonitor(), 0, 0, WidthOfScreen(screen), HeightOfScreen(screen), GLFW_DONT_CARE);
}
is_fullscreen = !is_fullscreen;
}
// 4 (Year + 1 (Hyphen) + 2 (Month) + 1 (Hyphen) + 2 (Day) + 1 (Underscore) + 2 (Hour) + 1 (Period) + 2 (Minute) + 1 (Period) + 2 (Second) + 1 (Terminator)
#define TIME_SIZE 20
// Take Screenshot
static void screenshot() {
time_t rawtime;
struct tm *timeinfo;
time(&rawtime);
timeinfo = localtime(&rawtime);
char time[TIME_SIZE];
strftime(time, TIME_SIZE, "%Y-%m-%d_%H.%M.%S", timeinfo);
char *screenshots = NULL;
asprintf(&screenshots, "%s/.minecraft/screenshots", getenv("HOME"));
int num = 1;
char *file = NULL;
asprintf(&file, "%s/%s.png", screenshots, time);
while (access(file, F_OK) != -1) {
asprintf(&file, "%s/%s-%i.png", screenshots, time, num);
num++;
}
int width;
int height;
glfwGetWindowSize(glfw_window, &width, &height);
int line_size = width * 3;
int size = height * line_size;
unsigned char pixels[size];
glReadPixels(0, 0, width, height, GL_RGB, GL_UNSIGNED_BYTE, pixels);
#if SDL_BYTEORDER == SDL_LIL_ENDIAN
// Swap Red And Blue
for (int i = 0; i < (size / 3); i++) {
int pixel = i * 3;
int red = pixels[pixel];
int blue = pixels[pixel + 2];
pixels[pixel] = blue;
pixels[pixel + 2] = red;
}
#endif
FIBITMAP *image = FreeImage_ConvertFromRawBits(pixels, width, height, line_size, 24, FI_RGBA_RED_MASK, FI_RGBA_GREEN_MASK, FI_RGBA_BLUE_MASK, 0);
if (!FreeImage_Save(FIF_PNG, image, file, 0)) {
fprintf(stderr, "Screenshot Failed: %s\n", file);
} else {
fprintf(stderr, "Screenshot Saved: %s\n", file);
}
FreeImage_Unload(image);
free(file);
free(screenshots);
}
// Intercept SDL Events
HOOK(SDL_PollEvent, int, (SDL_Event *event)) {
// Process GLFW Events
glfwPollEvents();
// Close Window (Ignore In Server Mode)
if (!is_server && glfwWindowShouldClose(glfw_window)) {
SDL_Event event;
event.type = SDL_QUIT;
SDL_PushEvent(&event);
glfwSetWindowShouldClose(glfw_window, GLFW_FALSE);
}
// Poll Events
ensure_SDL_PollEvent();
int ret = (*real_SDL_PollEvent)(event);
// Handle Events
if (ret == 1 && event != NULL) {
int handled = 0;
if (event->type == SDL_KEYDOWN) {
if (event->key.keysym.sym == SDLK_F11) {
toggle_fullscreen();
handled = 1;
} else if (event->key.keysym.sym == SDLK_F2) {
screenshot();
handled = 1;
}
}
if (handled) {
// Event Was Handled
return SDL_PollEvent(event);
}
}
return ret;
}
// Terminate GLFW
HOOK(SDL_Quit, void, ()) {
ensure_SDL_Quit();
(*real_SDL_Quit)();
// GLFW Is Disabled In Server Mode
if (!is_server) {
glfwDestroyWindow(glfw_window);
glfwTerminate();
}
}
static SDL_GrabMode fake_grab_mode = SDL_GRAB_OFF;
// Fix SDL Cursor Visibility/Grabbing
HOOK(SDL_WM_GrabInput, SDL_GrabMode, (SDL_GrabMode mode)) {
if (is_server) {
// Don't Grab Input In Server Mode
if (mode != SDL_GRAB_QUERY) {
fake_grab_mode = mode;
}
return fake_grab_mode;
} else {
if (mode != SDL_GRAB_QUERY && mode != SDL_WM_GrabInput(SDL_GRAB_QUERY)) {
glfwSetInputMode(glfw_window, GLFW_CURSOR, mode == SDL_GRAB_OFF ? GLFW_CURSOR_NORMAL : GLFW_CURSOR_DISABLED);
glfwSetInputMode(glfw_window, GLFW_RAW_MOUSE_MOTION, mode == SDL_GRAB_OFF ? GLFW_FALSE : GLFW_TRUE);
// GLFW Cursor Hiding is Broken
if (window_loaded) {
if (mode == SDL_GRAB_OFF) {
XFixesShowCursor(x11_display, x11_window);
} else {
XFixesHideCursor(x11_display, x11_window);
}
XFlush(x11_display);
}
}
return mode == SDL_GRAB_QUERY ? (glfwGetInputMode(glfw_window, GLFW_CURSOR) == GLFW_CURSOR_NORMAL ? SDL_GRAB_OFF : SDL_GRAB_ON) : mode;
}
}
// Stub SDL Cursor Visibility
HOOK(SDL_ShowCursor, int, (int toggle)) {
if (is_server) {
return toggle == SDL_QUERY ? (fake_grab_mode == SDL_GRAB_OFF ? SDL_ENABLE : SDL_DISABLE) : toggle;
} else {
return toggle == SDL_QUERY ? (glfwGetInputMode(glfw_window, GLFW_CURSOR) == GLFW_CURSOR_NORMAL ? SDL_ENABLE : SDL_DISABLE) : toggle;
}
}
// SDL Stub
HOOK(SDL_SetVideoMode, SDL_Surface *, (__attribute__((unused)) int width, __attribute__((unused)) int height, __attribute__((unused)) int bpp, __attribute__((unused)) uint32_t flags)) {
// Return Value Is Only Used For A NULL-Check
return (SDL_Surface *) 1;
}
HOOK(XTranslateCoordinates, int, (Display *display, Window src_w, Window dest_w, int src_x, int src_y, int *dest_x_return, int *dest_y_return, Window *child_return)) {
if (!is_server) {
ensure_XTranslateCoordinates();
if (window_loaded) {
return (*real_XTranslateCoordinates)(x11_display, x11_window, x11_root_window, src_x, src_y, dest_x_return, dest_y_return, child_return);
} else {
return (*real_XTranslateCoordinates)(display, src_w, dest_w, src_x, src_y, dest_x_return, dest_y_return, child_return);
}
} else {
// No X11
*dest_x_return = src_x;
*dest_y_return = src_y;
return 1;
}
}
HOOK(XGetWindowAttributes, int, (Display *display, Window w, XWindowAttributes *window_attributes_return)) {
if (!is_server) {
ensure_XGetWindowAttributes();
if (window_loaded) {
return (*real_XGetWindowAttributes)(x11_display, x11_window, window_attributes_return);
} else {
return (*real_XGetWindowAttributes)(display, w, window_attributes_return);
}
} else {
// No X11
XWindowAttributes attributes;
attributes.x = 0;
attributes.y = 0;
attributes.width = DEFAULT_WIDTH;
attributes.height = DEFAULT_HEIGHT;
*window_attributes_return = attributes;
return 1;
}
}
static void x11_nop() {
// NOP
}
HOOK(SDL_GetWMInfo, int, (SDL_SysWMinfo *info)) {
if (!is_server) {
ensure_SDL_GetWMInfo();
return (*real_SDL_GetWMInfo)(info);
} else {
// Return Fake Lock Functions In Server Mode Since X11 Is Disabled
SDL_SysWMinfo ret;
ret.info.x11.lock_func = x11_nop;
ret.info.x11.unlock_func = x11_nop;
*info = ret;
return 1;
}
}
#include <stdlib.h>
// Use VirGL
__attribute__((constructor)) static void init() {
int mode = extra_get_mode();
if (mode != 1) {
// Force Software Rendering When Not In Native Mode
setenv("LIBGL_ALWAYS_SOFTWARE", "1", 1);
}
if (mode == 0) {
// Use VirGL When In VirGL Mode
setenv("GALLIUM_DRIVER", "virpipe", 1);
}
is_server = mode == 2;
// Video is Handled By GLFW Not SDL
setenv("SDL_VIDEODRIVER", "dummy", 1);
}