diff --git a/dependencies/zenity/src b/dependencies/zenity/src
index 27cd9e88a..435bc154e 160000
--- a/dependencies/zenity/src
+++ b/dependencies/zenity/src
@@ -1 +1 @@
-Subproject commit 27cd9e88a72538b00d172dee67d94cb4ce6bc9b9
+Subproject commit 435bc154ef8dcec8a3f126ed7f5198b7db32ea29
diff --git a/launcher/src/bootstrap.c b/launcher/src/bootstrap.c
index 889b99f16..aa2488efc 100644
--- a/launcher/src/bootstrap.c
+++ b/launcher/src/bootstrap.c
@@ -101,6 +101,14 @@ void pre_bootstrap(int argc, char *argv[]) {
// Disable stdout Buffering
setvbuf(stdout, NULL, _IONBF, 0);
+ // --debug
+ for (int i = 1; i < argc; i++) {
+ if (strcmp(argv[i], "--debug") == 0) {
+ set_and_print_env("MCPI_DEBUG", "1");
+ break;
+ }
+ }
+
// Set Debug Tag
reborn_debug_tag = "(Launcher) ";
diff --git a/launcher/src/client/launcher.cpp b/launcher/src/client/launcher.cpp
index 73447d6c7..ec24a19ff 100644
--- a/launcher/src/client/launcher.cpp
+++ b/launcher/src/client/launcher.cpp
@@ -99,7 +99,7 @@ static void run_command_and_set_env(const char *env_name, const char *command[])
}
// Check Return Code
if (!is_exit_status_success(return_code)) {
- INFO("Launch Interrupted");
+ // Launch Interrupted
exit(EXIT_SUCCESS);
}
}
diff --git a/launcher/src/crash-report.c b/launcher/src/crash-report.c
index 3d64ef5d0..4df858024 100644
--- a/launcher/src/crash-report.c
+++ b/launcher/src/crash-report.c
@@ -19,20 +19,27 @@
#define CRASH_REPORT_DIALOG_WIDTH "640"
#define CRASH_REPORT_DIALOG_HEIGHT "480"
static void show_report(const char *log_filename) {
- const char *command[] = {
- "zenity",
- "--title", DIALOG_TITLE,
- "--name", MCPI_APP_ID,
- "--width", CRASH_REPORT_DIALOG_WIDTH,
- "--height", CRASH_REPORT_DIALOG_HEIGHT,
- "--text-info",
- "--text", "Minecraft: Pi Edition: Reborn has crashed!\n\nNeed help? Consider asking on the Discord server!",
- "--filename", log_filename,
- "--no-wrap",
- "--font", "Monospace",
- NULL
- };
- free(run_command(command, NULL));
+ // Fork
+ pid_t pid = fork();
+ if (pid == 0) {
+ // Child
+ setsid();
+ const char *command[] = {
+ "zenity",
+ "--title", DIALOG_TITLE,
+ "--name", MCPI_APP_ID,
+ "--width", CRASH_REPORT_DIALOG_WIDTH,
+ "--height", CRASH_REPORT_DIALOG_HEIGHT,
+ "--text-info",
+ "--text", "Minecraft: Pi Edition: Reborn has crashed!\n\nNeed help? Consider asking on the Discord server! If you believe this is a problem with Minecraft: Pi Edition: Reborn itself, please upload this crash report to the #bugs Discord channel.",
+ "--filename", log_filename,
+ "--no-wrap",
+ "--font", "Monospace",
+ NULL
+ };
+ reborn_debug_tag = CHILD_PROCESS_TAG;
+ safe_execvpe(command, (const char *const *) environ);
+ }
}
#endif
@@ -92,6 +99,7 @@ void setup_crash_report() {
act_sigterm.sa_flags = SA_RESTART;
act_sigterm.sa_handler = &exit_handler;
sigaction(SIGTERM, &act_sigterm, NULL);
+ atexit(murder_children);
// Close Unneeded File Descriptors
close(output_pipe[PIPE_WRITE]);
@@ -238,11 +246,6 @@ void setup_crash_report() {
}
#endif
- // Delete Log File
- if (unlink(log_filename) == -1) {
- ERR("Unable To Delete Log File: %s", strerror(errno));
- }
-
// Exit
exit(WIFEXITED(status) ? WEXITSTATUS(status) : EXIT_FAILURE);
}
diff --git a/libreborn/src/util/exec.c b/libreborn/src/util/exec.c
index 9e34717a4..b1aa21e67 100644
--- a/libreborn/src/util/exec.c
+++ b/libreborn/src/util/exec.c
@@ -11,11 +11,11 @@ static void setenv_safe(const char *name, const char *value) {
}
}
void set_and_print_env(const char *name, const char *value) {
- // Print New Value
- DEBUG("Set %s = %s", name, value != NULL ? value : "(unset)");
-
// Set The Value
setenv_safe(name, value);
+
+ // Print New Value
+ DEBUG("Set %s = %s", name, value != NULL ? value : "(unset)");
}
// Safe execvpe()
diff --git a/media-layer/core/src/media.c b/media-layer/core/src/media.c
index 49e95f3d4..640410197 100644
--- a/media-layer/core/src/media.c
+++ b/media-layer/core/src/media.c
@@ -4,6 +4,8 @@
#include
#ifndef MCPI_HEADLESS_MODE
+#include
+
#define GLFW_INCLUDE_NONE
#include
#endif
@@ -16,13 +18,26 @@
#endif
// Allow Disabling Interaction
+static void emit_events_after_is_interactable_change();
static void update_cursor();
static int is_interactable = 1;
void media_set_interactable(int toggle) {
- is_interactable = toggle;
- update_cursor();
+ if (toggle != is_interactable) {
+ is_interactable = toggle;
+ update_cursor();
+#ifndef MCPI_HEADLESS_MODE
+ emit_events_after_is_interactable_change();
+#endif
+ }
}
+// Track Media Layer State
+static volatile int is_running = 0;
+
+// Store Cursor State
+static int cursor_grabbed = 0;
+static int cursor_visible = 1;
+
// GLFW Code Not Needed In Headless Mode
#ifndef MCPI_HEADLESS_MODE
@@ -145,16 +160,19 @@ static SDLMod glfw_modifier_to_sdl_modifier(int mods) {
}
// Pass Key Presses To SDL
+static void glfw_key_raw(int key, int scancode, int action, 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 = glfw_modifier_to_sdl_modifier(mods);
+ event.key.keysym.sym = glfw_key_to_sdl_key(key);
+ SDL_PushEvent(&event);
+}
static void glfw_key(__attribute__((unused)) GLFWwindow *window, int key, int scancode, int action, int mods) {
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);
+ glfw_key_raw(key, scancode, action, mods);
}
}
@@ -218,23 +236,24 @@ static void glfw_motion(__attribute__((unused)) GLFWwindow *window, double xpos,
// Create And Push SDL Mouse Click Event
static void click_event(int button, int up) {
- 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);
- }
+ 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);
}
// Pass Mouse Click To SDL
+static void glfw_click_raw(int button, int action) {
+ 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);
+}
static void glfw_click(__attribute__((unused)) GLFWwindow *window, int button, int action, __attribute__((unused)) int mods) {
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);
+ glfw_click_raw(button, action);
}
}
@@ -247,10 +266,248 @@ static void glfw_scroll(__attribute__((unused)) GLFWwindow *window, __attribute_
}
}
-#endif
+// Controller Events
+static SDLKey glfw_controller_button_to_key(int button) {
+ switch (button) {
+ // Jump
+ case GLFW_GAMEPAD_BUTTON_A:
+ return GLFW_KEY_SPACE;
+ // Drop Item
+ case GLFW_GAMEPAD_BUTTON_DPAD_DOWN:
+ return GLFW_KEY_Q;
+ // Inventory
+ case GLFW_GAMEPAD_BUTTON_Y:
+ return GLFW_KEY_E;
+ // Third-Person
+ case GLFW_GAMEPAD_BUTTON_DPAD_UP:
+ return GLFW_KEY_F5;
+ // Sneak
+ case GLFW_GAMEPAD_BUTTON_B:
+ return GLFW_KEY_LEFT_SHIFT;
+ // Chat
+ case GLFW_GAMEPAD_BUTTON_DPAD_RIGHT:
+ return GLFW_KEY_T;
+ // Pause
+ case GLFW_GAMEPAD_BUTTON_START:
+ case GLFW_GAMEPAD_BUTTON_BACK:
+ return GLFW_KEY_ESCAPE;
+ // Unknown
+ default:
+ return GLFW_KEY_UNKNOWN;
+ }
+}
+static void glfw_controller_button(int button, int action) {
+ int key = glfw_controller_button_to_key(button);
+ if (key != GLFW_KEY_UNKNOWN) {
+ glfw_key_raw(key, glfwGetKeyScancode(key), action, 0);
+ } else {
+ // Scrolling
+ if (button == GLFW_GAMEPAD_BUTTON_LEFT_BUMPER) {
+ key = SDL_BUTTON_WHEELUP;
+ } else if (button == GLFW_GAMEPAD_BUTTON_RIGHT_BUMPER) {
+ key = SDL_BUTTON_WHEELDOWN;
+ }
+ if (key != GLFW_KEY_UNKNOWN) {
+ click_event(key, action == GLFW_PRESS);
+ }
+ }
+}
-// Track Media Layer State
-static volatile int is_running = 0;
+// Controller Movement Axis
+static int controller_horizontal_key = GLFW_KEY_UNKNOWN;
+static int controller_vertical_key = GLFW_KEY_UNKNOWN;
+static void release_and_press_key(int *old_key, int new_key) {
+ if (*old_key != new_key) {
+ if (*old_key != GLFW_KEY_UNKNOWN) {
+ glfw_key_raw(*old_key, glfwGetKeyScancode(*old_key), GLFW_RELEASE, 0);
+ }
+ if (new_key != GLFW_KEY_UNKNOWN) {
+ glfw_key_raw(new_key, glfwGetKeyScancode(new_key), GLFW_PRESS, 0);
+ }
+ }
+ *old_key = new_key;
+}
+#define verify_controller_axis_value(value, threshold) \
+ if ((value < (threshold) && value > 0) || (value > -(threshold) && value < 0)) { \
+ value = 0; \
+ }
+#define CONTROLLER_MOVEMENT_AXIS_THRESHOLD 0.5f
+static void glfw_controller_movement(float x, float y) {
+ // Verify
+ verify_controller_axis_value(x, CONTROLLER_MOVEMENT_AXIS_THRESHOLD);
+ verify_controller_axis_value(y, CONTROLLER_MOVEMENT_AXIS_THRESHOLD);
+ // Horizontal Movement
+ if (x > 0) {
+ release_and_press_key(&controller_horizontal_key, GLFW_KEY_D);
+ } else if (x < 0) {
+ release_and_press_key(&controller_horizontal_key, GLFW_KEY_A);
+ } else {
+ release_and_press_key(&controller_horizontal_key, GLFW_KEY_UNKNOWN);
+ }
+ // Vertical Movement
+ if (y < 0) {
+ release_and_press_key(&controller_vertical_key, GLFW_KEY_W);
+ } else if (y > 0) {
+ release_and_press_key(&controller_vertical_key, GLFW_KEY_S);
+ } else {
+ release_and_press_key(&controller_vertical_key, GLFW_KEY_UNKNOWN);
+ }
+}
+
+// Get Time
+#define NANOSECONDS_IN_SECOND 1000000000ll
+static long long int get_time() {
+ struct timespec ts;
+ clock_gettime(CLOCK_MONOTONIC_RAW, &ts);
+ long long int a = (long long int) ts.tv_nsec;
+ long long int b = ((long long int) ts.tv_sec) * NANOSECONDS_IN_SECOND;
+ return a + b;
+}
+
+// Controller Look Axis
+#define CONTROLLER_LOOK_EVENT_PERIOD 50000000ll // 1/20 Seconds
+#define CONTROLLER_LOOK_AXIS_THRESHOLD 0.2f
+#define CONTROLLER_LOOK_AXIS_SENSITIVITY 70
+static void glfw_controller_look(float x, float y) {
+ // Current Time
+ long long int current_time = get_time();
+ // Last Time
+ static long long int last_time = 0;
+ static int is_last_time_set = 0;
+ if (!is_last_time_set) {
+ is_last_time_set = 1;
+ last_time = current_time;
+ }
+
+ // Check If Period Has Passed
+ if ((current_time - last_time) > CONTROLLER_LOOK_EVENT_PERIOD) {
+ // Reset Last Time
+ last_time = current_time;
+
+ // Verify
+ verify_controller_axis_value(x, CONTROLLER_LOOK_AXIS_THRESHOLD);
+ verify_controller_axis_value(y, CONTROLLER_LOOK_AXIS_THRESHOLD);
+
+ // Send Event
+ SDL_Event event;
+ event.type = SDL_MOUSEMOTION;
+ event.motion.x = last_mouse_x;
+ event.motion.y = last_mouse_y;
+ event.motion.xrel = x * CONTROLLER_LOOK_AXIS_SENSITIVITY;
+ event.motion.yrel = y * CONTROLLER_LOOK_AXIS_SENSITIVITY;
+ SDL_PushEvent(&event);
+ }
+}
+
+// Controller Place/Mine Triggers
+#define CONTROLLER_TRIGGER_THRESHOLD 0
+#define CONTROLLER_TRIGGER_COUNT 2
+static void glfw_controller_trigger(int trigger, int action) {
+ glfw_click_raw(trigger == GLFW_GAMEPAD_AXIS_RIGHT_TRIGGER ? GLFW_MOUSE_BUTTON_LEFT : GLFW_MOUSE_BUTTON_RIGHT, action);
+}
+
+// Current Controller
+static int current_controller = -1;
+
+// Track Controller State
+static void update_controller_state() {
+ // Store Button/Trigger State
+ static int controller_buttons[GLFW_GAMEPAD_BUTTON_LAST + 1];
+ static int controller_triggers[CONTROLLER_TRIGGER_COUNT];
+
+ // Get State
+ GLFWgamepadstate state;
+ int controller_enabled = cursor_grabbed && is_interactable;
+ int controller_valid = controller_enabled && current_controller != -1 && glfwGetGamepadState(current_controller, &state);
+ if (!controller_valid) {
+ // Invalid Controller
+
+ // Generate Blank State
+ for (int i = GLFW_GAMEPAD_BUTTON_A; i <= GLFW_GAMEPAD_BUTTON_LAST; i++) {
+ state.buttons[i] = GLFW_RELEASE;
+ }
+ for (int i = GLFW_GAMEPAD_AXIS_LEFT_X; i <= GLFW_GAMEPAD_AXIS_LAST; i++) {
+ int is_trigger = i == GLFW_GAMEPAD_AXIS_LEFT_TRIGGER || i == GLFW_GAMEPAD_AXIS_RIGHT_TRIGGER;
+ state.axes[i] = is_trigger ? -1 : 0;
+ }
+ }
+
+ // Check Buttons
+ for (int i = GLFW_GAMEPAD_BUTTON_A; i <= GLFW_GAMEPAD_BUTTON_LAST; i++) {
+ int old_state = controller_buttons[i];
+ controller_buttons[i] = state.buttons[i];
+ if (old_state != controller_buttons[i]) {
+ // State Changed
+ glfw_controller_button(i, controller_buttons[i]);
+ }
+ }
+
+ // Handle Movement & Look
+ glfw_controller_movement(state.axes[GLFW_GAMEPAD_AXIS_LEFT_X], state.axes[GLFW_GAMEPAD_AXIS_LEFT_Y]);
+ glfw_controller_look(state.axes[GLFW_GAMEPAD_AXIS_RIGHT_X], state.axes[GLFW_GAMEPAD_AXIS_RIGHT_Y]);
+
+ // Check Triggers
+ for (int i = 0; i < CONTROLLER_TRIGGER_COUNT; i++) {
+ int old_state = controller_triggers[i];
+ int trigger_id = i == 0 ? GLFW_GAMEPAD_AXIS_LEFT_TRIGGER : GLFW_GAMEPAD_AXIS_RIGHT_TRIGGER;
+ controller_triggers[i] = state.axes[trigger_id] < CONTROLLER_TRIGGER_THRESHOLD ? GLFW_RELEASE : GLFW_PRESS;
+ if (old_state != controller_triggers[i]) {
+ // State Changed
+ glfw_controller_trigger(trigger_id, controller_triggers[i]);
+ }
+ }
+}
+
+// Pick Controller
+static int joysticks[GLFW_JOYSTICK_LAST + 1];
+static void pick_new_controller() {
+ current_controller = -1;
+ for (int i = GLFW_JOYSTICK_1; i <= GLFW_JOYSTICK_LAST; i++) {
+ if (joysticks[i] == 1) {
+ current_controller = i;
+ DEBUG("Using Controller: %s (%s)", glfwGetGamepadName(i), glfwGetJoystickName(i));
+ break;
+ }
+ }
+}
+static void find_controllers() {
+ for (int i = GLFW_JOYSTICK_1; i <= GLFW_JOYSTICK_LAST; i++) {
+ joysticks[i] = glfwJoystickIsGamepad(i);
+ }
+ pick_new_controller();
+}
+static void glfw_joystick(int jid, int event) {
+ if (event == GLFW_CONNECTED && glfwJoystickIsGamepad(jid)) {
+ joysticks[jid] = 1;
+ pick_new_controller();
+ } else if (event == GLFW_DISCONNECTED) {
+ joysticks[jid] = 0;
+ if (jid == current_controller) {
+ DEBUG("Controller Disconnected");
+ pick_new_controller();
+ }
+ }
+}
+
+// Release all keys/buttons when interaction is disabled and vice versa.
+static void emit_events_after_is_interactable_change() {
+ if (is_running) {
+ for (int i = GLFW_KEY_SPACE; i <= GLFW_KEY_LAST; i++) {
+ int state = glfwGetKey(glfw_window, i);
+ if (state == GLFW_PRESS) {
+ glfw_key_raw(i, glfwGetKeyScancode(i), is_interactable ? GLFW_PRESS : GLFW_RELEASE, 0);
+ }
+ }
+ for (int i = GLFW_MOUSE_BUTTON_1; i <= GLFW_MOUSE_BUTTON_LAST; i++) {
+ int state = glfwGetMouseButton(glfw_window, i);
+ if (state == GLFW_PRESS) {
+ glfw_click_raw(i, is_interactable ? GLFW_PRESS : GLFW_RELEASE);
+ }
+ }
+ }
+}
+
+#endif
// Track If Raw Mouse Motion Is Enabled
static int raw_mouse_motion_enabled = 1;
@@ -333,6 +590,10 @@ void SDL_WM_SetCaption(const char *title, __attribute__((unused)) const char *ic
glfwSetMouseButtonCallback(glfw_window, glfw_click);
glfwSetScrollCallback(glfw_window, glfw_scroll);
+ // Setup Controller Support
+ find_controllers();
+ glfwSetJoystickCallback(glfw_joystick);
+
// Make Window Context Current
glfwMakeContextCurrent(glfw_window);
@@ -408,6 +669,9 @@ void _media_handle_SDL_PollEvent() {
// Process GLFW Events
glfwPollEvents();
+ // Controller
+ update_controller_state();
+
// Close Window
if (glfwWindowShouldClose(glfw_window)) {
SDL_Event event;
@@ -439,10 +703,6 @@ void media_cleanup() {
}
}
-// Store Cursor State
-static int cursor_grabbed = 0;
-static int cursor_visible = 1;
-
// Update GLFW Cursor State (Client Only)
static void update_cursor() {
#ifndef MCPI_HEADLESS_MODE
@@ -475,9 +735,16 @@ static void update_cursor() {
glfwSetInputMode(glfw_window, GLFW_CURSOR, new_mode);
// Handle Cursor Lock/Unlock
- 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))) {
+ if ((new_mode == GLFW_CURSOR_DISABLED && old_mode != GLFW_CURSOR_DISABLED) || (new_mode != GLFW_CURSOR_DISABLED && old_mode == GLFW_CURSOR_DISABLED)) {
// Use Raw Mouse Motion
- glfwSetInputMode(glfw_window, GLFW_RAW_MOUSE_MOTION, new_mode == GLFW_CURSOR_DISABLED ? GLFW_TRUE : GLFW_FALSE);
+ if (raw_mouse_motion_enabled) {
+ glfwSetInputMode(glfw_window, GLFW_RAW_MOUSE_MOTION, new_mode == GLFW_CURSOR_DISABLED ? GLFW_TRUE : GLFW_FALSE);
+ }
+
+ // Request Focus
+ if (!glfwGetWindowAttrib(glfw_window, GLFW_FOCUSED)) {
+ glfwRequestWindowAttention(glfw_window);
+ }
}
// Reset Mouse Position When Unlocking