Handle reparented windows correctly
Also refactored to avoid dealing with window properties until a window is actually mapped. Fixes #10
This commit is contained in:
parent
b8bd07ce93
commit
3afc9ffa9d
6 changed files with 456 additions and 164 deletions
|
|
@ -1,6 +1,6 @@
|
||||||
mod clientside;
|
mod clientside;
|
||||||
mod server;
|
mod server;
|
||||||
mod xstate;
|
pub mod xstate;
|
||||||
|
|
||||||
use crate::server::{PendingSurfaceState, ServerState};
|
use crate::server::{PendingSurfaceState, ServerState};
|
||||||
use crate::xstate::XState;
|
use crate::xstate::XState;
|
||||||
|
|
|
||||||
|
|
@ -158,12 +158,12 @@ impl SurfaceData {
|
||||||
let width = if pending.width > 0 {
|
let width = if pending.width > 0 {
|
||||||
pending.width as _
|
pending.width as _
|
||||||
} else {
|
} else {
|
||||||
window.dims.width
|
window.attrs.dims.width
|
||||||
};
|
};
|
||||||
let height = if pending.height > 0 {
|
let height = if pending.height > 0 {
|
||||||
pending.height as _
|
pending.height as _
|
||||||
} else {
|
} else {
|
||||||
window.dims.height
|
window.attrs.dims.height
|
||||||
};
|
};
|
||||||
debug!(
|
debug!(
|
||||||
"configuring {:?}: {}x{}, {width}x{height}",
|
"configuring {:?}: {}x{}, {width}x{height}",
|
||||||
|
|
@ -178,7 +178,7 @@ impl SurfaceData {
|
||||||
height: height as _,
|
height: height as _,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
window.dims = WindowDims {
|
window.attrs.dims = WindowDims {
|
||||||
x: pending.x as _,
|
x: pending.x as _,
|
||||||
y: pending.y as _,
|
y: pending.y as _,
|
||||||
width,
|
width,
|
||||||
|
|
|
||||||
|
|
@ -68,19 +68,24 @@ where
|
||||||
u32::from(wenum).try_into().unwrap()
|
u32::from(wenum).try_into().unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Default, Debug)]
|
||||||
|
pub struct WindowAttributes {
|
||||||
|
pub override_redirect: bool,
|
||||||
|
pub popup_for: Option<x::Window>,
|
||||||
|
pub dims: WindowDims,
|
||||||
|
pub size_hints: Option<WmNormalHints>,
|
||||||
|
pub title: Option<WmName>,
|
||||||
|
pub class: Option<String>,
|
||||||
|
pub group: Option<x::Window>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct WindowData {
|
struct WindowData {
|
||||||
window: x::Window,
|
window: x::Window,
|
||||||
|
surface_id: u32,
|
||||||
surface_key: Option<ObjectKey>,
|
surface_key: Option<ObjectKey>,
|
||||||
mapped: bool,
|
mapped: bool,
|
||||||
surface_id: u32,
|
attrs: WindowAttributes,
|
||||||
popup_for: Option<x::Window>,
|
|
||||||
dims: WindowDims,
|
|
||||||
size_hints: Option<WmNormalHints>,
|
|
||||||
override_redirect: bool,
|
|
||||||
title: Option<WmName>,
|
|
||||||
class: Option<String>,
|
|
||||||
group: Option<x::Window>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WindowData {
|
impl WindowData {
|
||||||
|
|
@ -92,16 +97,15 @@ impl WindowData {
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
window,
|
window,
|
||||||
|
surface_id: 0,
|
||||||
surface_key: None,
|
surface_key: None,
|
||||||
mapped: false,
|
mapped: false,
|
||||||
popup_for: parent,
|
attrs: WindowAttributes {
|
||||||
surface_id: 0,
|
|
||||||
dims,
|
|
||||||
size_hints: None,
|
|
||||||
override_redirect,
|
override_redirect,
|
||||||
title: None,
|
dims,
|
||||||
class: None,
|
popup_for: parent,
|
||||||
group: None
|
..Default::default()
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -491,7 +495,7 @@ impl<C: XConnection> ServerState<C> {
|
||||||
pub fn set_win_title(&mut self, window: x::Window, name: WmName) {
|
pub fn set_win_title(&mut self, window: x::Window, name: WmName) {
|
||||||
let win = self.windows.get_mut(&window).unwrap();
|
let win = self.windows.get_mut(&window).unwrap();
|
||||||
|
|
||||||
let new_title = match &mut win.title {
|
let new_title = match &mut win.attrs.title {
|
||||||
Some(w) => {
|
Some(w) => {
|
||||||
if matches!(w, WmName::NetWmName(_)) && matches!(name, WmName::WmName(_)) {
|
if matches!(w, WmName::NetWmName(_)) && matches!(name, WmName::WmName(_)) {
|
||||||
debug!("skipping setting window name to {name:?} because a _NET_WM_NAME title is already set");
|
debug!("skipping setting window name to {name:?} because a _NET_WM_NAME title is already set");
|
||||||
|
|
@ -501,7 +505,7 @@ impl<C: XConnection> ServerState<C> {
|
||||||
Some(w)
|
Some(w)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None => Some(win.title.insert(name)),
|
None => Some(win.attrs.title.insert(name)),
|
||||||
};
|
};
|
||||||
|
|
||||||
let Some(title) = new_title else {
|
let Some(title) = new_title else {
|
||||||
|
|
@ -518,7 +522,7 @@ impl<C: XConnection> ServerState<C> {
|
||||||
pub fn set_win_class(&mut self, window: x::Window, class: String) {
|
pub fn set_win_class(&mut self, window: x::Window, class: String) {
|
||||||
let win = self.windows.get_mut(&window).unwrap();
|
let win = self.windows.get_mut(&window).unwrap();
|
||||||
|
|
||||||
let class = win.class.insert(class);
|
let class = win.attrs.class.insert(class);
|
||||||
if let Some(key) = win.surface_key {
|
if let Some(key) = win.surface_key {
|
||||||
let surface: &SurfaceData = self.objects[key].as_ref();
|
let surface: &SurfaceData = self.objects[key].as_ref();
|
||||||
if let Some(SurfaceRole::Toplevel(Some(data))) = &surface.role {
|
if let Some(SurfaceRole::Toplevel(Some(data))) = &surface.role {
|
||||||
|
|
@ -529,13 +533,13 @@ impl<C: XConnection> ServerState<C> {
|
||||||
|
|
||||||
pub fn set_win_hints(&mut self, window: x::Window, hints: WmHints) {
|
pub fn set_win_hints(&mut self, window: x::Window, hints: WmHints) {
|
||||||
let win = self.windows.get_mut(&window).unwrap();
|
let win = self.windows.get_mut(&window).unwrap();
|
||||||
win.group = hints.window_group;
|
win.attrs.group = hints.window_group;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_size_hints(&mut self, window: x::Window, hints: WmNormalHints) {
|
pub fn set_size_hints(&mut self, window: x::Window, hints: WmNormalHints) {
|
||||||
let win = self.windows.get_mut(&window).unwrap();
|
let win = self.windows.get_mut(&window).unwrap();
|
||||||
|
|
||||||
if win.size_hints.is_none() || *win.size_hints.as_ref().unwrap() != hints {
|
if win.attrs.size_hints.is_none() || *win.attrs.size_hints.as_ref().unwrap() != hints {
|
||||||
debug!("setting {window:?} hints {hints:?}");
|
debug!("setting {window:?} hints {hints:?}");
|
||||||
if let Some(surface) = win.surface_key {
|
if let Some(surface) = win.surface_key {
|
||||||
let surface: &SurfaceData = self.objects[surface].as_ref();
|
let surface: &SurfaceData = self.objects[surface].as_ref();
|
||||||
|
|
@ -548,7 +552,7 @@ impl<C: XConnection> ServerState<C> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
win.size_hints = Some(hints);
|
win.attrs.size_hints = Some(hints);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -577,7 +581,7 @@ impl<C: XConnection> ServerState<C> {
|
||||||
|
|
||||||
pub fn reconfigure_window(&mut self, event: x::ConfigureNotifyEvent) {
|
pub fn reconfigure_window(&mut self, event: x::ConfigureNotifyEvent) {
|
||||||
let win = self.windows.get_mut(&event.window()).unwrap();
|
let win = self.windows.get_mut(&event.window()).unwrap();
|
||||||
win.dims = WindowDims {
|
win.attrs.dims = WindowDims {
|
||||||
x: event.x(),
|
x: event.x(),
|
||||||
y: event.y(),
|
y: event.y(),
|
||||||
width: event.width(),
|
width: event.width(),
|
||||||
|
|
@ -693,21 +697,21 @@ impl<C: XConnection> ServerState<C> {
|
||||||
.get_xdg_surface(client, &self.qh, surface_key);
|
.get_xdg_surface(client, &self.qh, surface_key);
|
||||||
|
|
||||||
let window_data = self.windows.get_mut(&window).unwrap();
|
let window_data = self.windows.get_mut(&window).unwrap();
|
||||||
if window_data.override_redirect {
|
if window_data.attrs.override_redirect {
|
||||||
// Override redirect is hard to convert to Wayland!
|
// Override redirect is hard to convert to Wayland!
|
||||||
// We will just make them be popups for the last focused toplevel.
|
// We will just make them be popups for the last focused toplevel.
|
||||||
if let Some(win) = self.last_focused_toplevel {
|
if let Some(win) = self.last_focused_toplevel {
|
||||||
window_data.popup_for = Some(win)
|
window_data.attrs.popup_for = Some(win)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let window = self.windows.get(&window).unwrap();
|
let window = self.windows.get(&window).unwrap();
|
||||||
|
|
||||||
let role = if let Some(parent) = window.popup_for {
|
let role = if let Some(parent) = window.attrs.popup_for {
|
||||||
debug!(
|
debug!(
|
||||||
"creating popup ({:?}) {:?} {:?} {:?} {surface_key:?}",
|
"creating popup ({:?}) {:?} {:?} {:?} {surface_key:?}",
|
||||||
window.window,
|
window.window,
|
||||||
parent,
|
parent,
|
||||||
window.dims,
|
window.attrs.dims,
|
||||||
surface.client.id()
|
surface.client.id()
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -716,15 +720,15 @@ impl<C: XConnection> ServerState<C> {
|
||||||
self.objects[parent_window.surface_key.unwrap()].as_ref();
|
self.objects[parent_window.surface_key.unwrap()].as_ref();
|
||||||
|
|
||||||
let positioner = self.xdg_wm_base.create_positioner(&self.qh, ());
|
let positioner = self.xdg_wm_base.create_positioner(&self.qh, ());
|
||||||
positioner.set_size(window.dims.width as _, window.dims.height as _);
|
positioner.set_size(window.attrs.dims.width as _, window.attrs.dims.height as _);
|
||||||
positioner.set_offset(window.dims.x as i32, window.dims.y as i32);
|
positioner.set_offset(window.attrs.dims.x as i32, window.attrs.dims.y as i32);
|
||||||
positioner.set_anchor(Anchor::TopLeft);
|
positioner.set_anchor(Anchor::TopLeft);
|
||||||
positioner.set_gravity(Gravity::BottomRight);
|
positioner.set_gravity(Gravity::BottomRight);
|
||||||
positioner.set_anchor_rect(
|
positioner.set_anchor_rect(
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
parent_window.dims.width as _,
|
parent_window.attrs.dims.width as _,
|
||||||
parent_window.dims.height as _,
|
parent_window.attrs.dims.height as _,
|
||||||
);
|
);
|
||||||
let popup = xdg_surface.get_popup(
|
let popup = xdg_surface.get_popup(
|
||||||
Some(&parent_surface.xdg().unwrap().surface),
|
Some(&parent_surface.xdg().unwrap().surface),
|
||||||
|
|
@ -772,7 +776,7 @@ impl<C: XConnection> ServerState<C> {
|
||||||
debug!("creating toplevel for {:?}", window.window);
|
debug!("creating toplevel for {:?}", window.window);
|
||||||
|
|
||||||
let toplevel = xdg.get_toplevel(&self.qh, surface_key);
|
let toplevel = xdg.get_toplevel(&self.qh, surface_key);
|
||||||
if let Some(hints) = &window.size_hints {
|
if let Some(hints) = &window.attrs.size_hints {
|
||||||
if let Some(min) = &hints.min_size {
|
if let Some(min) = &hints.min_size {
|
||||||
toplevel.set_min_size(min.width, min.height);
|
toplevel.set_min_size(min.width, min.height);
|
||||||
}
|
}
|
||||||
|
|
@ -781,18 +785,20 @@ impl<C: XConnection> ServerState<C> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let group = window.group.and_then(|win| self.windows.get(&win));
|
let group = window.attrs.group.and_then(|win| self.windows.get(&win));
|
||||||
if let Some(class) = window
|
if let Some(class) = window
|
||||||
|
.attrs
|
||||||
.class
|
.class
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.or(group.and_then(|g| g.class.as_ref()))
|
.or(group.and_then(|g| g.attrs.class.as_ref()))
|
||||||
{
|
{
|
||||||
toplevel.set_app_id(class.to_string());
|
toplevel.set_app_id(class.to_string());
|
||||||
}
|
}
|
||||||
if let Some(title) = window
|
if let Some(title) = window
|
||||||
|
.attrs
|
||||||
.title
|
.title
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.or(group.and_then(|g| g.title.as_ref()))
|
.or(group.and_then(|g| g.attrs.title.as_ref()))
|
||||||
{
|
{
|
||||||
toplevel.set_title(title.name().to_string());
|
toplevel.set_title(title.name().to_string());
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -118,7 +118,7 @@ impl Compositor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Default)]
|
||||||
struct WindowData {
|
struct WindowData {
|
||||||
mapped: bool,
|
mapped: bool,
|
||||||
fullscreen: bool,
|
fullscreen: bool,
|
||||||
|
|
@ -135,7 +135,7 @@ impl FakeXConnection {
|
||||||
fn window(&mut self, window: Window) -> &mut WindowData {
|
fn window(&mut self, window: Window) -> &mut WindowData {
|
||||||
self.windows
|
self.windows
|
||||||
.get_mut(&window)
|
.get_mut(&window)
|
||||||
.expect("Unknown window: {window:?}")
|
.expect(&format!("Unknown window: {window:?}"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
use crate::server::WindowAttributes;
|
||||||
use bitflags::bitflags;
|
use bitflags::bitflags;
|
||||||
use log::{debug, trace, warn};
|
use log::{debug, trace, warn};
|
||||||
use std::ffi::CString;
|
use std::ffi::CString;
|
||||||
|
|
@ -12,6 +13,39 @@ pub struct XState {
|
||||||
pub atoms: Atoms,
|
pub atoms: Atoms,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Essentially a trait alias.
|
||||||
|
trait PropertyResolver {
|
||||||
|
type Output;
|
||||||
|
fn resolve(self, reply: x::GetPropertyReply) -> Self::Output;
|
||||||
|
}
|
||||||
|
impl<T, Output> PropertyResolver for T
|
||||||
|
where
|
||||||
|
T: FnOnce(x::GetPropertyReply) -> Output,
|
||||||
|
{
|
||||||
|
type Output = Output;
|
||||||
|
fn resolve(self, reply: x::GetPropertyReply) -> Self::Output {
|
||||||
|
(self)(reply)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct PropertyCookieWrapper<'a, F: PropertyResolver> {
|
||||||
|
connection: &'a xcb::Connection,
|
||||||
|
cookie: x::GetPropertyCookie,
|
||||||
|
resolver: F,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<F: PropertyResolver> PropertyCookieWrapper<'_, F> {
|
||||||
|
/// Get the result from our property cookie.
|
||||||
|
fn resolve(self) -> Option<F::Output> {
|
||||||
|
let reply = self.connection.wait_for_reply(self.cookie).unwrap();
|
||||||
|
if reply.r#type() == x::ATOM_NONE {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(self.resolver.resolve(reply))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum WmName {
|
pub enum WmName {
|
||||||
WmName(String),
|
WmName(String),
|
||||||
|
|
@ -107,11 +141,7 @@ impl XState {
|
||||||
|
|
||||||
self.set_root_property(self.atoms.wm_check, x::ATOM_WINDOW, &[window]);
|
self.set_root_property(self.atoms.wm_check, x::ATOM_WINDOW, &[window]);
|
||||||
self.set_root_property(self.atoms.active_win, x::ATOM_WINDOW, &[x::Window::none()]);
|
self.set_root_property(self.atoms.active_win, x::ATOM_WINDOW, &[x::Window::none()]);
|
||||||
self.set_root_property(
|
self.set_root_property(self.atoms.supported, x::ATOM_ATOM, &[self.atoms.active_win]);
|
||||||
self.atoms.supported,
|
|
||||||
x::ATOM_ATOM,
|
|
||||||
&[self.atoms.active_win],
|
|
||||||
);
|
|
||||||
|
|
||||||
self.connection
|
self.connection
|
||||||
.send_and_check_request(&x::ChangeProperty {
|
.send_and_check_request(&x::ChangeProperty {
|
||||||
|
|
@ -140,20 +170,6 @@ impl XState {
|
||||||
match event {
|
match event {
|
||||||
xcb::Event::X(x::Event::CreateNotify(e)) => {
|
xcb::Event::X(x::Event::CreateNotify(e)) => {
|
||||||
debug!("new window: {:?}", e);
|
debug!("new window: {:?}", e);
|
||||||
match self
|
|
||||||
.connection
|
|
||||||
.send_and_check_request(&x::ChangeWindowAttributes {
|
|
||||||
window: e.window(),
|
|
||||||
value_list: &[x::Cw::EventMask(x::EventMask::PROPERTY_CHANGE)],
|
|
||||||
}) {
|
|
||||||
// This can sometimes fail if the window was created and then immediately
|
|
||||||
// destroyed.
|
|
||||||
Ok(()) | Err(xcb::ProtocolError::X(x::Error::Window(_), _)) => {}
|
|
||||||
Err(other) => {
|
|
||||||
panic!("error subscribing to property change on new window: {other:?}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let parent = e.parent();
|
let parent = e.parent();
|
||||||
let parent = if parent.is_none() || parent == self.root {
|
let parent = if parent.is_none() || parent == self.root {
|
||||||
None
|
None
|
||||||
|
|
@ -162,6 +178,22 @@ impl XState {
|
||||||
};
|
};
|
||||||
server_state.new_window(e.window(), e.override_redirect(), (&e).into(), parent);
|
server_state.new_window(e.window(), e.override_redirect(), (&e).into(), parent);
|
||||||
}
|
}
|
||||||
|
xcb::Event::X(x::Event::ReparentNotify(e)) => {
|
||||||
|
debug!("reparent event: {e:?}");
|
||||||
|
if e.parent() == self.root {
|
||||||
|
let attrs = self.get_window_attributes(e.window());
|
||||||
|
server_state.new_window(
|
||||||
|
e.window(),
|
||||||
|
attrs.override_redirect,
|
||||||
|
attrs.dims,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
self.handle_window_attributes(server_state, e.window(), attrs);
|
||||||
|
} else {
|
||||||
|
debug!("destroying window since its parent is no longer root!");
|
||||||
|
server_state.destroy_window(e.window());
|
||||||
|
}
|
||||||
|
}
|
||||||
xcb::Event::X(x::Event::MapRequest(e)) => {
|
xcb::Event::X(x::Event::MapRequest(e)) => {
|
||||||
debug!("requested to map {:?}", e.window());
|
debug!("requested to map {:?}", e.window());
|
||||||
self.connection
|
self.connection
|
||||||
|
|
@ -169,7 +201,15 @@ impl XState {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
xcb::Event::X(x::Event::MapNotify(e)) => {
|
xcb::Event::X(x::Event::MapNotify(e)) => {
|
||||||
|
let attrs = self.get_window_attributes(e.window());
|
||||||
|
self.handle_window_attributes(server_state, e.window(), attrs);
|
||||||
server_state.map_window(e.window());
|
server_state.map_window(e.window());
|
||||||
|
self.connection
|
||||||
|
.send_and_check_request(&x::ChangeWindowAttributes {
|
||||||
|
window: e.window(),
|
||||||
|
value_list: &[x::Cw::EventMask(x::EventMask::PROPERTY_CHANGE)],
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
}
|
}
|
||||||
xcb::Event::X(x::Event::ConfigureNotify(e)) => {
|
xcb::Event::X(x::Event::ConfigureNotify(e)) => {
|
||||||
server_state.reconfigure_window(e);
|
server_state.reconfigure_window(e);
|
||||||
|
|
@ -177,6 +217,17 @@ impl XState {
|
||||||
xcb::Event::X(x::Event::UnmapNotify(e)) => {
|
xcb::Event::X(x::Event::UnmapNotify(e)) => {
|
||||||
trace!("unmap event: {:?}", e.event());
|
trace!("unmap event: {:?}", e.event());
|
||||||
server_state.unmap_window(e.window());
|
server_state.unmap_window(e.window());
|
||||||
|
match self
|
||||||
|
.connection
|
||||||
|
.send_and_check_request(&x::ChangeWindowAttributes {
|
||||||
|
window: e.window(),
|
||||||
|
value_list: &[x::Cw::EventMask(x::EventMask::empty())],
|
||||||
|
}) {
|
||||||
|
// Window error may occur if the window has been destroyed,
|
||||||
|
// which is fine
|
||||||
|
Ok(_) | Err(xcb::ProtocolError::X(x::Error::Window(_), _)) => {}
|
||||||
|
Err(other) => panic!("Error removing event mask from window: {other:?}"),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
xcb::Event::X(x::Event::DestroyNotify(e)) => {
|
xcb::Event::X(x::Event::DestroyNotify(e)) => {
|
||||||
debug!("destroying window {:?}", e.window());
|
debug!("destroying window {:?}", e.window());
|
||||||
|
|
@ -252,6 +303,173 @@ impl XState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_window_attributes(&self, window: x::Window) -> WindowAttributes {
|
||||||
|
let geometry = self.connection.send_request(&x::GetGeometry {
|
||||||
|
drawable: x::Drawable::Window(window),
|
||||||
|
});
|
||||||
|
let attrs = self
|
||||||
|
.connection
|
||||||
|
.send_request(&x::GetWindowAttributes { window });
|
||||||
|
|
||||||
|
let name = self.get_net_wm_name(window);
|
||||||
|
let class = self.get_wm_class(window);
|
||||||
|
let wm_hints = self.get_wm_hints(window);
|
||||||
|
let size_hints = self.get_wm_size_hints(window);
|
||||||
|
|
||||||
|
let geometry = self.connection.wait_for_reply(geometry).unwrap();
|
||||||
|
let attrs = self.connection.wait_for_reply(attrs).unwrap();
|
||||||
|
let title = name
|
||||||
|
.resolve()
|
||||||
|
.or_else(|| self.get_wm_name(window).resolve());
|
||||||
|
let class = class.resolve();
|
||||||
|
let wm_hints = wm_hints.resolve();
|
||||||
|
let size_hints = size_hints.resolve();
|
||||||
|
|
||||||
|
WindowAttributes {
|
||||||
|
override_redirect: attrs.override_redirect(),
|
||||||
|
popup_for: None,
|
||||||
|
dims: WindowDims {
|
||||||
|
x: geometry.x(),
|
||||||
|
y: geometry.y(),
|
||||||
|
width: geometry.width(),
|
||||||
|
height: geometry.height(),
|
||||||
|
},
|
||||||
|
title,
|
||||||
|
class,
|
||||||
|
group: wm_hints.map(|h| h.window_group).flatten(),
|
||||||
|
size_hints,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_window_attributes(
|
||||||
|
&self,
|
||||||
|
server_state: &mut super::RealServerState,
|
||||||
|
window: x::Window,
|
||||||
|
attrs: WindowAttributes,
|
||||||
|
) {
|
||||||
|
if let Some(name) = attrs.title {
|
||||||
|
debug!("setting {window:?} title to {name:?}");
|
||||||
|
server_state.set_win_title(window, name);
|
||||||
|
}
|
||||||
|
if let Some(class) = attrs.class {
|
||||||
|
debug!("setting {window:?} class to {class}");
|
||||||
|
server_state.set_win_class(window, class);
|
||||||
|
}
|
||||||
|
if let Some(hints) = attrs.size_hints {
|
||||||
|
debug!("{window:?} size hints: {hints:?}");
|
||||||
|
server_state.set_size_hints(window, hints);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_property_cookie(
|
||||||
|
&self,
|
||||||
|
window: x::Window,
|
||||||
|
property: x::Atom,
|
||||||
|
ty: x::Atom,
|
||||||
|
long_length: u32,
|
||||||
|
) -> x::GetPropertyCookie {
|
||||||
|
self.connection.send_request(&x::GetProperty {
|
||||||
|
delete: false,
|
||||||
|
window,
|
||||||
|
property,
|
||||||
|
r#type: ty,
|
||||||
|
long_offset: 0,
|
||||||
|
long_length,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_wm_class(
|
||||||
|
&self,
|
||||||
|
window: x::Window,
|
||||||
|
) -> PropertyCookieWrapper<impl PropertyResolver<Output = String>> {
|
||||||
|
let cookie = self.get_property_cookie(window, x::ATOM_WM_CLASS, x::ATOM_STRING, 256);
|
||||||
|
let resolver = move |reply: x::GetPropertyReply| {
|
||||||
|
let data: &[u8] = reply.value();
|
||||||
|
// wm class is instance + class - ignore instance
|
||||||
|
let class_start = data.iter().copied().position(|b| b == 0u8).unwrap() + 1;
|
||||||
|
let data = data[class_start..].to_vec();
|
||||||
|
let class = CString::from_vec_with_nul(data).unwrap();
|
||||||
|
debug!("{:?} class: {class:?}", window);
|
||||||
|
class.to_string_lossy().to_string()
|
||||||
|
};
|
||||||
|
PropertyCookieWrapper {
|
||||||
|
connection: &self.connection,
|
||||||
|
cookie,
|
||||||
|
resolver,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_wm_name(
|
||||||
|
&self,
|
||||||
|
window: x::Window,
|
||||||
|
) -> PropertyCookieWrapper<impl PropertyResolver<Output = WmName>> {
|
||||||
|
let cookie = self.get_property_cookie(window, x::ATOM_WM_NAME, x::ATOM_STRING, 256);
|
||||||
|
let resolver = |reply: x::GetPropertyReply| {
|
||||||
|
let data: &[u8] = reply.value();
|
||||||
|
WmName::WmName(String::from_utf8(data.to_vec()).unwrap())
|
||||||
|
};
|
||||||
|
|
||||||
|
PropertyCookieWrapper {
|
||||||
|
connection: &self.connection,
|
||||||
|
cookie,
|
||||||
|
resolver,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_net_wm_name(
|
||||||
|
&self,
|
||||||
|
window: x::Window,
|
||||||
|
) -> PropertyCookieWrapper<impl PropertyResolver<Output = WmName>> {
|
||||||
|
let cookie =
|
||||||
|
self.get_property_cookie(window, self.atoms.net_wm_name, self.atoms.utf8_string, 256);
|
||||||
|
let resolver = |reply: x::GetPropertyReply| {
|
||||||
|
let data: &[u8] = reply.value();
|
||||||
|
WmName::NetWmName(String::from_utf8(data.to_vec()).unwrap())
|
||||||
|
};
|
||||||
|
|
||||||
|
PropertyCookieWrapper {
|
||||||
|
connection: &self.connection,
|
||||||
|
cookie,
|
||||||
|
resolver,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_wm_hints(
|
||||||
|
&self,
|
||||||
|
window: x::Window,
|
||||||
|
) -> PropertyCookieWrapper<impl PropertyResolver<Output = WmHints>> {
|
||||||
|
let cookie = self.get_property_cookie(window, x::ATOM_WM_HINTS, x::ATOM_WM_HINTS, 9);
|
||||||
|
let resolver = |reply: x::GetPropertyReply| {
|
||||||
|
let data: &[u32] = reply.value();
|
||||||
|
let hints = WmHints::from(data);
|
||||||
|
debug!("wm hints: {hints:?}");
|
||||||
|
hints
|
||||||
|
};
|
||||||
|
PropertyCookieWrapper {
|
||||||
|
connection: &self.connection,
|
||||||
|
cookie,
|
||||||
|
resolver,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_wm_size_hints(
|
||||||
|
&self,
|
||||||
|
window: x::Window,
|
||||||
|
) -> PropertyCookieWrapper<impl PropertyResolver<Output = WmNormalHints>> {
|
||||||
|
let cookie =
|
||||||
|
self.get_property_cookie(window, x::ATOM_WM_NORMAL_HINTS, x::ATOM_WM_SIZE_HINTS, 9);
|
||||||
|
let resolver = |reply: x::GetPropertyReply| {
|
||||||
|
let data: &[u32] = reply.value();
|
||||||
|
WmNormalHints::from(data)
|
||||||
|
};
|
||||||
|
|
||||||
|
PropertyCookieWrapper {
|
||||||
|
connection: &self.connection,
|
||||||
|
cookie,
|
||||||
|
resolver,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn handle_property_change(
|
fn handle_property_change(
|
||||||
&self,
|
&self,
|
||||||
event: x::PropertyNotifyEvent,
|
event: x::PropertyNotifyEvent,
|
||||||
|
|
@ -276,19 +494,12 @@ impl XState {
|
||||||
};
|
};
|
||||||
|
|
||||||
match event.atom() {
|
match event.atom() {
|
||||||
x if x == self.atoms.wm_hints => {
|
x if x == x::ATOM_WM_HINTS => {
|
||||||
let prop = get_prop(self.atoms.wm_hints, 9).unwrap();
|
let hints = self.get_wm_hints(window).resolve().unwrap();
|
||||||
let data: &[u32] = prop.value();
|
server_state.set_win_hints(window, hints);
|
||||||
let hints = WmHints::from(data);
|
|
||||||
debug!("wm hints: {hints:?}");
|
|
||||||
server_state.set_win_hints(event.window(), hints);
|
|
||||||
}
|
}
|
||||||
x if x == x::ATOM_WM_NORMAL_HINTS => {
|
x if x == x::ATOM_WM_NORMAL_HINTS => {
|
||||||
let Ok(prop) = get_prop(x::ATOM_WM_SIZE_HINTS, 9) else {
|
let hints = self.get_wm_size_hints(window).resolve().unwrap();
|
||||||
return;
|
|
||||||
};
|
|
||||||
let data: &[u32] = prop.value();
|
|
||||||
let hints = WmNormalHints::from(data);
|
|
||||||
server_state.set_size_hints(window, hints);
|
server_state.set_size_hints(window, hints);
|
||||||
}
|
}
|
||||||
x if x == x::ATOM_WM_NAME || x == self.atoms.net_wm_name => {
|
x if x == x::ATOM_WM_NAME || x == self.atoms.net_wm_name => {
|
||||||
|
|
@ -309,14 +520,8 @@ impl XState {
|
||||||
server_state.set_win_title(window, name);
|
server_state.set_win_title(window, name);
|
||||||
}
|
}
|
||||||
x if x == x::ATOM_WM_CLASS => {
|
x if x == x::ATOM_WM_CLASS => {
|
||||||
let prop = get_prop(x::ATOM_STRING, 256).unwrap();
|
let class = self.get_wm_class(window).resolve().unwrap();
|
||||||
let data: &[u8] = prop.value();
|
server_state.set_win_class(window, class);
|
||||||
// wm class is instance + class - ignore instance
|
|
||||||
let class_start = data.iter().copied().position(|b| b == 0u8).unwrap() + 1;
|
|
||||||
let data = data[class_start..].to_vec();
|
|
||||||
let class = CString::from_vec_with_nul(data).unwrap();
|
|
||||||
debug!("{:?} class: {class:?}", window);
|
|
||||||
server_state.set_win_class(window, class.to_string_lossy().to_string());
|
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
if log::log_enabled!(log::Level::Debug) {
|
if log::log_enabled!(log::Level::Debug) {
|
||||||
|
|
@ -342,7 +547,6 @@ xcb::atoms_struct! {
|
||||||
pub wm_protocols => b"WM_PROTOCOLS" only_if_exists = false,
|
pub wm_protocols => b"WM_PROTOCOLS" only_if_exists = false,
|
||||||
pub wm_delete_window => b"WM_DELETE_WINDOW" only_if_exists = false,
|
pub wm_delete_window => b"WM_DELETE_WINDOW" only_if_exists = false,
|
||||||
pub wm_transient_for => b"WM_TRANSIENT_FOR" only_if_exists = false,
|
pub wm_transient_for => b"WM_TRANSIENT_FOR" only_if_exists = false,
|
||||||
pub wm_hints => b"WM_HINTS" only_if_exists = false,
|
|
||||||
pub wm_check => b"_NET_SUPPORTING_WM_CHECK" only_if_exists = false,
|
pub wm_check => b"_NET_SUPPORTING_WM_CHECK" only_if_exists = false,
|
||||||
pub net_wm_name => b"_NET_WM_NAME" only_if_exists = false,
|
pub net_wm_name => b"_NET_WM_NAME" only_if_exists = false,
|
||||||
pub wm_pid => b"_NET_WM_PID" only_if_exists = false,
|
pub wm_pid => b"_NET_WM_PID" only_if_exists = false,
|
||||||
|
|
@ -506,8 +710,8 @@ impl super::XConnection for Arc<xcb::Connection> {
|
||||||
.wait_for_reply(self.send_request(&x::GetProperty {
|
.wait_for_reply(self.send_request(&x::GetProperty {
|
||||||
delete: false,
|
delete: false,
|
||||||
window,
|
window,
|
||||||
property: atoms.wm_hints,
|
property: x::ATOM_WM_HINTS,
|
||||||
r#type: atoms.wm_hints,
|
r#type: x::ATOM_WM_HINTS,
|
||||||
long_offset: 0,
|
long_offset: 0,
|
||||||
long_length: 8,
|
long_length: 8,
|
||||||
}))
|
}))
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ use wayland_protocols::xdg::shell::server::xdg_toplevel;
|
||||||
use wayland_server::Resource;
|
use wayland_server::Resource;
|
||||||
use xcb::{x, Xid};
|
use xcb::{x, Xid};
|
||||||
use xwayland_satellite as xwls;
|
use xwayland_satellite as xwls;
|
||||||
|
use xwayland_satellite::xstate::WmSizeHintsFlags;
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
struct TestDataInner {
|
struct TestDataInner {
|
||||||
|
|
@ -87,6 +88,7 @@ impl Fixture {
|
||||||
env_logger::builder()
|
env_logger::builder()
|
||||||
.is_test(true)
|
.is_test(true)
|
||||||
.filter_level(log::LevelFilter::Debug)
|
.filter_level(log::LevelFilter::Debug)
|
||||||
|
.parse_default_env()
|
||||||
.init();
|
.init();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -127,7 +129,7 @@ impl Fixture {
|
||||||
// Give Xwayland time to do its thing
|
// Give Xwayland time to do its thing
|
||||||
|
|
||||||
let mut ready = our_data.display.lock().unwrap().is_some();
|
let mut ready = our_data.display.lock().unwrap().is_some();
|
||||||
while !ready && start.elapsed() < Duration::from_millis(3000) {
|
while !ready && start.elapsed() < Duration::from_millis(2000) {
|
||||||
let n = poll(&mut f, 100).unwrap();
|
let n = poll(&mut f, 100).unwrap();
|
||||||
if n > 0 {
|
if n > 0 {
|
||||||
testwl.dispatch();
|
testwl.dispatch();
|
||||||
|
|
@ -162,58 +164,13 @@ impl Fixture {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_and_map_window(
|
fn configure_and_verify_new_toplevel(
|
||||||
&mut self,
|
&mut self,
|
||||||
connection: &xcb::Connection,
|
connection: &mut Connection,
|
||||||
override_redirect: bool,
|
window: x::Window,
|
||||||
x: i16,
|
surface: testwl::SurfaceId,
|
||||||
y: i16,
|
) {
|
||||||
width: u16,
|
let data = self.testwl.get_surface_data(surface).unwrap();
|
||||||
height: u16,
|
|
||||||
) -> (x::Window, testwl::SurfaceId) {
|
|
||||||
let screen = connection.get_setup().roots().next().unwrap();
|
|
||||||
let wid = connection.generate_id();
|
|
||||||
let req = x::CreateWindow {
|
|
||||||
depth: x::COPY_FROM_PARENT as _,
|
|
||||||
wid,
|
|
||||||
parent: screen.root(),
|
|
||||||
x,
|
|
||||||
y,
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
border_width: 0,
|
|
||||||
class: x::WindowClass::InputOutput,
|
|
||||||
visual: screen.root_visual(),
|
|
||||||
value_list: &[
|
|
||||||
x::Cw::BackPixel(screen.white_pixel()),
|
|
||||||
x::Cw::OverrideRedirect(override_redirect),
|
|
||||||
],
|
|
||||||
};
|
|
||||||
connection.send_and_check_request(&req).unwrap();
|
|
||||||
|
|
||||||
let req = x::MapWindow { window: wid };
|
|
||||||
connection.send_and_check_request(&req).unwrap();
|
|
||||||
self.wait_and_dispatch();
|
|
||||||
|
|
||||||
let id = self
|
|
||||||
.testwl
|
|
||||||
.last_created_surface_id()
|
|
||||||
.expect("No surface created for window");
|
|
||||||
|
|
||||||
(wid, id)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn create_toplevel(
|
|
||||||
&mut self,
|
|
||||||
connection: &xcb::Connection,
|
|
||||||
width: u16,
|
|
||||||
height: u16,
|
|
||||||
) -> (x::Window, testwl::SurfaceId) {
|
|
||||||
let (window, surface) = self.create_and_map_window(connection, false, 0, 0, width, height);
|
|
||||||
let data = self
|
|
||||||
.testwl
|
|
||||||
.get_surface_data(surface)
|
|
||||||
.expect("No surface data");
|
|
||||||
assert!(
|
assert!(
|
||||||
matches!(data.role, Some(testwl::SurfaceRole::Toplevel(_))),
|
matches!(data.role, Some(testwl::SurfaceRole::Toplevel(_))),
|
||||||
"surface role was wrong: {:?}",
|
"surface role was wrong: {:?}",
|
||||||
|
|
@ -233,8 +190,6 @@ impl Fixture {
|
||||||
assert_eq!(geometry.y(), 0);
|
assert_eq!(geometry.y(), 0);
|
||||||
assert_eq!(geometry.width(), 100);
|
assert_eq!(geometry.width(), 100);
|
||||||
assert_eq!(geometry.height(), 100);
|
assert_eq!(geometry.height(), 100);
|
||||||
|
|
||||||
(window, surface)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Triggers a Wayland side toplevel Close event and processes the corresponding
|
/// Triggers a Wayland side toplevel Close event and processes the corresponding
|
||||||
|
|
@ -273,8 +228,6 @@ xcb::atoms_struct! {
|
||||||
struct Atoms {
|
struct Atoms {
|
||||||
wm_protocols => b"WM_PROTOCOLS",
|
wm_protocols => b"WM_PROTOCOLS",
|
||||||
wm_delete_window => b"WM_DELETE_WINDOW",
|
wm_delete_window => b"WM_DELETE_WINDOW",
|
||||||
wm_class => b"WM_CLASS",
|
|
||||||
wm_name => b"WM_NAME",
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -282,6 +235,8 @@ struct Connection {
|
||||||
inner: xcb::Connection,
|
inner: xcb::Connection,
|
||||||
pollfd: PollFd<'static>,
|
pollfd: PollFd<'static>,
|
||||||
atoms: Atoms,
|
atoms: Atoms,
|
||||||
|
root: x::Window,
|
||||||
|
visual: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::ops::Deref for Connection {
|
impl std::ops::Deref for Connection {
|
||||||
|
|
@ -297,14 +252,71 @@ impl Connection {
|
||||||
let fd = unsafe { BorrowedFd::borrow_raw(inner.as_raw_fd()) };
|
let fd = unsafe { BorrowedFd::borrow_raw(inner.as_raw_fd()) };
|
||||||
let pollfd = PollFd::from_borrowed_fd(fd, PollFlags::IN);
|
let pollfd = PollFd::from_borrowed_fd(fd, PollFlags::IN);
|
||||||
let atoms = Atoms::intern_all(&inner).unwrap();
|
let atoms = Atoms::intern_all(&inner).unwrap();
|
||||||
|
let screen = inner.get_setup().roots().next().unwrap();
|
||||||
|
let root = screen.root();
|
||||||
|
let visual = screen.root_visual();
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
inner,
|
inner,
|
||||||
pollfd,
|
pollfd,
|
||||||
atoms,
|
atoms,
|
||||||
|
root,
|
||||||
|
visual,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn new_window(
|
||||||
|
&self,
|
||||||
|
parent: x::Window,
|
||||||
|
x: i16,
|
||||||
|
y: i16,
|
||||||
|
width: u16,
|
||||||
|
height: u16,
|
||||||
|
override_redirect: bool,
|
||||||
|
) -> x::Window {
|
||||||
|
let wid = self.inner.generate_id();
|
||||||
|
let req = x::CreateWindow {
|
||||||
|
depth: 0,
|
||||||
|
wid,
|
||||||
|
parent,
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
border_width: 0,
|
||||||
|
class: x::WindowClass::InputOutput,
|
||||||
|
visual: self.visual,
|
||||||
|
value_list: &[x::Cw::OverrideRedirect(override_redirect)],
|
||||||
|
};
|
||||||
|
self.inner
|
||||||
|
.send_and_check_request(&req)
|
||||||
|
.expect("creating window failed");
|
||||||
|
|
||||||
|
wid
|
||||||
|
}
|
||||||
|
|
||||||
|
fn map_window(&self, window: x::Window) {
|
||||||
|
self.send_and_check_request(&x::MapWindow { window })
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_property<P: x::PropEl>(
|
||||||
|
&self,
|
||||||
|
window: x::Window,
|
||||||
|
r#type: x::Atom,
|
||||||
|
property: x::Atom,
|
||||||
|
data: &[P],
|
||||||
|
) {
|
||||||
|
self.send_and_check_request(&x::ChangeProperty {
|
||||||
|
mode: x::PropMode::Replace,
|
||||||
|
window,
|
||||||
|
r#type,
|
||||||
|
property,
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
fn await_event(&mut self) {
|
fn await_event(&mut self) {
|
||||||
assert!(
|
assert!(
|
||||||
|
|
@ -318,39 +330,85 @@ impl Connection {
|
||||||
fn toplevel_flow() {
|
fn toplevel_flow() {
|
||||||
let mut f = Fixture::new();
|
let mut f = Fixture::new();
|
||||||
let mut connection = Connection::new(&f.display);
|
let mut connection = Connection::new(&f.display);
|
||||||
let (window, surface) = f.create_toplevel(&connection.inner, 200, 200);
|
let window = connection.new_window(connection.root, 0, 0, 200, 200, false);
|
||||||
|
|
||||||
connection
|
// Pre-map properties
|
||||||
.inner
|
connection.set_property(
|
||||||
.send_and_check_request(&x::ChangeProperty {
|
|
||||||
mode: x::PropMode::Replace,
|
|
||||||
window,
|
window,
|
||||||
r#type: x::ATOM_STRING,
|
x::ATOM_STRING,
|
||||||
property: connection.atoms.wm_name,
|
x::ATOM_WM_NAME,
|
||||||
data: c"window".to_bytes(),
|
c"window".to_bytes(),
|
||||||
})
|
);
|
||||||
.unwrap();
|
connection.set_property(
|
||||||
|
|
||||||
connection
|
|
||||||
.inner
|
|
||||||
.send_and_check_request(&x::ChangeProperty {
|
|
||||||
mode: x::PropMode::Replace,
|
|
||||||
window,
|
window,
|
||||||
r#type: x::ATOM_STRING,
|
x::ATOM_STRING,
|
||||||
property: connection.atoms.wm_class,
|
x::ATOM_WM_CLASS,
|
||||||
data: &[
|
&[
|
||||||
c"instance".to_bytes_with_nul(),
|
c"instance".to_bytes_with_nul(),
|
||||||
c"class".to_bytes_with_nul(),
|
c"class".to_bytes_with_nul(),
|
||||||
]
|
]
|
||||||
.concat(),
|
.concat(),
|
||||||
})
|
);
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
|
let flags = (WmSizeHintsFlags::ProgramMaxSize | WmSizeHintsFlags::ProgramMinSize).bits();
|
||||||
|
connection.set_property(
|
||||||
|
window,
|
||||||
|
x::ATOM_WM_SIZE_HINTS,
|
||||||
|
x::ATOM_WM_NORMAL_HINTS,
|
||||||
|
&[flags, 0, 0, 0, 0, 50, 100, 300, 400],
|
||||||
|
);
|
||||||
|
connection.map_window(window);
|
||||||
f.wait_and_dispatch();
|
f.wait_and_dispatch();
|
||||||
|
|
||||||
|
let surface = f
|
||||||
|
.testwl
|
||||||
|
.last_created_surface_id()
|
||||||
|
.expect("No surface created!");
|
||||||
|
f.configure_and_verify_new_toplevel(&mut connection, window, surface);
|
||||||
|
|
||||||
let data = f.testwl.get_surface_data(surface).unwrap();
|
let data = f.testwl.get_surface_data(surface).unwrap();
|
||||||
assert_eq!(data.toplevel().title, Some("window".into()));
|
assert_eq!(data.toplevel().title, Some("window".into()));
|
||||||
assert_eq!(data.toplevel().app_id, Some("class".into()));
|
assert_eq!(data.toplevel().app_id, Some("class".into()));
|
||||||
|
assert_eq!(
|
||||||
|
data.toplevel().min_size,
|
||||||
|
Some(testwl::Vec2 { x: 50, y: 100 })
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
data.toplevel().max_size,
|
||||||
|
Some(testwl::Vec2 { x: 300, y: 400 })
|
||||||
|
);
|
||||||
|
|
||||||
|
// Post map properties
|
||||||
|
connection.set_property(
|
||||||
|
window,
|
||||||
|
x::ATOM_STRING,
|
||||||
|
x::ATOM_WM_NAME,
|
||||||
|
c"bindow".to_bytes(),
|
||||||
|
);
|
||||||
|
connection.set_property(
|
||||||
|
window,
|
||||||
|
x::ATOM_STRING,
|
||||||
|
x::ATOM_WM_CLASS,
|
||||||
|
&[c"f".to_bytes_with_nul(), c"ssalc".to_bytes_with_nul()].concat(),
|
||||||
|
);
|
||||||
|
connection.set_property(
|
||||||
|
window,
|
||||||
|
x::ATOM_WM_SIZE_HINTS,
|
||||||
|
x::ATOM_WM_NORMAL_HINTS,
|
||||||
|
&[flags, 1, 2, 3, 4, 25, 50, 150, 200],
|
||||||
|
);
|
||||||
|
f.wait_and_dispatch();
|
||||||
|
let data = f.testwl.get_surface_data(surface).unwrap();
|
||||||
|
assert_eq!(data.toplevel().title, Some("bindow".into()));
|
||||||
|
assert_eq!(data.toplevel().app_id, Some("ssalc".into()));
|
||||||
|
assert_eq!(
|
||||||
|
data.toplevel().min_size,
|
||||||
|
Some(testwl::Vec2 { x: 25, y: 50 })
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
data.toplevel().max_size,
|
||||||
|
Some(testwl::Vec2 { x: 150, y: 200 })
|
||||||
|
);
|
||||||
|
|
||||||
f.close_toplevel(&mut connection, window, surface);
|
f.close_toplevel(&mut connection, window, surface);
|
||||||
|
|
||||||
|
|
@ -361,3 +419,27 @@ fn toplevel_flow() {
|
||||||
let data = f.testwl.get_surface_data(surface).expect("No surface data");
|
let data = f.testwl.get_surface_data(surface).expect("No surface data");
|
||||||
assert!(!data.toplevel().toplevel.is_alive());
|
assert!(!data.toplevel().toplevel.is_alive());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn reparent() {
|
||||||
|
let mut f = Fixture::new();
|
||||||
|
let connection = Connection::new(&f.display);
|
||||||
|
|
||||||
|
let parent = connection.new_window(connection.root, 0, 0, 1, 1, false);
|
||||||
|
let child = connection.new_window(parent, 0, 0, 20, 20, false);
|
||||||
|
|
||||||
|
connection
|
||||||
|
.send_and_check_request(&x::ReparentWindow {
|
||||||
|
window: child,
|
||||||
|
parent: connection.root,
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
connection
|
||||||
|
.send_and_check_request(&x::MapWindow { window: child })
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
f.wait_and_dispatch();
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue