summaryrefslogtreecommitdiff
path: root/glfw
diff options
context:
space:
mode:
Diffstat (limited to 'glfw')
-rw-r--r--glfw/Cargo.toml11
-rw-r--r--glfw/build.rs28
-rw-r--r--glfw/glfw.h2
-rw-r--r--glfw/src/ffi.rs9
-rw-r--r--glfw/src/init.rs44
-rw-r--r--glfw/src/lib.rs50
-rw-r--r--glfw/src/util.rs10
-rw-r--r--glfw/src/window.rs128
8 files changed, 282 insertions, 0 deletions
diff --git a/glfw/Cargo.toml b/glfw/Cargo.toml
new file mode 100644
index 0000000..9f7ecde
--- /dev/null
+++ b/glfw/Cargo.toml
@@ -0,0 +1,11 @@
+[package]
+name = "glfw"
+version = "0.1.0"
+edition = "2021"
+
+[dependencies]
+libc = "0.2.148"
+thiserror = "1.0.49"
+
+[build-dependencies]
+bindgen = "0.68.1"
diff --git a/glfw/build.rs b/glfw/build.rs
new file mode 100644
index 0000000..aaf4446
--- /dev/null
+++ b/glfw/build.rs
@@ -0,0 +1,28 @@
+use std::env;
+use std::error::Error;
+use std::path::PathBuf;
+
+use bindgen::MacroTypeVariation;
+
+fn main() -> Result<(), Box<dyn Error>>
+{
+ println!("cargo:rustc-link-lib=glfw");
+
+ println!("cargo:rerun-if-changed=glfw.h");
+
+ let bindings = bindgen::Builder::default()
+ .header("glfw.h")
+ .clang_arg("-fretain-comments-from-system-headers")
+ .generate_comments(true)
+ .allowlist_function("glfw.*")
+ .allowlist_type("GLFW.*")
+ .allowlist_var("GLFW.*")
+ .default_macro_constant_type(MacroTypeVariation::Signed)
+ .generate()?;
+
+ let out_path = PathBuf::from(env::var("OUT_DIR")?);
+
+ bindings.write_to_file(out_path.join("bindings.rs"))?;
+
+ Ok(())
+}
diff --git a/glfw/glfw.h b/glfw/glfw.h
new file mode 100644
index 0000000..f5fa479
--- /dev/null
+++ b/glfw/glfw.h
@@ -0,0 +1,2 @@
+#include <GLFW/glfw3.h>
+
diff --git a/glfw/src/ffi.rs b/glfw/src/ffi.rs
new file mode 100644
index 0000000..d0affd0
--- /dev/null
+++ b/glfw/src/ffi.rs
@@ -0,0 +1,9 @@
+#![allow(
+ non_snake_case,
+ non_camel_case_types,
+ non_upper_case_globals,
+ unused,
+ clippy::unreadable_literal
+)]
+
+include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
diff --git a/glfw/src/init.rs b/glfw/src/init.rs
new file mode 100644
index 0000000..d102db6
--- /dev/null
+++ b/glfw/src/init.rs
@@ -0,0 +1,44 @@
+use crate::util::is_main_thread;
+use crate::{get_glfw_error, Error};
+
+/// Initializes GLFW and returns a initialization token.
+///
+/// # Errors
+/// Will return `Err` if
+/// - The current thread is not the main thread
+/// - A GLFW error occurs
+pub fn initialize() -> Result<Glfw, Error>
+{
+ if !is_main_thread() {
+ return Err(Error::NotInMainThread);
+ }
+
+ // SAFETY: The current thread is the main thread
+ let success = unsafe { crate::ffi::glfwInit() };
+
+ if success == crate::ffi::GLFW_FALSE {
+ get_glfw_error()?;
+ }
+
+ Ok(Glfw { _priv: &() })
+}
+
+/// GLFW initialization token.
+pub struct Glfw
+{
+ /// This field has two purposes
+ /// - To make the struct not constructable without calling [`initialize`].
+ /// - To make the struct `!Send` and `!Sync`.
+ _priv: *const (),
+}
+
+impl Drop for Glfw
+{
+ fn drop(&mut self)
+ {
+ // SAFETY: The current thread cannot be any other thread than the main thread
+ // since the initialize function checks it and the GLFW initialization token is
+ // neither Send or Sync
+ unsafe { crate::ffi::glfwTerminate() };
+ }
+}
diff --git a/glfw/src/lib.rs b/glfw/src/lib.rs
new file mode 100644
index 0000000..889015b
--- /dev/null
+++ b/glfw/src/lib.rs
@@ -0,0 +1,50 @@
+#![deny(clippy::all, clippy::pedantic)]
+
+use std::ffi::{c_char, CStr};
+use std::ptr::null;
+
+mod ffi;
+mod init;
+mod util;
+
+pub mod window;
+
+pub use window::{Size as WindowSize, Window};
+
+#[derive(Debug, thiserror::Error)]
+pub enum Error
+{
+ /// The current thread is not the main thread.
+ #[error("The current thread is not the main thread")]
+ NotInMainThread,
+
+ /// An internal nul byte was found in a window title.
+ #[error("An internal nul byte was found in the window title")]
+ InternalNulByteInWindowTitle,
+
+ /// GLFW error.
+ #[error("GLFW error {0} occured. {1}")]
+ GlfwError(i32, String),
+}
+
+fn get_glfw_error() -> Result<(), Error>
+{
+ let mut description_ptr: *const c_char = null();
+
+ let err = unsafe { crate::ffi::glfwGetError(&mut description_ptr) };
+
+ if err == crate::ffi::GLFW_NO_ERROR {
+ return Ok(());
+ }
+
+ // SAFETY: The description is guaranteed by GLFW to be valid UTF-8
+ let desc_str = unsafe {
+ std::str::from_utf8_unchecked(CStr::from_ptr(description_ptr).to_bytes())
+ };
+
+ // The description has to be copied because it is guaranteed to be valid only
+ // until the next GLFW error occurs or the GLFW library is terminated.
+ let description = desc_str.to_string();
+
+ Err(Error::GlfwError(err, description))
+}
diff --git a/glfw/src/util.rs b/glfw/src/util.rs
new file mode 100644
index 0000000..f77aaf8
--- /dev/null
+++ b/glfw/src/util.rs
@@ -0,0 +1,10 @@
+use libc::{c_long, getpid, syscall, SYS_gettid};
+
+pub fn is_main_thread() -> bool
+{
+ let ttid = unsafe { syscall(SYS_gettid) };
+
+ let pid = c_long::from(unsafe { getpid() });
+
+ ttid == pid
+}
diff --git a/glfw/src/window.rs b/glfw/src/window.rs
new file mode 100644
index 0000000..6f5c845
--- /dev/null
+++ b/glfw/src/window.rs
@@ -0,0 +1,128 @@
+use std::ffi::{CStr, CString};
+use std::ptr::null_mut;
+
+use crate::init::{initialize, Glfw};
+use crate::{get_glfw_error, Error};
+
+pub struct Window
+{
+ _init: Glfw,
+ handle: *mut crate::ffi::GLFWwindow,
+}
+
+impl Window
+{
+ /// Creates a new window.
+ ///
+ /// # Errors
+ /// Will return `Err` if
+ /// - The title contains an internal nul byte
+ /// - A GLFW error occurs
+ pub fn create(size: &Size, title: &str) -> Result<Self, Error>
+ {
+ let init = initialize()?;
+
+ let c_title =
+ CString::new(title).map_err(|_| Error::InternalNulByteInWindowTitle)?;
+
+ // SAFETY: The initialize function makes sure the current thread is the main
+ // thread
+ let handle = unsafe {
+ #[allow(clippy::cast_possible_wrap)]
+ crate::ffi::glfwCreateWindow(
+ size.width as i32,
+ size.height as i32,
+ c_title.as_ptr(),
+ null_mut(),
+ null_mut(),
+ )
+ };
+
+ get_glfw_error()?;
+
+ Ok(Self {
+ _init: init,
+ handle,
+ })
+ }
+
+ /// Makes the context of the window current for the calling thread.
+ ///
+ /// # Errors
+ /// Will return `Err` if a GLFW platform error occurs or if no OpenGL context is
+ /// present.
+ pub fn make_context_current(&self) -> Result<(), Error>
+ {
+ unsafe { crate::ffi::glfwMakeContextCurrent(self.handle) };
+
+ get_glfw_error()?;
+
+ Ok(())
+ }
+
+ /// Returns the address of the specified OpenGL function, if it is supported by the
+ /// current context.
+ ///
+ /// # Errors
+ /// Will return `Err` if a GLFW platform error occurs or if no current context has
+ /// been set.
+ pub fn get_proc_address(
+ &self,
+ proc_name: &CStr,
+ ) -> Result<unsafe extern "C" fn(), Error>
+ {
+ let proc_addr = unsafe { crate::ffi::glfwGetProcAddress(proc_name.as_ptr()) };
+
+ get_glfw_error()?;
+
+ // SAFETY: Is only None when a error has occured and that case is handled above
+ Ok(unsafe { proc_addr.unwrap_unchecked() })
+ }
+
+ /// Processes all pending events.
+ ///
+ /// # Errors
+ /// Will return `Err` if a GLFW platform error occurs.
+ pub fn poll_events(&self) -> Result<(), Error>
+ {
+ // SAFETY: The initialize function (called when the window is created) makes sure
+ // the current thread is the main thread
+ unsafe { crate::ffi::glfwPollEvents() };
+
+ get_glfw_error()?;
+
+ Ok(())
+ }
+
+ /// Swaps the front and back buffers of the window.
+ ///
+ /// # Errors
+ /// Will return `Err` if a GLFW platform error occurs or if no OpenGL window context
+ /// is present.
+ pub fn swap_buffers(&self) -> Result<(), Error>
+ {
+ unsafe {
+ crate::ffi::glfwSwapBuffers(self.handle);
+ };
+
+ get_glfw_error()?;
+
+ Ok(())
+ }
+
+ /// Returns whether or not the window should close.
+ #[must_use]
+ pub fn should_close(&self) -> bool
+ {
+ let should_close = unsafe { crate::ffi::glfwWindowShouldClose(self.handle) };
+
+ should_close == crate::ffi::GLFW_TRUE
+ }
+}
+
+/// Window size.
+pub struct Size
+{
+ pub width: u32,
+ pub height: u32,
+}