From b62a58c972292a9f9906e8b95b906977a5e13935 Mon Sep 17 00:00:00 2001
From: Allen
Date: Mon, 4 Aug 2025 21:34:30 +0800
Subject: [PATCH 01/10] MacOS: implement drag and drop support, fix cargo
clippy errors
---
examples/blobs.rs | 2 +-
examples/drag_drop_test.rs | 58 ++++++++++++++
examples/instancing.rs | 6 +-
examples/msaa_render_texture.rs | 12 +--
examples/offscreen.rs | 10 +--
examples/post_processing.rs | 14 ++--
examples/quad.rs | 2 +-
examples/triangle.rs | 2 +-
examples/triangle_color4b.rs | 2 +-
src/lib.rs | 1 +
src/native/apple/apple_util.rs | 42 +++++------
src/native/apple/frameworks.rs | 4 +-
src/native/macos.rs | 130 ++++++++++++++++++++++++++++++--
13 files changed, 229 insertions(+), 56 deletions(-)
create mode 100644 examples/drag_drop_test.rs
diff --git a/examples/blobs.rs b/examples/blobs.rs
index f29ea4e6a..b58ac54ab 100644
--- a/examples/blobs.rs
+++ b/examples/blobs.rs
@@ -47,7 +47,7 @@ impl Stage {
let bindings = Bindings {
vertex_buffers: vec![vertex_buffer],
- index_buffer: index_buffer,
+ index_buffer,
images: vec![],
};
diff --git a/examples/drag_drop_test.rs b/examples/drag_drop_test.rs
new file mode 100644
index 000000000..e16c97bf9
--- /dev/null
+++ b/examples/drag_drop_test.rs
@@ -0,0 +1,58 @@
+use miniquad::*;
+
+struct Stage {
+ ctx: GlContext,
+ dropped_files: Vec,
+}
+
+impl EventHandler for Stage {
+ fn update(&mut self) {}
+
+ fn draw(&mut self) {
+ // Clear the screen with a dark blue color
+ self.ctx.clear(Some((0.1, 0.1, 0.3, 1.0)), None, None);
+
+ // Note: miniquad doesn't have built-in text rendering
+ // In a real application, you would use a text rendering library
+ // For now, this just clears the screen and responds to file drops
+ }
+
+ fn files_dropped_event(&mut self) {
+ println!("Files dropped!");
+ self.dropped_files.clear();
+
+ let count = window::dropped_file_count();
+ println!("Number of dropped files: {count}");
+
+ for i in 0..count {
+ if let Some(path) = window::dropped_file_path(i) {
+ let path_str = path.to_string_lossy().to_string();
+ println!("Dropped file {i}: {path_str}");
+ self.dropped_files.push(path_str);
+
+ if let Some(bytes) = window::dropped_file_bytes(i) {
+ println!(" Size: {} bytes", bytes.len());
+ }
+ }
+ }
+
+ println!("Check the console output above to see the dropped files!");
+ }
+}
+
+fn main() {
+ miniquad::start(
+ conf::Conf {
+ window_title: "Drag and Drop Test".to_string(),
+ window_width: 800,
+ window_height: 600,
+ ..Default::default()
+ },
+ || {
+ Box::new(Stage {
+ ctx: GlContext::new(),
+ dropped_files: Vec::new(),
+ })
+ },
+ );
+}
diff --git a/examples/instancing.rs b/examples/instancing.rs
index 4084aad3f..c48b3726e 100644
--- a/examples/instancing.rs
+++ b/examples/instancing.rs
@@ -35,7 +35,7 @@ impl Stage {
let geometry_vertex_buffer = ctx.new_buffer(
BufferType::VertexBuffer,
BufferUsage::Immutable,
- BufferSource::slice(&vertices),
+ BufferSource::slice(vertices),
);
#[rustfmt::skip]
@@ -46,7 +46,7 @@ impl Stage {
let index_buffer = ctx.new_buffer(
BufferType::IndexBuffer,
BufferUsage::Immutable,
- BufferSource::slice(&indices),
+ BufferSource::slice(indices),
);
// empty, dynamic instance data vertex buffer
@@ -58,7 +58,7 @@ impl Stage {
let bindings = Bindings {
vertex_buffers: vec![geometry_vertex_buffer, positions_vertex_buffer],
- index_buffer: index_buffer,
+ index_buffer,
images: vec![],
};
diff --git a/examples/msaa_render_texture.rs b/examples/msaa_render_texture.rs
index d81ba16a6..d5659d6eb 100644
--- a/examples/msaa_render_texture.rs
+++ b/examples/msaa_render_texture.rs
@@ -84,7 +84,7 @@ impl Stage {
let vertex_buffer = ctx.new_buffer(
BufferType::VertexBuffer,
BufferUsage::Immutable,
- BufferSource::slice(&vertices),
+ BufferSource::slice(vertices),
);
#[rustfmt::skip]
@@ -100,12 +100,12 @@ impl Stage {
let index_buffer = ctx.new_buffer(
BufferType::IndexBuffer,
BufferUsage::Immutable,
- BufferSource::slice(&indices),
+ BufferSource::slice(indices),
);
let offscreen_bind = Bindings {
- vertex_buffers: vec![vertex_buffer.clone()],
- index_buffer: index_buffer.clone(),
+ vertex_buffers: vec![vertex_buffer],
+ index_buffer: index_buffer,
images: vec![],
};
@@ -120,7 +120,7 @@ impl Stage {
let vertex_buffer = ctx.new_buffer(
BufferType::VertexBuffer,
BufferUsage::Immutable,
- BufferSource::slice(&vertices),
+ BufferSource::slice(vertices),
);
let indices: [u16; 6] = [0, 1, 2, 0, 2, 3];
let index_buffer = ctx.new_buffer(
@@ -130,7 +130,7 @@ impl Stage {
);
Bindings {
vertex_buffers: vec![vertex_buffer],
- index_buffer: index_buffer,
+ index_buffer,
images: vec![color_resolve_img],
}
};
diff --git a/examples/offscreen.rs b/examples/offscreen.rs
index f8c32957a..a6b560fe6 100644
--- a/examples/offscreen.rs
+++ b/examples/offscreen.rs
@@ -69,7 +69,7 @@ impl Stage {
let vertex_buffer = ctx.new_buffer(
BufferType::VertexBuffer,
BufferUsage::Immutable,
- BufferSource::slice(&vertices),
+ BufferSource::slice(vertices),
);
#[rustfmt::skip]
@@ -85,18 +85,18 @@ impl Stage {
let index_buffer = ctx.new_buffer(
BufferType::IndexBuffer,
BufferUsage::Immutable,
- BufferSource::slice(&indices),
+ BufferSource::slice(indices),
);
let offscreen_bind = Bindings {
- vertex_buffers: vec![vertex_buffer.clone()],
- index_buffer: index_buffer.clone(),
+ vertex_buffers: vec![vertex_buffer],
+ index_buffer: index_buffer,
images: vec![],
};
let display_bind = Bindings {
vertex_buffers: vec![vertex_buffer],
- index_buffer: index_buffer,
+ index_buffer,
images: vec![color_img],
};
diff --git a/examples/post_processing.rs b/examples/post_processing.rs
index 3762d229d..f5c4d30ce 100644
--- a/examples/post_processing.rs
+++ b/examples/post_processing.rs
@@ -70,7 +70,7 @@ impl Stage {
let vertex_buffer = ctx.new_buffer(
BufferType::VertexBuffer,
BufferUsage::Immutable,
- BufferSource::slice(&vertices),
+ BufferSource::slice(vertices),
);
#[rustfmt::skip]
@@ -86,12 +86,12 @@ impl Stage {
let index_buffer = ctx.new_buffer(
BufferType::IndexBuffer,
BufferUsage::Immutable,
- BufferSource::slice(&indices),
+ BufferSource::slice(indices),
);
let offscreen_bind = Bindings {
- vertex_buffers: vec![vertex_buffer.clone()],
- index_buffer: index_buffer.clone(),
+ vertex_buffers: vec![vertex_buffer],
+ index_buffer: index_buffer,
images: vec![],
};
@@ -107,7 +107,7 @@ impl Stage {
let vertex_buffer = ctx.new_buffer(
BufferType::VertexBuffer,
BufferUsage::Immutable,
- BufferSource::slice(&vertices),
+ BufferSource::slice(vertices),
);
let indices: &[u16] = &[0, 1, 2, 0, 2, 3];
@@ -115,12 +115,12 @@ impl Stage {
let index_buffer = ctx.new_buffer(
BufferType::IndexBuffer,
BufferUsage::Immutable,
- BufferSource::slice(&indices),
+ BufferSource::slice(indices),
);
let post_processing_bind = Bindings {
vertex_buffers: vec![vertex_buffer],
- index_buffer: index_buffer,
+ index_buffer,
images: vec![color_img],
};
diff --git a/examples/quad.rs b/examples/quad.rs
index 821acb118..5addc8a02 100644
--- a/examples/quad.rs
+++ b/examples/quad.rs
@@ -53,7 +53,7 @@ impl Stage {
let bindings = Bindings {
vertex_buffers: vec![vertex_buffer],
- index_buffer: index_buffer,
+ index_buffer,
images: vec![texture],
};
diff --git a/examples/triangle.rs b/examples/triangle.rs
index 383dbf48b..d4c9234a5 100644
--- a/examples/triangle.rs
+++ b/examples/triangle.rs
@@ -37,7 +37,7 @@ impl Stage {
let bindings = Bindings {
vertex_buffers: vec![vertex_buffer],
- index_buffer: index_buffer,
+ index_buffer,
images: vec![],
};
diff --git a/examples/triangle_color4b.rs b/examples/triangle_color4b.rs
index e01a3c36c..b41214e7d 100644
--- a/examples/triangle_color4b.rs
+++ b/examples/triangle_color4b.rs
@@ -37,7 +37,7 @@ impl Stage {
let bindings = Bindings {
vertex_buffers: vec![vertex_buffer],
- index_buffer: index_buffer,
+ index_buffer,
images: vec![],
};
diff --git a/src/lib.rs b/src/lib.rs
index db1b3a7ef..73ccdf95d 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -99,6 +99,7 @@ fn set_display(display: native::NativeDisplayData) {
}
/// This for now is Android specific since the process can continue running but the display
/// is restarted. We support reinitializing the display.
+#[allow(dead_code)]
fn set_or_replace_display(display: native::NativeDisplayData) {
if let Some(m) = NATIVE_DISPLAY.get() {
// Replace existing display
diff --git a/src/native/apple/apple_util.rs b/src/native/apple/apple_util.rs
index 4b1973cc6..564c78bef 100644
--- a/src/native/apple/apple_util.rs
+++ b/src/native/apple/apple_util.rs
@@ -10,13 +10,11 @@ use crate::{
CursorIcon,
};
-pub fn nsstring_to_string(string: ObjcId) -> String {
- unsafe {
- let utf8_string: *const core::ffi::c_uchar = msg_send![string, UTF8String];
- let utf8_len: usize = msg_send![string, lengthOfBytesUsingEncoding: UTF8_ENCODING];
- let slice = std::slice::from_raw_parts(utf8_string, utf8_len);
- std::str::from_utf8_unchecked(slice).to_owned()
- }
+pub unsafe fn nsstring_to_string(string: ObjcId) -> String {
+ let utf8_string: *const core::ffi::c_uchar = msg_send![string, UTF8String];
+ let utf8_len: usize = msg_send![string, lengthOfBytesUsingEncoding: UTF8_ENCODING];
+ let slice = std::slice::from_raw_parts(utf8_string, utf8_len);
+ std::str::from_utf8_unchecked(slice).to_owned()
}
pub fn str_to_nsstring(str: &str) -> ObjcId {
@@ -50,7 +48,7 @@ pub fn load_undocumented_cursor(cursor_name: &str) -> ObjcId {
}
pub unsafe fn ccfstr_from_str(inp: &str) -> CFStringRef {
- let null = format!("{}\0", inp);
+ let null = format!("{inp}\0");
__CFStringMakeConstantString(null.as_ptr() as *const ::core::ffi::c_char)
}
@@ -124,23 +122,21 @@ pub fn load_webkit_cursor(cursor_name_str: &str) -> ObjcId {
}
}
-pub fn get_event_char(event: ObjcId) -> Option {
- unsafe {
- let characters: ObjcId = msg_send![event, characters];
- if characters == nil {
- return None;
- }
- let chars = nsstring_to_string(characters);
+pub unsafe fn get_event_char(event: ObjcId) -> Option {
+ let characters: ObjcId = msg_send![event, characters];
+ if characters == nil {
+ return None;
+ }
+ let chars = nsstring_to_string(characters);
- if chars.is_empty() {
- return None;
- }
- Some(chars.chars().next().unwrap())
+ if chars.is_empty() {
+ return None;
}
+ Some(chars.chars().next().unwrap())
}
-pub fn get_event_key_modifier(event: ObjcId) -> KeyMods {
- let flags: u64 = unsafe { msg_send![event, modifierFlags] };
+pub unsafe fn get_event_key_modifier(event: ObjcId) -> KeyMods {
+ let flags: u64 = msg_send![event, modifierFlags];
KeyMods {
shift: flags & NSEventModifierFlags::NSShiftKeyMask as u64 != 0,
ctrl: flags & NSEventModifierFlags::NSControlKeyMask as u64 != 0,
@@ -149,8 +145,8 @@ pub fn get_event_key_modifier(event: ObjcId) -> KeyMods {
}
}
-pub fn get_event_keycode(event: ObjcId) -> Option {
- let scan_code: core::ffi::c_ushort = unsafe { msg_send![event, keyCode] };
+pub unsafe fn get_event_keycode(event: ObjcId) -> Option {
+ let scan_code: core::ffi::c_ushort = msg_send![event, keyCode];
Some(match scan_code {
0x00 => KeyCode::A,
diff --git a/src/native/apple/frameworks.rs b/src/native/apple/frameworks.rs
index f92b05511..8b7c31799 100644
--- a/src/native/apple/frameworks.rs
+++ b/src/native/apple/frameworks.rs
@@ -1233,9 +1233,9 @@ impl OSError {
})
}
- pub fn from_nserror(ns_error: ObjcId) -> Result<(), Self> {
+ pub unsafe fn from_nserror(ns_error: ObjcId) -> Result<(), Self> {
if ns_error != nil {
- let code: i32 = unsafe { msg_send![ns_error, code] };
+ let code: i32 = msg_send![ns_error, code];
Self::from(code)
} else {
Ok(())
diff --git a/src/native/macos.rs b/src/native/macos.rs
index 22e5abc61..2a4fd0c44 100644
--- a/src/native/macos.rs
+++ b/src/native/macos.rs
@@ -543,15 +543,15 @@ unsafe fn view_base_decl(decl: &mut ClassDecl) {
extern "C" fn key_down(this: &Object, _sel: Sel, event: ObjcId) {
let payload = get_window_payload(this);
- let mods = get_event_key_modifier(event);
+ let mods = unsafe { get_event_key_modifier(event) };
let repeat: bool = unsafe { msg_send!(event, isARepeat) };
- if let Some(key) = get_event_keycode(event) {
+ if let Some(key) = unsafe { get_event_keycode(event) } {
if let Some(event_handler) = payload.context() {
event_handler.key_down_event(key, mods, repeat);
}
}
- if let Some(character) = get_event_char(event) {
+ if let Some(character) = unsafe { get_event_char(event) } {
if let Some(event_handler) = payload.context() {
event_handler.char_event(character, mods, repeat);
}
@@ -560,8 +560,8 @@ unsafe fn view_base_decl(decl: &mut ClassDecl) {
extern "C" fn key_up(this: &Object, _sel: Sel, event: ObjcId) {
let payload = get_window_payload(this);
- let mods = get_event_key_modifier(event);
- if let Some(key) = get_event_keycode(event) {
+ let mods = unsafe { get_event_key_modifier(event) };
+ if let Some(key) = unsafe { get_event_keycode(event) } {
if let Some(event_handler) = payload.context() {
event_handler.key_up_event(key, mods);
}
@@ -590,7 +590,7 @@ unsafe fn view_base_decl(decl: &mut ClassDecl) {
}
let payload = get_window_payload(this);
- let mods = get_event_key_modifier(event);
+ let mods = unsafe { get_event_key_modifier(event) };
let flags: u64 = unsafe { msg_send![event, modifierFlags] };
let new_modifiers = Modifiers::new(flags);
@@ -654,6 +654,102 @@ unsafe fn view_base_decl(decl: &mut ClassDecl) {
payload.modifiers = new_modifiers;
}
+ // Drag and Drop methods
+ extern "C" fn dragging_entered(_this: &Object, _sel: Sel, sender: ObjcId) -> u64 {
+ unsafe {
+ let pasteboard: ObjcId = msg_send![sender, draggingPasteboard];
+ let types: ObjcId = msg_send![pasteboard, types];
+ let contains_file_urls: bool =
+ msg_send![types, containsObject: NSPasteboardTypeFileURL];
+
+ if contains_file_urls {
+ use crate::native::apple::frameworks::NSDragOperation;
+ NSDragOperation::Copy as u64
+ } else {
+ use crate::native::apple::frameworks::NSDragOperation;
+ NSDragOperation::None as u64
+ }
+ }
+ }
+
+ extern "C" fn dragging_updated(_this: &Object, _sel: Sel, sender: ObjcId) -> u64 {
+ unsafe {
+ let pasteboard: ObjcId = msg_send![sender, draggingPasteboard];
+ let types: ObjcId = msg_send![pasteboard, types];
+ let contains_file_urls: bool =
+ msg_send![types, containsObject: NSPasteboardTypeFileURL];
+
+ if contains_file_urls {
+ use crate::native::apple::frameworks::NSDragOperation;
+ NSDragOperation::Copy as u64
+ } else {
+ use crate::native::apple::frameworks::NSDragOperation;
+ NSDragOperation::None as u64
+ }
+ }
+ }
+
+ extern "C" fn perform_drag_operation(this: &Object, _sel: Sel, sender: ObjcId) -> bool {
+ let payload = get_window_payload(this);
+
+ unsafe {
+ let pasteboard: ObjcId = msg_send![sender, draggingPasteboard];
+ let nsurl_class = class!(NSURL);
+ let classes_array: ObjcId = msg_send![class!(NSArray), arrayWithObject: nsurl_class];
+ let options: ObjcId = msg_send![class!(NSDictionary), dictionary];
+ let file_urls: ObjcId = msg_send![pasteboard,
+ readObjectsForClasses: classes_array
+ options: options
+ ];
+
+ if file_urls == nil {
+ return false;
+ }
+
+ let count: usize = msg_send![file_urls, count];
+ if count == 0 {
+ return false;
+ }
+
+ // Clear previous dropped files
+ {
+ let mut d = crate::native_display().lock().unwrap();
+ d.dropped_files.paths.clear();
+ d.dropped_files.bytes.clear();
+ }
+
+ // Process each dropped file
+ for i in 0..count {
+ let url: ObjcId = msg_send![file_urls, objectAtIndex: i];
+ if url == nil {
+ continue;
+ }
+
+ let path_nsstring: ObjcId = msg_send![url, path];
+ if path_nsstring == nil {
+ continue;
+ }
+
+ let path_str = crate::native::apple::apple_util::nsstring_to_string(path_nsstring);
+ let path = std::path::PathBuf::from(path_str);
+
+ // Try to read file contents
+ if let Ok(bytes) = std::fs::read(&path) {
+ let mut d = crate::native_display().lock().unwrap();
+ d.dropped_files.paths.push(path);
+ d.dropped_files.bytes.push(bytes);
+ }
+ }
+
+ // Trigger the dropped files event
+ if let Some(event_handler) = payload.context() {
+ event_handler.files_dropped_event();
+ }
+
+ true
+ }
+ }
+
decl.add_method(
sel!(canBecomeKey),
yes as extern "C" fn(&Object, Sel) -> BOOL,
@@ -720,6 +816,20 @@ unsafe fn view_base_decl(decl: &mut ClassDecl) {
flags_changed as extern "C" fn(&Object, Sel, ObjcId),
);
decl.add_method(sel!(keyUp:), key_up as extern "C" fn(&Object, Sel, ObjcId));
+
+ // Add drag and drop methods
+ decl.add_method(
+ sel!(draggingEntered:),
+ dragging_entered as extern "C" fn(&Object, Sel, ObjcId) -> u64,
+ );
+ decl.add_method(
+ sel!(draggingUpdated:),
+ dragging_updated as extern "C" fn(&Object, Sel, ObjcId) -> u64,
+ );
+ decl.add_method(
+ sel!(performDragOperation:),
+ perform_drag_operation as extern "C" fn(&Object, Sel, ObjcId) -> BOOL,
+ );
}
pub fn define_opengl_view_class() -> *const Class {
@@ -813,6 +923,7 @@ pub fn define_metal_view_class() -> *const Class {
decl.register()
}
+#[allow(clippy::mut_from_ref)]
fn get_window_payload(this: &Object) -> &mut MacosDisplay {
unsafe {
let ptr: *mut c_void = *this.get_ivar("display_ptr");
@@ -1163,6 +1274,13 @@ where
let () = msg_send![window, center];
let () = msg_send![window, setAcceptsMouseMovedEvents: YES];
+ // Register the view to accept dragged file URLs
+ unsafe {
+ let file_url_type = NSPasteboardTypeFileURL;
+ let types_array: ObjcId = msg_send![class!(NSArray), arrayWithObject: file_url_type];
+ let () = msg_send![view, registerForDraggedTypes: types_array];
+ }
+
if conf.fullscreen {
let () = msg_send![window, toggleFullScreen: nil];
}
From 8873298dcbe55e38652984ce067e0865f5e0407f Mon Sep 17 00:00:00 2001
From: Allen
Date: Mon, 4 Aug 2025 21:38:53 +0800
Subject: [PATCH 02/10] MacOS: fix CI build error on macos x86_64
---
src/native/macos.rs | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/src/native/macos.rs b/src/native/macos.rs
index 2a4fd0c44..47df600dd 100644
--- a/src/native/macos.rs
+++ b/src/native/macos.rs
@@ -689,7 +689,7 @@ unsafe fn view_base_decl(decl: &mut ClassDecl) {
}
}
- extern "C" fn perform_drag_operation(this: &Object, _sel: Sel, sender: ObjcId) -> bool {
+ extern "C" fn perform_drag_operation(this: &Object, _sel: Sel, sender: ObjcId) -> BOOL {
let payload = get_window_payload(this);
unsafe {
@@ -703,12 +703,12 @@ unsafe fn view_base_decl(decl: &mut ClassDecl) {
];
if file_urls == nil {
- return false;
+ return NO;
}
let count: usize = msg_send![file_urls, count];
if count == 0 {
- return false;
+ return NO;
}
// Clear previous dropped files
@@ -746,7 +746,7 @@ unsafe fn view_base_decl(decl: &mut ClassDecl) {
event_handler.files_dropped_event();
}
- true
+ YES
}
}
From 7dad947042a38a6c4ec718856eeee4aa1f05f1a1 Mon Sep 17 00:00:00 2001
From: Allen
Date: Tue, 5 Aug 2025 20:38:06 +0800
Subject: [PATCH 03/10] MacOS: fix wrong screen_metrics
---
examples/screen_metrics.rs | 44 ++++++++++++++++++++++++++++++++++++++
src/lib.rs | 21 ++++++++++++++++++
src/native/macos.rs | 8 ++++---
3 files changed, 70 insertions(+), 3 deletions(-)
create mode 100644 examples/screen_metrics.rs
diff --git a/examples/screen_metrics.rs b/examples/screen_metrics.rs
new file mode 100644
index 000000000..26888a505
--- /dev/null
+++ b/examples/screen_metrics.rs
@@ -0,0 +1,44 @@
+use miniquad::*;
+
+struct Stage {
+ ctx: GlContext,
+}
+
+impl EventHandler for Stage {
+ fn update(&mut self) {}
+
+ fn draw(&mut self) {
+ self.ctx.clear(Some((0.2, 0.2, 0.2, 1.0)), None, None);
+
+ let metrics = window::screen_metrics();
+ println!("Screen Metrics:");
+ println!(" Size: {}x{}", metrics.width, metrics.height);
+ println!(" Position: {:?}", metrics.position);
+ println!(" DPI Scale: {}", metrics.dpi_scale);
+ println!(" High DPI: {}", metrics.high_dpi);
+
+ let (width, height) = window::screen_size();
+ println!("Screen Size (legacy): {}x{}", width, height);
+
+ println!("DPI Scale (legacy): {}", window::dpi_scale());
+ println!("High DPI (legacy): {}", window::high_dpi());
+ println!("---");
+ }
+}
+
+fn main() {
+ miniquad::start(
+ conf::Conf {
+ window_title: "Screen Metrics Demo".to_string(),
+ window_width: 800,
+ window_height: 600,
+ high_dpi: true,
+ ..Default::default()
+ },
+ || {
+ Box::new(Stage {
+ ctx: GlContext::new(),
+ })
+ },
+ );
+}
diff --git a/src/lib.rs b/src/lib.rs
index 73ccdf95d..56b85b567 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -163,6 +163,18 @@ pub mod window {
d.high_dpi
}
+ /// Get comprehensive screen metrics including size, position, DPI scale, and high DPI status
+ pub fn screen_metrics() -> crate::ScreenMetrics {
+ let d = native_display().lock().unwrap();
+ crate::ScreenMetrics {
+ width: d.screen_width as f32,
+ height: d.screen_height as f32,
+ position: d.screen_position,
+ dpi_scale: d.dpi_scale,
+ high_dpi: d.high_dpi,
+ }
+ }
+
pub fn blocking_event_loop() -> bool {
let d = native_display().lock().unwrap();
d.blocking_event_loop
@@ -352,6 +364,15 @@ pub enum CursorIcon {
NWSEResize,
}
+#[derive(Debug, Copy, Clone, PartialEq)]
+pub struct ScreenMetrics {
+ pub width: f32,
+ pub height: f32,
+ pub position: (u32, u32),
+ pub dpi_scale: f32,
+ pub high_dpi: bool,
+}
+
/// Start miniquad.
pub fn start(conf: conf::Conf, f: F)
where
diff --git a/src/native/macos.rs b/src/native/macos.rs
index 47df600dd..e6cc8f3fd 100644
--- a/src/native/macos.rs
+++ b/src/native/macos.rs
@@ -171,9 +171,11 @@ impl MacosDisplay {
d.dpi_scale = (backing_size.width / bounds.size.width) as f32;
}
- let bounds: NSRect = msg_send![self.view, bounds];
- let screen_width = (bounds.size.width as f32 * d.dpi_scale) as i32;
- let screen_height = (bounds.size.height as f32 * d.dpi_scale) as i32;
+ // Get the actual screen/monitor resolution instead of window bounds
+ let screen: ObjcId = msg_send![self.window, screen];
+ let screen_frame: NSRect = msg_send![screen, frame];
+ let screen_width = (screen_frame.size.width as f32 * d.dpi_scale) as i32;
+ let screen_height = (screen_frame.size.height as f32 * d.dpi_scale) as i32;
let dim_changed = screen_width != d.screen_width || screen_height != d.screen_height;
From 816bd9db861a9d8a2681f7a9602d97481fca938e Mon Sep 17 00:00:00 2001
From: Allen
Date: Tue, 5 Aug 2025 20:52:20 +0800
Subject: [PATCH 04/10] MacOS: implement screen_metrics
---
src/native/android.rs | 14 +++-----------
src/native/macos.rs | 11 ++++++++---
2 files changed, 11 insertions(+), 14 deletions(-)
diff --git a/src/native/android.rs b/src/native/android.rs
index 2bd17f85f..6f6e9b2d1 100644
--- a/src/native/android.rs
+++ b/src/native/android.rs
@@ -187,10 +187,7 @@ impl MainThreadState {
Message::SurfaceDestroyed => unsafe {
self.destroy_surface();
},
- Message::SurfaceChanged {
- width,
- height,
- } => {
+ Message::SurfaceChanged { width, height } => {
{
let mut d = crate::native_display().lock().unwrap();
d.screen_width = width as _;
@@ -404,9 +401,7 @@ where
// it is important to create GL context only after a first SurfaceChanged
let window = 'a: loop {
match rx.try_recv() {
- Ok(Message::SurfaceCreated {
- window,
- }) => {
+ Ok(Message::SurfaceCreated { window }) => {
break 'a window;
}
_ => {}
@@ -414,10 +409,7 @@ where
};
let (screen_width, screen_height) = 'a: loop {
match rx.try_recv() {
- Ok(Message::SurfaceChanged {
- width,
- height,
- }) => {
+ Ok(Message::SurfaceChanged { width, height }) => {
break 'a (width as f32, height as f32);
}
_ => {}
diff --git a/src/native/macos.rs b/src/native/macos.rs
index e6cc8f3fd..b197a0d3d 100644
--- a/src/native/macos.rs
+++ b/src/native/macos.rs
@@ -177,10 +177,16 @@ impl MacosDisplay {
let screen_width = (screen_frame.size.width as f32 * d.dpi_scale) as i32;
let screen_height = (screen_frame.size.height as f32 * d.dpi_scale) as i32;
+ // Get window position
+ let window_frame: NSRect = msg_send![self.window, frame];
+ let window_x = (window_frame.origin.x * d.dpi_scale as f64) as u32;
+ let window_y = (window_frame.origin.y * d.dpi_scale as f64) as u32;
+
let dim_changed = screen_width != d.screen_width || screen_height != d.screen_height;
d.screen_width = screen_width;
d.screen_height = screen_height;
+ d.screen_position = (window_x, window_y);
if dim_changed {
Some((screen_width, screen_height))
@@ -326,10 +332,9 @@ pub fn define_cocoa_window_delegate() -> *const Class {
// Startup: the gl_context has not yet been created.
return;
}
- if let Some(event_handler) = payload.context() {
- event_handler.window_minimized_event();
- }
+ // Update screen position when window moves
unsafe {
+ payload.update_dimensions();
msg_send_![payload.gl_context, update];
}
}
From aab676c3ad5a1b206e4e8340b6f4a33f0c597650 Mon Sep 17 00:00:00 2001
From: Allen
Date: Tue, 5 Aug 2025 21:00:22 +0800
Subject: [PATCH 05/10] MacOS: implement set_window_position
---
src/native/macos.rs | 10 +++++++---
1 file changed, 7 insertions(+), 3 deletions(-)
diff --git a/src/native/macos.rs b/src/native/macos.rs
index b197a0d3d..a6b70fb2d 100644
--- a/src/native/macos.rs
+++ b/src/native/macos.rs
@@ -97,6 +97,12 @@ impl MacosDisplay {
};
let () = unsafe { msg_send![self.window, setFrame:frame display:true animate:true] };
}
+ fn set_window_position(&mut self, new_x: u32, new_y: u32) {
+ let mut frame: NSRect = unsafe { msg_send![self.window, frame] };
+ frame.origin.x = new_x as f64;
+ frame.origin.y = new_y as f64;
+ let () = unsafe { msg_send![self.window, setFrame:frame display:true animate:true] };
+ }
fn set_fullscreen(&mut self, fullscreen: bool) {
if self.fullscreen != fullscreen {
self.fullscreen = fullscreen;
@@ -209,9 +215,7 @@ impl MacosDisplay {
new_height,
} => self.set_window_size(new_width as _, new_height as _),
SetFullscreen(fullscreen) => self.set_fullscreen(fullscreen),
- SetWindowPosition { .. } => {
- eprintln!("Not implemented for macos");
- }
+ SetWindowPosition { new_x, new_y } => self.set_window_position(new_x, new_y),
_ => {}
}
}
From 1c8c912130a07b21483724773d9f921617909a4a Mon Sep 17 00:00:00 2001
From: Allen
Date: Tue, 5 Aug 2025 21:25:21 +0800
Subject: [PATCH 06/10] Fix screen_metrics and screen_position to return window
info, and add monitor info related APIs
---
examples/monitor_metrics.rs | 69 +++++++++++++++++++
src/lib.rs | 25 +++++++
src/native.rs | 70 ++++++++++++++++++++
src/native/android.rs | 13 ++++
src/native/ios.rs | 30 +++++++--
src/native/linux_wayland.rs | 26 ++++++++
src/native/linux_x11.rs | 51 ++++++++++++++
src/native/macos.rs | 114 +++++++++++++++++++++++++++++---
src/native/wasm.rs | 13 ++++
src/native/windows.rs | 128 ++++++++++++++++++++++++++++++++++++
10 files changed, 524 insertions(+), 15 deletions(-)
create mode 100644 examples/monitor_metrics.rs
diff --git a/examples/monitor_metrics.rs b/examples/monitor_metrics.rs
new file mode 100644
index 000000000..6ae4c6cf4
--- /dev/null
+++ b/examples/monitor_metrics.rs
@@ -0,0 +1,69 @@
+use miniquad::*;
+
+struct Stage {
+ ctx: GlContext,
+}
+
+impl EventHandler for Stage {
+ fn update(&mut self) {}
+
+ fn draw(&mut self) {
+ self.ctx.clear(Some((0.0, 0.0, 0.0, 1.0)), None, None);
+
+ // Print monitor information every 60 frames (approximately once per second at 60fps)
+ static mut FRAME_COUNT: u32 = 0;
+ unsafe {
+ FRAME_COUNT += 1;
+ if FRAME_COUNT % 60 == 0 {
+ println!("=== Monitor Information ===");
+
+ // Primary monitor
+ let primary = window::primary_monitor();
+ println!("Primary Monitor:");
+ println!(" Name: {:?}", primary.name);
+ println!(" Size: {}x{}", primary.width, primary.height);
+ println!(" Position: {:?}", primary.position);
+ println!(" DPI Scale: {}", primary.dpi_scale);
+ println!(" Refresh Rate: {:?}", primary.refresh_rate);
+
+ // Current monitor (where the window is displayed)
+ let current = window::current_monitor();
+ println!("\nCurrent Monitor (where window is displayed):");
+ println!(" Name: {:?}", current.name);
+ println!(" Size: {}x{}", current.width, current.height);
+ println!(" Position: {:?}", current.position);
+ println!(" DPI Scale: {}", current.dpi_scale);
+ println!(" Refresh Rate: {:?}", current.refresh_rate);
+
+ // All monitors
+ let monitors = window::monitors();
+ println!("\nAll Monitors ({} total):", monitors.len());
+ for (i, monitor) in monitors.iter().enumerate() {
+ println!(" Monitor {}:", i + 1);
+ println!(" Name: {:?}", monitor.name);
+ println!(" Size: {}x{}", monitor.width, monitor.height);
+ println!(" Position: {:?}", monitor.position);
+ println!(" DPI Scale: {}", monitor.dpi_scale);
+ println!(" Refresh Rate: {:?}", monitor.refresh_rate);
+ }
+
+ // Compare with window metrics
+ let window_metrics = window::screen_metrics();
+ println!("\nWindow Metrics (for comparison):");
+ println!(" Size: {}x{}", window_metrics.width, window_metrics.height);
+ println!(" Position: {:?}", window_metrics.position);
+ println!(" DPI Scale: {}", window_metrics.dpi_scale);
+ println!(" High DPI: {}", window_metrics.high_dpi);
+ println!("---");
+ }
+ }
+ }
+}
+
+fn main() {
+ miniquad::start(conf::Conf::default(), || {
+ Box::new(Stage {
+ ctx: GlContext::new(),
+ })
+ });
+}
diff --git a/src/lib.rs b/src/lib.rs
index 56b85b567..7c8cc97da 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -175,6 +175,21 @@ pub mod window {
}
}
+ /// Get metrics for the primary monitor
+ pub fn primary_monitor() -> crate::MonitorMetrics {
+ native::primary_monitor_impl()
+ }
+
+ /// Get metrics for all available monitors
+ pub fn monitors() -> Vec {
+ native::monitors_impl()
+ }
+
+ /// Get metrics for the monitor where the current window is displayed
+ pub fn current_monitor() -> crate::MonitorMetrics {
+ native::current_monitor_impl()
+ }
+
pub fn blocking_event_loop() -> bool {
let d = native_display().lock().unwrap();
d.blocking_event_loop
@@ -373,6 +388,16 @@ pub struct ScreenMetrics {
pub high_dpi: bool,
}
+#[derive(Debug, Clone, PartialEq)]
+pub struct MonitorMetrics {
+ pub width: f32,
+ pub height: f32,
+ pub position: (u32, u32),
+ pub dpi_scale: f32,
+ pub refresh_rate: Option,
+ pub name: Option,
+}
+
/// Start miniquad.
pub fn start(conf: conf::Conf, f: F)
where
diff --git a/src/native.rs b/src/native.rs
index 88640a55c..e110b6345 100644
--- a/src/native.rs
+++ b/src/native.rs
@@ -119,3 +119,73 @@ pub mod gl;
pub use wasm::webgl as gl;
pub mod query_stab;
+
+// Monitor metrics implementation functions
+pub fn primary_monitor_impl() -> crate::MonitorMetrics {
+ #[cfg(target_os = "macos")]
+ return macos::primary_monitor();
+ #[cfg(target_os = "windows")]
+ return windows::primary_monitor();
+ #[cfg(target_os = "linux")]
+ return linux_monitor();
+ #[cfg(target_os = "ios")]
+ return ios::primary_monitor();
+ #[cfg(target_os = "android")]
+ return android::primary_monitor();
+ #[cfg(target_arch = "wasm32")]
+ return wasm::primary_monitor();
+}
+
+pub fn monitors_impl() -> Vec {
+ #[cfg(target_os = "macos")]
+ return macos::monitors();
+ #[cfg(target_os = "windows")]
+ return windows::monitors();
+ #[cfg(target_os = "linux")]
+ return linux_monitors();
+ #[cfg(target_os = "ios")]
+ return vec![ios::primary_monitor()];
+ #[cfg(target_os = "android")]
+ return vec![android::primary_monitor()];
+ #[cfg(target_arch = "wasm32")]
+ return vec![wasm::primary_monitor()];
+}
+
+pub fn current_monitor_impl() -> crate::MonitorMetrics {
+ #[cfg(target_os = "macos")]
+ return macos::current_monitor();
+ #[cfg(target_os = "windows")]
+ return windows::current_monitor();
+ #[cfg(target_os = "linux")]
+ return linux_current_monitor();
+ #[cfg(target_os = "ios")]
+ return ios::primary_monitor();
+ #[cfg(target_os = "android")]
+ return android::primary_monitor();
+ #[cfg(target_arch = "wasm32")]
+ return wasm::primary_monitor();
+}
+
+#[cfg(target_os = "linux")]
+fn linux_monitor() -> crate::MonitorMetrics {
+ #[cfg(feature = "wayland")]
+ return linux_wayland::primary_monitor();
+ #[cfg(not(feature = "wayland"))]
+ return linux_x11::primary_monitor();
+}
+
+#[cfg(target_os = "linux")]
+fn linux_monitors() -> Vec {
+ #[cfg(feature = "wayland")]
+ return linux_wayland::monitors();
+ #[cfg(not(feature = "wayland"))]
+ return linux_x11::monitors();
+}
+
+#[cfg(target_os = "linux")]
+fn linux_current_monitor() -> crate::MonitorMetrics {
+ #[cfg(feature = "wayland")]
+ return linux_wayland::current_monitor();
+ #[cfg(not(feature = "wayland"))]
+ return linux_x11::current_monitor();
+}
diff --git a/src/native/android.rs b/src/native/android.rs
index 6f6e9b2d1..6c3636288 100644
--- a/src/native/android.rs
+++ b/src/native/android.rs
@@ -698,3 +698,16 @@ pub(crate) unsafe fn load_asset(filepath: *const ::core::ffi::c_char, out: *mut
(*out).content = buffer as _;
}
}
+
+pub fn primary_monitor() -> crate::MonitorMetrics {
+ // On Android, get the screen metrics from the current display
+ let d = crate::native_display().lock().unwrap();
+ crate::MonitorMetrics {
+ width: d.screen_width as f32,
+ height: d.screen_height as f32,
+ position: (0, 0), // Android is single screen, fullscreen
+ dpi_scale: d.dpi_scale,
+ refresh_rate: None, // Android doesn't easily expose refresh rate
+ name: Some("Android Screen".to_string()),
+ }
+}
diff --git a/src/native/ios.rs b/src/native/ios.rs
index a98831eda..028d01f24 100644
--- a/src/native/ios.rs
+++ b/src/native/ios.rs
@@ -312,20 +312,20 @@ pub fn define_glk_or_mtk_view_dlg(superclass: &Class) -> *const Class {
payload.init_event_handler();
}
- let main_screen: ObjcId = unsafe { msg_send![class!(UIScreen), mainScreen] };
- let screen_rect: NSRect = unsafe { msg_send![main_screen, bounds] };
+ // Get the view bounds for the drawable area instead of screen bounds
+ let view_bounds: NSRect = unsafe { msg_send![payload.view, bounds] };
let high_dpi = native_display().lock().unwrap().high_dpi;
let (screen_width, screen_height) = if high_dpi {
(
- screen_rect.size.width as i32 * 2,
- screen_rect.size.height as i32 * 2,
+ view_bounds.size.width as i32 * 2,
+ view_bounds.size.height as i32 * 2,
)
} else {
let content_scale_factor: f64 = unsafe { msg_send![payload.view, contentScaleFactor] };
(
- (screen_rect.size.width * content_scale_factor) as i32,
- (screen_rect.size.height * content_scale_factor) as i32,
+ (view_bounds.size.width * content_scale_factor) as i32,
+ (view_bounds.size.height * content_scale_factor) as i32,
)
};
@@ -843,3 +843,21 @@ where
UIApplicationMain(argc, &mut argv, nil, class_string);
}
+
+pub fn primary_monitor() -> crate::MonitorMetrics {
+ use crate::native::apple::{apple_util::*, frameworks::*};
+ unsafe {
+ let main_screen: ObjcId = msg_send![class!(UIScreen), mainScreen];
+ let screen_bounds: NSRect = msg_send![main_screen, bounds];
+ let scale: f64 = msg_send![main_screen, scale];
+
+ crate::MonitorMetrics {
+ width: screen_bounds.size.width as f32,
+ height: screen_bounds.size.height as f32,
+ position: (0, 0), // iOS is single screen, fullscreen
+ dpi_scale: scale as f32,
+ refresh_rate: None, // iOS doesn't expose refresh rate easily
+ name: Some("iOS Screen".to_string()),
+ }
+ }
+}
diff --git a/src/native/linux_wayland.rs b/src/native/linux_wayland.rs
index 3b8ff0b46..cb623803f 100644
--- a/src/native/linux_wayland.rs
+++ b/src/native/linux_wayland.rs
@@ -1311,3 +1311,29 @@ where
Some(())
}
+
+pub fn primary_monitor() -> crate::MonitorMetrics {
+ // For Wayland, getting monitor information requires a complex approach
+ // involving wl_output protocol. For simplicity, we'll return default values.
+ // A full implementation would need to track wl_output events during setup.
+ crate::MonitorMetrics {
+ width: 1920.0,
+ height: 1080.0,
+ position: (0, 0),
+ dpi_scale: 1.0,
+ refresh_rate: None,
+ name: Some("Wayland Monitor".to_string()),
+ }
+}
+
+pub fn monitors() -> Vec {
+ // Wayland monitor enumeration requires tracking wl_output protocol events
+ // For basic implementation, return just primary monitor
+ vec![primary_monitor()]
+}
+
+pub fn current_monitor() -> crate::MonitorMetrics {
+ // For Wayland, return the primary monitor
+ // A proper implementation would track which monitor the surface is on
+ primary_monitor()
+}
diff --git a/src/native/linux_x11.rs b/src/native/linux_x11.rs
index 31c9605bd..9c4ce50b2 100644
--- a/src/native/linux_x11.rs
+++ b/src/native/linux_x11.rs
@@ -701,3 +701,54 @@ where
}
Ok(())
}
+
+pub fn primary_monitor() -> crate::MonitorMetrics {
+ unsafe {
+ let display = (LIBX11.XOpenDisplay)(std::ptr::null());
+ if display.is_null() {
+ return crate::MonitorMetrics {
+ width: 1920.0,
+ height: 1080.0,
+ position: (0, 0),
+ dpi_scale: 1.0,
+ refresh_rate: None,
+ name: Some("Primary Monitor".to_string()),
+ };
+ }
+
+ let screen = (LIBX11.XDefaultScreen)(display);
+ let width = (LIBX11.XDisplayWidth)(display, screen) as f32;
+ let height = (LIBX11.XDisplayHeight)(display, screen) as f32;
+
+ // Get DPI information
+ let width_mm = (LIBX11.XDisplayWidthMM)(display, screen) as f32;
+ let dpi_scale = if width_mm > 0.0 {
+ (width * 25.4 / width_mm) / 96.0 // Convert to DPI scale factor
+ } else {
+ 1.0
+ };
+
+ (LIBX11.XCloseDisplay)(display);
+
+ crate::MonitorMetrics {
+ width,
+ height,
+ position: (0, 0),
+ dpi_scale,
+ refresh_rate: None,
+ name: Some("X11 Primary Monitor".to_string()),
+ }
+ }
+}
+
+pub fn monitors() -> Vec {
+ // For basic X11 implementation, return just the primary monitor
+ // A full implementation would use XRandR extension to enumerate all monitors
+ vec![primary_monitor()]
+}
+
+pub fn current_monitor() -> crate::MonitorMetrics {
+ // For basic X11 implementation, return the primary monitor
+ // A proper implementation would determine which monitor contains the window
+ primary_monitor()
+}
diff --git a/src/native/macos.rs b/src/native/macos.rs
index a6b70fb2d..88cfc74bb 100644
--- a/src/native/macos.rs
+++ b/src/native/macos.rs
@@ -177,16 +177,14 @@ impl MacosDisplay {
d.dpi_scale = (backing_size.width / bounds.size.width) as f32;
}
- // Get the actual screen/monitor resolution instead of window bounds
- let screen: ObjcId = msg_send![self.window, screen];
- let screen_frame: NSRect = msg_send![screen, frame];
- let screen_width = (screen_frame.size.width as f32 * d.dpi_scale) as i32;
- let screen_height = (screen_frame.size.height as f32 * d.dpi_scale) as i32;
+ // Get the window content view bounds for the drawable area
+ let bounds: NSRect = msg_send![self.view, bounds];
+ let screen_width = (bounds.size.width as f32 * d.dpi_scale) as i32;
+ let screen_height = (bounds.size.height as f32 * d.dpi_scale) as i32;
- // Get window position
- let window_frame: NSRect = msg_send![self.window, frame];
- let window_x = (window_frame.origin.x * d.dpi_scale as f64) as u32;
- let window_y = (window_frame.origin.y * d.dpi_scale as f64) as u32;
+ // For window metrics, position is relative to the window itself (0,0)
+ let window_x = 0u32;
+ let window_y = 0u32;
let dim_changed = screen_width != d.screen_width || screen_height != d.screen_height;
@@ -1353,3 +1351,101 @@ where
}
}
}
+
+pub fn primary_monitor() -> crate::MonitorMetrics {
+ use crate::native::apple::{apple_util::*, frameworks::*};
+ unsafe {
+ let main_screen: ObjcId = msg_send![class!(NSScreen), mainScreen];
+ let screen_frame: NSRect = msg_send![main_screen, frame];
+ let backing_scale: f64 = msg_send![main_screen, backingScaleFactor];
+ let screen_name: ObjcId = msg_send![main_screen, localizedName];
+ let name_str = if screen_name != nil {
+ Some(nsstring_to_string(screen_name))
+ } else {
+ None
+ };
+
+ crate::MonitorMetrics {
+ width: screen_frame.size.width as f32,
+ height: screen_frame.size.height as f32,
+ position: (screen_frame.origin.x as u32, screen_frame.origin.y as u32),
+ dpi_scale: backing_scale as f32,
+ refresh_rate: None, // NSScreen doesn't easily expose refresh rate
+ name: name_str,
+ }
+ }
+}
+
+pub fn monitors() -> Vec {
+ use crate::native::apple::{apple_util::*, frameworks::*};
+ unsafe {
+ let screens: ObjcId = msg_send![class!(NSScreen), screens];
+ let count: usize = msg_send![screens, count];
+ let mut monitors = Vec::new();
+
+ for i in 0..count {
+ let screen: ObjcId = msg_send![screens, objectAtIndex: i];
+ let screen_frame: NSRect = msg_send![screen, frame];
+ let backing_scale: f64 = msg_send![screen, backingScaleFactor];
+ let screen_name: ObjcId = msg_send![screen, localizedName];
+ let name_str = if screen_name != nil {
+ Some(nsstring_to_string(screen_name))
+ } else {
+ None
+ };
+
+ monitors.push(crate::MonitorMetrics {
+ width: screen_frame.size.width as f32,
+ height: screen_frame.size.height as f32,
+ position: (screen_frame.origin.x as u32, screen_frame.origin.y as u32),
+ dpi_scale: backing_scale as f32,
+ refresh_rate: None,
+ name: name_str,
+ });
+ }
+
+ monitors
+ }
+}
+
+pub fn current_monitor() -> crate::MonitorMetrics {
+ use crate::native::apple::{apple_util::*, frameworks::*};
+
+ // Get the current window's screen from the view
+ let d = crate::native_display().lock().unwrap();
+ let view = d.view;
+ drop(d); // Release lock early
+
+ unsafe {
+ // Get the window from the view
+ let window: ObjcId = msg_send![view, window];
+ if window == nil {
+ // Fallback to primary monitor if no window
+ return primary_monitor();
+ }
+
+ // Get the screen that contains this window
+ let screen: ObjcId = msg_send![window, screen];
+ if screen == nil {
+ return primary_monitor();
+ }
+
+ let screen_frame: NSRect = msg_send![screen, frame];
+ let backing_scale: f64 = msg_send![screen, backingScaleFactor];
+ let screen_name: ObjcId = msg_send![screen, localizedName];
+ let name_str = if screen_name != nil {
+ Some(nsstring_to_string(screen_name))
+ } else {
+ None
+ };
+
+ crate::MonitorMetrics {
+ width: screen_frame.size.width as f32,
+ height: screen_frame.size.height as f32,
+ position: (screen_frame.origin.x as u32, screen_frame.origin.y as u32),
+ dpi_scale: backing_scale as f32,
+ refresh_rate: None,
+ name: name_str,
+ }
+ }
+}
diff --git a/src/native/wasm.rs b/src/native/wasm.rs
index 6ac297e53..a5c87d61a 100644
--- a/src/native/wasm.rs
+++ b/src/native/wasm.rs
@@ -365,3 +365,16 @@ pub extern "C" fn on_file_dropped(
d.dropped_files.paths.push(path);
d.dropped_files.bytes.push(bytes);
}
+
+pub fn primary_monitor() -> crate::MonitorMetrics {
+ // For WASM, get the browser window/screen dimensions
+ // This is a basic implementation - could be enhanced with JS interop
+ crate::MonitorMetrics {
+ width: 1920.0, // Default fallback
+ height: 1080.0,
+ position: (0, 0),
+ dpi_scale: 1.0,
+ refresh_rate: None,
+ name: Some("Browser".to_string()),
+ }
+}
diff --git a/src/native/windows.rs b/src/native/windows.rs
index 28bf447cb..5e180a3d1 100644
--- a/src/native/windows.rs
+++ b/src/native/windows.rs
@@ -1008,3 +1008,131 @@ where
DestroyWindow(wnd);
}
}
+
+pub fn primary_monitor() -> crate::MonitorMetrics {
+ use winapi::um::winuser::*;
+ unsafe {
+ let primary_monitor = MonitorFromPoint(POINT { x: 0, y: 0 }, MONITOR_DEFAULTTOPRIMARY);
+ let mut monitor_info: MONITORINFOEXW = std::mem::zeroed();
+ monitor_info.cbSize = std::mem::size_of::() as u32;
+
+ if GetMonitorInfoW(primary_monitor, &mut monitor_info as *mut _ as *mut _) != 0 {
+ let rect = monitor_info.rcMonitor;
+ let width = (rect.right - rect.left) as f32;
+ let height = (rect.bottom - rect.top) as f32;
+
+ // Get DPI information
+ let dpi = GetDpiForSystem() as f32 / 96.0; // 96 is the default DPI
+
+ // Convert device name to String
+ let device_name = String::from_utf16_lossy(&monitor_info.szDevice);
+ let clean_name = device_name.trim_end_matches('\0');
+
+ crate::MonitorMetrics {
+ width,
+ height,
+ position: (rect.left as u32, rect.top as u32),
+ dpi_scale: dpi,
+ refresh_rate: None, // Would need additional API calls to get refresh rate
+ name: Some(clean_name.to_string()),
+ }
+ } else {
+ // Fallback if monitor info fails
+ crate::MonitorMetrics {
+ width: GetSystemMetrics(SM_CXSCREEN) as f32,
+ height: GetSystemMetrics(SM_CYSCREEN) as f32,
+ position: (0, 0),
+ dpi_scale: 1.0,
+ refresh_rate: None,
+ name: Some("Primary Monitor".to_string()),
+ }
+ }
+ }
+}
+
+pub fn monitors() -> Vec {
+ use winapi::shared::minwindef::*;
+ use winapi::shared::windef::*;
+ use winapi::um::winuser::*;
+
+ unsafe {
+ static mut MONITORS: Vec = Vec::new();
+
+ unsafe extern "system" fn enum_monitor_proc(
+ monitor: HMONITOR,
+ _hdc: HDC,
+ _rect: LPRECT,
+ _lparam: LPARAM,
+ ) -> BOOL {
+ let mut monitor_info: MONITORINFOEXW = std::mem::zeroed();
+ monitor_info.cbSize = std::mem::size_of::() as u32;
+
+ if GetMonitorInfoW(monitor, &mut monitor_info as *mut _ as *mut _) != 0 {
+ let rect = monitor_info.rcMonitor;
+ let width = (rect.right - rect.left) as f32;
+ let height = (rect.bottom - rect.top) as f32;
+
+ let dpi = GetDpiForSystem() as f32 / 96.0;
+ let device_name = String::from_utf16_lossy(&monitor_info.szDevice);
+ let clean_name = device_name.trim_end_matches('\0');
+
+ MONITORS.push(crate::MonitorMetrics {
+ width,
+ height,
+ position: (rect.left as u32, rect.top as u32),
+ dpi_scale: dpi,
+ refresh_rate: None,
+ name: Some(clean_name.to_string()),
+ });
+ }
+ TRUE
+ }
+
+ MONITORS.clear();
+ EnumDisplayMonitors(
+ std::ptr::null_mut(),
+ std::ptr::null(),
+ Some(enum_monitor_proc),
+ 0,
+ );
+ MONITORS.clone()
+ }
+}
+
+pub fn current_monitor() -> crate::MonitorMetrics {
+ use winapi::shared::windef::*;
+ use winapi::um::winuser::*;
+
+ unsafe {
+ // Get the current window handle - we need to find a way to get the HWND
+ // For now, let's use MonitorFromPoint with the current cursor position
+ let mut cursor_pos = POINT { x: 0, y: 0 };
+ GetCursorPos(&mut cursor_pos);
+
+ let monitor = MonitorFromPoint(cursor_pos, MONITOR_DEFAULTTONEAREST);
+ let mut monitor_info: MONITORINFOEXW = std::mem::zeroed();
+ monitor_info.cbSize = std::mem::size_of::() as u32;
+
+ if GetMonitorInfoW(monitor, &mut monitor_info as *mut _ as *mut _) != 0 {
+ let rect = monitor_info.rcMonitor;
+ let width = (rect.right - rect.left) as f32;
+ let height = (rect.bottom - rect.top) as f32;
+
+ let dpi = GetDpiForSystem() as f32 / 96.0;
+ let device_name = String::from_utf16_lossy(&monitor_info.szDevice);
+ let clean_name = device_name.trim_end_matches('\0');
+
+ crate::MonitorMetrics {
+ width,
+ height,
+ position: (rect.left as u32, rect.top as u32),
+ dpi_scale: dpi,
+ refresh_rate: None,
+ name: Some(clean_name.to_string()),
+ }
+ } else {
+ // Fallback to primary monitor
+ primary_monitor()
+ }
+ }
+}
From e8e56a007e66830f18c2a8713f942872b191d56e Mon Sep 17 00:00:00 2001
From: Allen
Date: Tue, 5 Aug 2025 21:41:54 +0800
Subject: [PATCH 07/10] Add window_position in Conf
---
examples/window_position_test.rs | 29 +++++++++++++++++++++++++++++
src/conf.rs | 22 ++++++++++++++++++++++
src/native/linux_x11/libx11_ex.rs | 10 ++++++++--
src/native/macos.rs | 9 ++++++++-
src/native/windows.rs | 13 +++++++++++--
5 files changed, 78 insertions(+), 5 deletions(-)
create mode 100644 examples/window_position_test.rs
diff --git a/examples/window_position_test.rs b/examples/window_position_test.rs
new file mode 100644
index 000000000..492bd3b1b
--- /dev/null
+++ b/examples/window_position_test.rs
@@ -0,0 +1,29 @@
+use miniquad::*;
+
+struct Stage {
+ ctx: GlContext,
+}
+impl EventHandler for Stage {
+ fn update(&mut self) {}
+
+ fn draw(&mut self) {
+ self.ctx.clear(Some((0.2, 0.7, 0.3, 1.0)), None, None);
+ }
+}
+
+fn main() {
+ miniquad::start(
+ conf::Conf {
+ window_title: "Window Position Test".to_string(),
+ window_width: 640,
+ window_height: 480,
+ window_position: Some((200, 150)), // Position window at (200, 150)
+ ..Default::default()
+ },
+ || {
+ Box::new(Stage {
+ ctx: GlContext::new(),
+ })
+ },
+ );
+}
diff --git a/src/conf.rs b/src/conf.rs
index a7a3d4070..018ef24a7 100644
--- a/src/conf.rs
+++ b/src/conf.rs
@@ -40,6 +40,21 @@
//!
//! [`dpi_scale`]: super::window::dpi_scale
//! [`screen_size`]: super::window::screen_size
+//!
+//! ## Window positioning
+//!
+//! You can set the initial window position using the [`Conf::window_position`] field:
+//! ```ignore
+//! Conf {
+//! window_width: 800,
+//! window_height: 600,
+//! window_position: Some((200, 100)), // Position at (200, 100)
+//! ..Default::default()
+//! }
+//! ```
+//!
+//! This feature only works on desktop platforms (Windows, macOS, Linux).
+//! It is ignored on WASM, iOS, and Android platforms.
/// Specifies how to load an OpenGL context on X11 in Linux.
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
@@ -214,6 +229,11 @@ pub struct Conf {
/// Defaults to `false`.
pub fullscreen: bool,
+ /// Optional initial window position (x, y) in screen coordinates.
+ /// Only works on desktop platforms (Windows, macOS, Linux).
+ /// Ignored on WASM, iOS, and Android. Defaults to `None` (system default).
+ pub window_position: Option<(i32, i32)>,
+
/// MSAA sample count.
/// Defaults to `1`.
pub sample_count: i32,
@@ -276,6 +296,7 @@ impl Default for Conf {
window_resizable: true,
icon: Some(Icon::miniquad_logo()),
platform: Default::default(),
+ window_position: None,
}
}
}
@@ -293,6 +314,7 @@ impl Default for Conf {
window_resizable: false, //
icon: Some(Icon::miniquad_logo()),
platform: Default::default(),
+ window_position: None,
}
}
}
diff --git a/src/native/linux_x11/libx11_ex.rs b/src/native/linux_x11/libx11_ex.rs
index 7a859d861..3abf1d264 100644
--- a/src/native/linux_x11/libx11_ex.rs
+++ b/src/native/linux_x11/libx11_ex.rs
@@ -202,11 +202,17 @@ impl LibX11 {
| PropertyChangeMask;
self.grab_error_handler();
+ let (win_x, win_y) = if let Some((x, y)) = conf.window_position {
+ (x as libc::c_int, y as libc::c_int)
+ } else {
+ (0 as libc::c_int, 0 as libc::c_int)
+ };
+
let window = (self.XCreateWindow)(
display,
root,
- 0 as libc::c_int,
- 0 as libc::c_int,
+ win_x,
+ win_y,
conf.window_width as _,
conf.window_height as _,
0 as libc::c_int as libc::c_uint,
diff --git a/src/native/macos.rs b/src/native/macos.rs
index 88cfc74bb..8cdc7985a 100644
--- a/src/native/macos.rs
+++ b/src/native/macos.rs
@@ -1221,7 +1221,14 @@ where
}
let window_frame = NSRect {
- origin: NSPoint { x: 0., y: 0. },
+ origin: if let Some((x, y)) = conf.window_position {
+ NSPoint {
+ x: x as f64,
+ y: y as f64,
+ }
+ } else {
+ NSPoint { x: 0., y: 0. }
+ },
size: NSSize {
width: conf.window_width as f64,
height: conf.window_height as f64,
diff --git a/src/native/windows.rs b/src/native/windows.rs
index 5e180a3d1..2519b671a 100644
--- a/src/native/windows.rs
+++ b/src/native/windows.rs
@@ -641,6 +641,7 @@ unsafe fn create_window(
resizable: bool,
width: i32,
height: i32,
+ position: Option<(i32, i32)>,
) -> (HWND, HDC) {
let mut wndclassw: WNDCLASSW = std::mem::zeroed();
@@ -691,13 +692,20 @@ unsafe fn create_window(
let class_name = "MINIQUADAPP\0".encode_utf16().collect::>();
let mut window_name = window_title.encode_utf16().collect::>();
window_name.push(0);
+
+ let (win_x, win_y) = if let Some((x, y)) = position {
+ (x, y)
+ } else {
+ (CW_USEDEFAULT, CW_USEDEFAULT)
+ };
+
let hwnd = CreateWindowExW(
win_ex_style, // dwExStyle
class_name.as_ptr(), // lpClassName
window_name.as_ptr(), // lpWindowName
win_style, // dwStyle
- CW_USEDEFAULT, // X
- CW_USEDEFAULT, // Y
+ win_x, // X
+ win_y, // Y
win_width, // nWidth
win_height, // nHeight
NULL as _, // hWndParent
@@ -895,6 +903,7 @@ where
conf.window_resizable,
conf.window_width as _,
conf.window_height as _,
+ conf.window_position,
);
if let Some(icon) = &conf.icon {
set_icon(wnd, icon);
From e329c6fd2bc7193a8cf9df38237115210cb2c5c7 Mon Sep 17 00:00:00 2001
From: Allen
Date: Tue, 5 Aug 2025 22:00:48 +0800
Subject: [PATCH 08/10] Remove window_position from Conf, add
desktop_center:bool
---
examples/window_position_test.rs | 4 ++--
src/conf.rs | 14 +++++++-------
src/native/linux_x11/libx11_ex.rs | 9 +++++++--
src/native/macos.rs | 31 ++++++++++++++++++++++---------
src/native/windows.rs | 13 +++++++++----
5 files changed, 47 insertions(+), 24 deletions(-)
diff --git a/examples/window_position_test.rs b/examples/window_position_test.rs
index 492bd3b1b..a77019f6b 100644
--- a/examples/window_position_test.rs
+++ b/examples/window_position_test.rs
@@ -14,10 +14,10 @@ impl EventHandler for Stage {
fn main() {
miniquad::start(
conf::Conf {
- window_title: "Window Position Test".to_string(),
+ window_title: "Desktop Center Test".to_string(),
window_width: 640,
window_height: 480,
- window_position: Some((200, 150)), // Position window at (200, 150)
+ desktop_center: true, // Center window on desktop
..Default::default()
},
|| {
diff --git a/src/conf.rs b/src/conf.rs
index 018ef24a7..f16ce1cfc 100644
--- a/src/conf.rs
+++ b/src/conf.rs
@@ -43,12 +43,12 @@
//!
//! ## Window positioning
//!
-//! You can set the initial window position using the [`Conf::window_position`] field:
+//! You can center the window on the desktop by setting [`Conf::desktop_center`]:
//! ```ignore
//! Conf {
//! window_width: 800,
//! window_height: 600,
-//! window_position: Some((200, 100)), // Position at (200, 100)
+//! desktop_center: true, // Center window on desktop
//! ..Default::default()
//! }
//! ```
@@ -229,10 +229,10 @@ pub struct Conf {
/// Defaults to `false`.
pub fullscreen: bool,
- /// Optional initial window position (x, y) in screen coordinates.
+ /// If `true`, center the window on the primary monitor.
/// Only works on desktop platforms (Windows, macOS, Linux).
- /// Ignored on WASM, iOS, and Android. Defaults to `None` (system default).
- pub window_position: Option<(i32, i32)>,
+ /// Ignored on WASM, iOS, and Android. Defaults to `false`.
+ pub desktop_center: bool,
/// MSAA sample count.
/// Defaults to `1`.
@@ -296,7 +296,7 @@ impl Default for Conf {
window_resizable: true,
icon: Some(Icon::miniquad_logo()),
platform: Default::default(),
- window_position: None,
+ desktop_center: false,
}
}
}
@@ -314,7 +314,7 @@ impl Default for Conf {
window_resizable: false, //
icon: Some(Icon::miniquad_logo()),
platform: Default::default(),
- window_position: None,
+ desktop_center: false,
}
}
}
diff --git a/src/native/linux_x11/libx11_ex.rs b/src/native/linux_x11/libx11_ex.rs
index 3abf1d264..0ead5213f 100644
--- a/src/native/linux_x11/libx11_ex.rs
+++ b/src/native/linux_x11/libx11_ex.rs
@@ -202,8 +202,13 @@ impl LibX11 {
| PropertyChangeMask;
self.grab_error_handler();
- let (win_x, win_y) = if let Some((x, y)) = conf.window_position {
- (x as libc::c_int, y as libc::c_int)
+ let (win_x, win_y) = if conf.desktop_center {
+ // For desktop centering, calculate center position
+ let screen_width = (self.XDisplayWidth)(display, screen) as i32;
+ let screen_height = (self.XDisplayHeight)(display, screen) as i32;
+ let center_x = (screen_width - conf.window_width) / 2;
+ let center_y = (screen_height - conf.window_height) / 2;
+ (center_x as libc::c_int, center_y as libc::c_int)
} else {
(0 as libc::c_int, 0 as libc::c_int)
};
diff --git a/src/native/macos.rs b/src/native/macos.rs
index 8cdc7985a..446d8fbc4 100644
--- a/src/native/macos.rs
+++ b/src/native/macos.rs
@@ -1221,14 +1221,7 @@ where
}
let window_frame = NSRect {
- origin: if let Some((x, y)) = conf.window_position {
- NSPoint {
- x: x as f64,
- y: y as f64,
- }
- } else {
- NSPoint { x: 0., y: 0. }
- },
+ origin: NSPoint { x: 0., y: 0. },
size: NSSize {
width: conf.window_width as f64,
height: conf.window_height as f64,
@@ -1287,7 +1280,6 @@ where
assert!(!view.is_null());
- let () = msg_send![window, center];
let () = msg_send![window, setAcceptsMouseMovedEvents: YES];
// Register the view to accept dragged file URLs
@@ -1304,6 +1296,27 @@ where
msg_send_![window, orderFront: nil];
let () = msg_send![window, makeKeyAndOrderFront: nil];
+ if conf.desktop_center {
+ // Get the primary monitor's frame for proper centering
+ unsafe {
+ let screen: ObjcId = msg_send![class!(NSScreen), mainScreen];
+ let screen_frame: NSRect = msg_send![screen, frame];
+ let window_frame: NSRect = msg_send![window, frame];
+
+ // Calculate center position
+ let center_x =
+ screen_frame.origin.x + (screen_frame.size.width - window_frame.size.width) / 2.0;
+ let center_y =
+ screen_frame.origin.y + (screen_frame.size.height - window_frame.size.height) / 2.0;
+
+ // Set the window position
+ let mut new_frame = window_frame;
+ new_frame.origin.x = center_x;
+ new_frame.origin.y = center_y;
+ let () = msg_send![window, setFrame:new_frame display:true animate:false];
+ }
+ }
+
let () = msg_send![ns_app, finishLaunching];
// Found this here: https://github.com/kovidgoyal/kitty/issues/6341#issuecomment-1578348104
diff --git a/src/native/windows.rs b/src/native/windows.rs
index 2519b671a..69faf030e 100644
--- a/src/native/windows.rs
+++ b/src/native/windows.rs
@@ -641,7 +641,7 @@ unsafe fn create_window(
resizable: bool,
width: i32,
height: i32,
- position: Option<(i32, i32)>,
+ desktop_center: bool,
) -> (HWND, HDC) {
let mut wndclassw: WNDCLASSW = std::mem::zeroed();
@@ -693,8 +693,13 @@ unsafe fn create_window(
let mut window_name = window_title.encode_utf16().collect::>();
window_name.push(0);
- let (win_x, win_y) = if let Some((x, y)) = position {
- (x, y)
+ let (win_x, win_y) = if desktop_center {
+ // For desktop centering, calculate center position
+ let screen_width = GetSystemMetrics(SM_CXSCREEN);
+ let screen_height = GetSystemMetrics(SM_CYSCREEN);
+ let center_x = (screen_width - win_width) / 2;
+ let center_y = (screen_height - win_height) / 2;
+ (center_x, center_y)
} else {
(CW_USEDEFAULT, CW_USEDEFAULT)
};
@@ -903,7 +908,7 @@ where
conf.window_resizable,
conf.window_width as _,
conf.window_height as _,
- conf.window_position,
+ conf.desktop_center,
);
if let Some(icon) = &conf.icon {
set_icon(wnd, icon);
From 8950818454024fb9e6d377db608490e245a7a474 Mon Sep 17 00:00:00 2001
From: Allen
Date: Tue, 5 Aug 2025 22:11:40 +0800
Subject: [PATCH 09/10] Fix CI build failure for linux
---
src/native/linux_x11/libx11.rs | 3 +++
src/native/linux_x11/libx11_ex.rs | 1 +
2 files changed, 4 insertions(+)
diff --git a/src/native/linux_x11/libx11.rs b/src/native/linux_x11/libx11.rs
index d0c49621d..dcdbcd78a 100644
--- a/src/native/linux_x11/libx11.rs
+++ b/src/native/linux_x11/libx11.rs
@@ -928,6 +928,9 @@ crate::declare_module!(
pub fn XCreatePixmapCursor(*mut Display, Pixmap, Pixmap, *mut XColor, *mut XColor, c_uint, c_uint) -> Cursor,
pub fn XFreePixmap(*mut Display, Pixmap) -> c_int,
pub fn XDefineCursor(*mut Display, Window, Cursor) -> c_int,
+ pub fn XDefaultScreen(*mut Display) -> c_int,
+ pub fn XDisplayWidth(*mut Display, c_int) -> c_int,
+ pub fn XDisplayHeight(*mut Display, c_int) -> c_int,
...
...
pub extensions: X11Extensions,
diff --git a/src/native/linux_x11/libx11_ex.rs b/src/native/linux_x11/libx11_ex.rs
index 0ead5213f..75bb74d21 100644
--- a/src/native/linux_x11/libx11_ex.rs
+++ b/src/native/linux_x11/libx11_ex.rs
@@ -204,6 +204,7 @@ impl LibX11 {
let (win_x, win_y) = if conf.desktop_center {
// For desktop centering, calculate center position
+ let screen = (self.XDefaultScreen)(display);
let screen_width = (self.XDisplayWidth)(display, screen) as i32;
let screen_height = (self.XDisplayHeight)(display, screen) as i32;
let center_x = (screen_width - conf.window_width) / 2;
From 0830fc8d71b0c4e9b045242876d15629800181c4 Mon Sep 17 00:00:00 2001
From: Allen
Date: Tue, 5 Aug 2025 22:19:24 +0800
Subject: [PATCH 10/10] Fix CI build for linux
---
src/native/linux_x11.rs | 27 +++++++++++++++++++++------
src/native/linux_x11/libx11.rs | 1 +
2 files changed, 22 insertions(+), 6 deletions(-)
diff --git a/src/native/linux_x11.rs b/src/native/linux_x11.rs
index 9c4ce50b2..a80804139 100644
--- a/src/native/linux_x11.rs
+++ b/src/native/linux_x11.rs
@@ -704,7 +704,22 @@ where
pub fn primary_monitor() -> crate::MonitorMetrics {
unsafe {
- let display = (LIBX11.XOpenDisplay)(std::ptr::null());
+ // Create a temporary LibX11 instance for monitor queries
+ let mut libx11 = match libx11::LibX11::try_load() {
+ Ok(lib) => lib,
+ Err(_) => {
+ return crate::MonitorMetrics {
+ width: 1920.0,
+ height: 1080.0,
+ position: (0, 0),
+ dpi_scale: 1.0,
+ refresh_rate: None,
+ name: Some("Primary Monitor".to_string()),
+ };
+ }
+ };
+
+ let display = (libx11.XOpenDisplay)(std::ptr::null());
if display.is_null() {
return crate::MonitorMetrics {
width: 1920.0,
@@ -716,19 +731,19 @@ pub fn primary_monitor() -> crate::MonitorMetrics {
};
}
- let screen = (LIBX11.XDefaultScreen)(display);
- let width = (LIBX11.XDisplayWidth)(display, screen) as f32;
- let height = (LIBX11.XDisplayHeight)(display, screen) as f32;
+ let screen = (libx11.XDefaultScreen)(display);
+ let width = (libx11.XDisplayWidth)(display, screen) as f32;
+ let height = (libx11.XDisplayHeight)(display, screen) as f32;
// Get DPI information
- let width_mm = (LIBX11.XDisplayWidthMM)(display, screen) as f32;
+ let width_mm = (libx11.XDisplayWidthMM)(display, screen) as f32;
let dpi_scale = if width_mm > 0.0 {
(width * 25.4 / width_mm) / 96.0 // Convert to DPI scale factor
} else {
1.0
};
- (LIBX11.XCloseDisplay)(display);
+ (libx11.XCloseDisplay)(display);
crate::MonitorMetrics {
width,
diff --git a/src/native/linux_x11/libx11.rs b/src/native/linux_x11/libx11.rs
index dcdbcd78a..672f06b97 100644
--- a/src/native/linux_x11/libx11.rs
+++ b/src/native/linux_x11/libx11.rs
@@ -931,6 +931,7 @@ crate::declare_module!(
pub fn XDefaultScreen(*mut Display) -> c_int,
pub fn XDisplayWidth(*mut Display, c_int) -> c_int,
pub fn XDisplayHeight(*mut Display, c_int) -> c_int,
+ pub fn XDisplayWidthMM(*mut Display, c_int) -> c_int,
...
...
pub extensions: X11Extensions,