From 6766d91af32bb7265ba4362317ed497cf523412a Mon Sep 17 00:00:00 2001
From: Federico Terzi <federicoterzi96@gmail.com>
Date: Mon, 22 Jun 2020 19:09:51 +0200
Subject: [PATCH] Prevent espanso crash when an X11 exception occurs. Fix #312

---
 native/liblinuxbridge/bridge.cpp | 14 ++++++++++++++
 native/liblinuxbridge/bridge.h   | 12 +++++++++++-
 src/bridge/linux.rs              |  3 +++
 src/context/linux.rs             | 12 +++++++++++-
 4 files changed, 39 insertions(+), 2 deletions(-)

diff --git a/native/liblinuxbridge/bridge.cpp b/native/liblinuxbridge/bridge.cpp
index 99f13e9..ec31d09 100644
--- a/native/liblinuxbridge/bridge.cpp
+++ b/native/liblinuxbridge/bridge.cpp
@@ -77,14 +77,20 @@ xdo_t * xdo_context;
 
 // Callback invoked when a new key event occur.
 void event_callback (XPointer, XRecordInterceptData*);
+int error_callback(Display *display, XErrorEvent *error);
 
 KeypressCallback keypress_callback;
+X11ErrorCallback x11_error_callback;
 void * context_instance;
 
 void register_keypress_callback(KeypressCallback callback) {
     keypress_callback = callback;
 }
 
+void register_error_callback(X11ErrorCallback callback) {
+    x11_error_callback = callback;
+}
+
 int32_t check_x11() {
     Display *check_disp = XOpenDisplay(NULL);
 
@@ -156,6 +162,9 @@ int32_t initialize(void * _context_instance) {
 
     xdo_context = xdo_new(NULL);
 
+    // Setup a custom error handler
+    XSetErrorHandler(&error_callback);
+
     /**
      * Note: We might never get a MappingNotify event if the
      * modifier and keymap information was never cached in Xlib.
@@ -272,6 +281,11 @@ void event_callback(XPointer p, XRecordInterceptData *hook)
     XRecordFreeData(hook);
 }
 
+int error_callback(Display *display, XErrorEvent *error) {
+    x11_error_callback(context_instance, error->error_code, error->request_code, error->minor_code);
+    return 0;
+}
+
 void release_all_keys() {
     char keys[32];
     XQueryKeymap(xdo_context->xdpy, keys);  // Get the current status of the keyboard
diff --git a/native/liblinuxbridge/bridge.h b/native/liblinuxbridge/bridge.h
index 5e2c359..5102d0c 100644
--- a/native/liblinuxbridge/bridge.h
+++ b/native/liblinuxbridge/bridge.h
@@ -49,7 +49,6 @@ extern "C" void cleanup();
  * while the second is the size of the array.
  */
 typedef void (*KeypressCallback)(void * self, const char *buffer, int32_t len, int32_t event_type, int32_t key_code);
-
 extern KeypressCallback keypress_callback;
 
 /*
@@ -57,6 +56,17 @@ extern KeypressCallback keypress_callback;
  */
 extern "C" void register_keypress_callback(KeypressCallback callback);
 
+/*
+ * Called when a X11 error occurs
+ */
+typedef void (*X11ErrorCallback)(void * self, char error_code, char request_code, char minor_code);
+extern X11ErrorCallback x11_error_callback;
+
+/*
+ * Register the callback that will be called when an X11 error occurs
+ */
+extern "C" void register_error_callback(X11ErrorCallback callback);
+
 /*
  * Type the given string by simulating Key Presses
  */
diff --git a/src/bridge/linux.rs b/src/bridge/linux.rs
index ef04d27..d7ceede 100644
--- a/src/bridge/linux.rs
+++ b/src/bridge/linux.rs
@@ -32,6 +32,9 @@ extern "C" {
     pub fn get_active_window_class(buffer: *mut c_char, size: i32) -> i32;
     pub fn get_active_window_executable(buffer: *mut c_char, size: i32) -> i32;
     pub fn is_current_window_special() -> i32;
+    pub fn register_error_callback(
+        cb: extern "C" fn(_self: *mut c_void, error_code: c_char, request_code: c_char, minor_code: c_char),
+    );
 
     // Keyboard
     pub fn register_keypress_callback(
diff --git a/src/context/linux.rs b/src/context/linux.rs
index 6a4ddb1..c1acae9 100644
--- a/src/context/linux.rs
+++ b/src/context/linux.rs
@@ -21,7 +21,7 @@ use crate::bridge::linux::*;
 use crate::config::Configs;
 use crate::event::KeyModifier::*;
 use crate::event::*;
-use log::{debug, error};
+use log::{debug, error, warn};
 use std::ffi::CStr;
 use std::os::raw::{c_char, c_void};
 use std::process::exit;
@@ -59,6 +59,7 @@ impl LinuxContext {
             let context_ptr = &*context as *const LinuxContext as *const c_void;
 
             register_keypress_callback(keypress_callback);
+            register_error_callback(error_callback);
 
             let res = initialize(context_ptr);
             if res <= 0 {
@@ -155,3 +156,12 @@ extern "C" fn keypress_callback(
         }
     }
 }
+
+extern "C" fn error_callback(
+    _self: *mut c_void,
+    error_code: c_char,
+    request_code: c_char,
+    minor_code: c_char,
+) {
+    warn!("X11 reported an error code: {}, request_code: {} and minor_code: {}", error_code, request_code, minor_code);
+}