wayland_window/
frame.rs

1use Location;
2use shell;
3use std::fs::File;
4use std::io::{Seek, SeekFrom, Write};
5use std::os::unix::io::AsRawFd;
6use std::sync::{Arc, Mutex};
7use tempfile::tempfile;
8use wayland_client::Proxy;
9use wayland_client::protocol::*;
10
11#[derive(Copy, Clone)]
12pub(crate) struct FrameMetadata {
13    pub(crate) dimensions: (i32, i32),
14    pub(crate) decorate: bool,
15    pub(crate) fullscreen: bool,
16    pub(crate) maximized: bool,
17    pub(crate) min_size: Option<(i32, i32)>,
18    pub(crate) max_size: Option<(i32, i32)>,
19    pub(crate) old_size: Option<(i32, i32)>,
20    pub(crate) activated: bool,
21    pub(crate) ready: bool,
22    pub(crate) need_redraw: bool,
23    pub(crate) ptr_location: Location,
24}
25
26impl FrameMetadata {
27    pub(crate) fn clamp_to_limits(&self, size: (i32, i32)) -> (i32, i32) {
28        use std::cmp::{max, min};
29        let (mut w, mut h) = size;
30        if self.decorate {
31            let (ww, hh) = ::subtract_borders(w, h);
32            w = ww;
33            h = hh;
34        }
35        if let Some((minw, minh)) = self.min_size {
36            w = max(minw, w);
37            h = max(minh, h);
38        }
39        if let Some((maxw, maxh)) = self.max_size {
40            w = min(maxw, w);
41            h = min(maxh, h);
42        }
43        (w, h)
44    }
45}
46
47/// A decorated frame for a window
48///
49/// This object allows you to interact with the shell_surface
50/// and frame.
51///
52/// You'll at least need to use it to resize the borders when you window is
53/// resized.
54///
55/// Dropping it will remove your window and unmap your wl_surface.
56pub struct Frame {
57    pub(crate) surface: wl_surface::WlSurface,
58    contents: wl_subsurface::WlSubsurface,
59    pub(crate) shell_surface: shell::Surface,
60    buffer: Option<wl_buffer::WlBuffer>,
61    tempfile: File,
62    pool: wl_shm_pool::WlShmPool,
63    pub(crate) pointer: Option<wl_pointer::WlPointer>,
64    pub(crate) meta: Arc<Mutex<FrameMetadata>>,
65    buffer_capacity: i32,
66}
67
68/// Possible requested state for a window
69pub enum State<'output> {
70    /// Regular floating window
71    Regular,
72    /// Minimized window
73    Minimized,
74    /// Maximized window
75    Maximized,
76    /// Fullscreen, with optional specification of an output to maximize over
77    Fullscreen(Option<&'output wl_output::WlOutput>),
78}
79
80impl Frame {
81    pub(crate) fn new(user_surface: &wl_surface::WlSurface, width: i32, height: i32,
82                      compositor: &wl_compositor::WlCompositor,
83                      subcompositor: &wl_subcompositor::WlSubcompositor, shm: &wl_shm::WlShm,
84                      shell: &shell::Shell)
85                      -> Result<Frame, ()> {
86        if width <= 0 || height <= 0 {
87            return Err(());
88        }
89
90        let tempfile = match tempfile() {
91            Ok(t) => t,
92            Err(_) => return Err(()),
93        };
94
95        match tempfile.set_len(100) {
96            Ok(()) => {}
97            Err(_) => return Err(()),
98        };
99
100        let pool = shm.create_pool(tempfile.as_raw_fd(), 100);
101
102        let meta = Arc::new(Mutex::new(FrameMetadata {
103            dimensions: (width, height),
104            decorate: false,
105            fullscreen: false,
106            maximized: false,
107            min_size: None,
108            max_size: None,
109            old_size: None,
110            activated: true,
111            ready: !shell.needs_readiness(),
112            need_redraw: shell.needs_readiness(),
113            ptr_location: Location::None,
114        }));
115
116        let frame_surface = compositor.create_surface();
117        let contents = subcompositor
118            .get_subsurface(&user_surface, &frame_surface)
119            .expect("Provided Subcompositor was defunct");
120        contents.set_position(0, 0);
121        contents.set_desync();
122
123        let shell_surface = shell::Surface::from_shell(&frame_surface, shell);
124
125        let mut frame = Frame {
126            surface: frame_surface,
127            contents: contents,
128            shell_surface: shell_surface,
129            buffer: None,
130            tempfile: tempfile,
131            pool: pool,
132            pointer: None,
133            meta: meta,
134            buffer_capacity: 100,
135        };
136
137        frame.redraw();
138
139        Ok(frame)
140    }
141
142    pub(crate) fn redraw(&mut self) {
143        let mut meta = self.meta.lock().unwrap();
144        if !meta.ready {
145            return;
146        }
147
148        if !meta.decorate || meta.fullscreen {
149            // setup a dummy surface so that the subsurface does all
150            // write a transparent buffer
151            self.tempfile.seek(SeekFrom::Start(0)).unwrap();
152            let _ = self.tempfile.write_all(&[0, 0, 0, 0]).unwrap();
153            self.tempfile.flush().unwrap();
154            if let Some(buffer) = self.buffer.take() {
155                // TODO: better handling of buffer release
156                buffer.destroy();
157            }
158            let buffer = self.pool
159                .create_buffer(0, 1, 1, 4, wl_shm::Format::Argb8888)
160                .expect("The pool cannot be defunct!");
161            self.surface.attach(Some(&buffer), 0, 0);
162            self.surface.commit();
163            return
164        }
165
166        let (w, h) = meta.dimensions;
167        let pxcount = ::theme::pxcount(w, h);
168
169        if pxcount * 4 > self.buffer_capacity {
170            // realloc needed!
171            self.tempfile.set_len((pxcount * 4) as u64).unwrap();
172            self.pool.resize(pxcount * 4);
173            self.buffer_capacity = pxcount * 4;
174        }
175        // rewrite the data
176        let mut mmap = unsafe {
177            ::memmap::MmapOptions::new()
178                .len(pxcount as usize * 4)
179                .map_mut(&self.tempfile)
180                .unwrap()
181        };
182        let _ = ::theme::draw_contents(
183            &mut *mmap,
184            w as u32,
185            h as u32,
186            meta.activated,
187            meta.maximized,
188            meta.max_size.is_none(),
189            meta.ptr_location,
190        );
191        mmap.flush().unwrap();
192        drop(mmap);
193
194        // commit a new buffer
195        if let Some(buffer) = self.buffer.take() {
196            // TODO: better handling of buffer release
197            buffer.destroy();
198        }
199        let (full_w, full_h) = ::theme::add_borders(w, h);
200        let buffer = self.pool
201            .create_buffer(0, full_w, full_h, full_w * 4, wl_shm::Format::Argb8888)
202            .expect("The pool cannot be defunct!");
203        self.surface.attach(Some(&buffer), 0, 0);
204        // damage the surface
205        if self.surface.version() >= 4 {
206            self.surface.damage_buffer(0, 0, full_w, full_h);
207        } else {
208            // surface is old and does not support damage_buffer, so we damage
209            // in surface coordinates and hope it is not rescaled
210            self.surface.damage(0, 0, full_w, full_h);
211        }
212        self.surface.commit();
213        self.buffer = Some(buffer);
214        meta.need_redraw = false;
215    }
216
217    /// Refreshes the frame
218    ///
219    /// Redraws the frame to match its requested state (dimensions, presence/
220    /// absence of decorations, ...)
221    ///
222    /// If the frame does not need a redraw, this method will do nothing,
223    /// so don't be afraid to call it frequently.
224    ///
225    /// You need to call this method after every change to the dimensions or state
226    /// of the decorations of your window, otherwise the drawn decorations may go
227    /// out of sync with the state of your content.
228    pub fn refresh(&mut self) {
229        let need_redraw = self.meta.lock().unwrap().need_redraw;
230        if need_redraw {
231            self.redraw();
232        }
233    }
234
235    /// Set a short title for the window.
236    ///
237    /// This string may be used to identify the surface in a task bar, window list, or other user
238    /// interface elements provided by the compositor.
239    pub fn set_title(&self, title: String) {
240        self.shell_surface.set_title(title)
241    }
242
243    /// Set an app id for the surface.
244    ///
245    /// The surface class identifies the general class of applications to which the surface
246    /// belongs.
247    ///
248    /// Several wayland compositors will try to find a `.desktop` file matching this name
249    /// to find metadata about your apps.
250    pub fn set_app_id(&self, app_id: String) {
251        self.shell_surface.set_app_id(app_id)
252    }
253
254    /// Set wether the window should be decorated or not
255    ///
256    /// You need to call `refresh()` afterwards for this to properly
257    /// take effect.
258    pub fn set_decorate(&mut self, decorate: bool) {
259        let mut meta = self.meta.lock().unwrap();
260        meta.decorate = decorate;
261        meta.need_redraw = true;
262        if decorate {
263            let (dx, dy) = ::theme::subsurface_offset();
264            self.contents.set_position(dx, dy);
265        } else {
266            self.contents.set_position(0, 0);
267        }
268    }
269
270    /// Resize the decorations
271    ///
272    /// You should call this whenever you change the size of the contents
273    /// of your window, with the new _inner size_ of your window.
274    ///
275    /// You need to call `refresh()` afterwards for this to properly
276    /// take effect.
277    pub fn resize(&mut self, w: i32, h: i32) {
278        use std::cmp::max;
279        let w = max(w, 1);
280        let h = max(h, 1);
281        let mut meta = self.meta.lock().unwrap();
282        meta.dimensions = (w, h);
283        meta.need_redraw = true;
284    }
285
286    /// Sets the requested state of this surface
287    pub fn set_state(&mut self, state: State) {
288        match state {
289            State::Regular => {
290                self.shell_surface.unset_fullscreen();
291                self.shell_surface.unset_maximized();
292            }
293            State::Minimized => {
294                self.shell_surface.unset_fullscreen();
295                self.shell_surface.set_minimized();
296            }
297            State::Maximized => {
298                self.shell_surface.unset_fullscreen();
299                self.shell_surface.set_maximized();
300            }
301            State::Fullscreen(output) => {
302                self.shell_surface.set_fullscreen(output);
303            }
304        }
305    }
306
307    /// Sets the minimum possible size for this window
308    ///
309    /// Provide either a tuple `Some((width, height))` or `None` to unset the
310    /// minimum size.
311    ///
312    /// The provided size is the interior size, not counting decorations
313    pub fn set_min_size(&mut self, size: Option<(i32, i32)>) {
314        let decorate = {
315            let mut meta = self.meta.lock().unwrap();
316            meta.min_size = size;
317            meta.decorate
318        };
319        self.shell_surface
320            .set_min_size(size.map(|(w, h)| if decorate {
321                ::theme::add_borders(w, h)
322            } else {
323                (w, h)
324            }));
325    }
326
327    /// Sets the maximum possible size for this window
328    ///
329    /// Provide either a tuple `Some((width, height))` or `None` to unset the
330    /// maximum size.
331    ///
332    /// The provided size is the interior size, not counting decorations
333    pub fn set_max_size(&mut self, size: Option<(i32, i32)>) {
334        let decorate = {
335            let mut meta = self.meta.lock().unwrap();
336            meta.max_size = size;
337            meta.decorate
338        };
339        self.shell_surface
340            .set_max_size(size.map(|(w, h)| if decorate {
341                ::theme::add_borders(w, h)
342            } else {
343                (w, h)
344            }));
345    }
346}
347
348impl Drop for Frame {
349    fn drop(&mut self) {
350        self.shell_surface.destroy();
351        self.surface.destroy();
352        self.contents.destroy();
353        if let Some(buffer) = self.buffer.take() {
354            buffer.destroy();
355        }
356        self.pool.destroy();
357        if let Some(ref pointer) = self.pointer {
358            if pointer.version() >= 3 {
359                pointer.release();
360            }
361        }
362    }
363}