use crate::client::Client;
use crate::ifs::wl_seat::text_input::zwp_input_method_v2::ZwpInputMethodV2;
use crate::ifs::wl_surface::SurfaceExt;
use crate::ifs::wl_surface::SurfaceRole;
use crate::ifs::wl_surface::WlSurface;
use crate::ifs::wl_surface::WlSurfaceError;
use crate::leaks::Tracker;
use crate::object::BreakLoops;
use crate::object::ObjectDebugfs;
use crate::object::Version;
use crate::rect::Rect;
use crate::state::State;
use crate::tree::NodeLayerLink;
use crate::tree::TreeTimeline::LiveTL;
use crate::tree::TreeTimeline::RenderTL;
use crate::tree::WorkspaceNode;
use crate::utils::fuse::fuse_inode::FuseInodeWithKey;
use crate::utils::liveness::Liveness;
use crate::wire::ObjectId;
use crate::wire::ZwpInputPopupSurfaceV2Id;
use crate::wire::zwp_input_popup_surface_v2::*;
use jay_proc::GetLiveness;
use jay_proc::Object;
use std::cell::Cell;
use std::rc::Rc;
use thiserror::Error;
mod zwp_input_popup_surface_v2_dfs_g_fuse;
#[derive(Object, GetLiveness)]
#[break_loops]
#[debugfs]
pub struct ZwpInputPopupSurfaceV2 {
pub id: ZwpInputPopupSurfaceV2Id,
pub client: Rc<Client>,
pub input_method: Rc<ZwpInputMethodV2>,
pub surface: Rc<WlSurface>,
pub version: Version,
pub tracker: Tracker<Self>,
pub positioning_scheduled: Cell<bool>,
pub was_on_screen: Cell<bool>,
pub liveness: Liveness,
}
impl SurfaceExt for ZwpInputPopupSurfaceV2 {
fn object_id(&self) -> Option<ObjectId> {
Some(self.id.into())
}
fn node_layer(&self) -> NodeLayerLink {
NodeLayerLink::InputMethod
}
fn after_apply_commit(self: Rc<Self>) {
self.update_visible();
if self.surface.visible[LiveTL].get() {
self.schedule_positioning();
}
}
fn workspace(&self) -> Option<Rc<WorkspaceNode>> {
None
}
fn unmap(self: Rc<Self>) {
if self.surface.visible[LiveTL].get() {
self.update_visible_(true);
}
}
}
pub async fn input_popup_positioning(state: Rc<State>) {
loop {
let popup = state.pending_input_popup_positioning.pop().await;
if popup.positioning_scheduled.get() {
popup.position();
}
}
}
impl ZwpInputPopupSurfaceV2 {
fn damage(&self) {
let (x, y) = self.surface.buffer_abs_pos[RenderTL].get().position();
let extents = self.surface.extents.get();
self.client.state.damage(extents.move_(x, y));
}
pub fn update_visible(self: &Rc<Self>) {
self.update_visible_(false);
}
fn update_visible_(self: &Rc<Self>, is_unmap: bool) {
let was_visible = self.surface.visible[LiveTL].get();
let is_visible = self.surface.buffer.is_some()
&& self.input_method.connection.is_some()
&& self.client.state.root_visible()
&& !is_unmap;
self.surface.set_visible(is_visible);
if was_visible != is_visible {
self.was_on_screen.set(false);
if is_visible {
self.schedule_positioning();
} else {
self.damage();
}
}
}
pub fn schedule_positioning(self: &Rc<Self>) {
if self.surface.visible[LiveTL].get() {
if !self.positioning_scheduled.replace(true) {
self.client
.state
.pending_input_popup_positioning
.push(self.clone());
}
}
}
fn position(&self) {
self.positioning_scheduled.set(false);
if !self.surface.visible[LiveTL].get() {
return;
}
let Some(con) = self.input_method.connection.get() else {
log::warn!("Popup has no connection but is visible");
return;
};
let output = con.surface.output.get().node_state[LiveTL].pos.get();
let surface_rect = con.surface.buffer_abs_pos[LiveTL].get();
let cursor_rect = con
.text_input
.cursor_rect()
.move_(surface_rect.x1(), surface_rect.y1());
let extents = self.surface.extents.get();
let mut rect = extents.at_point(cursor_rect.x1(), cursor_rect.y2());
let overflow = output.get_overflow(&rect);
if overflow.right > 0 {
let dx = -overflow.right.min(rect.width());
let rect2 = rect.move_(dx, 0);
if !output.get_overflow(&rect2).x_overflow() {
rect = rect2;
}
}
if overflow.bottom > 0 {
let rect2 = rect.move_(0, -(cursor_rect.height() + rect.height()));
if !output.get_overflow(&rect2).y_overflow() {
rect = rect2;
}
}
let old = self.surface.buffer_abs_pos[LiveTL].get();
let new = old.at_point(rect.x1() - extents.x1(), rect.y1() - extents.y1());
if self.was_on_screen.get() && new != old {
self.damage();
}
self.surface.buffer_abs_pos[LiveTL].set(new);
self.surface.buffer_abs_pos[RenderTL].set(new);
if !self.was_on_screen.get() || new != old {
self.damage();
}
self.was_on_screen.set(true);
}
pub fn install(self: &Rc<Self>) -> Result<(), ZwpInputPopupSurfaceV2Error> {
self.surface
.set_ext(SurfaceRole::InputPopup, self.clone())?;
self.input_method.popups.insert(self.id, self.clone());
if let Some(con) = self.input_method.connection.get() {
let output = con.surface.output.get();
self.surface.set_output_without_workspace(&output);
}
Ok(())
}
#[expect(unused)]
pub fn send_text_input_rectangle(&self, rect: Rect) {
self.client.event(TextInputRectangle {
self_id: self.id,
x: rect.x1(),
y: rect.y1(),
width: rect.width(),
height: rect.height(),
});
}
fn detach(&self) {
if self.surface.visible[LiveTL].get() {
self.damage();
}
self.surface.destroy_node();
self.surface.unset_ext();
self.input_method.popups.remove(&self.id);
}
}
impl ZwpInputPopupSurfaceV2RequestHandler for ZwpInputPopupSurfaceV2 {
type Error = ZwpInputPopupSurfaceV2Error;
fn destroy(&self, _req: Destroy, _slf: &Rc<Self>) -> Result<(), Self::Error> {
self.detach();
self.client.remove_obj(self);
Ok(())
}
}
impl BreakLoops for ZwpInputPopupSurfaceV2 {
fn break_loops(self: Rc<Self>) {
self.detach();
}
}
impl ObjectDebugfs for ZwpInputPopupSurfaceV2 {
fn object_debugfs(self: Rc<Self>, _client: &Rc<Client>) -> FuseInodeWithKey {
self.debugfs()
}
}
#[derive(Debug, Error)]
pub enum ZwpInputPopupSurfaceV2Error {
#[error(transparent)]
WlSurfaceError(Box<WlSurfaceError>),
}
efrom!(ZwpInputPopupSurfaceV2Error, WlSurfaceError);