1use std::ffi::CString;
2use std::mem::MaybeUninit;
3use std::ptr;
4use thiserror::Error;
5use x11::xlib;
6
7#[derive(Debug, Error)]
9pub enum XError {
10 #[error("X Error: Could not open Display")]
12 DisplayOpen,
13
14 #[error("X Error: Could not load font with name {0:?}")]
16 CouldNotLoadFont(CString),
17
18 #[error("CStrings cannot hold NUL values")]
20 NulError(#[from] std::ffi::NulError),
21}
22
23static_assertions::assert_impl_all!(XError: Sync, Send);
24
25enum Data {
26 FontSet {
27 display: *mut xlib::Display,
28 fontset: xlib::XFontSet,
29 },
30 XFont {
31 display: *mut xlib::Display,
32 xfont: *mut xlib::XFontStruct,
33 },
34}
35
36pub struct Context {
38 data: Data,
39}
40
41impl Context {
42 pub fn new(name: &str) -> Result<Self, XError> {
47 let name: CString = CString::new(name)?;
48 let dpy = unsafe { xlib::XOpenDisplay(ptr::null()) };
50 if dpy.is_null() {
51 return Err(XError::DisplayOpen);
52 }
53 let mut missing_ptr = MaybeUninit::uninit();
54 let mut missing_len = MaybeUninit::uninit();
55 let fontset = unsafe {
57 xlib::XCreateFontSet(
58 dpy,
59 name.as_ptr(),
60 missing_ptr.as_mut_ptr(),
61 missing_len.as_mut_ptr(),
62 ptr::null_mut(),
63 )
64 };
65
66 unsafe {
68 if !missing_ptr.assume_init().is_null() {
69 xlib::XFreeStringList(missing_ptr.assume_init());
70 }
71 }
72 if !fontset.is_null() {
73 Ok(Context {
74 data: Data::FontSet {
75 display: dpy,
76 fontset,
77 },
78 })
79 } else {
80 let xfont = unsafe { xlib::XLoadQueryFont(dpy, name.as_ptr()) };
82 if xfont.is_null() {
83 unsafe { xlib::XCloseDisplay(dpy) };
85 Err(XError::CouldNotLoadFont(name))
86 } else {
87 Ok(Context {
88 data: Data::XFont {
89 display: dpy,
90 xfont,
91 },
92 })
93 }
94 }
95 }
96
97 pub fn with_misc() -> Result<Self, XError> {
99 Self::new("-misc-fixed-*-*-*-*-*-*-*-*-*-*-*-*")
100 }
101
102 pub fn text_width<S: AsRef<str>>(&self, text: S) -> Result<u64, XError> {
104 get_text_width(&self, text)
105 }
106}
107
108impl Drop for Context {
109 fn drop(&mut self) {
110 unsafe {
111 match self.data {
112 Data::FontSet { display, fontset } => {
113 xlib::XFreeFontSet(display, fontset);
114 xlib::XCloseDisplay(display);
115 }
116 Data::XFont { display, xfont } => {
117 xlib::XFreeFont(display, xfont);
118 xlib::XCloseDisplay(display);
119 }
120 }
121 }
122 }
123}
124
125pub fn get_text_width<S: AsRef<str>>(ctx: &Context, text: S) -> Result<u64, XError> {
127 let text = CString::new(text.as_ref())?;
128 unsafe {
129 match ctx.data {
130 Data::FontSet { fontset, .. } => {
131 let mut rectangle = MaybeUninit::uninit();
132 xlib::XmbTextExtents(
133 fontset,
134 text.as_ptr(),
135 text.as_bytes().len() as i32,
136 ptr::null_mut(),
137 rectangle.as_mut_ptr(),
138 );
139 Ok(rectangle.assume_init().width as u64)
140 }
141 Data::XFont { xfont, .. } => {
142 Ok(xlib::XTextWidth(xfont, text.as_ptr(), text.as_bytes().len() as i32) as u64)
143 }
144 }
145 }
146}
147
148pub fn setup_multithreading() {
153 unsafe {
154 xlib::XInitThreads();
155 }
156}
157
158#[cfg(test)]
159mod test {
160 use super::{get_text_width, Context};
161 use std::sync::Once;
162 use x11::xlib;
163 static SETUP: Once = Once::new();
164 fn setup() {
166 SETUP.call_once(|| unsafe {
167 xlib::XInitThreads();
168 })
169 }
170 #[test]
171 fn test_context_new() {
172 setup();
173 let ctx = Context::with_misc();
174 assert!(ctx.is_ok());
175 }
176 #[test]
177 fn test_context_drop() {
178 setup();
179 let ctx = Context::with_misc();
180 drop(ctx);
181 assert!(true);
182 }
183 #[test]
184 fn test_text_width() {
185 setup();
186 let ctx = Context::with_misc().unwrap();
187 assert!(get_text_width(&ctx, "Hello World").unwrap() > 0);
188 }
189 #[test]
190 fn test_text_alternate() {
191 setup();
192 let ctx = Context::new("?");
193 assert!(ctx.is_err());
194 }
195}