🔒 Repository is read-only – file editing is disabled.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222
//! Klient testowy `wl_subcompositor` — okno z powierzchnią potomną.
//!
//! Uruchomienie w sesji Selene:
//!
//! ```text
//! WAYLAND_DISPLAY=wayland-0 cargo run -p selene --example subsurface-probe
//! ```
//!
//! Rodzic to niebieskie okno 200×200, dziecko to czerwony kwadrat 60×60
//! w pozycji (40,40), w trybie `desync` (zmiany działają natychmiast).
//! Dodatkowo tworzymy `xdg_popup` zakotwiczony do dolnej krawędzi rodzica —
//! sprawdza pełne `xdg_positioner` (anchor + gravity).
//!
//! Selene powinna zalogować `selene/subsurface: nowa powierzchnia potomna`,
//! `pierwszy obraz desync 60x60` oraz popup `@ (110,260)`.
use std::error::Error;
use std::time::Duration;
use smithay_client_toolkit::shm::{slot::SlotPool, Shm, ShmHandler};
use wayland_client::protocol::{
wl_buffer, wl_callback, wl_compositor, wl_registry, wl_shm, wl_shm_pool, wl_subcompositor,
wl_subsurface, wl_surface,
};
use wayland_client::{
globals::{registry_queue_init, GlobalListContents},
Connection, Dispatch, QueueHandle,
};
use wayland_protocols::xdg::shell::client::{
xdg_popup, xdg_positioner, xdg_surface, xdg_toplevel, xdg_wm_base,
};
#[derive(Default)]
struct App {
configured: bool,
shm: Option<Shm>,
}
smithay_client_toolkit::delegate_shm!(App);
impl ShmHandler for App {
fn shm_state(&mut self) -> &mut Shm {
self.shm.as_mut().expect("shm zainicjalizowany")
}
}
macro_rules! ignore_events {
($($iface:ty),* $(,)?) => {
$(
impl Dispatch<$iface, ()> for App {
fn event(
_state: &mut Self,
_proxy: &$iface,
_event: <$iface as wayland_client::Proxy>::Event,
_data: &(),
_conn: &Connection,
_qh: &QueueHandle<Self>,
) {
}
}
)*
};
}
ignore_events!(
wl_compositor::WlCompositor,
wl_shm_pool::WlShmPool,
wl_buffer::WlBuffer,
wl_surface::WlSurface,
wl_callback::WlCallback,
wl_subcompositor::WlSubcompositor,
wl_subsurface::WlSubsurface,
xdg_toplevel::XdgToplevel,
xdg_popup::XdgPopup,
xdg_positioner::XdgPositioner,
);
impl Dispatch<wl_registry::WlRegistry, GlobalListContents> for App {
fn event(
_state: &mut Self,
_proxy: &wl_registry::WlRegistry,
_event: wl_registry::Event,
_data: &GlobalListContents,
_conn: &Connection,
_qh: &QueueHandle<Self>,
) {
}
}
impl Dispatch<xdg_wm_base::XdgWmBase, ()> for App {
fn event(
_state: &mut Self,
proxy: &xdg_wm_base::XdgWmBase,
event: xdg_wm_base::Event,
_data: &(),
_conn: &Connection,
_qh: &QueueHandle<Self>,
) {
if let xdg_wm_base::Event::Ping { serial } = event {
proxy.pong(serial);
}
}
}
impl Dispatch<xdg_surface::XdgSurface, ()> for App {
fn event(
state: &mut Self,
proxy: &xdg_surface::XdgSurface,
event: xdg_surface::Event,
_data: &(),
_conn: &Connection,
_qh: &QueueHandle<Self>,
) {
if let xdg_surface::Event::Configure { serial } = event {
proxy.ack_configure(serial);
state.configured = true;
}
}
}
fn fill(canvas: &mut [u8], bytes: [u8; 4]) {
for px in canvas.chunks_exact_mut(4) {
px.copy_from_slice(&bytes);
}
}
fn main() -> Result<(), Box<dyn Error>> {
let conn = Connection::connect_to_env()?;
let (globals, mut queue) = registry_queue_init::<App>(&conn)?;
let qh = queue.handle();
let compositor = globals.bind::<wl_compositor::WlCompositor, _, _>(&qh, 1..=4, ())?;
let subcompositor = globals.bind::<wl_subcompositor::WlSubcompositor, _, _>(&qh, 1..=1, ())?;
let wm_base = globals.bind::<xdg_wm_base::XdgWmBase, _, _>(&qh, 1..=1, ())?;
let shm = Shm::bind(&globals, &qh)?;
let mut pool = SlotPool::new(256 * 256 * 4, &shm)?;
let mut app = App {
configured: false,
shm: Some(shm),
};
let parent = compositor.create_surface(&qh, ());
let child = compositor.create_surface(&qh, ());
let xdg = wm_base.get_xdg_surface(&parent, &qh, ());
let toplevel = xdg.get_toplevel(&qh, ());
toplevel.set_title("subsurface-probe".to_owned());
let subsurface = subcompositor.get_subsurface(&child, &parent, &qh, ());
subsurface.set_position(40, 40);
// `desync`: obraz dziecka wchodzi od razu, bez czekania na commit rodzica.
subsurface.set_desync();
// Pierwszy commit bez bufora → kompozytor wysyła `configure`.
parent.commit();
while !app.configured {
queue.blocking_dispatch(&mut app)?;
}
// Rodzic: niebieskie tło (ARGB8888 w pamięci = B,G,R,A).
let parent_buffer = {
let (buffer, canvas) = pool.create_buffer(200, 200, 800, wl_shm::Format::Argb8888)?;
fill(canvas, [0xff, 0x00, 0x00, 0xff]);
buffer.attach_to(&parent)?;
buffer
};
parent.commit();
// Dziecko: czerwony kwadrat.
let child_buffer = {
let (buffer, canvas) = pool.create_buffer(60, 60, 240, wl_shm::Format::Argb8888)?;
fill(canvas, [0x00, 0x00, 0xff, 0xff]);
buffer.attach_to(&child)?;
buffer
};
child.commit();
// Czekamy, aż rodzic będzie w modelu okien — popup pozycjonujemy względem niego.
queue.roundtrip(&mut app)?;
// Popup: kotwica na dolnej krawędzi rodzica, rośnie w dół. Oczekiwana pozycja
// to lewy-górny róg rodzica + (50, 220): x = środek kotwicy − pół popupu,
// y = dolna krawędź kotwicy.
let positioner = wm_base.create_positioner(&qh, ());
positioner.set_size(100, 80);
positioner.set_anchor_rect(0, 200, 200, 20);
positioner.set_anchor(xdg_positioner::Anchor::Bottom);
positioner.set_gravity(xdg_positioner::Gravity::Bottom);
positioner.set_constraint_adjustment(
xdg_positioner::ConstraintAdjustment::SlideX
| xdg_positioner::ConstraintAdjustment::SlideY
| xdg_positioner::ConstraintAdjustment::FlipX
| xdg_positioner::ConstraintAdjustment::FlipY,
);
let popup_surface = compositor.create_surface(&qh, ());
let popup_xdg = wm_base.get_xdg_surface(&popup_surface, &qh, ());
let popup = popup_xdg.get_popup(Some(&xdg), &positioner, &qh, ());
positioner.destroy();
popup_surface.commit();
queue.roundtrip(&mut app)?;
// Popup: żółty prostokąt.
let popup_buffer = {
let (buffer, canvas) = pool.create_buffer(100, 80, 400, wl_shm::Format::Argb8888)?;
fill(canvas, [0x00, 0xff, 0xff, 0xff]);
buffer.attach_to(&popup_surface)?;
buffer
};
popup_surface.commit();
queue.roundtrip(&mut app)?;
std::thread::sleep(Duration::from_secs(4));
drop((
child_buffer,
parent_buffer,
popup_buffer,
popup,
subsurface,
toplevel,
xdg,
));
Ok(())
}