//! Obsługa wejścia: `wl_seat` i `wl_pointer`. //! //! Ten moduł spina trzy światy: //! * surowe zdarzenia Waylanda (motion, button, enter/leave), //! * logikę okna ([`crate::window::MyToolkitWindow`]: hover, hit-testing, krawędzie resize), //! * żądania protokołu xdg-shell (`xdg_toplevel.move` / `xdg_toplevel.resize`). //! //! Kluczowy szczegół protokołu: żądania `move` i `resize` wymagają **serialu** //! zdarzenia `wl_pointer.button` o stanie „pressed". Serial zapamiętujemy przy //! kliknięciu i używamy od razu — nie ma sensu odkładać tego na później, bo serial //! może wygasnąć. use wayland_client::protocol::wl_pointer::{self, ButtonState, WlPointer}; use wayland_client::protocol::wl_seat::{self, WlSeat}; use wayland_client::{Connection, Dispatch, QueueHandle, WEnum}; use crate::connection::CompositorState; use crate::i18n::{tr, MsgKey}; use crate::window::WindowAction; /// Kod przycisku lewego myszy z `linux/input-event-codes.h` (BTN_LEFT). const BTN_LEFT: u32 = 0x110; /// Reagujemy na zdolności seata: gdy pojawi się wskaźnik, tworzymy `wl_pointer`. impl Dispatch for CompositorState { fn event( state: &mut Self, proxy: &WlSeat, event: wl_seat::Event, _data: &(), _conn: &Connection, qh: &QueueHandle, ) { if let wl_seat::Event::Capabilities { capabilities } = event { // API-NOTE: reprezentacja `wl_seat::Capability` (zwykły enum vs bitflags) // bywa różna między wersjami `wayland-rs`, dlatego świadomie nie odczytujemy // konkretnych flag. W kompozytorze desktopowym seat praktycznie zawsze ma // wskaźnik, więc prosimy o `wl_pointer` przy pierwszym zdarzeniu zdolności. let _ = capabilities; if state.pointer.is_none() { let pointer = proxy.get_pointer(qh, ()); tracing::debug!("utworzono obiekt wl_pointer"); state.pointer = Some(pointer); } } } } impl Dispatch for CompositorState { fn event( state: &mut Self, _proxy: &WlPointer, event: wl_pointer::Event, _data: &(), _conn: &Connection, _qh: &QueueHandle, ) { match event { wl_pointer::Event::Enter { serial, surface_x, surface_y, .. } => { state.pointer_serial = Some(serial); state.last_pointer = (surface_x, surface_y); // Wejście kursora na powierzchnię też może zmienić hover. if state .window .handle_mouse_motion(surface_x as i32, surface_y as i32) { state.needs_redraw = true; } } wl_pointer::Event::Motion { surface_x, surface_y, .. } => { state.last_pointer = (surface_x, surface_y); if state .window .handle_mouse_motion(surface_x as i32, surface_y as i32) { state.needs_redraw = true; } } wl_pointer::Event::Button { serial, button, state: button_state, .. } => { // Interesuje nas wyłącznie wciśnięcie lewego przycisku i tylko wtedy, // gdy to naprawdę „pressed" (a nie „released", który ignorujemy). if button == BTN_LEFT && matches!(button_state, WEnum::Value(ButtonState::Pressed)) { state.pointer_serial = Some(serial); let (x, y) = state.last_pointer; handle_press(state, x as i32, y as i32, serial); } } wl_pointer::Event::Leave { .. } => { // Kursor opuścił powierzchnię → gaśmy hover (poza oknem nic nie podświetlamy). if state.window.handle_mouse_motion(-1, -1) { state.needs_redraw = true; } } _ => {} } } } /// Wspólna logika „press": najpierw resize za krawędź, potem zwykły hit-test kontrolek. /// /// Rozdzielenie jest istotne, bo te dwie akcje mają różne wymagania protokołu: /// `resize` potrzebuje `Edges`, a `move` tylko serialu. fn handle_press(state: &mut CompositorState, x: i32, y: i32, serial: u32) { // Rozwinięte menu aplikacji obsługuje klik samo (wybór pozycji albo zamknięcie). // Sprawdzamy to PRZED krawędziami — inaczej klik przy krawędzi zaczynałby // resize zamiast zamykać menu. if state.window.menu_is_open() { let action = state.window.handle_mouse_click(x, y); state.pending_action = Some(action); state.needs_redraw = true; return; } // 1. Krawędzie okna mają priorytet — nawet jeśli nakładają się na pasek tytułu. if let Some(edges) = state.window.resize_edge_at(x, y) { if let (Some(toplevel), Some(seat)) = (state.toplevel.as_ref(), state.seat.as_ref()) { toplevel.resize(seat, serial, edges.to_xdg_resize_edge()); // Log na poziomie `info`, żeby było widać w demo, że resize zadziałał // (przy `debug` ginęło w natłoku zdarzeń). tracing::info!("{}", tr(state.locale, MsgKey::LogResize)); } state.pending_action = Some(WindowAction::None); return; } // 2. Zwykły hit-test przycisków / paska tytułu. let action = state.window.handle_mouse_click(x, y); if let WindowAction::StartMove = action { // `move` przekazujemy kompozytorowi od razu, z serialem button-press. // API-NOTE: w `wayland-protocols` 0.32 metodą żądania `move` jest `_move` // (kolidowałoby z `move` w Rust); w innych wersjach może to być `move_`. if let (Some(toplevel), Some(seat)) = (state.toplevel.as_ref(), state.seat.as_ref()) { toplevel._move(seat, serial); tracing::debug!("przekazano xdg_toplevel.move do kompozytora"); } } // Akcję i tak wystawiamy aplikacji — niech zaloguje/zdecyduje o dalszych krokach. state.pending_action = Some(action); }