server: auto-fullscreen windows that match an output's dimensions
Fixes #93
This commit is contained in:
parent
3944c9a0e4
commit
653391c7c9
4 changed files with 141 additions and 30 deletions
|
|
@ -1296,7 +1296,7 @@ impl<C: XConnection> GlobalDispatch<WlOutput, Global> for ServerState<C> {
|
||||||
data: &Global,
|
data: &Global,
|
||||||
data_init: &mut wayland_server::DataInit<'_, Self>,
|
data_init: &mut wayland_server::DataInit<'_, Self>,
|
||||||
) {
|
) {
|
||||||
state.objects.insert_with_key(|key| {
|
let key = state.objects.insert_with_key(|key| {
|
||||||
let server = data_init.init(resource, key);
|
let server = data_init.init(resource, key);
|
||||||
let client = state
|
let client = state
|
||||||
.clientside
|
.clientside
|
||||||
|
|
@ -1310,6 +1310,7 @@ impl<C: XConnection> GlobalDispatch<WlOutput, Global> for ServerState<C> {
|
||||||
);
|
);
|
||||||
Output::new(client, server).into()
|
Output::new(client, server).into()
|
||||||
});
|
});
|
||||||
|
state.output_keys.insert(key, ());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
global_dispatch_with_events!(WlDrmServer, WlDrmClient);
|
global_dispatch_with_events!(WlDrmServer, WlDrmClient);
|
||||||
|
|
|
||||||
|
|
@ -128,13 +128,12 @@ impl SurfaceData {
|
||||||
debug!("{} entered {}", self.server.id(), output.server.id());
|
debug!("{} entered {}", self.server.id(), output.server.id());
|
||||||
let windows = &mut state.windows;
|
let windows = &mut state.windows;
|
||||||
if let Some(win_data) = self.window.as_ref().and_then(|win| windows.get_mut(win)) {
|
if let Some(win_data) = self.window.as_ref().and_then(|win| windows.get_mut(win)) {
|
||||||
let (x, y) = match output.position {
|
|
||||||
OutputPosition::Xdg { x, y } => (x, y),
|
|
||||||
OutputPosition::Wl { x, y } => (x, y),
|
|
||||||
};
|
|
||||||
win_data.update_output_offset(
|
win_data.update_output_offset(
|
||||||
key,
|
key,
|
||||||
WindowOutputOffset { x, y },
|
WindowOutputOffset {
|
||||||
|
x: output.dimensions.x,
|
||||||
|
y: output.dimensions.y,
|
||||||
|
},
|
||||||
state.connection.as_mut().unwrap(),
|
state.connection.as_mut().unwrap(),
|
||||||
);
|
);
|
||||||
let window = win_data.window;
|
let window = win_data.window;
|
||||||
|
|
@ -656,9 +655,18 @@ pub struct XdgOutput {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone)]
|
#[derive(Copy, Clone)]
|
||||||
pub enum OutputPosition {
|
enum OutputDimensionsSource {
|
||||||
Wl { x: i32, y: i32 },
|
Wl,
|
||||||
Xdg { x: i32, y: i32 },
|
Xdg,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
|
pub(super) struct OutputDimensions {
|
||||||
|
source: OutputDimensionsSource,
|
||||||
|
x: i32,
|
||||||
|
y: i32,
|
||||||
|
pub width: i32,
|
||||||
|
pub height: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Output {
|
pub struct Output {
|
||||||
|
|
@ -666,7 +674,7 @@ pub struct Output {
|
||||||
pub server: WlOutput,
|
pub server: WlOutput,
|
||||||
pub xdg: Option<XdgOutput>,
|
pub xdg: Option<XdgOutput>,
|
||||||
windows: HashSet<x::Window>,
|
windows: HashSet<x::Window>,
|
||||||
position: OutputPosition,
|
pub(super) dimensions: OutputDimensions,
|
||||||
name: String,
|
name: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -677,7 +685,13 @@ impl Output {
|
||||||
server,
|
server,
|
||||||
xdg: None,
|
xdg: None,
|
||||||
windows: HashSet::new(),
|
windows: HashSet::new(),
|
||||||
position: OutputPosition::Wl { x: 0, y: 0 },
|
dimensions: OutputDimensions {
|
||||||
|
source: OutputDimensionsSource::Wl,
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
width: 0,
|
||||||
|
height: 0,
|
||||||
|
},
|
||||||
name: "<unknown>".to_string(),
|
name: "<unknown>".to_string(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -715,19 +729,23 @@ impl HandleEvent for Output {
|
||||||
impl Output {
|
impl Output {
|
||||||
fn update_offset<C: XConnection>(
|
fn update_offset<C: XConnection>(
|
||||||
&mut self,
|
&mut self,
|
||||||
offset: OutputPosition,
|
source: OutputDimensionsSource,
|
||||||
|
x: i32,
|
||||||
|
y: i32,
|
||||||
state: &mut ServerState<C>,
|
state: &mut ServerState<C>,
|
||||||
) {
|
) {
|
||||||
if matches!(offset, OutputPosition::Wl { .. })
|
if matches!(source, OutputDimensionsSource::Wl)
|
||||||
&& matches!(self.position, OutputPosition::Xdg { .. })
|
&& matches!(self.dimensions.source, OutputDimensionsSource::Xdg)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.position = offset;
|
self.dimensions.source = source;
|
||||||
let (x, y, id) = match offset {
|
self.dimensions.x = x;
|
||||||
OutputPosition::Xdg { x, y } => (x, y, self.xdg.as_ref().unwrap().server.id()),
|
self.dimensions.y = y;
|
||||||
OutputPosition::Wl { x, y } => (x, y, self.server.id()),
|
let id = match source {
|
||||||
|
OutputDimensionsSource::Xdg => self.xdg.as_ref().unwrap().server.id(),
|
||||||
|
OutputDimensionsSource::Wl => self.server.id(),
|
||||||
};
|
};
|
||||||
debug!("moving {id} to {x}x{y}");
|
debug!("moving {id} to {x}x{y}");
|
||||||
self.windows.retain(|window| {
|
self.windows.retain(|window| {
|
||||||
|
|
@ -750,7 +768,15 @@ impl Output {
|
||||||
state: &mut ServerState<C>,
|
state: &mut ServerState<C>,
|
||||||
) {
|
) {
|
||||||
if let client::wl_output::Event::Geometry { x, y, .. } = event {
|
if let client::wl_output::Event::Geometry { x, y, .. } = event {
|
||||||
self.update_offset(OutputPosition::Wl { x, y }, state);
|
self.update_offset(OutputDimensionsSource::Wl, x, y, state);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let client::wl_output::Event::Mode { width, height, .. } = event {
|
||||||
|
if matches!(self.dimensions.source, OutputDimensionsSource::Wl) {
|
||||||
|
self.dimensions.width = width;
|
||||||
|
self.dimensions.height = height;
|
||||||
|
debug!("{} dimensions: {width}x{height} (wl)", self.server.id());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
simple_event_shunt! {
|
simple_event_shunt! {
|
||||||
|
|
@ -790,9 +816,15 @@ impl Output {
|
||||||
state: &mut ServerState<C>,
|
state: &mut ServerState<C>,
|
||||||
) {
|
) {
|
||||||
if let zxdg_output_v1::Event::LogicalPosition { x, y } = event {
|
if let zxdg_output_v1::Event::LogicalPosition { x, y } = event {
|
||||||
self.update_offset(OutputPosition::Xdg { x, y }, state);
|
self.update_offset(OutputDimensionsSource::Xdg, x, y, state);
|
||||||
}
|
}
|
||||||
let xdg = &self.xdg.as_ref().unwrap().server;
|
let xdg = &self.xdg.as_ref().unwrap().server;
|
||||||
|
if let zxdg_output_v1::Event::LogicalSize { width, height } = event {
|
||||||
|
self.dimensions.source = OutputDimensionsSource::Xdg;
|
||||||
|
self.dimensions.width = width;
|
||||||
|
self.dimensions.height = height;
|
||||||
|
debug!("{} dimensions: {width}x{height} (xdg)", self.server.id());
|
||||||
|
}
|
||||||
simple_event_shunt! {
|
simple_event_shunt! {
|
||||||
xdg, event: zxdg_output_v1::Event => [
|
xdg, event: zxdg_output_v1::Event => [
|
||||||
LogicalPosition { x, y },
|
LogicalPosition { x, y },
|
||||||
|
|
|
||||||
|
|
@ -480,6 +480,7 @@ pub struct ServerState<C: XConnection> {
|
||||||
clientside: ClientState,
|
clientside: ClientState,
|
||||||
objects: ObjectMap,
|
objects: ObjectMap,
|
||||||
associated_windows: SparseSecondaryMap<ObjectKey, x::Window>,
|
associated_windows: SparseSecondaryMap<ObjectKey, x::Window>,
|
||||||
|
output_keys: SparseSecondaryMap<ObjectKey, ()>,
|
||||||
windows: HashMap<x::Window, WindowData>,
|
windows: HashMap<x::Window, WindowData>,
|
||||||
|
|
||||||
qh: ClientQueueHandle,
|
qh: ClientQueueHandle,
|
||||||
|
|
@ -539,6 +540,7 @@ impl<C: XConnection> ServerState<C> {
|
||||||
last_hovered: None,
|
last_hovered: None,
|
||||||
connection: None,
|
connection: None,
|
||||||
objects: Default::default(),
|
objects: Default::default(),
|
||||||
|
output_keys: Default::default(),
|
||||||
associated_windows: Default::default(),
|
associated_windows: Default::default(),
|
||||||
xdg_wm_base,
|
xdg_wm_base,
|
||||||
clipboard_data,
|
clipboard_data,
|
||||||
|
|
@ -922,15 +924,20 @@ impl<C: XConnection> ServerState<C> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_role_window(&mut self, window: x::Window, surface_key: ObjectKey) {
|
fn create_role_window(&mut self, window: x::Window, surface_key: ObjectKey) {
|
||||||
let surface: &mut SurfaceData = self.objects[surface_key].as_mut();
|
// Temporarily remove surface to placate borrow checker
|
||||||
|
let mut surface: SurfaceData = self.objects[surface_key]
|
||||||
|
.0
|
||||||
|
.take()
|
||||||
|
.unwrap()
|
||||||
|
.try_into()
|
||||||
|
.unwrap();
|
||||||
surface.window = Some(window);
|
surface.window = Some(window);
|
||||||
let client = &surface.client;
|
surface.client.attach(None, 0, 0);
|
||||||
client.attach(None, 0, 0);
|
surface.client.commit();
|
||||||
client.commit();
|
|
||||||
|
|
||||||
let xdg_surface = self
|
let xdg_surface = self
|
||||||
.xdg_wm_base
|
.xdg_wm_base
|
||||||
.get_xdg_surface(client, &self.qh, surface_key);
|
.get_xdg_surface(&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.attrs.override_redirect {
|
if window_data.attrs.override_redirect {
|
||||||
|
|
@ -941,15 +948,27 @@ impl<C: XConnection> ServerState<C> {
|
||||||
window_data.attrs.popup_for = Some(win);
|
window_data.attrs.popup_for = Some(win);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let mut fullscreen = false;
|
||||||
|
let (width, height) = (window_data.attrs.dims.width, window_data.attrs.dims.height);
|
||||||
|
for (key, _) in &self.output_keys {
|
||||||
|
let output: &Output = self.objects[key].as_ref();
|
||||||
|
if output.dimensions.width == width as i32 && output.dimensions.height == height as i32
|
||||||
|
{
|
||||||
|
fullscreen = true;
|
||||||
|
window_data.attrs.popup_for = None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let surface_id = surface.client.id();
|
||||||
|
// Reinsert surface
|
||||||
|
self.objects[surface_key].0 = Some(surface.into());
|
||||||
let window = self.windows.get(&window).unwrap();
|
let window = self.windows.get(&window).unwrap();
|
||||||
|
|
||||||
let role = if let Some(parent) = window.attrs.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, window.attrs.dims, surface_id
|
||||||
parent,
|
|
||||||
window.attrs.dims,
|
|
||||||
client.id()
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let parent_window = self.windows.get(&parent).unwrap();
|
let parent_window = self.windows.get(&parent).unwrap();
|
||||||
|
|
@ -988,7 +1007,7 @@ impl<C: XConnection> ServerState<C> {
|
||||||
};
|
};
|
||||||
SurfaceRole::Popup(Some(popup))
|
SurfaceRole::Popup(Some(popup))
|
||||||
} else {
|
} else {
|
||||||
let data = self.create_toplevel(window, surface_key, xdg_surface);
|
let data = self.create_toplevel(window, surface_key, xdg_surface, fullscreen);
|
||||||
SurfaceRole::Toplevel(Some(data))
|
SurfaceRole::Toplevel(Some(data))
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -1013,6 +1032,7 @@ impl<C: XConnection> ServerState<C> {
|
||||||
window: &WindowData,
|
window: &WindowData,
|
||||||
surface_key: ObjectKey,
|
surface_key: ObjectKey,
|
||||||
xdg: XdgSurface,
|
xdg: XdgSurface,
|
||||||
|
fullscreen: bool,
|
||||||
) -> ToplevelData {
|
) -> ToplevelData {
|
||||||
debug!("creating toplevel for {:?}", window.window);
|
debug!("creating toplevel for {:?}", window.window);
|
||||||
|
|
||||||
|
|
@ -1044,6 +1064,10 @@ impl<C: XConnection> ServerState<C> {
|
||||||
toplevel.set_title(title.name().to_string());
|
toplevel.set_title(title.name().to_string());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if fullscreen {
|
||||||
|
toplevel.set_fullscreen(None);
|
||||||
|
}
|
||||||
|
|
||||||
ToplevelData {
|
ToplevelData {
|
||||||
xdg: XdgSurfaceData {
|
xdg: XdgSurfaceData {
|
||||||
surface: xdg,
|
surface: xdg,
|
||||||
|
|
|
||||||
|
|
@ -1651,6 +1651,60 @@ fn tablet_smoke_test() {
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn fullscreen_heuristic() {
|
||||||
|
let (mut f, comp) = TestFixture::new_with_compositor();
|
||||||
|
let (_, output) = f.new_output(0, 0);
|
||||||
|
|
||||||
|
let window1 = unsafe { Window::new(1) };
|
||||||
|
let (_, id) = f.create_toplevel(&comp, window1);
|
||||||
|
f.testwl.move_surface_to_output(id, &output);
|
||||||
|
f.run();
|
||||||
|
|
||||||
|
let mut check_fullscreen = |id, override_redirect| {
|
||||||
|
let window = unsafe { Window::new(id) };
|
||||||
|
let (buffer, surface) = comp.create_surface();
|
||||||
|
let data = WindowData {
|
||||||
|
mapped: true,
|
||||||
|
dims: WindowDims {
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
// Outputs default to 1000x1000 in testwl
|
||||||
|
width: 1000,
|
||||||
|
height: 1000,
|
||||||
|
},
|
||||||
|
fullscreen: false,
|
||||||
|
};
|
||||||
|
f.new_window(window, override_redirect, data, None);
|
||||||
|
f.map_window(&comp, window, &surface.obj, &buffer);
|
||||||
|
f.run();
|
||||||
|
let id = f.check_new_surface();
|
||||||
|
let surface_data = f.testwl.get_surface_data(id).unwrap();
|
||||||
|
assert!(
|
||||||
|
surface_data.surface
|
||||||
|
== f.testwl
|
||||||
|
.get_object::<s_proto::wl_surface::WlSurface>(id)
|
||||||
|
.unwrap()
|
||||||
|
);
|
||||||
|
|
||||||
|
let Some(testwl::SurfaceRole::Toplevel(toplevel_data)) = &surface_data.role else {
|
||||||
|
panic!("Expected toplevel, got {:?}", surface_data.role);
|
||||||
|
};
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
toplevel_data
|
||||||
|
.states
|
||||||
|
.contains(&xdg_toplevel::State::Fullscreen),
|
||||||
|
"states: {:?}",
|
||||||
|
toplevel_data.states
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
check_fullscreen(2, false);
|
||||||
|
check_fullscreen(3, true);
|
||||||
|
}
|
||||||
|
|
||||||
/// See Pointer::handle_event for an explanation.
|
/// See Pointer::handle_event for an explanation.
|
||||||
#[test]
|
#[test]
|
||||||
fn popup_pointer_motion_workaround() {}
|
fn popup_pointer_motion_workaround() {}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue