Integrate native Rime and stabilize input, output, and rendering

- Add optional modifier-only Super binding whose tap is cancelled by
  other input, focus changes, grabs, locks, and session teardown.
- Ship the native ferese-rime user service, Rime defaults, installer
  inventory, package dependencies, session environment, and docs.
- Vendor Smithay 8a27023 with the SHM texture-cache format fix and add
  GPU regressions for format changes and resize snapshots.
- Release reused CRTC owners before staging output modes, then restore
  changed outputs before former owners when a transaction fails.
- Wait for resize-capture GPU completion, expand filtered surface
  damage, and preserve IME popup origin under fractional scaling.
- Mark weekends like holidays in both calendar layouts.
This commit is contained in:
aria committed 2026-10-30 03:13:43 +09:00
1 parent a2be421974
commit 993bc0f88f
391 files changed
+130908 -76

No files matched your search

+10
View File
@@ -0,0 +1,10 @@
[package]
name = "test_clients"
version = "0.1.0"
edition = "2024"
license = "MIT"
[dependencies]
smithay-client-toolkit = "0.19.2"
tracing = { version = "0.1.37" }
tracing-subscriber = { version = "0.3.16", features = ["env-filter"] }
@@ -0,0 +1,355 @@
//! Constrain the pointer to surface region
use smithay_client_toolkit::{
compositor::{CompositorHandler, CompositorState},
delegate_compositor, delegate_output, delegate_pointer, delegate_pointer_constraints, delegate_registry,
delegate_seat, delegate_shm, delegate_xdg_shell, delegate_xdg_window,
output::{OutputHandler, OutputState},
reexports::{
calloop, client as wayland_client,
protocols::wp::pointer_constraints::zv1::client::{
zwp_confined_pointer_v1::ZwpConfinedPointerV1, zwp_locked_pointer_v1::ZwpLockedPointerV1,
zwp_pointer_constraints_v1,
},
},
registry::{ProvidesRegistryState, RegistryState},
registry_handlers,
seat::{
Capability, SeatHandler, SeatState,
pointer::{PointerEvent, PointerEventKind, PointerHandler},
pointer_constraints::{PointerConstraintsHandler, PointerConstraintsState},
},
shell::{
WaylandSurface,
xdg::{
XdgShell,
window::{Window, WindowConfigure, WindowDecorations, WindowHandler},
},
},
shm::{
Shm, ShmHandler,
slot::{Buffer, SlotPool},
},
};
use wayland_client::{
Connection, QueueHandle, delegate_noop,
protocol::{
wl_output::{self, WlOutput},
wl_pointer::WlPointer,
wl_region::WlRegion,
wl_seat,
wl_surface::{self, WlSurface},
},
};
use tracing::{info, warn};
fn main() {
test_clients::init_logging();
let (mut event_loop, globals, qh) = test_clients::init_connection::<App>();
let compositor_state = CompositorState::bind(&globals, &qh).unwrap();
let xdg_shell = XdgShell::bind(&globals, &qh).unwrap();
let surface = compositor_state.create_surface(&qh);
let window = xdg_shell.create_window(surface, WindowDecorations::RequestServer, &qh);
let shm = Shm::bind(&globals, &qh).unwrap();
let pool = SlotPool::new(256 * 256 * 4, &shm).unwrap();
let mut simple_window = App {
registry_state: RegistryState::new(&globals),
output_state: OutputState::new(&globals, &qh),
compositor_state,
seat_state: SeatState::new(&globals, &qh),
shm,
pointer: None,
confined_pointer: None,
pointer_constraint_state: PointerConstraintsState::bind(&globals, &qh),
should_be_mapped: false,
first_configure: true,
pool,
width: 256,
height: 256,
shift: 0,
buffer: None,
window,
loop_signal: event_loop.get_signal(),
};
simple_window.map();
event_loop.run(None, &mut simple_window, |_| {}).unwrap();
}
struct App {
registry_state: RegistryState,
output_state: OutputState,
compositor_state: CompositorState,
seat_state: SeatState,
shm: Shm,
pointer: Option<WlPointer>,
confined_pointer: Option<ZwpConfinedPointerV1>,
pointer_constraint_state: PointerConstraintsState,
should_be_mapped: bool,
first_configure: bool,
pool: SlotPool,
width: u32,
height: u32,
shift: u32,
buffer: Option<Buffer>,
window: Window,
loop_signal: calloop::LoopSignal,
}
impl CompositorHandler for App {
fn frame(
&mut self,
conn: &Connection,
qh: &QueueHandle<Self>,
_surface: &wl_surface::WlSurface,
_time: u32,
) {
self.draw(conn, qh);
}
fn surface_enter(&mut self, _: &Connection, _: &QueueHandle<Self>, _: &WlSurface, _: &WlOutput) {}
fn surface_leave(&mut self, _: &Connection, _: &QueueHandle<Self>, _: &WlSurface, _: &WlOutput) {}
fn scale_factor_changed(&mut self, _: &Connection, _: &QueueHandle<Self>, _: &WlSurface, _: i32) {}
fn transform_changed(
&mut self,
_: &Connection,
_: &QueueHandle<Self>,
_: &WlSurface,
_: wl_output::Transform,
) {
}
}
impl WindowHandler for App {
fn request_close(&mut self, _: &Connection, _: &QueueHandle<Self>, _: &Window) {
self.loop_signal.stop();
}
fn configure(
&mut self,
conn: &Connection,
qh: &QueueHandle<Self>,
_window: &Window,
configure: WindowConfigure,
_serial: u32,
) {
self.buffer = None;
self.width = configure.new_size.0.map(|v| v.get()).unwrap_or(256);
self.height = configure.new_size.1.map(|v| v.get()).unwrap_or(256);
if self.first_configure {
self.first_configure = false;
self.draw(conn, qh);
}
if let Some(confined_pointer) = &self.confined_pointer {
confined_pointer.destroy();
self.confined_pointer = None;
}
if let Some(pointer) = &self.pointer {
let region = self.compositor_state.wl_compositor().create_region(qh, ());
let w = self.width as i32;
let h = self.height as i32;
region.add(0, 0, w, h);
region.subtract(w / 2, h / 2, w / 2, h / 2);
region.subtract(0, 0, w / 4, h / 4);
let confined_pointer = self
.pointer_constraint_state
.confine_pointer(
self.window.wl_surface(),
&pointer,
Some(&region),
zwp_pointer_constraints_v1::Lifetime::Persistent,
qh,
)
.unwrap();
self.confined_pointer = Some(confined_pointer);
}
}
}
impl App {
fn map(&mut self) {
self.first_configure = true;
self.should_be_mapped = true;
self.window.commit();
}
fn draw(&mut self, _conn: &Connection, qh: &QueueHandle<Self>) {
if !self.should_be_mapped {
return;
}
test_clients::draw(
qh,
self.window.wl_surface(),
&mut self.pool,
&mut self.buffer,
self.width,
self.height,
&mut self.shift,
);
}
}
delegate_compositor!(App);
delegate_output!(App);
delegate_shm!(App);
delegate_xdg_shell!(App);
delegate_xdg_window!(App);
delegate_registry!(App);
delegate_seat!(App);
delegate_pointer!(App);
delegate_pointer_constraints!(App);
delegate_noop!(App: WlRegion);
impl SeatHandler for App {
fn seat_state(&mut self) -> &mut SeatState {
&mut self.seat_state
}
fn new_seat(&mut self, _: &Connection, _: &QueueHandle<Self>, _: wl_seat::WlSeat) {}
fn new_capability(
&mut self,
_conn: &Connection,
qh: &QueueHandle<Self>,
seat: wl_seat::WlSeat,
capability: Capability,
) {
info!("add capability: {capability}");
if capability == Capability::Pointer && self.pointer.is_none() {
let pointer = self
.seat_state
.get_pointer(qh, &seat)
.expect("Failed to create pointer");
self.pointer = Some(pointer);
}
}
fn remove_capability(
&mut self,
_conn: &Connection,
_: &QueueHandle<Self>,
_: wl_seat::WlSeat,
capability: Capability,
) {
warn!("remove capability: {capability}");
if capability == Capability::Pointer && self.pointer.is_some() {
self.confined_pointer.take().unwrap().destroy();
self.pointer.take().unwrap().release();
}
}
fn remove_seat(&mut self, _: &Connection, _: &QueueHandle<Self>, _: wl_seat::WlSeat) {}
}
impl PointerHandler for App {
fn pointer_frame(
&mut self,
_conn: &Connection,
_qh: &QueueHandle<Self>,
_pointer: &WlPointer,
events: &[PointerEvent],
) {
for event in events {
match event.kind {
PointerEventKind::Enter { .. } => {
info!("Pointer entered @{:?}", event.position);
}
PointerEventKind::Leave { .. } => {
info!("Pointer left");
}
PointerEventKind::Motion { .. } => {
info!("Pointer motion: {:?}", event.position);
}
_ => {}
}
}
}
}
impl PointerConstraintsHandler for App {
fn confined(
&mut self,
_conn: &Connection,
_qh: &QueueHandle<Self>,
_confined_pointer: &ZwpConfinedPointerV1,
_surface: &WlSurface,
_pointer: &WlPointer,
) {
info!("Pointer confined");
}
fn unconfined(
&mut self,
_conn: &Connection,
_qh: &QueueHandle<Self>,
_confined_pointer: &ZwpConfinedPointerV1,
_surface: &WlSurface,
_pointer: &WlPointer,
) {
warn!("Pointer unconfined");
}
fn locked(
&mut self,
_conn: &Connection,
_qh: &QueueHandle<Self>,
_locked_pointer: &ZwpLockedPointerV1,
_surface: &WlSurface,
_pointer: &WlPointer,
) {
}
fn unlocked(
&mut self,
_conn: &Connection,
_qh: &QueueHandle<Self>,
_locked_pointer: &ZwpLockedPointerV1,
_surface: &WlSurface,
_pointer: &WlPointer,
) {
}
}
impl OutputHandler for App {
fn output_state(&mut self) -> &mut OutputState {
&mut self.output_state
}
fn new_output(&mut self, _: &Connection, _: &QueueHandle<Self>, _: WlOutput) {}
fn update_output(&mut self, _: &Connection, _: &QueueHandle<Self>, _: WlOutput) {}
fn output_destroyed(&mut self, _: &Connection, _: &QueueHandle<Self>, _: WlOutput) {}
}
impl ShmHandler for App {
fn shm_state(&mut self) -> &mut Shm {
&mut self.shm
}
}
impl ProvidesRegistryState for App {
fn registry(&mut self) -> &mut RegistryState {
&mut self.registry_state
}
registry_handlers![OutputState,];
}
+236
View File
@@ -0,0 +1,236 @@
//! Attempt to reproduce https://github.com/Smithay/smithay/issues/1894
use smithay_client_toolkit::delegate_subcompositor;
use smithay_client_toolkit::reexports::client::protocol::wl_subsurface::WlSubsurface;
use smithay_client_toolkit::reexports::{calloop, client as wayland_client};
use smithay_client_toolkit::subcompositor::SubcompositorState;
use smithay_client_toolkit::{
compositor::{CompositorHandler, CompositorState},
delegate_compositor, delegate_output, delegate_registry, delegate_shm, delegate_xdg_shell,
delegate_xdg_window,
output::{OutputHandler, OutputState},
registry::{ProvidesRegistryState, RegistryState},
registry_handlers,
shell::{
WaylandSurface,
xdg::{
XdgShell,
window::{Window, WindowConfigure, WindowDecorations, WindowHandler},
},
},
shm::{
Shm, ShmHandler,
slot::{Buffer, SlotPool},
},
};
use tracing::info;
use wayland_client::{
Connection, QueueHandle,
protocol::{
wl_output::{self, WlOutput},
wl_surface::{self, WlSurface},
},
};
fn main() {
test_clients::init_logging();
let (mut event_loop, globals, qh) = test_clients::init_connection::<App>();
let compositor = CompositorState::bind(&globals, &qh).unwrap();
let subcompositor = SubcompositorState::bind(compositor.wl_compositor().clone(), &globals, &qh).unwrap();
let xdg_shell = XdgShell::bind(&globals, &qh).unwrap();
let surface = compositor.create_surface(&qh);
let subsurface = subcompositor.create_subsurface(surface.clone(), &qh);
let window = xdg_shell.create_window(surface, WindowDecorations::RequestServer, &qh);
let shm = Shm::bind(&globals, &qh).unwrap();
let pool = SlotPool::new(256 * 256 * 4, &shm).unwrap();
let mut app = App {
registry_state: RegistryState::new(&globals),
output_state: OutputState::new(&globals, &qh),
shm,
should_be_mapped: false,
first_configure: true,
pool,
width: 256,
height: 256,
shift: 0,
buffer: None,
window,
subsurface,
commit_only_once: true,
loop_signal: event_loop.get_signal(),
};
app.map_toggle();
event_loop.run(None, &mut app, |_| {}).unwrap();
}
struct App {
registry_state: RegistryState,
output_state: OutputState,
shm: Shm,
should_be_mapped: bool,
first_configure: bool,
pool: SlotPool,
width: u32,
height: u32,
shift: u32,
buffer: Option<Buffer>,
window: Window,
subsurface: (WlSubsurface, WlSurface),
commit_only_once: bool,
loop_signal: calloop::LoopSignal,
}
impl CompositorHandler for App {
fn frame(
&mut self,
conn: &Connection,
qh: &QueueHandle<Self>,
_surface: &wl_surface::WlSurface,
_time: u32,
) {
self.draw(conn, qh);
}
fn surface_enter(&mut self, _: &Connection, _: &QueueHandle<Self>, _: &WlSurface, _: &WlOutput) {}
fn surface_leave(&mut self, _: &Connection, _: &QueueHandle<Self>, _: &WlSurface, _: &WlOutput) {}
fn scale_factor_changed(&mut self, _: &Connection, _: &QueueHandle<Self>, _: &WlSurface, _: i32) {}
fn transform_changed(
&mut self,
_: &Connection,
_: &QueueHandle<Self>,
_: &WlSurface,
_: wl_output::Transform,
) {
}
}
impl WindowHandler for App {
fn request_close(&mut self, _: &Connection, _: &QueueHandle<Self>, _: &Window) {
self.loop_signal.stop();
}
fn configure(
&mut self,
conn: &Connection,
qh: &QueueHandle<Self>,
_window: &Window,
configure: WindowConfigure,
_serial: u32,
) {
if self.first_configure {
info!("Window initial configure");
} else {
info!("Window configure");
}
self.buffer = None;
self.width = configure.new_size.0.map(|v| v.get()).unwrap_or(256);
self.height = configure.new_size.1.map(|v| v.get()).unwrap_or(256);
if self.first_configure {
self.first_configure = false;
self.draw(conn, qh);
}
}
}
impl App {
fn map_toggle(&mut self) {
if self.should_be_mapped {
self.unmap()
} else {
self.map()
}
}
fn map(&mut self) {
self.first_configure = true;
self.should_be_mapped = true;
self.window.commit();
}
fn unmap(&mut self) {
self.buffer = None;
self.first_configure = false;
self.should_be_mapped = false;
self.window.attach(None, 0, 0);
self.window.commit();
}
fn draw(&mut self, _conn: &Connection, qh: &QueueHandle<Self>) {
if !self.should_be_mapped {
return;
}
if std::mem::take(&mut self.commit_only_once) {
test_clients::draw(
qh,
&self.subsurface.1,
&mut self.pool,
&mut self.buffer,
self.width,
self.height,
&mut self.shift,
);
}
// Test if setting the pos and never calling commit on the subsurface still applies the pos on partent commit
self.subsurface.0.set_position(self.shift as i32, 20);
test_clients::draw(
qh,
self.window.wl_surface(),
&mut self.pool,
&mut self.buffer,
self.width,
self.height,
&mut self.shift,
);
}
}
delegate_compositor!(App);
delegate_subcompositor!(App);
delegate_output!(App);
delegate_shm!(App);
delegate_xdg_shell!(App);
delegate_xdg_window!(App);
delegate_registry!(App);
impl OutputHandler for App {
fn output_state(&mut self) -> &mut OutputState {
&mut self.output_state
}
fn new_output(&mut self, _: &Connection, _: &QueueHandle<Self>, _: WlOutput) {}
fn update_output(&mut self, _: &Connection, _: &QueueHandle<Self>, _: WlOutput) {}
fn output_destroyed(&mut self, _: &Connection, _: &QueueHandle<Self>, _: WlOutput) {}
}
impl ShmHandler for App {
fn shm_state(&mut self) -> &mut Shm {
&mut self.shm
}
}
impl ProvidesRegistryState for App {
fn registry(&mut self) -> &mut RegistryState {
&mut self.registry_state
}
registry_handlers![OutputState,];
}
@@ -0,0 +1,216 @@
//! Cycles through initial commit-configure sequence, over and over again (every 1s)
use std::time::Duration;
use smithay_client_toolkit::reexports::{calloop, client as wayland_client};
use smithay_client_toolkit::{
compositor::{CompositorHandler, CompositorState},
delegate_compositor, delegate_output, delegate_registry, delegate_shm, delegate_xdg_shell,
delegate_xdg_window,
output::{OutputHandler, OutputState},
registry::{ProvidesRegistryState, RegistryState},
registry_handlers,
shell::{
WaylandSurface,
xdg::{
XdgShell,
window::{Window, WindowConfigure, WindowDecorations, WindowHandler},
},
},
shm::{
Shm, ShmHandler,
slot::{Buffer, SlotPool},
},
};
use tracing::info;
use wayland_client::{
Connection, QueueHandle,
protocol::{
wl_output::{self, WlOutput},
wl_surface::{self, WlSurface},
},
};
fn main() {
test_clients::init_logging();
let (mut event_loop, globals, qh) = test_clients::init_connection::<App>();
let compositor = CompositorState::bind(&globals, &qh).unwrap();
let xdg_shell = XdgShell::bind(&globals, &qh).unwrap();
let surface = compositor.create_surface(&qh);
let window = xdg_shell.create_window(surface, WindowDecorations::RequestServer, &qh);
let shm = Shm::bind(&globals, &qh).unwrap();
let pool = SlotPool::new(256 * 256 * 4, &shm).unwrap();
let mut simple_window = App {
registry_state: RegistryState::new(&globals),
output_state: OutputState::new(&globals, &qh),
shm,
should_be_mapped: false,
first_configure: true,
pool,
width: 256,
height: 256,
shift: 0,
buffer: None,
window,
loop_signal: event_loop.get_signal(),
};
event_loop
.handle()
.insert_source(calloop::timer::Timer::immediate(), |_, _, app| {
app.map_toggle();
calloop::timer::TimeoutAction::ToDuration(Duration::from_secs(1))
})
.unwrap();
event_loop.run(None, &mut simple_window, |_| {}).unwrap();
}
struct App {
registry_state: RegistryState,
output_state: OutputState,
shm: Shm,
should_be_mapped: bool,
first_configure: bool,
pool: SlotPool,
width: u32,
height: u32,
shift: u32,
buffer: Option<Buffer>,
window: Window,
loop_signal: calloop::LoopSignal,
}
impl CompositorHandler for App {
fn frame(
&mut self,
conn: &Connection,
qh: &QueueHandle<Self>,
_surface: &wl_surface::WlSurface,
_time: u32,
) {
self.draw(conn, qh);
}
fn surface_enter(&mut self, _: &Connection, _: &QueueHandle<Self>, _: &WlSurface, _: &WlOutput) {}
fn surface_leave(&mut self, _: &Connection, _: &QueueHandle<Self>, _: &WlSurface, _: &WlOutput) {}
fn scale_factor_changed(&mut self, _: &Connection, _: &QueueHandle<Self>, _: &WlSurface, _: i32) {}
fn transform_changed(
&mut self,
_: &Connection,
_: &QueueHandle<Self>,
_: &WlSurface,
_: wl_output::Transform,
) {
}
}
impl WindowHandler for App {
fn request_close(&mut self, _: &Connection, _: &QueueHandle<Self>, _: &Window) {
self.loop_signal.stop();
}
fn configure(
&mut self,
conn: &Connection,
qh: &QueueHandle<Self>,
_window: &Window,
configure: WindowConfigure,
_serial: u32,
) {
if self.first_configure {
info!("Window initial configure");
} else {
info!("Window configure");
}
self.buffer = None;
self.width = configure.new_size.0.map(|v| v.get()).unwrap_or(256);
self.height = configure.new_size.1.map(|v| v.get()).unwrap_or(256);
if self.first_configure {
self.first_configure = false;
self.draw(conn, qh);
}
}
}
impl App {
fn map_toggle(&mut self) {
if self.should_be_mapped {
self.unmap()
} else {
self.map()
}
}
fn map(&mut self) {
self.first_configure = true;
self.should_be_mapped = true;
self.window.commit();
}
fn unmap(&mut self) {
self.buffer = None;
self.first_configure = false;
self.should_be_mapped = false;
self.window.attach(None, 0, 0);
self.window.commit();
}
fn draw(&mut self, _conn: &Connection, qh: &QueueHandle<Self>) {
if !self.should_be_mapped {
return;
}
test_clients::draw(
qh,
self.window.wl_surface(),
&mut self.pool,
&mut self.buffer,
self.width,
self.height,
&mut self.shift,
);
}
}
delegate_compositor!(App);
delegate_output!(App);
delegate_shm!(App);
delegate_xdg_shell!(App);
delegate_xdg_window!(App);
delegate_registry!(App);
impl OutputHandler for App {
fn output_state(&mut self) -> &mut OutputState {
&mut self.output_state
}
fn new_output(&mut self, _: &Connection, _: &QueueHandle<Self>, _: WlOutput) {}
fn update_output(&mut self, _: &Connection, _: &QueueHandle<Self>, _: WlOutput) {}
fn output_destroyed(&mut self, _: &Connection, _: &QueueHandle<Self>, _: WlOutput) {}
}
impl ShmHandler for App {
fn shm_state(&mut self) -> &mut Shm {
&mut self.shm
}
}
impl ProvidesRegistryState for App {
fn registry(&mut self) -> &mut RegistryState {
&mut self.registry_state
}
registry_handlers![OutputState,];
}
+107
View File
@@ -0,0 +1,107 @@
use smithay_client_toolkit::{
reexports::{
calloop,
calloop_wayland_source::WaylandSource,
client::{
self as wayland_client,
globals::GlobalList,
protocol::{wl_callback::WlCallback, wl_shm, wl_surface::WlSurface},
},
},
shm::slot::{Buffer, SlotPool},
};
use calloop::EventLoop;
use wayland_client::{
Connection, Dispatch, QueueHandle, globals::GlobalListContents, globals::registry_queue_init,
protocol::wl_registry::WlRegistry,
};
pub fn init_logging() {
if let Ok(env_filter) = tracing_subscriber::EnvFilter::try_from_default_env() {
tracing_subscriber::fmt()
.compact()
.with_env_filter(env_filter)
.init();
} else {
tracing_subscriber::fmt().compact().init();
}
}
pub fn init_connection<APP>() -> (EventLoop<'static, APP>, GlobalList, QueueHandle<APP>)
where
APP: Dispatch<WlRegistry, GlobalListContents> + 'static,
{
let conn = Connection::connect_to_env().unwrap();
let (globals, event_queue) = registry_queue_init(&conn).unwrap();
let qh = event_queue.handle();
let event_loop: EventLoop<APP> = EventLoop::try_new().unwrap();
let loop_handle = event_loop.handle();
WaylandSource::new(conn.clone(), event_queue)
.insert(loop_handle)
.unwrap();
(event_loop, globals, qh)
}
pub fn fill_with_gradient_bytes(canvas: &mut [u8], shift: u32, width: u32, height: u32) {
canvas.chunks_exact_mut(4).enumerate().for_each(|(index, chunk)| {
let x = ((index + shift as usize) % width as usize) as u32;
let y = (index / width as usize) as u32;
let a = 0xFF;
let r = u32::min(((width - x) * 0xFF) / width, ((height - y) * 0xFF) / height);
let g = u32::min((x * 0xFF) / width, ((height - y) * 0xFF) / height);
let b = u32::min(((width - x) * 0xFF) / width, (y * 0xFF) / height);
let color = (a << 24) + (r << 16) + (g << 8) + b;
let array: &mut [u8; 4] = chunk.try_into().unwrap();
*array = color.to_le_bytes();
});
}
pub fn draw<D>(
qh: &QueueHandle<D>,
wl_surface: &WlSurface,
pool: &mut SlotPool,
buffer: &mut Option<Buffer>,
width: u32,
height: u32,
shift: &mut u32,
) where
D: 'static,
D: Dispatch<WlCallback, WlSurface>,
{
let stride = width as i32 * 4;
let buffer = buffer.get_or_insert_with(|| {
pool.create_buffer(width as i32, height as i32, stride, wl_shm::Format::Argb8888)
.unwrap()
.0
});
let canvas = match pool.canvas(buffer) {
Some(canvas) => canvas,
None => {
// This should be rare, but if the compositor has not released the previous
// buffer, we need double-buffering.
let (second_buffer, canvas) = pool
.create_buffer(width as i32, height as i32, stride, wl_shm::Format::Argb8888)
.unwrap();
*buffer = second_buffer;
canvas
}
};
// Draw to the window:
fill_with_gradient_bytes(canvas, *shift, width, height);
*shift = (*shift + 1) % width;
wl_surface.damage_buffer(0, 0, width as i32, height as i32);
wl_surface.frame(qh, wl_surface.clone());
buffer.attach_to(wl_surface).expect("buffer attach");
wl_surface.commit();
}