smithay-client-toolkit 0.2.3

Toolkit for making client wayland applications.
Documentation
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
432
433
434
435
436
437
438
439
440
441
extern crate byteorder;
extern crate image;
extern crate smithay_client_toolkit as sctk;
extern crate tempfile;

use std::cell::Cell;
use std::env;
use std::io::{BufWriter, Seek, SeekFrom, Write};
use std::rc::Rc;
use std::sync::{Arc, Mutex};

use byteorder::{NativeEndian, WriteBytesExt};

use sctk::reexports::client::protocol::wl_buffer::RequestsTrait as BufferRequests;
use sctk::reexports::client::protocol::wl_compositor::RequestsTrait as CompositorRequests;
use sctk::reexports::client::protocol::wl_display::RequestsTrait as DisplayRequests;
use sctk::reexports::client::protocol::wl_surface::RequestsTrait as SurfaceRequests;
use sctk::reexports::client::protocol::{wl_buffer, wl_seat, wl_shm};
use sctk::reexports::client::{Display, NewProxy, Proxy};
use sctk::utils::{DoubleMemPool, MemPool};
use sctk::window::{BasicFrame, Event as WEvent, State, Window};
use sctk::Environment;

fn main() {
    // First of all, retrieve the path from the program arguments:
    let path = match env::args_os().skip(1).next() {
        Some(p) => p,
        None => {
            println!("USAGE: ./image_wiewer <PATH>");
            return;
        }
    };
    // now, try to open the image
    // the image crate will take care of auto-detecting the file format
    let image = match image::open(&path) {
        Ok(i) => i,
        Err(e) => {
            println!("Failed to open image {}.", path.to_string_lossy());
            println!("Error was: {:?}", e);
            return;
        }
    };
    // We'll need the image in RGBA for drawing it
    let image = image.to_rgba();

    /*
     * Initalize the wayland connexion
     */
    let (display, mut event_queue) =
        Display::connect_to_env().expect("Failed to connect to the wayland server.");

    // All request method of wayland objects return a result, as wayland-client
    // returns an error if you try to send a message on an object that has already
    // been destroyed by a previous message. We know our display has not been
    // destroyed, so we unwrap().
    let registry = display.get_registry().unwrap();

    // The Environment takes a registry and a mutable borrow of the wayland event
    // queue. It uses the event queue internally to exchange messages with the server
    // to process the registry event.
    // Like all manipulation of the event loop, it can fail (if the connexion is
    // unexpectedly lost), and thus returns a result. This failing would prevent
    // us to continue though, so we unrap().
    let env = Environment::from_registry(registry, &mut event_queue).unwrap();

    // Use the compositor global to create a new surface
    let surface = env.compositor
        .create_surface()
        .unwrap() // unwrap for the same reasons as before, we know the compositor
                  // is not yet destroyed
        .implement(|_event, _surface| {
            // Here we implement this surface with a closure processing
            // the event
            //
            // Surface events notify us when it enters or leave an output,
            // this is mostly useful to track DPI-scaling.
            //
            // We don't do it in this example, so this closures ignores all
            // events by doing nothing.
        });

    /*
     * Init the window
     */

    // First of all, this Arc<Mutex<Option<WEvent>>> will store
    // any event from the window that we'll need to process. We
    // store them and will process them later in the event loop
    // rather that process them directly because in a batch of
    // generated events, often only the last one needs to actually
    // be processed, and some events may render other obsoletes.
    // See the closure a few lines below for details
    let next_action = Arc::new(Mutex::new(None::<WEvent>));

    // We clone the arc to pass one end to the closure
    let waction = next_action.clone();
    // Now we actually create the window. The type parameter `BasicFrame` here
    // specifies the type we want to use to draw the borders. SCTK currently
    // only provide this one, but if you don't like the (arguably ugly)
    // borders it draws, you just need to implement the appropriate trait
    // to create your own.
    let mut window = Window::<BasicFrame>::init(
        surface,            // the wl_surface that serves as the basis of this window
        image.dimensions(), // the initial internal dimensions of the window
        &env.compositor,    // -+
        &env.subcompositor, //  | The Window constructor needs access to these globals
        &env.shm,           //  | to initialize the window
        &env.shell,         // -+
        move |evt, ()| {
            // This is the closure that process the Window events.
            // There are 3 possible events:
            // - Close: the user requested the window to be closed, we'll then quit
            // - Configure: the server suggested a new state for the window (possibly
            //   a new size if a resize is in progress). We'll likely need to redraw
            //   our contents
            // - Refresh: the frame itself needs to be redrawn. SCTK does not do this
            //   automatically because it has a cost and should only be done in periods
            //   of the event loop where the client actually wants to draw
            // Here we actually only keep the last event receive according to a priority
            // order of Close > Configure > Refresh.
            // Indeed, if we received a Close, there is not point drawing anything more as
            // we will exit. A new Configure overrides a previous one, and if we received
            // a Configure we will refresh the frame anyway.
            let mut next_action = waction.lock().unwrap();
            // Check if we need to replace the old event by the new one
            let replace = match (&evt, &*next_action) {
                // replace if there is no old event
                (_, &None)
                // or the old event is refresh
                | (_, &Some(WEvent::Refresh))
                // or we had a configure and received a new one
                | (&WEvent::Configure { .. }, &Some(WEvent::Configure { .. }))
                // or the new event is close
                | (&WEvent::Close, _) => true,
                // keep the old event otherwise
                _ => false,
            };
            if replace {
                *next_action = Some(evt);
            }
        },
        // creating the window may fail if the code drawing the frame
        // fails to initialize itself. For BasicFrame this should not happen
        // unless the system is utterly broken, though.
    ).expect("Failed to create a window !");

    // The window needs access to pointer inputs to handle the user interaction
    // with it: resizing/moving the window, or clicking its button.
    // user interaction in wayland is done via the wl_seat object, which represent
    // a set of pointer,keyboard,touchscreen (some may be absent) that logically
    // represent a single user. Most systems have only one seat, multi-seat configurations
    // are quite exotic.
    // Thus, we just automatically bind the first seat we find.
    let seat = env.manager
        .instantiate_auto::<wl_seat::WlSeat>()
        .unwrap()
        .implement(move |_, _| {});
    // And advertize it to the Window so it knows of it and can process the
    // required pointer events.
    window.new_seat(&seat);

    /*
     * Initialization of the memory pool
     */

    // DoubleMemPool::new() requires access to the wl_shm global, which represents
    // the capability of the server to use shared memory (SHM). All wayland servers
    // are required to support it.
    let mut pools = DoubleMemPool::new(&env.shm).expect("Failed to create the memory pools.");

    /*
     * Event Loop preparation and running
     */

    // First, we initialize a few boolean flags that we'll use to track our state:
    // - the window needs to be redrawn
    let mut need_redraw = false;
    // - the frame needs to be refreshed
    let mut need_refresh = false;
    // - have we done the original bootstrap? Depending on the underlying shell
    //   protocol, we may need to wait until we receive a first configure event
    //   before drawing our window. This flag checks if this is done.
    let mut bootstrapped = false;
    // - number of currently free memory pool, which will be shared by the buffer
    //   implementations
    let free_pools = Rc::new(Cell::new(2u32));
    // - are we currently in the process of being resized? (to draw the image or
    //   black content)
    let mut resizing = false;
    // - a QueueToken, we'll need it to implement the buffers
    let token = event_queue.get_token();
    // - the size of our contents
    let mut dimensions = image.dimensions();

    // if our shell does not need to ait for a configure event, we draw right away.
    //
    // Note that this is only the case for the old wl_shell protocol, which is now
    // deprecated. This code is only for compatibility with old server that do not
    // support the new standard xdg_shell protocol.
    //
    // But if we have fallbacked to wl_shell, we need to draw right away because we'll
    // never receive a configure event if we don't draw something...
    if !env.shell.needs_configure() {
        need_redraw = true;
        bootstrapped = true;
    }

    // We can now actually enter the event loop!
    loop {
        // First, check if any pending action was received by the
        // Window implementation:
        match next_action.lock().unwrap().take() {
            // We received a Close event, just break from the loop
            // and let the app quit
            Some(WEvent::Close) => break,
            // We receive a Refresh event, store that we need to refresh the
            // frame
            Some(WEvent::Refresh) => {
                need_refresh = true;
            }
            // We received a configure event, our action depends on its
            // contents
            Some(WEvent::Configure { new_size, states }) => {
                // the configure event contains a suggested size,
                // if it is different from our current size, we need to
                // update it and redraw
                if let Some((w, h)) = new_size {
                    if dimensions != (w, h) {
                        dimensions = (w, h);
                        need_redraw = true;
                    }
                }
                // Are we currently resizing ?
                // We check if a resizing just started or stopped,
                // because in this case we'll swap between drawing black
                // and drawing the window (or the reverse), and thus we need to
                // redraw
                let new_resizing = states.contains(&State::Resizing);
                if new_resizing != resizing {
                    need_redraw = true;
                }
                resizing = new_resizing;

                // If we have not bootstrapped yet, this is the first configure
                // event, time to draw for the first time.
                if !bootstrapped {
                    bootstrapped = true;
                    need_redraw = true;
                }
            }
            // No event, nothing new to do.
            None => {}
        }

        if need_redraw {
            // We need to redraw, but can only do it if at least one of the
            // memory pools is not currently used by the server. If both are
            // used, we'll keep the `need_redraw` flag to `true` and try again
            // at next iteration of the loop.
            if free_pools.get() > 0 {
                // Draw the contents in the pool and retrieve the buffer
                let new_buffer = draw(
                    pools.pool(),
                    if resizing { None } else { Some(&image) },
                    dimensions,
                );
                // We implement this buffer to check when the server sends us the Release
                // event.
                // This method is unsafe for thread-safety reasons, as the callback we provide
                // is not Send/Synd (it uses an Rc<Cell<>>). But our app is not multi-threaded
                // here, so this is fine. =)
                let buffer = unsafe {
                    new_buffer.implement_nonsend(
                        {
                            // get a handle to the free pools counter, for the closure
                            let notify = free_pools.clone();
                            // The closure that will be called when the Release event is
                            // received
                            // - wl_buffer::Event is an enum with a single variant
                            // - we need to annotate the buffer argument because rust's type
                            //   inference is unfortunately not good enough to find it alone
                            move |wl_buffer::Event::Release, buffer: Proxy<wl_buffer::WlBuffer>| {
                                // This buffer has been released by the server. Increment the
                                // count of free pools...
                                notify.set(notify.get() + 1);
                                // ... and destroy the buffer
                                buffer.destroy();
                            }
                        },
                        &token,
                    )
                };
                // Now, swap the MemPools, so next time we'll draw on the other.
                // THis is Double-buffering.
                pools.swap();
                // Register that one more pool is in use.
                free_pools.set(free_pools.get() - 1);
                // Attach this new buffer to our surface.
                window.surface().attach(Some(&buffer), 0, 0);
                // We need to tell the server which part of the surface have changed,
                // it uses it to only reload/redraw relevant parts, for performance.
                // In our case, everything has likely changed, so we damage everything.
                //
                // Two ways are possible for notifying this damage:
                if window.surface().version() >= 4 {
                    // If our server is recent enough and supports at least version 4 of the
                    // wl_surface interface, we can specify the damage in buffer coordinates.
                    // This is obviously the best and do that if possible.
                    window
                        .surface()
                        .damage_buffer(0, 0, dimensions.0 as i32, dimensions.1 as i32);
                } else {
                    // Otherwise, we fallback to compatilibity mode. Here we specify damage
                    // in surface coordinates, which would have been different if we had drawn
                    // our buffer at HiDPI resolution. We didn't though, so it is ok.
                    // Using `damage_buffer` in general is better though.
                    window
                        .surface()
                        .damage(0, 0, dimensions.0 as i32, dimensions.1 as i32);
                }
                // We resize our frame, so that is is draw at the right size by SCTK
                window.resize(dimensions.0, dimensions.1);
                // We also refresh it, so that SCTK actually draw it with the new size
                window.refresh();
                // We commit our drawing surface, so that the server atomically applies
                // all the changes we previously did.
                window.surface().commit();
                // We don't need to redraw or refresh anymore =)
                need_redraw = false;
                need_refresh = false;
            }
        } else if need_refresh {
            // If we don't need to redraw but only to refresh do it
            window.refresh();
            // We still need to commit our surface, as the decorations are drawn as
            // subsurfaces of it.
            window.surface().commit();
            need_refresh = false;
        }

        // These last two calls are necessary for the processing of wayland messages:
        // - first, flush all our requests to the server on the unix socket
        display.flush().unwrap();
        // - then, retrieve all the incoming messages
        //   this method blocks until a message arrives from the server, and will process
        //   all events by calling the implementation of the target object or each,
        //   and only return once all pending messages have been processed.
        event_queue.dispatch().unwrap();
    }
}

