Repository: PistonDevelopers/glfw-rs (language: rust)

Issue:
Custom callbacks for window events
I would like to implement custom callbacks for window events. I am searching for a function  like set_key_callback in the c implementation. I tried to figure out how c binding in rust works and to implement it on my own in the glfw-rs sourcecode. Sadly I did not go far and do not really understand further than when the first *ffi* appears in the sourcecode. I do not know if the methods are left out intentionally and I am a rust newcomer and am so not 100 percent sure if there are language specific reasons for this. I hope I did not see any obvious reasons or notes why this is not implemented.


Discussion / hints:
A year later and still waiting on this 🙁
Anyone willing to make an attempt at this?

Fix patch:
diff --git a/Cargo.toml b/Cargo.toml
index 1bc40bef..d6f7dff7 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -40,7 +40,8 @@ version = "0.37.2"
 log = "0.4"
 
 [features]
-all = ["image", "vulkan", "log", "wayland"]
+all = ["image", "vulkan", "log", "wayland", "glfw-callbacks"]
 default = ["glfw-sys"]
 vulkan = ["ash"]
 wayland = ["glfw-sys/wayland"]
+glfw-callbacks = []
diff --git a/src/callbacks.rs b/src/callbacks.rs
index c09708b7..9bbd6548 100644
--- a/src/callbacks.rs
+++ b/src/callbacks.rs
@@ -15,16 +15,6 @@
 
 //! Private callback support functions.
 
-use std::ffi::CStr;
-use std::mem;
-use std::os::raw::{c_char, c_double, c_int, c_uint};
-use std::path::PathBuf;
-use std::slice;
-use std::str;
-use std::sync::mpsc::Sender;
-
-use super::*;
-
 macro_rules! callback (
     (
         type Args = ($($arg:ident: $arg_ty:ty),*);
@@ -120,10 +110,6 @@ pub mod joystick {
     );
 }
 
-unsafe fn get_sender(window: &*mut ffi::GLFWwindow) -> &Sender<(f64, WindowEvent)> {
-    &*(ffi::glfwGetWindowUserPointer(*window) as *const std::sync::mpsc::Sender<(f64, WindowEvent)>)
-}
-
 pub mod unbuffered {
     use crate::{WindowEvent, WindowId};
     use std::cell::RefCell;
@@ -187,53 +173,4 @@ pub mod unbuffered {
         });
         UnsetHandlerGuard { _private: () }
     }
-}
-
-// Note that this macro creates a static function pointer rather than a plain function.
-// This makes it more ergonomic to embed in an Option; see set_window_callback! in lib.rs
-macro_rules! window_callback (
-    (fn $name:ident () => $event:ident) => (
-        pub static $name: extern "C" fn(window: *mut ffi::GLFWwindow) = {
-            extern "C" fn actual_callback(window: *mut ffi::GLFWwindow) {
-                unsafe {
-                    let event = (ffi::glfwGetTime() as f64, WindowEvent::$event);
-                    if let Some(event) = unbuffered::handle(window as WindowId, event) {
-                        get_sender(&window).send(event).unwrap();
-                    }
-                }
-            }
-            actual_callback
-        };
-     );
-    (fn $name:ident ($($ext_arg:ident: $ext_arg_ty:ty),*) => $event:ident($($arg_conv:expr),*)) => (
-        pub static $name: extern "C" fn(window: *mut ffi::GLFWwindow $(, $ext_arg: $ext_arg_ty)*) = {
-            extern "C" fn actual_callback(window: *mut ffi::GLFWwindow $(, $ext_arg: $ext_arg_ty)*) {
-                unsafe {
-                    let event = (ffi::glfwGetTime() as f64, WindowEvent::$event($($arg_conv),*));
-                    if let Some(event) = unbuffered::handle(window as WindowId, event) {
-                        get_sender(&window).send(event).unwrap();
-                    }
-                }
-            }
-            actual_callback
-        };
-     );
-);
-
-window_callback!(fn window_pos_callback(xpos: c_int, ypos: c_int)                           => Pos(xpos as i32, ypos as i32));
-window_callback!(fn window_size_callback(width: c_int, height: c_int)                       => Size(width as i32, height as i32));
-window_callback!(fn window_close_callback()                                                 => Close);
-window_callback!(fn window_refresh_callback()                                               => Refresh);
-window_callback!(fn window_focus_callback(focused: c_int)                                   => Focus(focused == ffi::TRUE));
-window_callback!(fn window_iconify_callback(iconified: c_int)                               => Iconify(iconified == ffi::TRUE));
-window_callback!(fn framebuffer_size_callback(width: c_int, height: c_int)                  => FramebufferSize(width as i32, height as i32));
-window_callback!(fn mouse_button_callback(button: c_int, action: c_int, mods: c_int)        => MouseButton(mem::transmute(button), mem::transmute(action), Modifiers::from_bits(mods).unwrap()));
-window_callback!(fn cursor_pos_callback(xpos: c_double, ypos: c_double)                     => CursorPos(xpos as f64, ypos as f64));
-window_callback!(fn cursor_enter_callback(entered: c_int)                                   => CursorEnter(entered == ffi::TRUE));
-window_callback!(fn scroll_callback(xpos: c_double, ypos: c_double)                         => Scroll(xpos as f64, ypos as f64));
-window_callback!(fn key_callback(key: c_int, scancode: c_int, action: c_int, mods: c_int)   => Key(mem::transmute(key), scancode, mem::transmute(action), Modifiers::from_bits(mods).unwrap()));
-window_callback!(fn char_callback(character: c_uint)                                        => Char(::std::char::from_u32(character).unwrap()));
-window_callback!(fn char_mods_callback(character: c_uint, mods: c_int)                      => CharModifiers(::std::char::from_u32(character).unwrap(), Modifiers::from_bits(mods).unwrap()));
-window_callback!(fn drop_callback(num_paths: c_int, paths: *mut *const c_char)              => FileDrop(slice::from_raw_parts(paths, num_paths as usize).iter().map(|path| PathBuf::from(str::from_utf8(CStr::from_ptr(*path).to_bytes()).unwrap().to_string())).collect()));
-window_callback!(fn window_maximize_callback(maximized: c_int)                              => Maximize(maximized == ffi::TRUE));
-window_callback!(fn window_content_scale_callback(xscale: c_float, yscale: c_float)         => ContentScale(xscale as f32, yscale as f32));
+}
\ No newline at end of file
diff --git a/src/lib.rs b/src/lib.rs
index 4077f133..efb7a197 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -81,6 +81,174 @@
 
 // TODO: Document differences between GLFW and glfw-rs
 
+macro_rules! make_user_callback_functions {
+    (
+        doc -> $doc:literal,
+        set -> $set:ident,
+        unset -> $unset:ident,
+        poll -> $poll:ident,
+        callback_field -> $callback_field:ident,
+        poll_field -> $poll_field:ident,
+        glfw -> $glfw:ident,
+        args -> ($($args:ty),*),
+        secret -> $secret:ident
+    ) => {
+
+        #[cfg(feature = "glfw-callbacks")]
+        #[doc = $doc]
+        pub fn $set<T>(&mut self, callback: T)
+        where T: FnMut(&mut Window, $($args),*) + 'static {
+            unsafe {
+                let callbacks = WindowCallbacks::get_callbacks(self.ptr);
+                callbacks.$callback_field = Some(Box::new(callback));
+                ffi::$glfw(self.ptr, Some(Self::$secret));
+            }
+        }
+
+        #[cfg(not(feature = "glfw-callbacks"))]
+        #[doc = $doc]
+        pub fn $set<T>(&mut self, callback: T)
+        where T: FnMut($($args),*) + 'static {
+            unsafe {
+                let callbacks = WindowCallbacks::get_callbacks(self.ptr);
+                callbacks.$callback_field = Some(Box::new(callback));
+                ffi::$glfw(self.ptr, Some(Self::$secret));
+            }
+        }
+
+        #[doc = $doc]
+        pub fn $unset(&mut self) {
+            unsafe {
+                let callbacks = WindowCallbacks::get_callbacks(self.ptr);
+                callbacks.$callback_field = None;
+
+                // We're removing the callback, if theres no polling either, set to null
+                if !callbacks.$poll_field {
+                    ffi::$glfw(self.ptr, None);
+                }
+            }
+        }
+
+        #[doc = $doc]
+        pub fn $poll(&mut self, should_poll: bool) {
+            unsafe {
+                let callbacks = WindowCallbacks::get_callbacks(self.ptr);
+                callbacks.$poll_field = should_poll;
+
+                // If no polling and not custom callback, set glfw callback to null
+                if should_poll {
+                    ffi::$glfw(self.ptr, Some(Self::$secret));
+                } else if callbacks.$callback_field.is_none() {
+                    ffi::$glfw(self.ptr, None);
+                }
+            }
+        }
+    }
+}
+
+macro_rules! new_callback {
+    (
+        doc -> $doc:literal,
+        set -> $set:ident,
+        unset -> $unset:ident,
+        poll -> $poll:ident,
+        callback_field -> $callback_field:ident,
+        poll_field -> $poll_field:ident,
+        window_event -> $window_event:ident ($($args:ty),+),
+        glfw -> $glfw:ident ($($glfw_arg_names:ident: $glfw_args:ty),*),
+        convert_args -> ($($convert_args:expr),*),
+        secret -> $secret:ident
+    ) => {
+
+        #[allow(unused_unsafe)]
+        extern "C" fn $secret(glfw_window: *mut GLFWwindow, $($glfw_arg_names: $glfw_args),*) {
+            unsafe {
+                let callbacks = WindowCallbacks::get_callbacks(glfw_window);
+                #[cfg(feature = "glfw-callbacks")]
+                let window = &mut *callbacks.window_ptr;
+                if let Some(func) = &mut callbacks.$callback_field {
+                    #[cfg(feature = "glfw-callbacks")]
+                    {
+                        func(window, $($convert_args),*);
+                    }
+                    #[cfg(not(feature = "glfw-callbacks"))]
+                    {
+                        func($($convert_args),*);
+                    }
+                }
+                if callbacks.$poll_field {
+                    let event = (ffi::glfwGetTime() as f64, WindowEvent::$window_event($($convert_args),*));
+                    if let Some(event) = callbacks::unbuffered::handle(glfw_window as WindowId, event) {
+                        callbacks.sender.send(event).unwrap();
+                    }
+                }
+            }
+        }
+
+        make_user_callback_functions!(
+            doc -> $doc,
+            set -> $set,
+            unset -> $unset,
+            poll -> $poll,
+            callback_field -> $callback_field,
+            poll_field -> $poll_field,
+            glfw -> $glfw,
+            args -> ($($args),*),
+            secret -> $secret
+        );
+    };
+    (
+        doc -> $doc:literal,
+        set -> $set:ident,
+        unset -> $unset:ident,
+        poll -> $poll:ident,
+        callback_field -> $callback_field:ident,
+        poll_field -> $poll_field:ident,
+        window_event -> $window_event:ident,
+        glfw -> $glfw:ident ($($glfw_arg_names:ident: $glfw_args:ty),*),
+        convert_args -> ($($convert_args:expr),*),
+        secret -> $secret:ident
+    ) => {
+
+        #[allow(unused_unsafe)]
+        extern "C" fn $secret(glfw_window: *mut GLFWwindow, $($glfw_arg_names: $glfw_args),*) {
+            unsafe {
+                let callbacks = WindowCallbacks::get_callbacks(glfw_window);
+                #[cfg(feature = "glfw-callbacks")]
+                let window = &mut *callbacks.window_ptr;
+                if let Some(func) = &mut callbacks.$callback_field {
+                    #[cfg(feature = "glfw-callbacks")]
+                    {
+                        func(window);
+                    }
+                    #[cfg(not(feature = "glfw-callbacks"))]
+                    {
+                        func();
+                    }
+                }
+                if callbacks.$poll_field {
+                    let event = (ffi::glfwGetTime() as f64, WindowEvent::$window_event);
+                    if let Some(event) = callbacks::unbuffered::handle(glfw_window as WindowId, event) {
+                        callbacks.sender.send(event).unwrap();
+                    }
+                }
+            }
+        }
+
+        make_user_callback_functions!(
+            doc -> $doc,
+            set -> $set,
+            unset -> $unset,
+            poll -> $poll,
+            callback_field -> $callback_field,
+            poll_field -> $poll_field,
+            glfw -> $glfw,
+            args -> (),
+            secret -> $secret
+        );
+    }
+}
+
 #[cfg(feature = "log")]
 #[macro_use]
 extern crate log;
@@ -97,7 +265,7 @@ use raw_window_handle::{HasRawWindowHandle, RawWindowHandle, HasRawDisplayHandle
 use std::error;
 use std::ffi::{CStr, CString};
 use std::fmt;
-use std::marker::Send;
+use std::marker::{Send};
 use std::mem;
 #[cfg(feature = "vulkan")]
 use std::os::raw::c_uint;
@@ -110,9 +278,12 @@ use std::ptr;
 use std::slice;
 use std::sync::atomic::{AtomicUsize, Ordering};
 use std::sync::mpsc::{channel, Receiver, Sender};
+#[allow(unused)]
+use std::ffi::*;
 
 #[cfg(feature = "vulkan")]
 use ash::vk;
+use crate::ffi::GLFWwindow;
 
 /// Alias to `MouseButton1`, supplied for improved clarity.
 pub use self::MouseButton::Button1 as MouseButtonLeft;
@@ -124,6 +295,14 @@ pub use self::MouseButton::Button3 as MouseButtonMiddle;
 mod callbacks;
 pub mod ffi;
 
+#[cfg(feature = "glfw-callbacks")]
+/// Type returned by creating a window
+pub type WindowType = Box<Window>;
+
+#[cfg(not(feature = "glfw-callbacks"))]
+/// Type returned by creating a window
+pub type WindowType = Window;
+
 /// Unique identifier for a `Window`.
 pub type WindowId = usize;
 
@@ -1078,7 +1257,7 @@ impl Glfw {
         height: u32,
         title: &str,
         mode: WindowMode<'_>,
-    ) -> Option<(Window, Receiver<(f64, WindowEvent)>)> {
+    ) -> Option<(WindowType, Receiver<(f64, WindowEvent)>)> {
         #[cfg(feature = "wayland")]
         {
             // Has to be set otherwise wayland refuses to open window.
@@ -1095,7 +1274,7 @@ impl Glfw {
         title: &str,
         mode: WindowMode<'_>,
         share: Option<&Window>,
-    ) -> Option<(Window, Receiver<(f64, WindowEvent)>)> {
+    ) -> Option<(WindowType, Receiver<(f64, WindowEvent)>)> {
         let ptr = unsafe {
             with_c_str(title, |title| {
                 ffi::glfwCreateWindow(
@@ -1115,20 +1294,29 @@ impl Glfw {
         } else {
             let (drop_sender, drop_receiver) = channel();
             let (sender, receiver) = channel();
-            unsafe {
-                ffi::glfwSetWindowUserPointer(ptr, mem::transmute(Box::new(sender)));
-            }
-            Some((
-                Window {
+            let window = Window {
                     ptr,
                     glfw: self.clone(),
                     is_shared: share.is_some(),
                     drop_sender: Some(drop_sender),
                     drop_receiver,
                     current_cursor: None,
-                },
-                receiver,
-            ))
+            };
+            #[allow(unused_mut)]
+            let mut callbacks = Box::new(WindowCallbacks::new(sender));
+
+            #[cfg(feature = "glfw-callbacks")]
+            let window = Box::new(window);
+
+            unsafe {
+                #[cfg(feature = "glfw-callbacks")]
+                {
+                    callbacks.window_ptr = mem::transmute(&*window);
+                }
+                ffi::glfwSetWindowUserPointer(ptr, mem::transmute(callbacks));
+            }
+
+            Some((window, receiver))
         }
     }
 
@@ -1420,6 +1608,136 @@ impl Drop for Glfw {
     }
 }
 
+#[cfg(feature = "glfw-callbacks")]
+struct WindowCallbacks {
+    window_ptr: *mut Window,
+    sender: Sender<(f64, WindowEvent)>,
+    pos_callback: Option<Box<dyn FnMut(&mut Window, i32, i32)>>,
+    size_callback: Option<Box<dyn FnMut(&mut Window, i32, i32)>>,
+    close_callback: Option<Box<dyn FnMut(&mut Window)>>,
+    refresh_callback: Option<Box<dyn FnMut(&mut Window)>>,
+    focus_callback: Option<Box<dyn FnMut(&mut Window, bool)>>,
+    iconify_callback: Option<Box<dyn FnMut(&mut Window, bool)>>,
+    framebuffer_size_callback: Option<Box<dyn FnMut(&mut Window, i32, i32)>>,
+    key_callback: Option<Box<dyn FnMut(&mut Window, Key, Scancode, Action, Modifiers)>>,
+    char_callback: Option<Box<dyn FnMut(&mut Window, char)>>,
+    char_mods_callback: Option<Box<dyn FnMut(&mut Window, char, Modifiers)>>,
+    mouse_button_callback: Option<Box<dyn FnMut(&mut Window, MouseButton, Action, Modifiers)>>,
+    cursor_pos_callback: Option<Box<dyn FnMut(&mut Window, f64, f64)>>,
+    cursor_enter_callback: Option<Box<dyn FnMut(&mut Window, bool)>>,
+    scroll_callback: Option<Box<dyn FnMut(&mut Window, f64, f64)>>,
+    drag_and_drop_callback: Option<Box<dyn FnMut(&mut Window, Vec<PathBuf>)>>,
+    maximize_callback: Option<Box<dyn FnMut(&mut Window, bool)>>,
+    content_scale_callback: Option<Box<dyn FnMut(&mut Window, f32, f32)>>,
+    pos_polling: bool,
+    size_polling: bool,
+    close_polling: bool,
+    refresh_polling: bool,
+    focus_polling: bool,
+    iconify_polling: bool,
+    framebuffer_size_polling: bool,
+    key_polling: bool,
+    char_polling: bool,
+    char_mods_polling: bool,
+    mouse_button_polling: bool,
+    cursor_pos_polling: bool,
+    cursor_enter_polling: bool,
+    scroll_polling: bool,
+    drag_and_drop_polling: bool,
+    maximize_polling: bool,
+    content_scale_polling: bool
+}
+
+#[cfg(not(feature = "glfw-callbacks"))]
+struct WindowCallbacks {
+    sender: Sender<(f64, WindowEvent)>,
+    pos_callback: Option<Box<dyn FnMut(i32, i32)>>,
+    size_callback: Option<Box<dyn FnMut(i32, i32)>>,
+    close_callback: Option<Box<dyn FnMut()>>,
+    refresh_callback: Option<Box<dyn FnMut()>>,
+    focus_callback: Option<Box<dyn FnMut(bool)>>,
+    iconify_callback: Option<Box<dyn FnMut(bool)>>,
+    framebuffer_size_callback: Option<Box<dyn FnMut(i32, i32)>>,
+    key_callback: Option<Box<dyn FnMut(Key, Scancode, Action, Modifiers)>>,
+    char_callback: Option<Box<dyn FnMut(char)>>,
+    char_mods_callback: Option<Box<dyn FnMut(char, Modifiers)>>,
+    mouse_button_callback: Option<Box<dyn FnMut(MouseButton, Action, Modifiers)>>,
+    cursor_pos_callback: Option<Box<dyn FnMut(f64, f64)>>,
+    cursor_enter_callback: Option<Box<dyn FnMut(bool)>>,
+    scroll_callback: Option<Box<dyn FnMut(f64, f64)>>,
+    drag_and_drop_callback: Option<Box<dyn FnMut(Vec<PathBuf>)>>,
+    maximize_callback: Option<Box<dyn FnMut(bool)>>,
+    content_scale_callback: Option<Box<dyn FnMut(f32, f32)>>,
+    pos_polling: bool,
+    size_polling: bool,
+    close_polling: bool,
+    refresh_polling: bool,
+    focus_polling: bool,
+    iconify_polling: bool,
+    framebuffer_size_polling: bool,
+    key_polling: bool,
+    char_polling: bool,
+    char_mods_polling: bool,
+    mouse_button_polling: bool,
+    cursor_pos_polling: bool,
+    cursor_enter_polling: bool,
+    scroll_polling: bool,
+    drag_and_drop_polling: bool,
+    maximize_polling: bool,
+    content_scale_polling: bool
+}
+
+impl WindowCallbacks {
+
+    fn new(receiver: Sender<(f64, WindowEvent)>) -> Self {
+        Self {
+            #[cfg(feature = "glfw-callbacks")]
+            window_ptr: std::ptr::null_mut(),
+            sender: receiver,
+            pos_callback: None,
+            size_callback: None,
+            close_callback: None,
+            refresh_callback: None,
+            focus_callback: None,
+            iconify_callback: None,
+            framebuffer_size_callback: None,
+            key_callback: None,
+            char_callback: None,
+            char_mods_callback: None,
+            mouse_button_callback: None,
+            cursor_pos_callback: None,
+            cursor_enter_callback: None,
+            scroll_callback: None,
+            drag_and_drop_callback: None,
+            maximize_callback: None,
+            content_scale_callback: None,
+            pos_polling: false,
+            size_polling: false,
+            close_polling: false,
+            refresh_polling: false,
+            focus_polling: false,
+            iconify_polling: false,
+            framebuffer_size_polling: false,
+            key_polling: false,
+            char_polling: false,
+            char_mods_polling: false,
+            mouse_button_polling: false,
+            cursor_pos_polling: false,
+            cursor_enter_polling: false,
+            scroll_polling: false,
+            drag_and_drop_polling: false,
+            maximize_polling: false,
+            content_scale_polling: false
+        }
+    }
+
+    fn get_callbacks<'a>(window: *mut GLFWwindow) -> &'a mut WindowCallbacks {
+        unsafe {
+            &mut *(ffi::glfwGetWindowUserPointer(window) as *mut WindowCallbacks)
+        }
+    }
+}
+
 /// Wrapper for `glfwGetVersion`.
 pub fn get_version() -> Version {
     unsafe {
@@ -1971,20 +2289,6 @@ pub struct Window {
     pub glfw: Glfw,
 }
 
-macro_rules! set_window_callback {
-    ($window:ident, $should_poll:expr, $ll_fn:ident, $callback:ident) => {{
-        if $should_poll {
-            unsafe {
-                ffi::$ll_fn($window.ptr, Some(callbacks::$callback));
-            }
-        } else {
-            unsafe {
-                ffi::$ll_fn($window.ptr, None);
-            }
-        }
-    }};
-}
-
 impl Window {
     /// Returns the address of the specified client API or extension function if
     /// it is supported by the context associated with this Window. If this Window is not the
@@ -2041,6 +2345,9 @@ impl Window {
     ) -> vk::Result {
         unsafe { ffi::glfwCreateWindowSurface(instance, self.ptr, allocator, surface) }
     }
+
+    /// Creates a new shared window.
+    ///
     /// Wrapper for `glfwCreateWindow`.
     pub fn create_shared(
         &self,
@@ -2048,7 +2355,7 @@ impl Window {
         height: u32,
         title: &str,
         mode: WindowMode<'_>,
-    ) -> Option<(Window, Receiver<(f64, WindowEvent)>)> {
+    ) -> Option<(WindowType, Receiver<(f64, WindowEvent)>)> {
         self.glfw
             .create_window_intern(width, height, title, mode, Some(self))
     }
@@ -2394,15 +2701,247 @@ impl Window {
         unsafe { ffi::glfwGetWindowAttrib(self.ptr, ffi::HOVERED) == ffi::TRUE }
     }
 
-    /// Wrapper for `glfwSetWindowPosCallback`.
-    pub fn set_pos_polling(&mut self, should_poll: bool) {
-        set_window_callback!(
-            self,
-            should_poll,
-            glfwSetWindowPosCallback,
-            window_pos_callback
-        );
-    }
+    new_callback!(
+        doc -> "Wrapper for `glfwSetWindowPosCallback`.",
+        set -> set_pos_callback,
+        unset -> unset_pos_callback,
+        poll -> set_pos_polling,
+        callback_field -> pos_callback,
+        poll_field -> pos_polling,
+        window_event -> Pos(i32, i32),
+        glfw -> glfwSetWindowPosCallback(x: c_int, y: c_int),
+        convert_args -> (x as i32, y as i32),
+        secret -> _pos_callback
+    );
+
+    new_callback!(
+        doc -> "Wrapper for `glfwSetWindowSizeCallback`.",
+        set -> set_size_callback,
+        unset -> unset_size_callback,
+        poll -> set_size_polling,
+        callback_field -> size_callback,
+        poll_field -> size_polling,
+        window_event -> Size(i32, i32),
+        glfw -> glfwSetWindowSizeCallback(width: c_int, height: c_int),
+        convert_args -> (width as i32, height as i32),
+        secret -> _size_callback
+    );
+
+    new_callback!(
+        doc -> "Wrapper for `glfwSetWindowCloseCallback`.",
+        set -> set_close_callback,
+        unset -> unset_close_callback,
+        poll -> set_close_polling,
+        callback_field -> close_callback,
+        poll_field -> close_polling,
+        window_event -> Close,
+        glfw -> glfwSetWindowCloseCallback(),
+        convert_args -> (),
+        secret -> _close_callback
+    );
+
+    new_callback!(
+        doc -> "Wrapper for `glfwSetWindowRefreshCallback`.",
+        set -> set_refresh_callback,
+        unset -> unset_refresh_callback,
+        poll -> set_refresh_polling,
+        callback_field -> refresh_callback,
+        poll_field -> refresh_polling,
+        window_event -> Refresh,
+        glfw -> glfwSetWindowRefreshCallback(),
+        convert_args -> (),
+        secret -> _refresh_callback
+    );
+
+    new_callback!(
+        doc -> "Wrapper for `glfwSetWindowFocusCallback`.",
+        set -> set_focus_callback,
+        unset -> unset_focus_callback,
+        poll -> set_focus_polling,
+        callback_field -> focus_callback,
+        poll_field -> focus_polling,
+        window_event -> Focus(bool),
+        glfw -> glfwSetWindowFocusCallback(focused: c_int),
+        convert_args -> (focused == ffi::TRUE),
+        secret -> _focus_callback
+    );
+
+    new_callback!(
+        doc -> "Wrapper for `glfwSetWindowIconifyCallback`.",
+        set -> set_iconify_callback,
+        unset -> unset_iconify_callback,
+        poll -> set_iconify_polling,
+        callback_field -> iconify_callback,
+        poll_field -> iconify_polling,
+        window_event -> Iconify(bool),
+        glfw -> glfwSetWindowIconifyCallback(iconified: c_int),
+        convert_args -> (iconified == ffi::TRUE),
+        secret -> _iconify_callback
+    );
+
+    new_callback!(
+        doc -> "Wrapper for `glfwSetFramebufferSizeCallback`.",
+        set -> set_framebuffer_size_callback,
+        unset -> unset_framebuffer_size_callback,
+        poll -> set_framebuffer_size_polling,
+        callback_field -> framebuffer_size_callback,
+        poll_field -> framebuffer_size_polling,
+        window_event -> FramebufferSize(i32, i32),
+        glfw -> glfwSetFramebufferSizeCallback(width: c_int, height: c_int),
+        convert_args -> (width as i32, height as i32),
+        secret -> _framebuffer_size_callback
+    );
+
+    new_callback!(
+        doc -> "Wrapper for `glfwSetKeyCallback`.",
+        set -> set_key_callback,
+        unset -> unset_key_callback,
+        poll -> set_key_polling,
+        callback_field -> key_callback,
+        poll_field -> key_polling,
+        window_event -> Key(Key, Scancode, Action, Modifiers),
+        glfw -> glfwSetKeyCallback(key: c_int, scancode: c_int, action: c_int, mods: c_int),
+        convert_args -> (
+            mem::transmute(key),
+            scancode, mem::transmute(action),
+            Modifiers::from_bits(mods).unwrap()
+        ),
+        secret -> _key_callback
+    );
+
+    new_callback!(
+        doc -> "Wrapper for `glfwSetCharCallback`.",
+        set -> set_char_callback,
+        unset -> unset_char_callback,
+        poll -> set_char_polling,
+        callback_field -> char_callback,
+        poll_field -> char_polling,
+        window_event -> Char(char),
+        glfw -> glfwSetCharCallback(character: c_uint),
+        convert_args -> (::std::char::from_u32(character).unwrap()),
+        secret -> _char_callback
+    );
+
+    new_callback!(
+        doc -> "Wrapper for `glfwSetCharModsCallback`.",
+        set -> set_char_mods_callback,
+        unset -> unset_char_mods_callback,
+        poll -> set_char_mods_polling,
+        callback_field -> char_mods_callback,
+        poll_field -> char_mods_polling,
+        window_event -> CharModifiers(char, Modifiers),
+        glfw -> glfwSetCharModsCallback(character: c_uint, mods: c_int),
+        convert_args -> (
+            ::std::char::from_u32(character).unwrap(),
+            Modifiers::from_bits(mods).unwrap()
+        ),
+        secret -> _char_mods_callback
+    );
+
+    new_callback!(
+        doc -> "Wrapper for `glfwSetMouseButtonCallback`.",
+        set -> set_mouse_button_callback,
+        unset -> unset_mouse_button_callback,
+        poll -> set_mouse_button_polling,
+        callback_field -> mouse_button_callback,
+        poll_field -> mouse_button_polling,
+        window_event -> MouseButton(MouseButton, Action, Modifiers),
+        glfw -> glfwSetMouseButtonCallback(button: c_int, action: c_int, mods: c_int),
+        convert_args -> (
+            mem::transmute(button),
+            mem::transmute(action),
+            Modifiers::from_bits(mods).unwrap()
+        ),
+        secret -> _mouse_button_callback
+    );
+
+    new_callback!(
+        doc -> "Wrapper for `glfwSetCursorPosCallback`.",
+        set -> set_cursor_pos_callback,
+        unset -> unset_cursor_pos_callback,
+        poll -> set_cursor_pos_polling,
+        callback_field -> cursor_pos_callback,
+        poll_field -> cursor_pos_polling,
+        window_event -> CursorPos(f64, f64),
+        glfw -> glfwSetCursorPosCallback(x: c_double, y: c_double),
+        convert_args -> (x as f64, y as f64),
+        secret -> _cursor_pos_callback
+    );
+
+    new_callback!(
+        doc -> "Wrapper for `glfwSetCursorEnterCallback`.",
+        set -> set_cursor_enter_callback,
+        unset -> unset_cursor_enter_callback,
+        poll -> set_cursor_enter_polling,
+        callback_field -> cursor_enter_callback,
+        poll_field -> cursor_enter_polling,
+        window_event -> CursorEnter(bool),
+        glfw -> glfwSetCursorEnterCallback(entered: c_int),
+        convert_args -> (entered == ffi::TRUE),
+        secret -> _cursor_enter_callback
+    );
+
+    new_callback!(
+        doc -> "Wrapper for `glfwSetScrollCallback`.",
+        set -> set_scroll_callback,
+        unset -> unset_scroll_callback,
+        poll -> set_scroll_polling,
+        callback_field -> scroll_callback,
+        poll_field -> scroll_polling,
+        window_event -> Scroll(f64, f64),
+        glfw -> glfwSetScrollCallback(x: c_double, y: c_double),
+        convert_args -> (x as f64, y as f64),
+        secret -> _scroll_callback
+    );
+
+    new_callback!(
+        doc -> "Wrapper for `glfwSetDropCallback`.",
+        set -> set_drag_and_drop_callback,
+        unset -> unset_drag_and_drop_callback,
+        poll -> set_drag_and_drop_polling,
+        callback_field -> drag_and_drop_callback,
+        poll_field -> drag_and_drop_polling,
+        window_event -> FileDrop(Vec<PathBuf>),
+        glfw -> glfwSetDropCallback(num_paths: c_int, paths: *mut *const c_char),
+        convert_args -> ({
+            slice::from_raw_parts(paths, num_paths as usize)
+            .iter()
+            .map(|path| PathBuf::from(std::str::from_utf8({
+                CStr::from_ptr(*path)
+                    .to_bytes()
+            })
+            .unwrap()
+            .to_string()))
+            .collect()
+        }),
+        secret -> _drag_and_drop_callback
+    );
+
+    new_callback!(
+        doc -> "Wrapper for `glfwSetWindowMaximizeCallback`.",
+        set -> set_maximize_callback,
+        unset -> unset_maximize_callback,
+        poll -> set_maximize_polling,
+        callback_field -> maximize_callback,
+        poll_field -> maximize_polling,
+        window_event -> Maximize(bool),
+        glfw -> glfwSetWindowMaximizeCallback(maximized: c_int),
+        convert_args -> (maximized == ffi::TRUE),
+        secret -> _maximize_callback
+    );
+
+    new_callback!(
+        doc -> "Wrapper for `glfwSetWindowContentScaleCallback`.",
+        set -> set_content_scale_callback,
+        unset -> unset_content_scale_callback,
+        poll -> set_content_scale_polling,
+        callback_field -> content_scale_callback,
+        poll_field -> content_scale_polling,
+        window_event -> ContentScale(f32, f32),
+        glfw -> glfwSetWindowContentScaleCallback(xscale: c_float, yscale: c_float),
+        convert_args -> (xscale as f32, yscale as f32),
+        secret -> _content_scale_callback
+    );
 
     /// Starts or stops polling for all available events
     pub fn set_all_polling(&mut self, should_poll: bool) {
@@ -2425,91 +2964,6 @@ impl Window {
         self.set_content_scale_polling(should_poll);
     }
 
-    /// Wrapper for `glfwSetWindowSizeCallback`.
-    pub fn set_size_polling(&mut self, should_poll: bool) {
-        set_window_callback!(
-            self,
-            should_poll,
-            glfwSetWindowSizeCallback,
-            window_size_callback
-        );
-    }
-
-    /// Wrapper for `glfwSetWindowCloseCallback`.
-    pub fn set_close_polling(&mut self, should_poll: bool) {
-        set_window_callback!(
-            self,
-            should_poll,
-            glfwSetWindowCloseCallback,
-            window_close_callback
-        );
-    }
-
-    /// Wrapper for `glfwSetWindowRefreshCallback`.
-    pub fn set_refresh_polling(&mut self, should_poll: bool) {
-        set_window_callback!(
-            self,
-            should_poll,
-            glfwSetWindowRefreshCallback,
-            window_refresh_callback
-        );
-    }
-
-    /// Wrapper for `glfwSetWindowFocusCallback`.
-    pub fn set_focus_polling(&mut self, should_poll: bool) {
-        set_window_callback!(
-            self,
-            should_poll,
-            glfwSetWindowFocusCallback,
-            window_focus_callback
-        );
-    }
-
-    /// Wrapper for `glfwSetWindowIconifyCallback`.
-    pub fn set_iconify_polling(&mut self, should_poll: bool) {
-        set_window_callback!(
-            self,
-            should_poll,
-            glfwSetWindowIconifyCallback,
-            window_iconify_callback
-        );
-    }
-
-    /// Wrapper for `glfwSetFramebufferSizeCallback`.
-    pub fn set_framebuffer_size_polling(&mut self, should_poll: bool) {
-        set_window_callback!(
-            self,
-            should_poll,
-            glfwSetFramebufferSizeCallback,
-            framebuffer_size_callback
-        );
-    }
-
-    /// Wrapper for `glfwSetDropCallback`.
-    pub fn set_drag_and_drop_polling(&mut self, should_poll: bool) {
-        set_window_callback!(self, should_poll, glfwSetDropCallback, drop_callback);
-    }
-
-    /// Wrapper for `glfwSetWindowMaximizeCallback`.
-    pub fn set_maximize_polling(&mut self, should_poll: bool) {
-        set_window_callback!(
-            self,
-            should_poll,
-            glfwSetWindowMaximizeCallback,
-            window_maximize_callback
-        );
-    }
-
-    /// Wrapper for `glfwSetWindowContentScaleCallback`.
-    pub fn set_content_scale_polling(&mut self, should_poll: bool) {
-        set_window_callback!(
-            self,
-            should_poll,
-            glfwSetWindowContentScaleCallback,
-            window_content_scale_callback
-        );
-    }
-
     /// Wrapper for `glfwGetInputMode` called with `CURSOR`.
     pub fn get_cursor_mode(&self) -> CursorMode {
         unsafe { mem::transmute(ffi::glfwGetInputMode(self.ptr, ffi::CURSOR)) }
@@ -2682,61 +3136,6 @@ impl Window {
         }
     }
 
-    /// Wrapper for `glfwSetKeyCallback`.
-    pub fn set_key_polling(&mut self, should_poll: bool) {
-        set_window_callback!(self, should_poll, glfwSetKeyCallback, key_callback);
-    }
-
-    /// Wrapper for `glfwSetCharCallback`.
-    pub fn set_char_polling(&mut self, should_poll: bool) {
-        set_window_callback!(self, should_poll, glfwSetCharCallback, char_callback);
-    }
-
-    /// Wrapper for `glfwSetCharModsCallback`
-    pub fn set_char_mods_polling(&mut self, should_poll: bool) {
-        set_window_callback!(
-            self,
-            should_poll,
-            glfwSetCharModsCallback,
-            char_mods_callback
-        );
-    }
-
-    /// Wrapper for `glfwSetMouseButtonCallback`.
-    pub fn set_mouse_button_polling(&mut self, should_poll: bool) {
-        set_window_callback!(
-            self,
-            should_poll,
-            glfwSetMouseButtonCallback,
-            mouse_button_callback
-        );
-    }
-
-    /// Wrapper for `glfwSetCursorPosCallback`.
-    pub fn set_cursor_pos_polling(&mut self, should_poll: bool) {
-        set_window_callback!(
-            self,
-            should_poll,
-            glfwSetCursorPosCallback,
-            cursor_pos_callback
-        );
-    }
-
-    /// Wrapper for `glfwSetCursorEnterCallback`.
-    pub fn set_cursor_enter_polling(&mut self, should_poll: bool) {
-        set_window_callback!(
-            self,
-            should_poll,
-            glfwSetCursorEnterCallback,
-            cursor_enter_callback
-        );
-    }
-
-    /// Wrapper for `glfwSetScrollCallback`.
-    pub fn set_scroll_polling(&mut self, should_poll: bool) {
-        set_window_callback!(self, should_poll, glfwSetScrollCallback, scroll_callback);
-    }
-
     /// Wrapper for `glfwGetClipboardString`.
     pub fn set_clipboard_string(&mut self, string: &str) {
         unsafe {
@@ -2838,7 +3237,7 @@ impl Drop for Window {
 
         if !self.ptr.is_null() {
             unsafe {
-                let _: Box<Sender<(f64, WindowEvent)>> =
+                let _: Box<WindowCallbacks> =
                     mem::transmute(ffi::glfwGetWindowUserPointer(self.ptr));
             }
         }


Test patch: