window/
window.rs

1// Based on https://github.com/Smithay/client-toolkit/blob/master/examples/themed_window.rs.
2
3use std::sync::Arc;
4use std::time::Duration;
5use std::{convert::TryInto, num::NonZeroU32};
6
7use smithay_client_toolkit::reexports::client::{
8    globals::registry_queue_init,
9    protocol::{wl_output, wl_pointer, wl_seat, wl_shm, wl_surface},
10    Connection, Proxy, QueueHandle,
11};
12use smithay_client_toolkit::reexports::csd_frame::{
13    CursorIcon, DecorationsFrame, FrameAction, FrameClick, ResizeEdge,
14};
15use smithay_client_toolkit::reexports::protocols::xdg::shell::client::xdg_toplevel::ResizeEdge as XdgResizeEdge;
16use smithay_client_toolkit::{
17    compositor::{CompositorHandler, CompositorState},
18    delegate_compositor, delegate_output, delegate_pointer, delegate_registry, delegate_seat,
19    delegate_shm, delegate_subcompositor, delegate_xdg_shell, delegate_xdg_window,
20    output::{OutputHandler, OutputState},
21    registry::{ProvidesRegistryState, RegistryState},
22    registry_handlers,
23    seat::{
24        pointer::{
25            PointerData, PointerEvent, PointerEventKind, PointerHandler, ThemeSpec, ThemedPointer,
26        },
27        Capability, SeatHandler, SeatState,
28    },
29    shell::{
30        xdg::{
31            window::{DecorationMode, Window, WindowConfigure, WindowDecorations, WindowHandler},
32            XdgShell, XdgSurface,
33        },
34        WaylandSurface,
35    },
36    shm::{
37        slot::{Buffer, SlotPool},
38        Shm, ShmHandler,
39    },
40    subcompositor::SubcompositorState,
41};
42
43use sctk_adwaita::{AdwaitaFrame, FrameConfig};
44
45fn main() {
46    let conn = Connection::connect_to_env().unwrap();
47
48    let (globals, mut event_queue) = registry_queue_init(&conn).unwrap();
49    let qh = event_queue.handle();
50    let registry_state = RegistryState::new(&globals);
51    let seat_state = SeatState::new(&globals, &qh);
52    let output_state = OutputState::new(&globals, &qh);
53    let compositor_state =
54        CompositorState::bind(&globals, &qh).expect("wl_compositor not available");
55    let subcompositor_state =
56        SubcompositorState::bind(compositor_state.wl_compositor().clone(), &globals, &qh)
57            .expect("wl_subcompositor not available");
58    let shm_state = Shm::bind(&globals, &qh).expect("wl_shm not available");
59    let xdg_shell_state = XdgShell::bind(&globals, &qh).expect("xdg shell not available");
60
61    let width = 256;
62    let height = 256;
63    let pool = SlotPool::new(width as usize * height as usize * 4, &shm_state)
64        .expect("Failed to create pool");
65
66    let window_surface = compositor_state.create_surface(&qh);
67
68    let window = xdg_shell_state.create_window(window_surface, WindowDecorations::ClientOnly, &qh);
69    window.set_title("A wayland window");
70    // GitHub does not let projects use the `org.github` domain but the `io.github` domain is fine.
71    window.set_app_id("simple-window");
72    window.set_min_size(Some((2, 1)));
73
74    // In order for the window to be mapped, we need to perform an initial commit with no attached buffer.
75    // For more info, see WaylandSurface::commit
76    //
77    // The compositor will respond with an initial configure that we can then use to present to the window with
78    // the correct options.
79    window.commit();
80
81    let mut simple_window = SimpleWindow {
82        title: String::from("/usr/lib/xorg/modules/input"),
83        registry_state,
84        seat_state,
85        output_state,
86        compositor_state: Arc::new(compositor_state),
87        subcompositor_state: Arc::new(subcompositor_state),
88        shm_state,
89        _xdg_shell_state: xdg_shell_state,
90
91        exit: false,
92        first_configure: true,
93        pool,
94        width: NonZeroU32::new(width).unwrap(),
95        height: NonZeroU32::new(height).unwrap(),
96        shift: None,
97        buffer: None,
98        window,
99        window_frame: None,
100        themed_pointer: None,
101        set_cursor: false,
102        cursor_icon: CursorIcon::Crosshair,
103        hide_titlebar: false,
104    };
105
106    // We don't draw immediately, the configure will notify us when to first draw.
107
108    loop {
109        event_queue.blocking_dispatch(&mut simple_window).unwrap();
110
111        if simple_window.exit {
112            println!("exiting example");
113            break;
114        }
115    }
116}
117
118struct SimpleWindow {
119    title: String,
120    registry_state: RegistryState,
121    seat_state: SeatState,
122    output_state: OutputState,
123    compositor_state: Arc<CompositorState>,
124    subcompositor_state: Arc<SubcompositorState>,
125    shm_state: Shm,
126    _xdg_shell_state: XdgShell,
127
128    exit: bool,
129    first_configure: bool,
130    pool: SlotPool,
131    width: NonZeroU32,
132    height: NonZeroU32,
133    shift: Option<u32>,
134    buffer: Option<Buffer>,
135    window: Window,
136    window_frame: Option<AdwaitaFrame<Self>>,
137    themed_pointer: Option<ThemedPointer>,
138    set_cursor: bool,
139    cursor_icon: CursorIcon,
140
141    hide_titlebar: bool,
142}
143
144impl CompositorHandler for SimpleWindow {
145    fn scale_factor_changed(
146        &mut self,
147        _conn: &Connection,
148        _qh: &QueueHandle<Self>,
149        surface: &wl_surface::WlSurface,
150        new_factor: i32,
151    ) {
152        if self.window.wl_surface() == surface {
153            if let Some(frame) = self.window_frame.as_mut() {
154                frame.set_scaling_factor(new_factor as f64);
155            }
156        }
157    }
158
159    fn transform_changed(
160        &mut self,
161        _: &Connection,
162        _: &QueueHandle<Self>,
163        _: &wl_surface::WlSurface,
164        _: wl_output::Transform,
165    ) {
166        // Not needed for this example.
167    }
168
169    fn frame(
170        &mut self,
171        conn: &Connection,
172        qh: &QueueHandle<Self>,
173        _surface: &wl_surface::WlSurface,
174        _time: u32,
175    ) {
176        self.draw(conn, qh);
177    }
178
179    fn surface_enter(
180        &mut self,
181        _conn: &Connection,
182        _qh: &QueueHandle<Self>,
183        _surface: &wl_surface::WlSurface,
184        _output: &wl_output::WlOutput,
185    ) {
186    }
187
188    fn surface_leave(
189        &mut self,
190        _conn: &Connection,
191        _qh: &QueueHandle<Self>,
192        _surface: &wl_surface::WlSurface,
193        _output: &wl_output::WlOutput,
194    ) {
195    }
196}
197
198impl OutputHandler for SimpleWindow {
199    fn output_state(&mut self) -> &mut OutputState {
200        &mut self.output_state
201    }
202
203    fn new_output(
204        &mut self,
205        _conn: &Connection,
206        _qh: &QueueHandle<Self>,
207        _output: wl_output::WlOutput,
208    ) {
209    }
210
211    fn update_output(
212        &mut self,
213        _conn: &Connection,
214        _qh: &QueueHandle<Self>,
215        _output: wl_output::WlOutput,
216    ) {
217    }
218
219    fn output_destroyed(
220        &mut self,
221        _conn: &Connection,
222        _qh: &QueueHandle<Self>,
223        _output: wl_output::WlOutput,
224    ) {
225    }
226}
227
228impl WindowHandler for SimpleWindow {
229    fn request_close(&mut self, _: &Connection, _: &QueueHandle<Self>, _: &Window) {
230        self.exit = true;
231    }
232
233    fn configure(
234        &mut self,
235        conn: &Connection,
236        qh: &QueueHandle<Self>,
237        window: &Window,
238        configure: WindowConfigure,
239        _serial: u32,
240    ) {
241        self.buffer = None;
242
243        println!(
244            "Configure size {:?}, decorations: {:?}",
245            configure.new_size, configure.decoration_mode
246        );
247
248        let (width, height) = if configure.decoration_mode == DecorationMode::Client {
249            let window_frame = self.window_frame.get_or_insert_with(|| {
250                let mut frame = AdwaitaFrame::new(
251                    &self.window,
252                    &self.shm_state,
253                    self.compositor_state.clone(),
254                    self.subcompositor_state.clone(),
255                    qh.clone(),
256                    FrameConfig::auto().hide_titlebar(self.hide_titlebar),
257                )
258                .expect("failed to create client side decorations frame.");
259                frame.set_title(self.title.clone());
260                frame
261            });
262
263            // Un-hide the frame.
264            window_frame.set_hidden(false);
265
266            // Configure state before touching any resizing.
267            window_frame.update_state(configure.state);
268
269            // Configure the button state.
270            window_frame.update_wm_capabilities(configure.capabilities);
271
272            let (width, height) = match configure.new_size {
273                (Some(width), Some(height)) => {
274                    // The size could be 0.
275                    window_frame.subtract_borders(width, height)
276                }
277                _ => {
278                    // You might want to consider checking for configure bounds.
279                    (Some(self.width), Some(self.height))
280                }
281            };
282
283            // Clamp the size to at least one pixel.
284            let width = width.unwrap_or(NonZeroU32::new(1).unwrap());
285            let height = height.unwrap_or(NonZeroU32::new(1).unwrap());
286
287            window_frame.resize(width, height);
288
289            let (x, y) = window_frame.location();
290            let outer_size = window_frame.add_borders(width.get(), height.get());
291            window.xdg_surface().set_window_geometry(
292                x,
293                y,
294                outer_size.0 as i32,
295                outer_size.1 as i32,
296            );
297
298            (width, height)
299        } else {
300            // Hide the frame, if any.
301            if let Some(frame) = self.window_frame.as_mut() {
302                frame.set_hidden(true)
303            }
304            let width = configure.new_size.0.unwrap_or(self.width);
305            let height = configure.new_size.1.unwrap_or(self.height);
306            self.window.xdg_surface().set_window_geometry(
307                0,
308                0,
309                width.get() as i32,
310                height.get() as i32,
311            );
312            (width, height)
313        };
314
315        // Update new width and height;
316        self.width = width;
317        self.height = height;
318
319        // Initiate the first draw.
320        if self.first_configure {
321            self.first_configure = false;
322            self.draw(conn, qh);
323        }
324    }
325}
326
327impl SeatHandler for SimpleWindow {
328    fn seat_state(&mut self) -> &mut SeatState {
329        &mut self.seat_state
330    }
331
332    fn new_seat(&mut self, _: &Connection, _: &QueueHandle<Self>, _: wl_seat::WlSeat) {}
333
334    fn new_capability(
335        &mut self,
336        _conn: &Connection,
337        qh: &QueueHandle<Self>,
338        seat: wl_seat::WlSeat,
339        capability: Capability,
340    ) {
341        if capability == Capability::Pointer && self.themed_pointer.is_none() {
342            println!("Set pointer capability");
343            println!("Creating pointer theme");
344            let surface = self.compositor_state.create_surface(qh);
345            let themed_pointer = self
346                .seat_state
347                .get_pointer_with_theme(
348                    qh,
349                    &seat,
350                    self.shm_state.wl_shm(),
351                    surface,
352                    ThemeSpec::default(),
353                )
354                .expect("Failed to create pointer");
355            self.themed_pointer.replace(themed_pointer);
356        }
357    }
358
359    fn remove_capability(
360        &mut self,
361        _conn: &Connection,
362        _: &QueueHandle<Self>,
363        _: wl_seat::WlSeat,
364        capability: Capability,
365    ) {
366        if capability == Capability::Pointer && self.themed_pointer.is_some() {
367            println!("Unset pointer capability");
368            self.themed_pointer.take().unwrap().pointer().release();
369        }
370    }
371
372    fn remove_seat(&mut self, _: &Connection, _: &QueueHandle<Self>, _: wl_seat::WlSeat) {}
373}
374
375impl PointerHandler for SimpleWindow {
376    fn pointer_frame(
377        &mut self,
378        _conn: &Connection,
379        _qh: &QueueHandle<Self>,
380        pointer: &wl_pointer::WlPointer,
381        events: &[PointerEvent],
382    ) {
383        use PointerEventKind::*;
384        for event in events {
385            let (x, y) = event.position;
386            match event.kind {
387                Enter { .. } => {
388                    self.set_cursor = true;
389                    self.cursor_icon = self
390                        .window_frame
391                        .as_mut()
392                        .and_then(|frame| {
393                            frame.click_point_moved(Duration::ZERO, &event.surface.id(), x, y)
394                        })
395                        .unwrap_or(CursorIcon::Crosshair)
396                        .to_owned();
397
398                    if &event.surface == self.window.wl_surface() {
399                        println!("Pointer entered @{:?}", event.position);
400                    }
401                }
402                Leave { .. } => {
403                    if &event.surface != self.window.wl_surface() {
404                        if let Some(window_frame) = self.window_frame.as_mut() {
405                            window_frame.click_point_left();
406                        }
407                    }
408                    println!("Pointer left");
409                }
410                Motion { time } => {
411                    if let Some(new_cursor) = self.window_frame.as_mut().and_then(|frame| {
412                        frame.click_point_moved(
413                            Duration::from_millis(time as u64),
414                            &event.surface.id(),
415                            x,
416                            y,
417                        )
418                    }) {
419                        self.set_cursor = true;
420                        self.cursor_icon = new_cursor.to_owned();
421                    }
422                }
423                Press {
424                    button,
425                    serial,
426                    time,
427                }
428                | Release {
429                    button,
430                    serial,
431                    time,
432                } => {
433                    let pressed = if matches!(event.kind, Press { .. }) {
434                        true
435                    } else {
436                        false
437                    };
438                    if &event.surface != self.window.wl_surface() {
439                        let click = match button {
440                            0x110 => FrameClick::Normal,
441                            0x111 => FrameClick::Alternate,
442                            _ => continue,
443                        };
444
445                        if let Some(action) = self.window_frame.as_mut().and_then(|frame| {
446                            frame.on_click(Duration::from_millis(time as u64), click, pressed)
447                        }) {
448                            self.frame_action(pointer, serial, action);
449                        }
450                    } else if pressed {
451                        println!("Press {:x} @ {:?}", button, event.position);
452
453                        // Hide/Show titlebar on right click
454                        if button == 0x111 {
455                            self.hide_titlebar = !self.hide_titlebar;
456
457                            if let Some(frame) = self.window_frame.as_mut() {
458                                // FrameConfig::auto() is not free, this shouldn't be called here
459                                let config = FrameConfig::auto();
460
461                                if self.hide_titlebar {
462                                    frame.set_config(config.hide_titlebar(true));
463                                    self.window.xdg_surface().set_window_geometry(
464                                        0,
465                                        0,
466                                        self.width.get() as i32,
467                                        self.height.get() as i32,
468                                    );
469                                } else {
470                                    let (width, height) = (self.width, self.height);
471
472                                    frame.set_config(config.hide_titlebar(false));
473                                    frame.resize(width, height);
474
475                                    let (x, y) = frame.location();
476                                    let outer_size = frame.add_borders(width.get(), height.get());
477                                    self.window.xdg_surface().set_window_geometry(
478                                        x,
479                                        y,
480                                        outer_size.0 as i32,
481                                        outer_size.1 as i32,
482                                    );
483
484                                    // Update new width and height;
485                                    self.width = width;
486                                    self.height = height;
487                                }
488                            }
489                        } else {
490                            self.shift = self.shift.xor(Some(0));
491                        }
492                    }
493                }
494                Axis {
495                    horizontal,
496                    vertical,
497                    ..
498                } => {
499                    if &event.surface == self.window.wl_surface() {
500                        println!("Scroll H:{horizontal:?}, V:{vertical:?}");
501                    }
502                }
503            }
504        }
505    }
506}
507impl SimpleWindow {
508    fn frame_action(&mut self, pointer: &wl_pointer::WlPointer, serial: u32, action: FrameAction) {
509        let pointer_data = pointer.data::<PointerData>().unwrap();
510        let seat = pointer_data.seat();
511        match action {
512            FrameAction::Close => self.exit = true,
513            FrameAction::Minimize => self.window.set_minimized(),
514            FrameAction::Maximize => self.window.set_maximized(),
515            FrameAction::UnMaximize => self.window.unset_maximized(),
516            FrameAction::ShowMenu(x, y) => self.window.show_window_menu(seat, serial, (x, y)),
517            FrameAction::Resize(edge) => {
518                let edge = match edge {
519                    ResizeEdge::None => XdgResizeEdge::None,
520                    ResizeEdge::Top => XdgResizeEdge::Top,
521                    ResizeEdge::Bottom => XdgResizeEdge::Bottom,
522                    ResizeEdge::Left => XdgResizeEdge::Left,
523                    ResizeEdge::TopLeft => XdgResizeEdge::TopLeft,
524                    ResizeEdge::BottomLeft => XdgResizeEdge::BottomLeft,
525                    ResizeEdge::Right => XdgResizeEdge::Right,
526                    ResizeEdge::TopRight => XdgResizeEdge::TopRight,
527                    ResizeEdge::BottomRight => XdgResizeEdge::BottomRight,
528                    _ => return,
529                };
530                self.window.resize(seat, serial, edge);
531            }
532            FrameAction::Move => self.window.move_(seat, serial),
533            _ => (),
534        }
535    }
536}
537
538impl ShmHandler for SimpleWindow {
539    fn shm_state(&mut self) -> &mut Shm {
540        &mut self.shm_state
541    }
542}
543
544impl SimpleWindow {
545    pub fn draw(&mut self, conn: &Connection, qh: &QueueHandle<Self>) {
546        if self.set_cursor {
547            let _ = self
548                .themed_pointer
549                .as_mut()
550                .unwrap()
551                .set_cursor(conn, self.cursor_icon);
552            self.set_cursor = false;
553        }
554
555        let width = self.width.get();
556        let height = self.height.get();
557        let stride = width as i32 * 4;
558
559        let buffer = self.buffer.get_or_insert_with(|| {
560            self.pool
561                .create_buffer(
562                    width as i32,
563                    height as i32,
564                    stride,
565                    wl_shm::Format::Argb8888,
566                )
567                .expect("create buffer")
568                .0
569        });
570
571        let canvas = match self.pool.canvas(buffer) {
572            Some(canvas) => canvas,
573            None => {
574                // This should be rare, but if the compositor has not released the previous
575                // buffer, we need double-buffering.
576                let (second_buffer, canvas) = self
577                    .pool
578                    .create_buffer(
579                        width as i32,
580                        height as i32,
581                        stride,
582                        wl_shm::Format::Argb8888,
583                    )
584                    .expect("create buffer");
585                *buffer = second_buffer;
586                canvas
587            }
588        };
589
590        // Draw to the window:
591        {
592            let shift = self.shift.unwrap_or(0);
593            canvas
594                .chunks_exact_mut(4)
595                .enumerate()
596                .for_each(|(index, chunk)| {
597                    let x = ((index + shift as usize) % width as usize) as u32;
598                    let y = (index / width as usize) as u32;
599
600                    let a = 0xFF;
601                    let r = u32::min(((width - x) * 0xFF) / width, ((height - y) * 0xFF) / height);
602                    let g = u32::min((x * 0xFF) / width, ((height - y) * 0xFF) / height);
603                    let b = u32::min(((width - x) * 0xFF) / width, (y * 0xFF) / height);
604                    let color = (a << 24) + (r << 16) + (g << 8) + b;
605
606                    let array: &mut [u8; 4] = chunk.try_into().unwrap();
607                    *array = color.to_le_bytes();
608                });
609
610            if let Some(shift) = &mut self.shift {
611                *shift = (*shift + 1) % width;
612            }
613        }
614
615        // Draw the decorations frame.
616        self.window_frame.as_mut().map(|frame| {
617            if frame.is_dirty() && !frame.is_hidden() {
618                frame.draw();
619            }
620        });
621
622        // Damage the entire window
623        self.window.wl_surface().damage_buffer(
624            0,
625            0,
626            self.width.get() as i32,
627            self.height.get() as i32,
628        );
629
630        // Request our next frame
631        self.window
632            .wl_surface()
633            .frame(qh, self.window.wl_surface().clone());
634
635        // Attach and commit to present.
636        buffer
637            .attach_to(self.window.wl_surface())
638            .expect("buffer attach");
639        self.window.wl_surface().commit();
640    }
641}
642
643delegate_compositor!(SimpleWindow);
644delegate_subcompositor!(SimpleWindow);
645delegate_output!(SimpleWindow);
646delegate_shm!(SimpleWindow);
647
648delegate_seat!(SimpleWindow);
649delegate_pointer!(SimpleWindow);
650
651delegate_xdg_shell!(SimpleWindow);
652delegate_xdg_window!(SimpleWindow);
653
654delegate_registry!(SimpleWindow);
655
656impl ProvidesRegistryState for SimpleWindow {
657    fn registry(&mut self) -> &mut RegistryState {
658        &mut self.registry_state
659    }
660    registry_handlers![OutputState, SeatState,];
661}