// The draw function, which drawn `base_image` in the provided `MemPool`,
// at given dimensions, and returns a wl_buffer to it.
//
// It simply returns the `NewProxy`, as we'll take care of implementing the
// wl_buffer outside of this function.
//
// If `base_image` is `None`, it'll just draw black contents. This is to
// improve performance during resizing: we need to redraw the window frequently
// so that its dimensions follow the pointer during the resizing, but resizing the
// image is costly and long. So during an interactive resize of the window we'll
// just draw black contents to not feel laggy.
fn draw(
    pool: &mut MemPool,
    base_image: Option<&image::ImageBuffer<image::Rgba<u8>, Vec<u8>>>,
    (buf_x, buf_y): (u32, u32),
) -> NewProxy<wl_buffer::WlBuffer> {
    // First of all, we make sure the pool is big enough to hold our
    // image. We'll write in ARGB8888 format, meaning 4 bytes per pixel.
    // This resize method will only resize the pool if the requested size is bigger
    // than the current size, as wayland SHM pools are not allowed to shrink.
    //
    // While writing on the file will automatically grow it, we need to advertize the
    // server of its new size, so the call to this method is necessary.
    pool.resize((4 * buf_x * buf_y) as usize)
        .expect("Failed to resize the memory pool.");

    // Now, we can write the contents. MemPool implement the `Seek` and `Write` traits,
    // so we use it directly as a file to write on.

    // First, seek to the beginning, to overwrite our previous content.
    let _ = pool.seek(SeekFrom::Start(0));
    {
        // A sub-scope to limit our borrow of the pool by this BufWriter.
        // This BufWrite will significantly improve our drawing performance,
        // by reducing the number of syscals we do. =)
        let mut writer = BufWriter::new(&mut *pool);
        if let Some(base_image) = base_image {
            // We have an image to draw

            // first, resize it to the requested size. We just use the function provided
            // by the image crate here.
            let image =
                image::imageops::resize(base_image, buf_x, buf_y, image::FilterType::Nearest);

            // Now, we'll write the pixels of the image to the MemPool.
            //
            // We do this in an horribly inneficient maneer, for the sake of simplicity.
            // We'll send piels to the server in ARGB8888 format (this is one of the only
            // formats that are guaranteed to be supported), but image provides it in
            // RGBA8888, so we need to do the conversion.
            //
            // Aditionnaly, if the image has some transparent parts, we'll blend them into
            // a white background, otherwise the server will draw our window with a
            // transparent background!
            for pixel in image.pixels() {
                // retrieve the pixel values
                let r = pixel.data[0] as u32;
                let g = pixel.data[1] as u32;
                let b = pixel.data[2] as u32;
                let a = pixel.data[3] as u32;
                // blend them
                let r = ::std::cmp::min(0xFF, (0xFF * (0xFF - a) + a * r) / 0xFF);
                let g = ::std::cmp::min(0xFF, (0xFF * (0xFF - a) + a * g) / 0xFF);
                let b = ::std::cmp::min(0xFF, (0xFF * (0xFF - a) + a * b) / 0xFF);
                // write the pixel
                // We use byteorder, as the wayland protocol explicitly specifies
                // that the pixels must be written in native endianness
                let _ = writer.write_u32::<NativeEndian>((0xFF << 24) + (r << 16) + (g << 8) + b);
            }
        } else {
            // We do not have any image to draw, so we draw black contents
            for _ in 0..(buf_x * buf_y) {
                let _ = writer.write_u32::<NativeEndian>(0xFF000000);
            }
        }
        // Don't forget to flush the writer, to make sure all the contents are
        // indeed written to the file.
        let _ = writer.flush();
    }
    // Now, we create a buffer to the memory pool pointing to the contents
    // we just wrote
    return pool.buffer(
        0,                // initial offset of the buffer in the pool
        buf_x as i32,     // width of the buffer, in pixels
        buf_y as i32,     // height of the buffer, in pixels
        4 * buf_x as i32, // stride: number of bytes between the start of two
        //   consecutive rows of pixels
        wl_shm::Format::Argb8888, // the pixel format we wrote in
    );
}