1
0
Fork 0
forked from wry/wry

config: add Window

This commit is contained in:
Julian Orth 2025-04-29 15:29:39 +02:00
parent ab095b89cf
commit 9977f9dfdf
19 changed files with 1172 additions and 203 deletions

View file

@ -9,8 +9,8 @@ use {
ifs::wl_seat::SeatId,
state::State,
utils::{
clonecell::CloneCell, numcell::NumCell, ptr_ext::PtrExt, unlink_on_drop::UnlinkOnDrop,
xrd::xrd,
clonecell::CloneCell, numcell::NumCell, ptr_ext::PtrExt,
toplevel_identifier::ToplevelIdentifier, unlink_on_drop::UnlinkOnDrop, xrd::xrd,
},
},
bincode::Options,
@ -151,6 +151,15 @@ impl ConfigProxy {
event,
});
}
pub fn toplevel_removed(&self, id: ToplevelIdentifier) {
let Some(handler) = self.handler.get() else {
return;
};
if let Some(win) = handler.windows_from_tl_id.remove(&id) {
handler.windows_to_tl_id.remove(&win);
}
}
}
impl Drop for ConfigProxy {
@ -202,6 +211,9 @@ impl ConfigProxy {
timers_by_id: Default::default(),
pollable_id: Default::default(),
pollables: Default::default(),
window_ids: NumCell::new(1),
windows_from_tl_id: Default::default(),
windows_to_tl_id: Default::default(),
});
let init_msg = bincode_ops()
.serialize(&InitMessage::V1(V1InitMessage {}))

View file

