Add initial support for toplevel titles and app ids
Should work with most app titles, but for some reason some app ids have the first letter capitalized (Remmina) and some windows don't get the class/title set at all (xterm) Part of #9
This commit is contained in:
parent
e70cb81751
commit
7976e3ad37
5 changed files with 337 additions and 99 deletions
|
|
@ -7,7 +7,7 @@ mod tests;
|
|||
use self::event::*;
|
||||
use super::FromServerState;
|
||||
use crate::clientside::*;
|
||||
use crate::xstate::{Atoms, WindowDims, WmNormalHints};
|
||||
use crate::xstate::{Atoms, WindowDims, WmHints, WmName, WmNormalHints};
|
||||
use crate::XConnection;
|
||||
use log::{debug, warn};
|
||||
use rustix::event::{poll, PollFd, PollFlags};
|
||||
|
|
@ -76,8 +76,11 @@ struct WindowData {
|
|||
surface_id: u32,
|
||||
popup_for: Option<x::Window>,
|
||||
dims: WindowDims,
|
||||
hints: Option<WmNormalHints>,
|
||||
size_hints: Option<WmNormalHints>,
|
||||
override_redirect: bool,
|
||||
title: Option<WmName>,
|
||||
class: Option<String>,
|
||||
group: Option<x::Window>,
|
||||
}
|
||||
|
||||
impl WindowData {
|
||||
|
|
@ -94,8 +97,11 @@ impl WindowData {
|
|||
popup_for: parent,
|
||||
surface_id: 0,
|
||||
dims,
|
||||
hints: None,
|
||||
size_hints: None,
|
||||
override_redirect,
|
||||
title: None,
|
||||
class: None,
|
||||
group: None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -482,10 +488,54 @@ impl<C: XConnection> ServerState<C> {
|
|||
);
|
||||
}
|
||||
|
||||
pub fn set_win_hints(&mut self, window: x::Window, hints: WmNormalHints) {
|
||||
pub fn set_win_title(&mut self, window: x::Window, name: WmName) {
|
||||
let win = self.windows.get_mut(&window).unwrap();
|
||||
|
||||
if win.hints.is_none() || *win.hints.as_ref().unwrap() != hints {
|
||||
let new_title = match &mut win.title {
|
||||
Some(w) => {
|
||||
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");
|
||||
None
|
||||
} else {
|
||||
*w = name;
|
||||
Some(w)
|
||||
}
|
||||
}
|
||||
None => Some(win.title.insert(name)),
|
||||
};
|
||||
|
||||
let Some(title) = new_title else {
|
||||
return;
|
||||
};
|
||||
if let Some(key) = win.surface_key {
|
||||
let surface: &SurfaceData = self.objects[key].as_ref();
|
||||
if let Some(SurfaceRole::Toplevel(Some(data))) = &surface.role {
|
||||
data.toplevel.set_title(title.name().to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_win_class(&mut self, window: x::Window, class: String) {
|
||||
let win = self.windows.get_mut(&window).unwrap();
|
||||
|
||||
let class = win.class.insert(class);
|
||||
if let Some(key) = win.surface_key {
|
||||
let surface: &SurfaceData = self.objects[key].as_ref();
|
||||
if let Some(SurfaceRole::Toplevel(Some(data))) = &surface.role {
|
||||
data.toplevel.set_app_id(class.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_win_hints(&mut self, window: x::Window, hints: WmHints) {
|
||||
let win = self.windows.get_mut(&window).unwrap();
|
||||
win.group = hints.window_group;
|
||||
}
|
||||
|
||||
pub fn set_size_hints(&mut self, window: x::Window, hints: WmNormalHints) {
|
||||
let win = self.windows.get_mut(&window).unwrap();
|
||||
|
||||
if win.size_hints.is_none() || *win.size_hints.as_ref().unwrap() != hints {
|
||||
debug!("setting {window:?} hints {hints:?}");
|
||||
if let Some(surface) = win.surface_key {
|
||||
let surface: &SurfaceData = self.objects[surface].as_ref();
|
||||
|
|
@ -498,7 +548,7 @@ impl<C: XConnection> ServerState<C> {
|
|||
}
|
||||
}
|
||||
}
|
||||
win.hints = Some(hints);
|
||||
win.size_hints = Some(hints);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -720,8 +770,9 @@ impl<C: XConnection> ServerState<C> {
|
|||
xdg: XdgSurface,
|
||||
) -> ToplevelData {
|
||||
debug!("creating toplevel for {:?}", window.window);
|
||||
|
||||
let toplevel = xdg.get_toplevel(&self.qh, surface_key);
|
||||
if let Some(hints) = &window.hints {
|
||||
if let Some(hints) = &window.size_hints {
|
||||
if let Some(min) = &hints.min_size {
|
||||
toplevel.set_min_size(min.width, min.height);
|
||||
}
|
||||
|
|
@ -730,6 +781,22 @@ impl<C: XConnection> ServerState<C> {
|
|||
}
|
||||
}
|
||||
|
||||
let group = window.group.and_then(|win| self.windows.get(&win));
|
||||
if let Some(class) = window
|
||||
.class
|
||||
.as_ref()
|
||||
.or(group.and_then(|g| g.class.as_ref()))
|
||||
{
|
||||
toplevel.set_app_id(class.to_string());
|
||||
}
|
||||
if let Some(title) = window
|
||||
.title
|
||||
.as_ref()
|
||||
.or(group.and_then(|g| g.title.as_ref()))
|
||||
{
|
||||
toplevel.set_title(title.name().to_string());
|
||||
}
|
||||
|
||||
ToplevelData {
|
||||
xdg: XdgSurfaceData {
|
||||
surface: xdg,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
use super::{ServerState, WindowDims};
|
||||
use crate::xstate::SetState;
|
||||
use crate::xstate::{SetState, WmName};
|
||||
use paste::paste;
|
||||
use rustix::event::{poll, PollFd, PollFlags};
|
||||
use std::collections::HashMap;
|
||||
|
|
@ -354,7 +354,7 @@ impl TestFixture {
|
|||
width: 50,
|
||||
height: 50,
|
||||
},
|
||||
fullscreen: false
|
||||
fullscreen: false,
|
||||
};
|
||||
|
||||
let dims = data.dims;
|
||||
|
|
@ -393,7 +393,7 @@ impl TestFixture {
|
|||
width: 50,
|
||||
height: 50,
|
||||
},
|
||||
fullscreen: false
|
||||
fullscreen: false,
|
||||
};
|
||||
|
||||
let dims = data.dims;
|
||||
|
|
@ -468,7 +468,7 @@ impl TestFixture {
|
|||
width: 50,
|
||||
height: 50,
|
||||
},
|
||||
fullscreen: false
|
||||
fullscreen: false,
|
||||
};
|
||||
let dims = data.dims;
|
||||
self.register_window(window, data);
|
||||
|
|
@ -861,6 +861,87 @@ fn fullscreen() {
|
|||
.contains(&xdg_toplevel::State::Fullscreen));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn window_title_and_class() {
|
||||
let (mut f, comp) = TestFixture::new_with_compositor();
|
||||
let win = unsafe { Window::new(1) };
|
||||
let (_, id) = f.create_toplevel(&comp, win);
|
||||
|
||||
f.exwayland
|
||||
.set_win_title(win, WmName::WmName("window".into()));
|
||||
f.exwayland.set_win_class(win, "class".into());
|
||||
f.run();
|
||||
|
||||
let data = f.testwl.get_surface_data(id).unwrap();
|
||||
assert_eq!(data.toplevel().title, Some("window".into()));
|
||||
assert_eq!(data.toplevel().app_id, Some("class".into()));
|
||||
|
||||
f.exwayland
|
||||
.set_win_title(win, WmName::NetWmName("superwindow".into()));
|
||||
f.run();
|
||||
|
||||
let data = f.testwl.get_surface_data(id).unwrap();
|
||||
assert_eq!(data.toplevel().title, Some("superwindow".into()));
|
||||
|
||||
f.exwayland
|
||||
.set_win_title(win, WmName::WmName("shwindow".into()));
|
||||
f.run();
|
||||
|
||||
let data = f.testwl.get_surface_data(id).unwrap();
|
||||
assert_eq!(data.toplevel().title, Some("superwindow".into()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn window_group_properties() {
|
||||
let (mut f, comp) = TestFixture::new_with_compositor();
|
||||
let prop_win = unsafe { Window::new(1) };
|
||||
f.exwayland.new_window(
|
||||
prop_win,
|
||||
false,
|
||||
super::WindowDims {
|
||||
width: 1,
|
||||
height: 1,
|
||||
..Default::default()
|
||||
},
|
||||
None,
|
||||
);
|
||||
f.exwayland
|
||||
.set_win_title(prop_win, WmName::WmName("window".into()));
|
||||
f.exwayland.set_win_class(prop_win, "class".into());
|
||||
|
||||
let win = unsafe { Window::new(2) };
|
||||
let data = WindowData {
|
||||
mapped: true,
|
||||
dims: WindowDims {
|
||||
width: 50,
|
||||
height: 50,
|
||||
..Default::default()
|
||||
},
|
||||
fullscreen: false,
|
||||
};
|
||||
|
||||
let (_, surface) = comp.create_surface();
|
||||
let dims = data.dims;
|
||||
f.register_window(win, data);
|
||||
f.exwayland.new_window(win, false, dims, None);
|
||||
f.exwayland.set_win_hints(
|
||||
win,
|
||||
super::WmHints {
|
||||
window_group: Some(prop_win),
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
f.exwayland.map_window(win);
|
||||
f.exwayland
|
||||
.associate_window(win, surface.id().protocol_id());
|
||||
f.run();
|
||||
|
||||
let id = f.testwl.last_created_surface_id().unwrap();
|
||||
let data = f.testwl.get_surface_data(id).unwrap();
|
||||
|
||||
assert_eq!(data.toplevel().title, Some("window".into()));
|
||||
assert_eq!(data.toplevel().app_id, Some("class".into()));
|
||||
}
|
||||
/// See Pointer::handle_event for an explanation.
|
||||
#[test]
|
||||
fn popup_pointer_motion_workaround() {}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
use bitflags::bitflags;
|
||||
use log::{debug, trace, warn};
|
||||
use std::ffi::CString;
|
||||
use std::os::fd::{AsRawFd, BorrowedFd};
|
||||
use std::sync::Arc;
|
||||
use xcb::{x, Xid, XidNew};
|
||||
|
|
@ -9,7 +10,21 @@ pub struct XState {
|
|||
pub connection: Arc<xcb::Connection>,
|
||||
root: x::Window,
|
||||
pub atoms: Atoms,
|
||||
window_types: WindowTypes,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum WmName {
|
||||
WmName(String),
|
||||
NetWmName(String),
|
||||
}
|
||||
|
||||
impl WmName {
|
||||
pub fn name(&self) -> &str {
|
||||
match self {
|
||||
Self::WmName(n) => n,
|
||||
Self::NetWmName(n) => n,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl XState {
|
||||
|
|
@ -32,7 +47,6 @@ impl XState {
|
|||
|
||||
let atoms = Atoms::intern_all(&connection).unwrap();
|
||||
trace!("atoms: {atoms:#?}");
|
||||
let window_types = WindowTypes::new(&connection);
|
||||
|
||||
// This makes Xwayland spit out damage tracking
|
||||
connection
|
||||
|
|
@ -56,7 +70,6 @@ impl XState {
|
|||
connection,
|
||||
root,
|
||||
atoms,
|
||||
window_types,
|
||||
};
|
||||
r.create_ewmh_window();
|
||||
r
|
||||
|
|
@ -97,7 +110,7 @@ impl XState {
|
|||
self.set_root_property(
|
||||
self.atoms.supported,
|
||||
x::ATOM_ATOM,
|
||||
&[self.atoms.active_win, self.atoms.client_list],
|
||||
&[self.atoms.active_win],
|
||||
);
|
||||
|
||||
self.connection
|
||||
|
|
@ -114,7 +127,7 @@ impl XState {
|
|||
.send_and_check_request(&x::ChangeProperty {
|
||||
mode: x::PropMode::Replace,
|
||||
window,
|
||||
property: self.atoms.wm_name,
|
||||
property: self.atoms.net_wm_name,
|
||||
r#type: x::ATOM_STRING,
|
||||
data: b"exwayland wm",
|
||||
})
|
||||
|
|
@ -244,10 +257,16 @@ impl XState {
|
|||
event: x::PropertyNotifyEvent,
|
||||
server_state: &mut super::RealServerState,
|
||||
) {
|
||||
if event.state() != x::Property::NewValue {
|
||||
println!("ignoring non newvalue for property {:?}", event.atom());
|
||||
return;
|
||||
}
|
||||
|
||||
let window = event.window();
|
||||
let get_prop = |r#type, long_length| {
|
||||
self.connection
|
||||
.wait_for_reply(self.connection.send_request(&x::GetProperty {
|
||||
window: event.window(),
|
||||
window,
|
||||
property: event.atom(),
|
||||
r#type,
|
||||
long_offset: 0,
|
||||
|
|
@ -255,23 +274,14 @@ impl XState {
|
|||
delete: false,
|
||||
}))
|
||||
};
|
||||
if event.state() != x::Property::NewValue {
|
||||
return;
|
||||
}
|
||||
|
||||
match event.atom() {
|
||||
x if x == self.atoms.wm_window_type => {
|
||||
let Ok(prop) = get_prop(x::ATOM_ATOM, 8) else {
|
||||
return;
|
||||
};
|
||||
let types: &[x::Atom] = prop.value();
|
||||
let win_type = types.iter().find_map(|a| self.window_types.get_type(*a));
|
||||
debug!(
|
||||
"set {:?} type to {} ({})",
|
||||
event.window(),
|
||||
win_type.unwrap_or("[Unknown/Unrecognized]".to_string()),
|
||||
types.len()
|
||||
);
|
||||
x if x == self.atoms.wm_hints => {
|
||||
let prop = get_prop(self.atoms.wm_hints, 9).unwrap();
|
||||
let data: &[u32] = prop.value();
|
||||
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 => {
|
||||
let Ok(prop) = get_prop(x::ATOM_WM_SIZE_HINTS, 9) else {
|
||||
|
|
@ -279,9 +289,37 @@ impl XState {
|
|||
};
|
||||
let data: &[u32] = prop.value();
|
||||
let hints = WmNormalHints::from(data);
|
||||
server_state.set_win_hints(event.window(), hints);
|
||||
server_state.set_size_hints(window, hints);
|
||||
}
|
||||
x if x == x::ATOM_WM_NAME || x == self.atoms.net_wm_name => {
|
||||
let ty = if x == x::ATOM_WM_NAME {
|
||||
x::ATOM_STRING
|
||||
} else {
|
||||
self.atoms.utf8_string
|
||||
};
|
||||
let prop = get_prop(ty, 256).unwrap();
|
||||
let data: &[u8] = prop.value();
|
||||
let name = String::from_utf8(data.to_vec()).unwrap();
|
||||
debug!("{:?} named: {name}", window);
|
||||
let name = if x == x::ATOM_WM_NAME {
|
||||
WmName::WmName(name)
|
||||
} else {
|
||||
WmName::NetWmName(name)
|
||||
};
|
||||
server_state.set_win_title(window, name);
|
||||
}
|
||||
x if x == x::ATOM_WM_CLASS => {
|
||||
let prop = get_prop(x::ATOM_STRING, 256).unwrap();
|
||||
let data: &[u8] = prop.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);
|
||||
server_state.set_win_class(window, class.to_string_lossy().to_string());
|
||||
}
|
||||
_ => {
|
||||
if log::log_enabled!(log::Level::Debug) {
|
||||
let prop = self
|
||||
.connection
|
||||
.wait_for_reply(
|
||||
|
|
@ -290,11 +328,8 @@ impl XState {
|
|||
)
|
||||
.unwrap();
|
||||
|
||||
debug!(
|
||||
"changed property {:?} for {:?}",
|
||||
prop.name(),
|
||||
event.window()
|
||||
);
|
||||
debug!("changed property {:?} for {:?}", prop.name(), window);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -309,14 +344,14 @@ xcb::atoms_struct! {
|
|||
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_name => b"_NET_WM_NAME" only_if_exists = false,
|
||||
pub wm_window_type => b"_NET_WM_WINDOW_TYPE" 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 net_wm_state => b"_NET_WM_STATE" only_if_exists = false,
|
||||
pub wm_fullscreen => b"_NET_WM_STATE_FULLSCREEN" only_if_exists = false,
|
||||
pub active_win => b"_NET_ACTIVE_WINDOW" only_if_exists = false,
|
||||
pub client_list => b"_NET_CLIENT_LIST" only_if_exists = false,
|
||||
pub supported => b"_NET_SUPPORTED" only_if_exists = false,
|
||||
pub utf8_string => b"UTF8_STRING" only_if_exists = false,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -330,25 +365,6 @@ xcb::atoms_struct! {
|
|||
}
|
||||
}
|
||||
|
||||
impl WindowTypes {
|
||||
pub fn new(connection: &xcb::Connection) -> Self {
|
||||
let r = Self::intern_all(connection).unwrap();
|
||||
assert_ne!(r.normal, x::ATOM_NONE);
|
||||
assert_ne!(r.dialog, x::ATOM_NONE);
|
||||
assert_ne!(r.utility, x::ATOM_NONE);
|
||||
r
|
||||
}
|
||||
pub fn get_type(&self, atom: x::Atom) -> Option<String> {
|
||||
match atom {
|
||||
x if x == self.normal => Some("Normal".to_string()),
|
||||
x if x == self.dialog => Some("Dialog".to_string()),
|
||||
x if x == self.utility => Some("Utility".to_string()),
|
||||
x if x == self.menu => Some("Menu".to_string()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
pub struct WindowDims {
|
||||
pub x: i16,
|
||||
|
|
@ -361,16 +377,16 @@ bitflags! {
|
|||
/// From ICCCM spec.
|
||||
/// https://tronche.com/gui/x/icccm/sec-4.html#s-4.1.2.3
|
||||
pub struct WmSizeHintsFlags: u32 {
|
||||
const UserPosition = 1;
|
||||
const UserSize = 2;
|
||||
const ProgramPosition = 4;
|
||||
const ProgramSize = 8;
|
||||
const ProgramMinSize = 16;
|
||||
const ProgramMaxSize = 32;
|
||||
const ProgramResizeIncrement = 64;
|
||||
const ProgramAspect = 128;
|
||||
const ProgramBaseSize = 256;
|
||||
const ProgramWinGravity = 512;
|
||||
}
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
/// https://tronche.com/gui/x/icccm/sec-4.html#s-4.1.2.4
|
||||
pub struct WmHintsFlags: u32 {
|
||||
const Input = 1;
|
||||
const WindowGroup = 64;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -386,6 +402,49 @@ pub struct WmNormalHints {
|
|||
pub max_size: Option<WinSize>,
|
||||
}
|
||||
|
||||
impl From<&[u32]> for WmNormalHints {
|
||||
fn from(value: &[u32]) -> Self {
|
||||
let mut ret = Self::default();
|
||||
let flags = WmSizeHintsFlags::from_bits_truncate(value[0]);
|
||||
|
||||
if flags.contains(WmSizeHintsFlags::ProgramMinSize) {
|
||||
ret.min_size = Some(WinSize {
|
||||
width: value[5] as _,
|
||||
height: value[6] as _,
|
||||
});
|
||||
}
|
||||
|
||||
if flags.contains(WmSizeHintsFlags::ProgramMaxSize) {
|
||||
ret.max_size = Some(WinSize {
|
||||
width: value[7] as _,
|
||||
height: value[8] as _,
|
||||
});
|
||||
}
|
||||
|
||||
ret
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, PartialEq, Eq)]
|
||||
pub struct WmHints {
|
||||
pub input: Option<bool>,
|
||||
pub window_group: Option<x::Window>,
|
||||
}
|
||||
|
||||
impl From<&[u32]> for WmHints {
|
||||
fn from(value: &[u32]) -> Self {
|
||||
let mut ret = Self::default();
|
||||
let flags = WmHintsFlags::from_bits_truncate(value[0]);
|
||||
|
||||
if flags.contains(WmHintsFlags::WindowGroup) {
|
||||
let window = unsafe { x::Window::new(value[8]) };
|
||||
ret.window_group = Some(window);
|
||||
}
|
||||
|
||||
ret
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum SetState {
|
||||
Remove,
|
||||
|
|
@ -405,29 +464,6 @@ impl TryFrom<u32> for SetState {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<&[u32]> for WmNormalHints {
|
||||
fn from(value: &[u32]) -> Self {
|
||||
let mut ret = Self::default();
|
||||
let flags = WmSizeHintsFlags::from_bits(value[0]).unwrap();
|
||||
|
||||
if flags.contains(WmSizeHintsFlags::ProgramMinSize) {
|
||||
ret.min_size = Some(WinSize {
|
||||
width: value[5] as _,
|
||||
height: value[6] as _,
|
||||
});
|
||||
}
|
||||
|
||||
if flags.contains(WmSizeHintsFlags::ProgramMaxSize) {
|
||||
ret.max_size = Some(WinSize {
|
||||
width: value[7] as _,
|
||||
height: value[8] as _,
|
||||
});
|
||||
}
|
||||
|
||||
ret
|
||||
}
|
||||
}
|
||||
|
||||
impl super::XConnection for Arc<xcb::Connection> {
|
||||
type ExtraData = Atoms;
|
||||
|
||||
|
|
|
|||
|
|
@ -30,6 +30,8 @@ xcb::atoms_struct! {
|
|||
struct Atoms {
|
||||
wm_protocols => b"WM_PROTOCOLS",
|
||||
wm_delete_window => b"WM_DELETE_WINDOW",
|
||||
wm_class => b"WM_CLASS",
|
||||
wm_name => b"WM_NAME",
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -108,7 +110,7 @@ impl Fixture {
|
|||
}
|
||||
}
|
||||
|
||||
fn create_window(
|
||||
fn create_and_map_window(
|
||||
&mut self,
|
||||
connection: &xcb::Connection,
|
||||
override_redirect: bool,
|
||||
|
|
@ -155,7 +157,7 @@ impl Fixture {
|
|||
width: u16,
|
||||
height: u16,
|
||||
) -> (x::Window, testwl::SurfaceId) {
|
||||
let (window, surface) = self.create_window(connection, false, 0, 0, width, height);
|
||||
let (window, surface) = self.create_and_map_window(connection, false, 0, 0, width, height);
|
||||
let data = self
|
||||
.testwl
|
||||
.get_surface_data(surface)
|
||||
|
|
@ -258,6 +260,39 @@ fn toplevel_flow() {
|
|||
let mut f = Fixture::new();
|
||||
let mut connection = Connection::new();
|
||||
let (window, surface) = f.create_toplevel(&connection.inner, 200, 200);
|
||||
|
||||
connection
|
||||
.inner
|
||||
.send_and_check_request(&x::ChangeProperty {
|
||||
mode: x::PropMode::Replace,
|
||||
window,
|
||||
r#type: x::ATOM_STRING,
|
||||
property: connection.atoms.wm_name,
|
||||
data: c"window".to_bytes(),
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
connection
|
||||
.inner
|
||||
.send_and_check_request(&x::ChangeProperty {
|
||||
mode: x::PropMode::Replace,
|
||||
window,
|
||||
r#type: x::ATOM_STRING,
|
||||
property: connection.atoms.wm_class,
|
||||
data: &[
|
||||
c"instance".to_bytes_with_nul(),
|
||||
c"class".to_bytes_with_nul(),
|
||||
]
|
||||
.concat(),
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
f.wait_and_dispatch();
|
||||
|
||||
let data = f.testwl.get_surface_data(surface).unwrap();
|
||||
assert_eq!(data.toplevel().title, Some("window".into()));
|
||||
assert_eq!(data.toplevel().app_id, Some("class".into()));
|
||||
|
||||
f.close_toplevel(&mut connection, window, surface);
|
||||
|
||||
// Simulate killing client
|
||||
|
|
|
|||
|
|
@ -91,6 +91,8 @@ pub struct Toplevel {
|
|||
pub max_size: Option<Vec2>,
|
||||
pub states: Vec<xdg_toplevel::State>,
|
||||
pub closed: bool,
|
||||
pub title: Option<String>,
|
||||
pub app_id: Option<String>
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
|
|
@ -486,6 +488,21 @@ impl Dispatch<XdgToplevel, SurfaceId> for State {
|
|||
state.configure_toplevel(*surface_id, 100, 100, states);
|
||||
}
|
||||
xdg_toplevel::Request::Destroy => {}
|
||||
xdg_toplevel::Request::SetTitle { title } => {
|
||||
let data = state.surfaces.get_mut(surface_id).unwrap();
|
||||
let Some(SurfaceRole::Toplevel(toplevel)) = &mut data.role else {
|
||||
unreachable!();
|
||||
};
|
||||
toplevel.title = title.into();
|
||||
}
|
||||
xdg_toplevel::Request::SetAppId { app_id } => {
|
||||
let data = state.surfaces.get_mut(surface_id).unwrap();
|
||||
let Some(SurfaceRole::Toplevel(toplevel)) = &mut data.role else {
|
||||
unreachable!();
|
||||
};
|
||||
toplevel.app_id = app_id.into();
|
||||
|
||||
}
|
||||
other => todo!("unhandled request {other:?}"),
|
||||
}
|
||||
}
|
||||
|
|
@ -513,6 +530,8 @@ impl Dispatch<XdgSurface, SurfaceId> for State {
|
|||
max_size: None,
|
||||
states: Vec::new(),
|
||||
closed: false,
|
||||
title: None,
|
||||
app_id: None
|
||||
};
|
||||
let data = state.surfaces.get_mut(surface_id).unwrap();
|
||||
data.role = Some(SurfaceRole::Toplevel(t));
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue