1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
use std::sync::{Arc, Mutex};

use wayland_client::commons::Implementation;
use wayland_client::protocol::{wl_compositor, wl_output, wl_seat, wl_shm, wl_subcompositor,
                               wl_surface};
use wayland_client::Proxy;

use wayland_protocols::xdg_shell::client::xdg_toplevel::ResizeEdge;
pub use wayland_protocols::xdg_shell::client::xdg_toplevel::State;

use Shell;

mod basic_frame;
mod shell;

pub use self::basic_frame::BasicFrame;

/// Possible events generated by a window that you need to handle
#[derive(Clone, Debug)]
pub enum Event {
    /// The state of your window has been changed
    Configure {
        /// Optionnal new size for your *inner* surface
        ///
        /// This is the new size of the contents of your window
        /// as suggested by the server. You can ignore it and choose
        /// a new size if you want better control on the possible
        /// sizes of your window.
        ///
        /// In all cases, these events can be generated in large batches
        /// during an interactive resize, and you should buffer them before
        /// processing them. You only need to handle the last one of a batch.
        new_size: Option<(u32, u32)>,
        /// New combination of states of your window
        ///
        /// Typically tells you if your surface is active/inactive, maximized,
        /// etc...
        states: Vec<State>,
    },
    /// A close request has been received
    ///
    /// Most likely the user has clicked on the close button of the decorations
    /// or something equivalent
    Close,
    /// The decorations need to be refreshed
    Refresh,
}

struct WindowInner<F> {
    frame: Arc<Mutex<F>>,
    shell_surface: Arc<Box<shell::ShellSurface>>,
    user_impl: Box<Implementation<(), Event> + Send>,
    min_size: Option<(u32, u32)>,
    max_size: Option<(u32, u32)>,
    current_size: (u32, u32),
    old_size: Option<(u32, u32)>,
}

/// A window
///
/// This wrapper handles for you the decoration of your window
/// and the interaction with the server regarding the shell protocol.
///
/// You are still entirely responsible for drawing the contents of your
/// window.
///
/// Note also that as the dimensions of wayland surfaces is defined by
/// their attached buffer, you need to keep the decorations in sync with
/// your contents via the `resize(..)` method.
///
/// Different kind of decorations can be used by customizing the type
/// parameter. A few are provided in this crate, but any type implementing
/// the `Frame` trait can do.
pub struct Window<F: Frame> {
    frame: Arc<Mutex<F>>,
    surface: Proxy<wl_surface::WlSurface>,
    shell_surface: Arc<Box<shell::ShellSurface>>,
    inner: Arc<Mutex<Option<WindowInner<F>>>>,
}

impl<F: Frame + 'static> Window<F> {
    /// Create a new window wrapping a given wayland surface as its main content
    ///
    /// It can fail if the initialization of the frame fails (for example if the
    /// frame class fails to initialize its SHM).
    pub fn init<Impl>(
        surface: Proxy<wl_surface::WlSurface>,
        initial_dims: (u32, u32),
        compositor: &Proxy<wl_compositor::WlCompositor>,
        subcompositor: &Proxy<wl_subcompositor::WlSubcompositor>,
        shm: &Proxy<wl_shm::WlShm>,
        shell: &Shell,
        implementation: Impl,
    ) -> Result<Window<F>, F::Error>
    where
        Impl: Implementation<(), Event> + Send,
    {
        let inner = Arc::new(Mutex::new(None::<WindowInner<F>>));
        let frame_inner = inner.clone();
        let shell_inner = inner.clone();
        let mut frame = F::init(
            &surface,
            compositor,
            subcompositor,
            shm,
            Box::new(move |req, serial| {
                if let Some(ref mut inner) = *shell_inner.lock().unwrap() {
                    match req {
                        FrameRequest::Minimize => inner.shell_surface.set_minimized(),
                        FrameRequest::Maximize => inner.shell_surface.set_maximized(),
                        FrameRequest::UnMaximize => inner.shell_surface.unset_maximized(),
                        FrameRequest::Move(seat) => inner.shell_surface.move_(&seat, serial),
                        FrameRequest::Resize(seat, edges) => {
                            inner.shell_surface.resize(&seat, serial, edges)
                        }
                        FrameRequest::Close => inner.user_impl.receive(Event::Close, ()),
                        FrameRequest::Refresh => inner.user_impl.receive(Event::Refresh, ()),
                    }
                }
            }) as Box<_>,
        )?;
        frame.resize(initial_dims);
        let frame = Arc::new(Mutex::new(frame));
        let shell_surface = Arc::new(shell::create_shell_surface(
            shell,
            &surface,
            move |evt, ()| {
                if let Some(ref mut inner) = *frame_inner.lock().unwrap() {
                    if let Event::Configure {
                        states,
                        mut new_size,
                    } = evt
                    {
                        let mut frame = inner.frame.lock().unwrap();
                        // clamp size
                        new_size = new_size.map(|(w, h)| {
                            use std::cmp::{max, min};
                            let (mut w, mut h) = frame.subtract_borders(w as i32, h as i32);
                            if let Some((minw, minh)) = inner.min_size {
                                w = max(w, minw as i32);
                                h = max(h, minh as i32);
                            }
                            if let Some((maxw, maxh)) = inner.max_size {
                                w = min(w, maxw as i32);
                                h = min(h, maxh as i32);
                            }
                            (w as u32, h as u32)
                        });
                        // compute frame changes
                        let mut need_refresh = false;
                        need_refresh |= frame.set_maximized(states.contains(&State::Maximized));
                        if need_refresh {
                            // the maximization state changed
                            if states.contains(&State::Maximized) {
                                // we are getting maximized, store the size for restoration
                                inner.old_size = Some(inner.current_size);
                            } else {
                                // we are getting de-maximized, restore the size
                                if new_size.is_none() {
                                    new_size = inner.old_size.take();
                                }
                            }
                        }
                        need_refresh |= frame.set_active(states.contains(&State::Activated));
                        if need_refresh {
                            inner.user_impl.receive(Event::Refresh, ());
                        }
                        inner
                            .user_impl
                            .receive(Event::Configure { states, new_size }, ());
                    } else {
                        inner.user_impl.receive(evt, ());
                    }
                }
            },
        ));
        *(inner.lock().unwrap()) = Some(WindowInner {
            frame: frame.clone(),
            shell_surface: shell_surface.clone(),
            user_impl: Box::new(implementation) as Box<_>,
            min_size: None,
            max_size: None,
            current_size: initial_dims,
            old_size: None,
        });
        Ok(Window {
            frame,
            shell_surface,
            surface,
            inner,
        })
    }

    /// Notify this window that a new seat is accessible
    ///
    /// This allows the decoration manager to get an handle to the pointer
    /// to manage pointer events and change the pointer image appropriately.
    pub fn new_seat(&mut self, seat: &Proxy<wl_seat::WlSeat>) {
        self.frame.lock().unwrap().new_seat(seat);
    }

    /// Access the surface wrapped in this Window
    pub fn surface(&self) -> &Proxy<wl_surface::WlSurface> {
        &self.surface
    }

    /// Refreshes the frame
    ///
    /// Redraws the frame to match its requested state (dimensions, presence/
    /// absence of decorations, ...)
    ///
    /// You need to call this method after every change to the dimensions or state
    /// of the decorations of your window, otherwise the drawn decorations may go
    /// out of sync with the state of your content.
    ///
    /// Your implementation will also receive `Refresh` events when the frame requests
    /// to be redrawn (to provide some frame animations for example).
    pub fn refresh(&mut self) {
        self.frame.lock().unwrap().redraw();
    }

    /// Set a short title for the window.
    ///
    /// This string may be used to identify the surface in a task bar, window list, or othe
    /// user interface elements provided by the compositor.
    pub fn set_title(&self, title: String) {
        self.shell_surface.set_title(title);
    }

    /// Set an app id for the surface.
    ///
    /// The surface class identifies the general class of applications to which the surface
    /// belongs.
    ///
    /// Several wayland compositors will try to find a `.desktop` file matching this name
    /// to find metadata about your apps.
    pub fn set_app_id(&self, app_id: String) {
        self.shell_surface.set_app_id(app_id);
    }

    /// Set whether the window should be decorated or not
    ///
    /// You need to call `refresh()` afterwards for this to properly
    /// take effect.
    pub fn set_decorate(&self, decorate: bool) {
        self.frame.lock().unwrap().set_hidden(!decorate);
    }

    /// Set whether the window should be resizeable by the user
    ///
    /// This is not an hard blocking, as the compositor can always
    /// resize you forcibly if it wants. However it signals it that
    /// you don't want this window to be resized.
    ///
    /// Additionnaly, the decorations will stop suggesting the user
    /// to resize by dragging the borders if you set the window as
    /// non-resizable.
    ///
    /// When re-activating resizability, any previously set min/max
    /// sizes are restored.
    pub fn set_resizable(&self, resizable: bool) {
        self.frame.lock().unwrap().set_resizable(resizable);
        let mut inner = self.inner.lock().unwrap();
        if let Some(ref mut inner) = *inner {
            if resizable {
                // restore the min/max sizes
                self.shell_surface.set_min_size(inner.min_size.map(u_to_i));
                self.shell_surface.set_max_size(inner.max_size.map(u_to_i));
            } else {
                // lock the min/max sizes to current size
                self.shell_surface
                    .set_min_size(Some(u_to_i(inner.current_size)));
                self.shell_surface
                    .set_max_size(Some(u_to_i(inner.current_size)));
            }
        }
    }

    /// Resize the decorations
    ///
    /// You should call this whenever you change the size of the contents
    /// of your window, with the new _inner size_ of your window.
    ///
    /// You need to call `refresh()` afterwards for this to properly
    /// take effect.
    pub fn resize(&mut self, w: u32, h: u32) {
        use std::cmp::max;
        let w = max(w, 1);
        let h = max(h, 1);
        if let Some(ref mut inner) = *self.inner.lock().unwrap() {
            inner.current_size = (w, h);
        }
        self.frame.lock().unwrap().resize((w, h));
    }

    /// Request the window to be maximized
    pub fn set_maximized(&self) {
        self.shell_surface.set_maximized();
    }

    /// Request the window to be un-maximized
    pub fn unset_maximized(&self) {
        self.shell_surface.unset_maximized();
    }

    /// Request the window to be minimized
    pub fn set_minimized(&self) {
        self.shell_surface.set_minimized();
    }

    /// Request the window to be set fullscreen
    ///
    /// Note: you need to manually disable the decorations if you
    /// want to hide them!
    pub fn set_fullscreen(&self, output: Option<&Proxy<wl_output::WlOutput>>) {
        self.shell_surface.set_fullscreen(output);
    }

    /// Request the window to quit fullscreen mode
    pub fn unset_fullscreen(&self) {
        self.shell_surface.unset_fullscreen();
    }

    /// Sets the minimum possible size for this window
    ///
    /// Provide either a tuple `Some((width, height))` or `None` to unset the
    /// minimum size.
    ///
    /// The provided size is the interior size, not counting decorations
    pub fn set_min_size(&mut self, size: Option<(u32, u32)>) {
        let min_size =
            size.map(|(w, h)| self.frame.lock().unwrap().add_borders(w as i32, h as i32));
        self.shell_surface.set_min_size(min_size);
        if let Some(ref mut inner) = *(self.inner.lock().unwrap()) {
            inner.min_size = min_size.map(|(w, h)| (w as u32, h as u32));
        }
    }

    /// Sets the maximum possible size for this window
    ///
    /// Provide either a tuple `Some((width, height))` or `None` to unset the
    /// maximum size.
    ///
    /// The provided size is the interior size, not counting decorations
    pub fn set_max_size(&mut self, size: Option<(u32, u32)>) {
        let max_size =
            size.map(|(w, h)| self.frame.lock().unwrap().add_borders(w as i32, h as i32));
        self.shell_surface.set_max_size(max_size);
        if let Some(ref mut inner) = *(self.inner.lock().unwrap()) {
            inner.max_size = max_size.map(|(w, h)| (w as u32, h as u32));
        }
    }
}

impl<F: Frame> Drop for Window<F> {
    fn drop(&mut self) {
        self.inner.lock().unwrap().take();
    }
}

/// Request generated by a Frame
///
/// These requests are generated by a Frame and the Window will
/// forward them appropriately to the server.
pub enum FrameRequest {
    /// The window should be minimized
    Minimize,
    /// The window should be maximized
    Maximize,
    /// The window should be unmaximized
    UnMaximize,
    /// The window should be closed
    Close,
    /// An interactive move should be started
    Move(Proxy<wl_seat::WlSeat>),
    /// An interactive resize should be started
    Resize(Proxy<wl_seat::WlSeat>, ResizeEdge),
    /// The frame requests to be refreshed
    Refresh,
}

/// Interface for defining the drawing of decorations
///
/// A type implementing this trait can be used to define custom
/// decorations additionnaly to the ones provided by this crate
/// and be used with `Window`.
pub trait Frame: Sized + Send {
    /// Type of errors that may occur when attempting to create a frame
    type Error;
    /// Initialize the Frame
    fn init(
        base_surface: &Proxy<wl_surface::WlSurface>,
        compositor: &Proxy<wl_compositor::WlCompositor>,
        subcompositor: &Proxy<wl_subcompositor::WlSubcompositor>,
        shm: &Proxy<wl_shm::WlShm>,
        implementation: Box<Implementation<u32, FrameRequest> + Send>,
    ) -> Result<Self, Self::Error>;
    /// Set whether the decorations should be drawn as active or not
    ///
    /// Calling this should *not* trigger a redraw, but return `true` if
    /// a redraw is needed.
    fn set_active(&mut self, active: bool) -> bool;
    /// Set whether the decorations should be drawn as maximized or not
    ///
    /// Calling this should *not* trigger a redraw, but return `true` if
    /// a redraw is needed.
    fn set_maximized(&mut self, maximized: bool) -> bool;
    /// Hide or show the decorations
    ///
    /// Calling this should *not* trigger a redraw
    fn set_hidden(&mut self, hidden: bool);
    /// Set whether interactive resize hints should be displayed
    /// and reacted to
    fn set_resizable(&mut self, resizable: bool);
    /// Notify that a new wl_seat should be handled
    fn new_seat(&mut self, seat: &Proxy<wl_seat::WlSeat>);
    /// Change the size of the decorations
    ///
    /// Calling this should *not* trigger a redraw
    fn resize(&mut self, newsize: (u32, u32));
    /// Redraw the decorations
    fn redraw(&mut self);
    /// Subtracts the border dimensions from the given dimensions.
    fn subtract_borders(&self, width: i32, height: i32) -> (i32, i32);
    /// Adds the border dimensions to the given dimensions.
    fn add_borders(&self, width: i32, height: i32) -> (i32, i32);
}

fn u_to_i(v: (u32, u32)) -> (i32, i32) {
    (v.0 as i32, v.1 as i32)
}