🔒 Repository is read-only – file editing is disabled.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157
//! 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<WlSeat, ()> for CompositorState {
fn event(
state: &mut Self,
proxy: &WlSeat,
event: wl_seat::Event,
_data: &(),
_conn: &Connection,
qh: &QueueHandle<Self>,
) {
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<WlPointer, ()> for CompositorState {
fn event(
state: &mut Self,
_proxy: &WlPointer,
event: wl_pointer::Event,
_data: &(),
_conn: &Connection,
_qh: &QueueHandle<Self>,
) {
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);
}