flutterbug/
lib.rs

1/* -----------------------------------------------------------------------------------
2 * src/lib.rs - Root of the Flutterbug library, for safe X11 bindings.
3 * beetle - Simple graphics framework for Rust
4 * Copyright © 2020 not_a_seagull
5 *
6 * This project is licensed under either the Apache 2.0 license or the MIT license, at
7 * your option. For more information, please consult the LICENSE-APACHE or LICENSE-MIT
8 * files in the repository root.
9 * -----------------------------------------------------------------------------------
10 * MIT License:
11 *
12 * Permission is hereby granted, free of charge, to any person obtaining a copy
13 * of this software and associated documentation files (the “Software”), to deal
14 * in the Software without restriction, including without limitation the rights
15 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
16 * copies of the Software, and to permit persons to whom the Software is
17 * furnished to do so, subject to the following conditions:
18 *
19 * The above copyright notice and this permission notice shall be included in
20 * all copies or substantial portions of the Software.
21 *
22 * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
25 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
27 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
28 * THE SOFTWARE.
29 * -----------------------------------------------------------------------------------
30 * Apache 2.0 License Declaration:
31 *
32 * Licensed under the Apache License, Version 2.0 (the "License");
33 * you may not use this file except in compliance with the License.
34 * You may obtain a copy of the License at
35 *
36 *     http://www.apache.org/licenses/LICENSE-2.0
37 *
38 * Unless required by applicable law or agreed to in writing, software
39 * distributed under the License is distributed on an "AS IS" BASIS,
40 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
41 * See the License for the specific language governing permissions and
42 * limitations under the License.
43 * ----------------------------------------------------------------------------------
44 */
45
46//! X11 wrapper library.
47
48pub extern crate x11;
49
50use euclid::default::{Point2D, Size2D};
51use std::{
52    ffi::CString,
53    fmt, mem,
54    os::raw::{c_char, c_int, c_uint},
55    ptr::{self, NonNull},
56    sync::{Arc, Weak},
57};
58use x11::xlib::{self, XID};
59
60pub use x11::xlib::{Atom, KeySym};
61
62pub mod color;
63pub use color::*;
64pub mod context;
65pub use context::*;
66pub mod drawable;
67pub use drawable::*;
68pub mod error;
69pub use error::*;
70pub mod event;
71pub use event::*;
72pub mod font;
73pub use font::*;
74pub mod image;
75pub use image::*;
76pub mod pixmap;
77pub use pixmap::*;
78mod screen;
79pub use screen::*;
80pub mod text;
81pub use text::*;
82pub mod window;
83pub use window::*;
84
85/// Utility function to convert a String into an ASCII *mut c_char
86#[inline]
87pub(crate) unsafe fn to_cstring(s: &str) -> Result<*mut c_char, FlutterbugError> {
88    Ok(CString::new(s)?.into_raw())
89}
90
91/// Utility function to create a string buffer of a certain length.
92#[inline]
93pub(crate) fn cstring_buffer(len: usize) -> CString {
94    let mut buffer: Vec<u8> = Vec::with_capacity(len + 1);
95    buffer.extend([b' '].iter().cycle().take(len));
96    unsafe { CString::from_vec_unchecked(buffer) }
97}
98
99/// A trait that represents that something can be transformed into an XID.
100pub trait HasXID {
101    /// Get the XID for this instance.
102    fn xid(&self) -> XID;
103}
104
105impl HasXID for XID {
106    #[inline]
107    fn xid(&self) -> XID {
108        *self
109    }
110}
111
112/// The X11 display. This is the context object used for the X11 window.
113///
114/// Note: This object is not clonable. Use the reference() method to get
115/// a cheap reference to this object.
116pub struct Display {
117    raw: Arc<NonNull<xlib::Display>>,
118}
119
120impl PartialEq for Display {
121    #[inline]
122    fn eq(&self, other: &Self) -> bool {
123        // check if the pointers are equal
124        self.raw.as_ptr() == other.raw.as_ptr()
125    }
126}
127
128impl Eq for Display {}
129
130// make sure it can be debugged
131impl fmt::Debug for Display {
132    #[inline]
133    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
134        write!(f, "X11 Display Object")
135    }
136}
137
138impl Drop for Display {
139    fn drop(&mut self) {
140        unsafe { xlib::XCloseDisplay(self.raw.as_ptr()) };
141    }
142}
143
144impl Display {
145    /// Create a new Display. This will call the XOpenDisplay function and
146    /// store the result in an Arc. If XOpenDisplay returns null, the
147    /// UnableToOpenDisplay error is returned instead.
148    #[inline]
149    pub fn new() -> Result<Self, FlutterbugError> {
150        let display_ptr = unsafe { xlib::XOpenDisplay(ptr::null()) };
151        match NonNull::new(display_ptr) {
152            Some(dpy) => Ok(Self::from_raw(Arc::new(dpy))),
153            None => Err(FlutterbugError::UnableToOpenDisplay),
154        }
155    }
156
157    /// Since the Display object is a cheap wrapper around the Display pointer,
158    /// we can use it to forward calls to the Arc<> once we upgrade a Weak<> to
159    /// it. This just creates the wrapper.
160    #[inline]
161    pub(crate) fn from_raw(raw: Arc<NonNull<xlib::Display>>) -> Self {
162        Self { raw }
163    }
164}
165
166/// A reference to the X11 display. Unlike the Display object, this is
167/// clonable. However, it will also decay if its parent Display object
168/// is dropped.
169pub struct DisplayReference {
170    reference: Weak<NonNull<xlib::Display>>,
171}
172
173// make sure it can be debuged
174impl fmt::Debug for DisplayReference {
175    #[inline]
176    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
177        write!(f, "X11 Display Reference")
178    }
179}
180
181impl PartialEq for DisplayReference {
182    fn eq(&self, other: &Self) -> bool {
183        self.reference.ptr_eq(&other.reference)
184    }
185}
186
187impl Clone for DisplayReference {
188    #[inline]
189    fn clone(&self) -> Self {
190        Self {
191            reference: self.reference.clone(),
192        }
193    }
194}
195
196impl DisplayReference {
197    #[inline]
198    pub(crate) fn from_ref(reference: Weak<NonNull<xlib::Display>>) -> Self {
199        Self { reference }
200    }
201}
202
203/// This trait is applied to both Display and DisplayReference to ensure you can do
204/// the same things with both.
205///
206/// Note: All methods return a Result<T, FlutterbugError> since upgrading the reference
207/// to a full object can generate an error if the real Display is already dropped.
208pub trait GenericDisplay: fmt::Debug {
209    /// Create a reference to this object.
210    fn reference(&self) -> DisplayReference;
211
212    /// Get the pointer to the raw Display object.
213    fn raw(&self) -> Result<NonNull<xlib::Display>, FlutterbugError>;
214
215    /// Get the default screen for this instance.
216    #[inline]
217    fn default_screen(&self) -> Result<Screen, FlutterbugError> {
218        Ok(Screen::new(unsafe {
219            xlib::XDefaultScreen(self.raw()?.as_mut())
220        }))
221    }
222
223    /// Get the visual for the screen.
224    #[inline]
225    fn visual(&self, screen: Screen) -> Result<*mut xlib::Visual, FlutterbugError> {
226        Ok(unsafe { xlib::XDefaultVisual(self.raw()?.as_mut(), screen.value()) })
227    }
228
229    /// Get the default visual for the default screen.
230    #[inline]
231    fn default_visual(&self) -> Result<*mut xlib::Visual, FlutterbugError> {
232        self.visual(self.default_screen()?)
233    }
234
235    /// Get the black pixel for the screen.
236    #[inline]
237    fn black_pixel(&self, screen: Screen) -> Result<Color, FlutterbugError> {
238        Ok(Color::PixelID(unsafe {
239            xlib::XBlackPixel(self.raw()?.as_mut(), screen.value())
240        }))
241    }
242
243    /// Get the default black pixel for the default screen.
244    #[inline]
245    fn default_black_pixel(&self) -> Result<Color, FlutterbugError> {
246        self.black_pixel(self.default_screen()?)
247    }
248
249    /// Get the white pixel for the screen.
250    #[inline]
251    fn white_pixel(&self, screen: Screen) -> Result<Color, FlutterbugError> {
252        Ok(Color::PixelID(unsafe {
253            xlib::XWhitePixel(self.raw()?.as_mut(), screen.value())
254        }))
255    }
256
257    /// Get the default white pixel for the default screen.
258    #[inline]
259    fn default_white_pixel(&self) -> Result<Color, FlutterbugError> {
260        self.white_pixel(self.default_screen()?)
261    }
262
263    /// Get the colormap for the screen.
264    #[inline]
265    fn colormap(&self, screen: Screen) -> Result<ColorMap, FlutterbugError> {
266        let cmp = unsafe { xlib::XDefaultColormap(self.raw()?.as_mut(), screen.value()) };
267        Ok(ColorMap::from_raw(cmp, self.reference(), true)?)
268    }
269
270    /// Get the default colormap for the default screen.
271    #[inline]
272    fn default_colormap(&self) -> Result<ColorMap, FlutterbugError> {
273        self.colormap(self.default_screen()?)
274    }
275
276    /// Get the default graphics context for a screen.
277    #[inline] 
278    fn gc(&self, screen: Screen) -> Result<GraphicsContext, FlutterbugError> {
279        let gc = unsafe { xlib::XDefaultGC(self.raw()?.as_mut(), screen.value()) };
280        let gc = NonNull::new(gc).ok_or_else(|| FlutterbugError::GCWasNull)?;
281        Ok(GraphicsContext::from_raw(Arc::new(gc), self.reference(), true))
282    }
283
284    /// Get the graphics context for the default screen.
285    #[inline]
286    fn default_gc(&self) -> Result<GraphicsContext, FlutterbugError> {
287        self.gc(self.default_screen()?)
288    }
289
290    /// Create a simple window from this display.
291    fn create_simple_window(
292        &self,
293        parent: Option<&Window>,
294        origin: Point2D<i32>,
295        size: Size2D<u32>,
296        border_width: u32,
297        border_color: Color,
298        background_color: Color,
299    ) -> Result<Window, FlutterbugError> {
300        macro_rules! test_color {
301            ($cname: ident) => {
302                if $cname != self.default_black_pixel()? && $cname != self.default_white_pixel()? {
303                    return Err(FlutterbugError::Msg(format!(
304                        "{} must be either black or white",
305                        &stringify!($cname)
306                    )));
307                }
308            };
309        }
310
311        test_color!(border_color);
312        test_color!(background_color);
313
314        let win = unsafe {
315            xlib::XCreateSimpleWindow(
316                self.raw()?.as_mut(),
317                match parent {
318                    Some(p) => p.window(),
319                    None => xlib::XRootWindow(self.raw()?.as_mut(), self.default_screen()?.value()),
320                },
321                origin.x as c_int,
322                origin.y as c_int,
323                size.width as c_uint,
324                size.height as c_uint,
325                border_width as c_uint,
326                border_color.pixel_id(),
327                background_color.pixel_id(),
328            )
329        };
330
331        // create a graphics context
332        let gc = unsafe { xlib::XCreateGC(self.raw()?.as_mut(), win, 0, ptr::null_mut()) };
333        let gc = NonNull::new(gc).ok_or_else(|| FlutterbugError::GCWasNull)?;
334        let gc = GraphicsContext::from_raw(Arc::new(gc), self.reference(), false);
335
336        Ok(Window::from_raw(win, self.reference(), gc))
337    }
338    /// Create a context using this connection.
339    fn create_context(&self) -> Result<Context, FlutterbugError> {
340        Ok(Context::from_dpy(self.reference()))
341    }
342    /// Get an internal atom based upon its name.
343    fn internal_atom(
344        &self,
345        name: &str,
346        create_if_exists: bool,
347    ) -> Result<xlib::Atom, FlutterbugError> {
348        let txt = unsafe { to_cstring(name) }?;
349        let val = Ok(unsafe {
350            xlib::XInternAtom(
351                self.raw()?.as_mut(),
352                txt,
353                if create_if_exists { 1 } else { 0 },
354            )
355        });
356        let _ = unsafe { CString::from_raw(txt) };
357        val
358    }
359    /// Create a new input method based on this display.
360    fn input_method(&self) -> Result<InputMethod, FlutterbugError> {
361        // try to get the XIM based on the environment vars
362        unsafe { libc::setlocale(libc::LC_ALL, (&[0]).as_ptr()) };
363        unsafe { xlib::XSetLocaleModifiers((&[0]).as_ptr()) };
364
365        #[inline]
366        fn open_im(mut dpy: NonNull<xlib::Display>) -> xlib::XIM {
367            unsafe {
368                xlib::XOpenIM(
369                    dpy.as_mut(),
370                    ptr::null_mut(),
371                    ptr::null_mut(),
372                    ptr::null_mut(),
373                )
374            }
375        }
376
377        let xim = NonNull::new(open_im(self.raw()?));
378        Ok(InputMethod::from_raw(
379            self.reference(),
380            match xim {
381                Some(x) => x,
382                None => {
383                    // try setting the locale to the internal input method
384                    let txt = unsafe { to_cstring("@im=none") }?;
385                    unsafe { xlib::XSetLocaleModifiers(txt) };
386                    let _ = unsafe { CString::from_raw(txt) };
387
388                    NonNull::new(open_im(self.raw()?))
389                        .ok_or_else(|| FlutterbugError::InputMethodNull)?
390                }
391            },
392        ))
393    }
394    /// Create a new image from this display.
395    #[inline]
396    fn create_image(
397        &self,
398        bounds: Size2D<u32>,
399        depth: u32,
400        data: Vec<c_char>,
401    ) -> Result<Image, FlutterbugError> {
402        let mut boxed = data.into_boxed_slice();
403        let ptr = boxed.as_mut_ptr();
404        let raw = self.raw()?;
405        let img = unsafe {
406            xlib::XCreateImage(
407                raw.as_ptr(),
408                self.default_visual()?,
409                depth as c_uint,
410                xlib::ZPixmap,
411                0,
412                ptr,
413                bounds.width,
414                bounds.height,
415                32,
416                0,
417            )
418        };
419        let img = NonNull::new(img).ok_or_else(|| FlutterbugError::ImageWasNull)?;
420
421        // don't dealloc ptr
422        mem::forget(boxed);
423
424        Ok(Image::from_raw(Arc::new(img)))
425    }
426
427    fn sync(&self, s: bool) -> Result<(), FlutterbugError> {
428        unsafe { xlib::XSync(self.raw()?.as_mut(), if s { 1 } else { 0 }) };
429        Ok(())
430    }
431
432    /// Get the depth for a particular screen.
433    fn depth(&self, screen: Screen) -> Result<i32, FlutterbugError> {
434        Ok(unsafe { xlib::XDefaultDepth(self.raw()?.as_mut(), screen.value()) })
435    }
436
437    /// Get the depth for the default screen.
438    fn default_depth(&self) -> Result<i32, FlutterbugError> {
439        self.depth(self.default_screen()?)
440    }
441}
442
443impl GenericDisplay for Display {
444    #[inline]
445    fn reference(&self) -> DisplayReference {
446        DisplayReference::from_ref(Arc::downgrade(&self.raw))
447    }
448    #[inline]
449    fn raw(&self) -> Result<NonNull<xlib::Display>, FlutterbugError> {
450        Ok(*self.raw)
451    }
452}
453
454// just forward calls to the inner Display
455impl GenericDisplay for DisplayReference {
456    #[inline]
457    fn reference(&self) -> DisplayReference {
458        self.clone()
459    }
460    #[inline]
461    fn raw(&self) -> Result<NonNull<xlib::Display>, FlutterbugError> {
462        Ok(*self
463            .reference
464            .upgrade()
465            .ok_or_else(|| FlutterbugError::PointerWasDropped(DroppableObject::Display))?)
466    }
467}
468
469/// Traits that should be imported in order to ensure the function of the library.
470pub mod prelude {
471    pub use super::{
472        DerivesAnEvent, DerivesEvent, Drawable, GenericDisplay, GenericGraphicsContext,
473        GenericImage, GenericInputContext, HasXID,
474    };
475}