@ -19,7 +19,9 @@ use {
theme::{Color, ThemeSized},
tree::{
ContainerNode, ContainerSplit, FloatNode, Node, NodeVisitorBase, OutputNode,
TearingMode, VrrMode, WsMoveConfig, move_ws_to_output,
TearingMode, ToplevelNode, VrrMode, WorkspaceNode, WsMoveConfig, move_ws_to_output,
toplevel_create_split, toplevel_parent_container, toplevel_set_floating,
toplevel_set_workspace,
},
utils::{
asyncevent::AsyncEvent,
@ -30,6 +32,7 @@ use {
oserror::OsError,
stack::Stack,
timer::{TimerError, TimerFd},
toplevel_identifier::ToplevelIdentifier,
},
},
bincode::Options,
@ -57,6 +60,7 @@ use {
TearingMode as ConfigTearingMode, TransferFunction as ConfigTransferFunction,
Transform, VrrMode as ConfigVrrMode,
},
window::Window,
xwayland::XScalingMode,
},
libloading::Library,
@ -89,6 +93,10 @@ pub(super) struct ConfigProxyHandler {
pub pollable_id: NumCell<u64>,
pub pollables: CopyHashMap<PollableId, Rc<Pollable>>,
pub window_ids: NumCell<u64>,
pub windows_from_tl_id: CopyHashMap<ToplevelIdentifier, Window>,
pub windows_to_tl_id: CopyHashMap<Window, ToplevelIdentifier>,
}
pub struct Pollable {
@ -315,6 +323,24 @@ impl ConfigProxyHandler {
Ok(())
}
fn handle_get_window_fullscreen(&self, window: Window) -> Result<(), CphError> {
let tl = self.get_window(window)?;
self.respond(Response::GetWindowFullscreen {
fullscreen: tl.tl_data().is_fullscreen.get(),
});
Ok(())
}
fn handle_set_window_fullscreen(
&self,
window: Window,
fullscreen: bool,
) -> Result<(), CphError> {
let tl = self.get_window(window)?;
tl.tl_set_fullscreen(fullscreen);
Ok(())
}
fn handle_set_keymap(&self, seat: Seat, keymap: Keymap) -> Result<(), CphError> {
let seat = self.get_seat(seat)?;
let keymap = if keymap.is_invalid() {
@ -492,6 +518,12 @@ impl ConfigProxyHandler {
Ok(())
}
fn handle_window_close(&self, window: Window) -> Result<(), CphError> {
let window = self.get_window(window)?;
window.tl_close();
Ok(())
}
fn handle_seat_focus(&self, seat: Seat, direction: Direction) -> Result<(), CphError> {
let seat = self.get_seat(seat)?;
seat.move_focus(direction.into());
@ -504,6 +536,14 @@ impl ConfigProxyHandler {
Ok(())
}
fn handle_window_move(&self, window: Window, direction: Direction) -> Result<(), CphError> {
let window = self.get_window(window)?;
if let Some(c) = toplevel_parent_container(&*window) {
c.move_child(window, direction.into());
}
Ok(())
}
fn handle_get_repeat_rate(&self, seat: Seat) -> Result<(), CphError> {
let seat = self.get_seat(seat)?;
let (rate, delay) = seat.get_rate();
@ -530,6 +570,11 @@ impl ConfigProxyHandler {
}
}
fn get_existing_workspace(&self, ws: Workspace) -> Result<Option<Rc<WorkspaceNode>>, CphError> {
self.get_workspace(ws)
.map(|ws| self.state.workspaces.get(&*ws))
}
fn get_device_handler_data(
&self,
device: InputDevice,
@ -720,8 +765,8 @@ impl ConfigProxyHandler {
}
fn handle_get_workspace_capture(&self, workspace: Workspace) -> Result<(), CphError> {
let name = self.get_workspace(workspace)?;
let capture = match self.state.workspaces.get(name.as_str()) {
let ws = self.get_existing_workspace(workspace)?;
let capture = match ws {
Some(ws) => ws.may_capture.get(),
None => self.state.default_workspace_capture.get(),
};
@ -734,8 +779,7 @@ impl ConfigProxyHandler {
workspace: Workspace,
capture: bool,
) -> Result<(), CphError> {
let name = self.get_workspace(workspace)?;
if let Some(ws) = self.state.workspaces.get(name.as_str()) {
if let Some(ws) = self.get_existing_workspace(workspace)? {
ws.may_capture.set(capture);
ws.update_has_captures();
}
@ -856,6 +900,20 @@ impl ConfigProxyHandler {
Ok(())
}
fn handle_set_window_workspace(&self, window: Window, ws: Workspace) -> Result<(), CphError> {
let window = self.get_window(window)?;
let name = self.get_workspace(ws)?;
let workspace = match self.state.workspaces.get(name.deref()) {
Some(ws) => ws,
_ => match window.node_output() {
Some(o) => o.create_workspace(name.deref()),
_ => return Ok(()),
},
};
toplevel_set_workspace(&self.state, window, &workspace);
Ok(())
}
fn handle_get_device_name(&self, device: InputDevice) -> Result<(), CphError> {
let dev = self.get_device_handler_data(device)?;
let name = dev.device.name();
@ -888,13 +946,10 @@ impl ConfigProxyHandler {
) -> Result<(), CphError> {
let output = self.get_output_node(connector)?;
let ws = match workspace {
WorkspaceSource::Explicit(ws) => {
let name = self.get_workspace(ws)?;
match self.state.workspaces.get(name.as_str()) {
Some(ws) => ws,
_ => return Ok(()),
}
}
WorkspaceSource::Explicit(ws) => match self.get_existing_workspace(ws)? {
Some(ws) => ws,
_ => return Ok(()),
},
WorkspaceSource::Seat(s) => match self.get_seat(s)?.get_output().workspace.get() {
Some(ws) => ws,
_ => return Ok(()),
@ -1180,6 +1235,20 @@ impl ConfigProxyHandler {
Ok(())
}
fn handle_get_window_float_pinned(&self, window: Window) -> Result<(), CphError> {
let window = self.get_window(window)?;
self.respond(Response::GetWindowFloatPinned {
pinned: window.tl_pinned(),
});
Ok(())
}
fn handle_set_window_float_pinned(&self, window: Window, pinned: bool) -> Result<(), CphError> {
let window = self.get_window(window)?;
window.tl_set_pinned(true, pinned);
Ok(())
}
fn handle_set_vrr_mode(
&self,
connector: Option<Connector>,
@ -1360,6 +1429,24 @@ impl ConfigProxyHandler {
Ok(())
}
fn handle_get_window_mono(&self, window: Window) -> Result<(), CphError> {
let window = self.get_window(window)?;
self.respond(Response::GetWindowMono {
mono: toplevel_parent_container(&*window)
.map(|c| c.mono_child.is_some())
.unwrap_or(false),
});
Ok(())
}
fn handle_set_window_mono(&self, window: Window, mono: bool) -> Result<(), CphError> {
let window = self.get_window(window)?;
if let Some(c) = toplevel_parent_container(&*window) {
c.set_mono(mono.then_some(window.as_ref()));
}
Ok(())
}
fn handle_get_seat_split(&self, seat: Seat) -> Result<(), CphError> {
let seat = self.get_seat(seat)?;
self.respond(Response::GetSplit {
@ -1377,6 +1464,25 @@ impl ConfigProxyHandler {
Ok(())
}
fn handle_get_window_split(&self, window: Window) -> Result<(), CphError> {
let window = self.get_window(window)?;
self.respond(Response::GetWindowSplit {
axis: toplevel_parent_container(&*window)
.map(|c| c.split.get())
.unwrap_or(ContainerSplit::Horizontal)
.into(),
});
Ok(())
}
fn handle_set_window_split(&self, window: Window, axis: Axis) -> Result<(), CphError> {
let window = self.get_window(window)?;
if let Some(c) = toplevel_parent_container(&*window) {
c.set_split(axis.into());
}
Ok(())
}
fn handle_add_shortcut(
&self,
seat: Seat,
@ -1480,6 +1586,12 @@ impl ConfigProxyHandler {
Ok(())
}
fn handle_create_window_split(&self, window: Window, axis: Axis) -> Result<(), CphError> {
let window = self.get_window(window)?;
toplevel_create_split(&self.state, window, axis.into());
Ok(())
}
fn handle_focus_seat_parent(&self, seat: Seat) -> Result<(), CphError> {
let seat = self.get_seat(seat)?;
seat.focus_parent();
@ -1509,6 +1621,20 @@ impl ConfigProxyHandler {
Ok(())
}
fn handle_get_window_floating(&self, window: Window) -> Result<(), CphError> {
let window = self.get_window(window)?;
self.respond(Response::GetWindowFloating {
floating: window.tl_data().is_floating.get(),
});
Ok(())
}
fn handle_set_window_floating(&self, window: Window, floating: bool) -> Result<(), CphError> {
let window = self.get_window(window)?;
toplevel_set_floating(&self.state, window, floating);
Ok(())
}
fn handle_add_pollable(self: &Rc<Self>, fd: i32) -> Result<(), CphError> {
let fd = match fcntl_dupfd_cloexec(fd, 0) {
Ok(fd) => Rc::new(fd),
@ -1577,6 +1703,28 @@ impl ConfigProxyHandler {
Ok(())
}
fn tl_to_window(&self, tl: &dyn ToplevelNode) -> Window {
self.tl_id_to_window(tl.tl_data().identifier.get())
}
fn tl_id_to_window(&self, tl: ToplevelIdentifier) -> Window {
if let Some(win) = self.windows_from_tl_id.get(&tl) {
return win;
}
let id = Window(self.window_ids.fetch_add(1));
self.windows_from_tl_id.set(tl, id);
self.windows_to_tl_id.set(id, tl);
id
}
fn get_window(&self, window: Window) -> Result<Rc<dyn ToplevelNode>, CphError> {
self.windows_to_tl_id
.get(&window)
.and_then(|id| self.state.toplevels.get(&id))
.and_then(|tl| tl.upgrade())
.ok_or(CphError::WindowDoesNotExist(window))
}
fn spaces_change(&self) {
struct V;
impl NodeVisitorBase for V {
@ -1752,6 +1900,123 @@ impl ConfigProxyHandler {
self.state.clients.kill(ClientId::from_raw(client.0));
}
fn handle_get_workspace_window(&self, ws: Workspace) -> Result<(), CphError> {
let window = self
.get_existing_workspace(ws)?
.and_then(|ws| ws.container.get())
.map(|c| self.tl_to_window(&*c))
.unwrap_or(Window(0));
self.respond(Response::GetWorkspaceWindow { window });
Ok(())
}
fn handle_get_seat_keyboard_window(&self, seat: Seat) -> Result<(), CphError> {
let window = self
.get_seat(seat)?
.get_keyboard_node()
.node_toplevel()
.map(|tl| self.tl_to_window(&*tl))
.unwrap_or(Window(0));
self.respond(Response::GetSeatKeyboardWindow { window });
Ok(())
}
fn handle_seat_focus_window(&self, seat: Seat, window_id: Window) -> Result<(), CphError> {
let seat = self.get_seat(seat)?;
let window = self.get_window(window_id)?;
if !window.node_visible() {
return Err(CphError::WindowNotVisible(window_id));
}
seat.focus_toplevel(window);
Ok(())
}
fn handle_get_window_title(&self, window: Window) -> Result<(), CphError> {
let title = self.get_window(window)?.tl_data().title.borrow().clone();
self.respond(Response::GetWindowTitle { title });
Ok(())
}
fn handle_get_window_type(&self, window: Window) -> Result<(), CphError> {
let kind = self.get_window(window)?.tl_data().kind.to_window_type();
self.respond(Response::GetWindowType { kind });
Ok(())
}
fn handle_window_exists(&self, window: Window) {
self.respond(Response::WindowExists {
exists: self.get_window(window).is_ok(),
});
}
fn handle_get_window_id(&self, window: Window) -> Result<(), CphError> {
let id = self
.get_window(window)?
.tl_data()
.identifier
.get()
.to_string();
self.respond(Response::GetWindowId { id: id.to_string() });
Ok(())
}
fn handle_get_window_is_visible(&self, window: Window) -> Result<(), CphError> {
let window = self.get_window(window)?;
self.respond(Response::GetWindowIsVisible {
visible: window.node_visible(),
});
Ok(())
}
fn handle_get_window_client(&self, window: Window) -> Result<(), CphError> {
let window = self.get_window(window)?;
self.respond(Response::GetWindowClient {
client: window
.tl_data()
.client
.as_ref()
.map(|c| ConfigClient(c.id.raw()))
.unwrap_or(ConfigClient(0)),
});
Ok(())
}
fn handle_get_window_parent(&self, window: Window) -> Result<(), CphError> {
let window = self
.get_window(window)?
.tl_data()
.parent
.get()
.and_then(|tl| tl.node_into_toplevel())
.map(|tl| self.tl_to_window(&*tl))
.unwrap_or(Window(0));
self.respond(Response::GetWindowParent { window });
Ok(())
}
fn handle_get_window_workspace(&self, window: Window) -> Result<(), CphError> {
let workspace = self
.get_window(window)?
.tl_data()
.workspace
.get()
.map(|ws| self.get_workspace_by_name(&ws.name))
.unwrap_or(Workspace(0));
self.respond(Response::GetWindowWorkspace { workspace });
Ok(())
}
fn handle_get_window_children(&self, window: Window) -> Result<(), CphError> {
let mut windows = vec![];
if let Some(c) = self.get_window(window)?.node_into_container() {
for c in c.children.iter() {
windows.push(self.tl_to_window(&*c.node));
}
}
self.respond(Response::GetWindowChildren { windows });
Ok(())
}
pub fn handle_request(self: &Rc<Self>, msg: &[u8]) {
if let Err(e) = self.handle_request_(msg) {
log::error!("Could not handle client request: {}", ErrorFmt(e));
@ -2171,6 +2436,82 @@ impl ConfigProxyHandler {
.handle_client_is_xwayland(client)
.wrn("client_is_xwayland")?,
ClientMessage::ClientKill { client } => self.handle_client_kill(client),
ClientMessage::WindowExists { window } => self.handle_window_exists(window),
ClientMessage::GetWorkspaceWindow { workspace } => self
.handle_get_workspace_window(workspace)
.wrn("get_workspace_window")?,
ClientMessage::GetSeatKeyboardWindow { seat } => self
.handle_get_seat_keyboard_window(seat)
.wrn("get_seat_keyboard_window")?,
ClientMessage::SeatFocusWindow { seat, window } => self
.handle_seat_focus_window(seat, window)
.wrn("seat_focus_window")?,
ClientMessage::GetWindowTitle { window } => self
.handle_get_window_title(window)
.wrn("get_window_title")?,
ClientMessage::GetWindowType { window } => {
self.handle_get_window_type(window).wrn("get_window_type")?
}
ClientMessage::GetWindowId { window } => {
self.handle_get_window_id(window).wrn("get_window_id")?
}
ClientMessage::GetWindowParent { window } => self
.handle_get_window_parent(window)
.wrn("get_window_parent")?,
ClientMessage::GetWindowWorkspace { window } => self
.handle_get_window_workspace(window)
.wrn("get_window_workspace")?,
ClientMessage::GetWindowChildren { window } => self
.handle_get_window_children(window)
.wrn("get_window_children")?,
ClientMessage::GetWindowSplit { window } => self
.handle_get_window_split(window)
.wrn("get_window_split")?,
ClientMessage::SetWindowSplit { window, axis } => self
.handle_set_window_split(window, axis)
.wrn("set_window_split")?,
ClientMessage::GetWindowMono { window } => {
self.handle_get_window_mono(window).wrn("get_window_mono")?
}
ClientMessage::SetWindowMono { window, mono } => self
.handle_set_window_mono(window, mono)
.wrn("set_window_mono")?,
ClientMessage::WindowMove { window, direction } => self
.handle_window_move(window, direction)
.wrn("window_move")?,
ClientMessage::CreateWindowSplit { window, axis } => self
.handle_create_window_split(window, axis)
.wrn("create_window_split")?,
ClientMessage::WindowClose { window } => {
self.handle_window_close(window).wrn("close_window")?
}
ClientMessage::GetWindowFloating { window } => self
.handle_get_window_floating(window)
.wrn("get_window_floating")?,
ClientMessage::SetWindowFloating { window, floating } => self
.handle_set_window_floating(window, floating)
.wrn("set_window_floating")?,
ClientMessage::SetWindowWorkspace { window, workspace } => self
.handle_set_window_workspace(window, workspace)
.wrn("set_window_workspace")?,
ClientMessage::SetWindowFullscreen { window, fullscreen } => self
.handle_set_window_fullscreen(window, fullscreen)
.wrn("set_window_fullscreen")?,
ClientMessage::GetWindowFullscreen { window } => self
.handle_get_window_fullscreen(window)
.wrn("get_window_fullscreen")?,
ClientMessage::GetWindowFloatPinned { window } => self
.handle_get_window_float_pinned(window)
.wrn("get_window_float_pinned")?,
ClientMessage::SetWindowFloatPinned { window, pinned } => self
.handle_set_window_float_pinned(window, pinned)
.wrn("set_window_float_pinned")?,
ClientMessage::GetWindowIsVisible { window } => self
.handle_get_window_is_visible(window)
.wrn("get_window_is_visible")?,
ClientMessage::GetWindowClient { window } => self
.handle_get_window_client(window)
.wrn("get_window_client")?,
}
Ok(())
}
@ -2248,6 +2589,10 @@ enum CphError {
UnknownTransferFunction(ConfigTransferFunction),
#[error("Client {0:?} does not exist")]
ClientDoesNotExist(ConfigClient),
#[error("Window {0:?} does not exist")]
WindowDoesNotExist(Window),
#[error("Window {0:?} is not visible")]
WindowNotVisible(Window),
}
trait WithRequestName {

View file

@ -79,7 +79,8 @@ use {
state::{DeviceHandlerData, State},
tree::{
ContainerNode, ContainerSplit, Direction, FoundNode, Node, OutputNode, ToplevelNode,
WorkspaceNode, generic_node_visitor,
WorkspaceNode, generic_node_visitor, toplevel_create_split, toplevel_parent_container,
toplevel_set_floating, toplevel_set_workspace,
},
utils::{
asyncevent::AsyncEvent, bindings::PerClientBindings, clonecell::CloneCell,
@ -401,6 +402,10 @@ impl WlSeatGlobal {
self.cursor_user_group.latest_output()
}
pub fn get_keyboard_node(&self) -> Rc<dyn Node> {
self.keyboard_node.get()
}
pub fn get_keyboard_output(&self) -> Option<Rc<OutputNode>> {
self.keyboard_node.get().node_output()
}
@ -410,38 +415,7 @@ impl WlSeatGlobal {
Some(tl) => tl,
_ => return,
};
if tl.tl_data().is_fullscreen.get() {
return;
}
let old_ws = match tl.tl_data().workspace.get() {
Some(ws) => ws,
_ => return,
};
if old_ws.id == ws.id {
return;
}
let cn = match tl.tl_data().parent.get() {
Some(cn) => cn,
_ => return,
};
let kb_foci = collect_kb_foci(tl.clone());
cn.cnode_remove_child2(&*tl, true);
if !ws.visible.get() {
for focus in kb_foci {
old_ws.clone().node_do_focus(&focus, Direction::Unspecified);
}
}
if tl.tl_data().is_floating.get() {
self.state.map_floating(
tl.clone(),
tl.tl_data().float_width.get(),
tl.tl_data().float_height.get(),
ws,
None,
);
} else {
self.state.map_tiled_on(tl, ws);
}
toplevel_set_workspace(&self.state, tl, ws);
}
pub fn mark_last_active(self: &Rc<Self>) {
@ -556,11 +530,7 @@ impl WlSeatGlobal {
pub fn kb_parent_container(&self) -> Option<Rc<ContainerNode>> {
if let Some(tl) = self.keyboard_node.get().node_toplevel() {
if let Some(parent) = tl.tl_data().parent.get() {
if let Some(container) = parent.node_into_container() {
return Some(container);
}
}
return toplevel_parent_container(&*tl);
}
None
}
@ -595,21 +565,7 @@ impl WlSeatGlobal {
Some(tl) => tl,
_ => return,
};
if tl.tl_data().is_fullscreen.get() {
return;
}
let ws = match tl.tl_data().workspace.get() {
Some(ws) => ws,
_ => return,
};
let pn = match tl.tl_data().parent.get() {
Some(pn) => pn,
_ => return,
};
if let Some(pn) = pn.node_into_containing_node() {
let cn = ContainerNode::new(&self.state, &ws, tl.clone(), axis);
pn.cnode_replace_child(&*tl, cn);
}
toplevel_create_split(&self.state, tl, axis);
}
pub fn focus_parent(self: &Rc<Self>) {
@ -634,29 +590,7 @@ impl WlSeatGlobal {
Some(tl) => tl,
_ => return,
};
self.set_tl_floating(tl, floating);
}
pub fn set_tl_floating(self: &Rc<Self>, tl: Rc<dyn ToplevelNode>, floating: bool) {
let data = tl.tl_data();
if data.is_fullscreen.get() {
return;
}
if data.is_floating.get() == floating {
return;
}
let parent = match data.parent.get() {
Some(p) => p,
_ => return,
};
if !floating {
parent.cnode_remove_child2(&*tl, true);
self.state.map_tiled(tl);
} else if let Some(ws) = data.workspace.get() {
parent.cnode_remove_child2(&*tl, true);
let (width, height) = data.float_size(&ws);
self.state.map_floating(tl, width, height, &ws, None);
}
toplevel_set_floating(&self.state, tl, floating);
}
pub fn get_rate(&self) -> (i32, i32) {

View file

@ -13,7 +13,7 @@ use {
tree::{
ContainerSplit, Direction, FindTreeResult, FindTreeUsecase, FoundNode, Node, NodeId,
NodeVisitor, OutputNode, StackedNode, TileDragDestination, ToplevelData, ToplevelNode,
ToplevelNodeBase, WorkspaceNode, default_tile_drag_destination,
ToplevelNodeBase, ToplevelType, WorkspaceNode, default_tile_drag_destination,
},
utils::{clonecell::CloneCell, copyhashmap::CopyHashMap, linkedlist::LinkedNode},
wire::WlSurfaceId,
@ -205,16 +205,19 @@ impl Xwindow {
if xsurface.xwindow.is_some() {
return Err(XWindowError::AlreadyAttached);
}
let id = data.state.node_ids.next();
let slf = Rc::new_cyclic(|weak| {
let tld = ToplevelData::new(
&data.state,
data.info.title.borrow_mut().clone().unwrap_or_default(),
Some(surface.client.clone()),
ToplevelType::XWindow,
id,
weak,
);
tld.pos.set(surface.extents.get());
Self {
id: data.state.node_ids.next(),
id,
data: data.clone(),
display_link: Default::default(),
toplevel_data: tld,

View file

@ -27,7 +27,8 @@ use {
tree::{
ContainerSplit, Direction, FindTreeResult, FindTreeUsecase, FoundNode, Node, NodeId,
NodeVisitor, OutputNode, TileDragDestination, ToplevelData, ToplevelNode,
ToplevelNodeBase, ToplevelNodeId, WorkspaceNode, default_tile_drag_destination,
ToplevelNodeBase, ToplevelNodeId, ToplevelType, WorkspaceNode,
default_tile_drag_destination,
},
utils::{clonecell::CloneCell, hash_map_ext::HashMapExt},
wire::{XdgToplevelId, xdg_toplevel::*},
@ -133,11 +134,12 @@ impl XdgToplevel {
states.insert(STATE_CONSTRAINED_BOTTOM);
}
let state = &surface.surface.client.state;
let node_id = state.node_ids.next();
Self {
id,
state: state.clone(),
xdg: surface.clone(),
node_id: state.node_ids.next(),
node_id,
parent: Default::default(),
children: RefCell::new(Default::default()),
states: RefCell::new(states),
@ -152,6 +154,8 @@ impl XdgToplevel {
state,
String::new(),
Some(surface.surface.client.clone()),
ToplevelType::XdgToplevel,
node_id,
slf,
),
drag: Default::default(),

View file

@ -951,6 +951,13 @@ impl State {
self.workspace_managers.clear();
}
pub fn remove_toplevel_id(&self, id: ToplevelIdentifier) {
self.toplevels.remove(&id);
if let Some(config) = self.config.get() {
config.toplevel_removed(id);
}
}
pub fn damage_hardware_cursors(&self, render: bool) {
for output in self.root.outputs.lock().values() {
if let Some(hc) = output.hardware_cursor.get() {

View file

@ -19,7 +19,8 @@ use {
tree::{
ContainingNode, Direction, FindTreeResult, FindTreeUsecase, FoundNode, Node, NodeId,
OutputNode, TddType, TileDragDestination, ToplevelData, ToplevelNode, ToplevelNodeBase,
WorkspaceNode, default_tile_drag_bounds, walker::NodeVisitor,
ToplevelType, WorkspaceNode, default_tile_drag_bounds, toplevel_set_floating,
walker::NodeVisitor,
},
utils::{
asyncevent::AsyncEvent,
@ -212,8 +213,9 @@ impl ContainerNode {
let child_node_ref = child_node.clone();
let mut child_nodes = AHashMap::new();
child_nodes.insert(child.node_id(), child_node);
let id = state.node_ids.next();
let slf = Rc::new_cyclic(|weak| Self {
id: state.node_ids.next(),
id,
split: Cell::new(split),
mono_child: CloneCell::new(None),
mono_body: Cell::new(Default::default()),
@ -237,7 +239,14 @@ impl ContainerNode {
state: state.clone(),
render_data: Default::default(),
scroller: Default::default(),
toplevel_data: ToplevelData::new(state, Default::default(), None, weak),
toplevel_data: ToplevelData::new(
state,
Default::default(),
None,
ToplevelType::Container,
id,
weak,
),
attention_requests: Default::default(),
});
child.tl_set_parent(slf.clone());
@ -1239,7 +1248,7 @@ impl ContainerNode {
&& kind == SeatOpKind::Move
{
drop(seat_datas);
seat.set_tl_floating(child.node.clone(), true);
toplevel_set_floating(&self.state, child.node.clone(), true);
return;
}
seat_data.op = Some(SeatOp {

View file

@ -16,7 +16,7 @@ use {
tree::{
ContainingNode, Direction, FindTreeResult, FindTreeUsecase, FoundNode, Node, NodeId,
OutputNode, PinnedNode, StackedNode, TileDragDestination, ToplevelNode, WorkspaceNode,
walker::NodeVisitor,
toplevel_set_floating, walker::NodeVisitor,
},
utils::{
asyncevent::AsyncEvent, clonecell::CloneCell, double_click_state::DoubleClickState,
@ -603,7 +603,7 @@ impl FloatNode {
{
if let Some(tl) = self.child.get() {
drop(cursors);
seat.set_tl_floating(tl, false);
toplevel_set_floating(&self.state, tl, false);
return;
}
}

View file

@ -12,7 +12,7 @@ use {
tree::{
ContainerSplit, Direction, FindTreeResult, FindTreeUsecase, FoundNode, Node, NodeId,
NodeVisitor, OutputNode, TileDragDestination, ToplevelData, ToplevelNode,
ToplevelNodeBase, default_tile_drag_destination,
ToplevelNodeBase, ToplevelType, default_tile_drag_destination,
},
utils::{
asyncevent::AsyncEvent, errorfmt::ErrorFmt, on_drop_event::OnDropEvent,
@ -49,12 +49,15 @@ pub async fn placeholder_render_textures(state: Rc<State>) {
impl PlaceholderNode {
pub fn new_for(state: &Rc<State>, node: Rc<dyn ToplevelNode>, slf: &Weak<Self>) -> Self {
let id = state.node_ids.next();
Self {
id: state.node_ids.next(),
id,
toplevel: ToplevelData::new(
state,
node.tl_data().title.borrow().clone(),
node.node_client(),
ToplevelType::Placeholder,
id,
slf,
),
destroyed: Default::default(),
@ -65,9 +68,17 @@ impl PlaceholderNode {
}
pub fn new_empty(state: &Rc<State>, slf: &Weak<Self>) -> Self {
let id = state.node_ids.next();
Self {
id: state.node_ids.next(),
toplevel: ToplevelData::new(state, String::new(), None, slf),
id,
toplevel: ToplevelData::new(
state,
String::new(),
None,
ToplevelType::Placeholder,
id,
slf,
),
destroyed: Default::default(),
update_textures_scheduled: Default::default(),
state: state.clone(),

View file

@ -30,6 +30,7 @@ use {
JayToplevelId,
},
},
jay_config::{window, window::WindowType},
std::{
cell::{Cell, RefCell},
ops::Deref,
@ -254,7 +255,29 @@ impl ToplevelOpt {
}
}
#[derive(Debug)]
pub enum ToplevelType {
Container,
Placeholder,
XdgToplevel,
XWindow,
}
impl ToplevelType {
pub fn to_window_type(&self) -> WindowType {
match self {
ToplevelType::Container => window::CONTAINER,
ToplevelType::Placeholder => window::PLACEHOLDER,
ToplevelType::XdgToplevel => window::XDG_TOPLEVEL,
ToplevelType::XWindow => window::X_WINDOW,
}
}
}
pub struct ToplevelData {
#[expect(dead_code)]
pub node_id: NodeId,
pub kind: ToplevelType,
pub self_active: Cell<bool>,
pub client: Option<Rc<Client>>,
pub state: Rc<State>,
@ -291,11 +314,16 @@ impl ToplevelData {
state: &Rc<State>,
title: String,
client: Option<Rc<Client>>,
kind: ToplevelType,
node_id: impl Into<NodeId>,
slf: &Weak<T>,
) -> Self {
let node_id = node_id.into();
let id = toplevel_identifier();
state.toplevels.set(id, slf.clone());
Self {
node_id,
kind,
self_active: Cell::new(false),
client,
state: state.clone(),
@ -372,7 +400,7 @@ impl ToplevelData {
{
let id = toplevel_identifier();
let prev = self.identifier.replace(id);
self.state.toplevels.remove(&prev);
self.state.remove_toplevel_id(prev);
self.state.toplevels.set(id, self.slf.clone());
}
{
@ -620,7 +648,7 @@ impl ToplevelData {
impl Drop for ToplevelData {
fn drop(&mut self) {
self.state.toplevels.remove(&self.identifier.get());
self.state.remove_toplevel_id(self.identifier.get());
}
}
@ -662,3 +690,82 @@ pub fn default_tile_drag_bounds<T: ToplevelNodeBase + ?Sized>(t: &T, split: Cont
ContainerSplit::Vertical => t.node_absolute_position().height() / FACTOR,
}
}
pub fn toplevel_parent_container(tl: &dyn ToplevelNode) -> Option<Rc<ContainerNode>> {
if let Some(parent) = tl.tl_data().parent.get() {
if let Some(container) = parent.node_into_container() {
return Some(container);
}
}
None
}
pub fn toplevel_create_split(state: &Rc<State>, tl: Rc<dyn ToplevelNode>, axis: ContainerSplit) {
if tl.tl_data().is_fullscreen.get() {
return;
}
let ws = match tl.tl_data().workspace.get() {
Some(ws) => ws,
_ => return,
};
let pn = match tl.tl_data().parent.get() {
Some(pn) => pn,
_ => return,
};
if let Some(pn) = pn.node_into_containing_node() {
let cn = ContainerNode::new(state, &ws, tl.clone(), axis);
pn.cnode_replace_child(&*tl, cn);
}
}
pub fn toplevel_set_floating(state: &Rc<State>, tl: Rc<dyn ToplevelNode>, floating: bool) {
let data = tl.tl_data();
if data.is_fullscreen.get() {
return;
}
if data.is_floating.get() == floating {
return;
}
let parent = match data.parent.get() {
Some(p) => p,
_ => return,
};
if !floating {
parent.cnode_remove_child2(&*tl, true);
state.map_tiled(tl);
} else if let Some(ws) = data.workspace.get() {
parent.cnode_remove_child2(&*tl, true);
let (width, height) = data.float_size(&ws);
state.map_floating(tl, width, height, &ws, None);
}
}
pub fn toplevel_set_workspace(state: &Rc<State>, tl: Rc<dyn ToplevelNode>, ws: &Rc<WorkspaceNode>) {
if tl.tl_data().is_fullscreen.get() {
return;
}
let old_ws = match tl.tl_data().workspace.get() {
Some(ws) => ws,
_ => return,
};
if old_ws.id == ws.id {
return;
}
let cn = match tl.tl_data().parent.get() {
Some(cn) => cn,
_ => return,
};
let kb_foci = collect_kb_foci(tl.clone());
cn.cnode_remove_child2(&*tl, true);
if !ws.visible.get() {
for focus in kb_foci {
old_ws.clone().node_do_focus(&focus, Direction::Unspecified);
}
}
if tl.tl_data().is_floating.get() {
let (width, height) = tl.tl_data().float_size(ws);
state.map_floating(tl.clone(), width, height, ws, None);
} else {
state.map_tiled_on(tl, ws);
}
}

View file

@ -1,9 +1,12 @@
use {
crate::utils::{
linkedlist::NodeRef,
ptr_ext::{MutPtrExt, PtrExt},
crate::{
tree::NodeId,
utils::{
linkedlist::NodeRef,
ptr_ext::{MutPtrExt, PtrExt},
},
},
jay_config::keyboard::mods::Modifiers,
jay_config::{keyboard::mods::Modifiers, window::Window},
std::{
cell::UnsafeCell,
fmt::{Debug, Formatter},
@ -97,3 +100,7 @@ unsafe impl UnsafeCellCloneSafe for usize {}
unsafe impl<A: UnsafeCellCloneSafe, B: UnsafeCellCloneSafe> UnsafeCellCloneSafe for (A, B) {}
unsafe impl UnsafeCellCloneSafe for Modifiers {}
unsafe impl UnsafeCellCloneSafe for NodeId {}
unsafe impl UnsafeCellCloneSafe for Window {}

View file

@ -1,5 +1,8 @@
use {
crate::utils::opaque::{OPAQUE_LEN, Opaque, OpaqueError, opaque},
crate::utils::{
clonecell::UnsafeCellCloneSafe,
opaque::{OPAQUE_LEN, Opaque, OpaqueError, opaque},
},
arrayvec::ArrayString,
std::{
fmt::{Display, Formatter},
@ -10,6 +13,8 @@ use {
#[derive(Debug, Eq, PartialEq, Copy, Clone, Hash)]
pub struct ToplevelIdentifier(Opaque);
unsafe impl UnsafeCellCloneSafe for ToplevelIdentifier {}
pub fn toplevel_identifier() -> ToplevelIdentifier {
ToplevelIdentifier(opaque())
}