Set primary output when window focused

Closes #80
This commit is contained in:
Shawn Wallace 2024-12-18 01:49:55 -05:00
parent c45c2ed990
commit 03a53b6ad7
9 changed files with 338 additions and 59 deletions

View file

@ -4,14 +4,13 @@ mod server;
pub mod xstate;
use crate::server::{PendingSurfaceState, ServerState};
use crate::xstate::XState;
use crate::xstate::{RealConnection, XState};
use log::{error, info};
use rustix::event::{poll, PollFd, PollFlags};
use std::io::{BufRead, BufReader, Read, Write};
use std::os::fd::{AsFd, AsRawFd, BorrowedFd};
use std::os::unix::net::UnixStream;
use std::process::{Command, Stdio};
use std::sync::Arc;
use wayland_server::{Display, ListeningSocket};
use xcb::x;
@ -22,7 +21,12 @@ pub trait XConnection: Sized + 'static {
fn root_window(&self) -> x::Window;
fn set_window_dims(&mut self, window: x::Window, dims: PendingSurfaceState);
fn set_fullscreen(&mut self, window: x::Window, fullscreen: bool, data: Self::ExtraData);
fn focus_window(&mut self, window: x::Window, data: Self::ExtraData);
fn focus_window(
&mut self,
window: x::Window,
output_name: Option<String>,
data: Self::ExtraData,
);
fn close_window(&mut self, window: x::Window, data: Self::ExtraData);
fn raise_to_top(&mut self, window: x::Window);
}
@ -36,7 +40,7 @@ pub trait MimeTypeData {
fn data(&self) -> &[u8];
}
type RealServerState = ServerState<Arc<xcb::Connection>>;
type RealServerState = ServerState<RealConnection>;
pub trait RunData {
fn display(&self) -> Option<&str>;
@ -156,8 +160,7 @@ pub fn main(data: impl RunData) -> Option<()> {
display.insert(0, ':');
info!("Connected to Xwayland on {display}");
data.xwayland_ready(display);
server_state.set_x_connection(xstate.connection.clone());
server_state.atoms = Some(xstate.atoms.clone());
xstate.server_state_setup(&mut server_state);
#[cfg(feature = "systemd")]
{

View file

@ -241,6 +241,7 @@ impl<C: XConnection>
role: None,
xwl: None,
window: None,
output_key: None,
}
.into()
});

View file

@ -91,8 +91,25 @@ impl HandleEvent for SurfaceData {
}
impl SurfaceData {
fn get_output_name(&self, state: &ServerState<impl XConnection>) -> Option<String> {
let output_name = self
.output_key
.and_then(|key| state.objects.get(key))
.map(|obj| <_ as AsRef<Output>>::as_ref(obj).name.clone());
if output_name.is_none() {
warn!(
"{} has no output name ({:?})",
self.server.id(),
self.output_key
);
}
output_name
}
fn surface_event<C: XConnection>(
&self,
&mut self,
event: client::wl_surface::Event,
state: &mut ServerState<C>,
) {
@ -106,10 +123,14 @@ impl SurfaceData {
};
let output: &mut Output = object.as_mut();
self.server.enter(&output.server);
self.output_key = Some(key);
debug!("{} entered {}", self.server.id(), output.server.id());
let windows = &mut state.windows;
if let Some(win_data) = self
.window
.as_ref()
.map(|win| state.windows.get_mut(&win).unwrap())
.map(|win| windows.get_mut(&win).unwrap())
{
let (x, y) = match output.position {
OutputPosition::Xdg { x, y } => (x, y),
@ -120,10 +141,16 @@ impl SurfaceData {
WindowOutputOffset { x, y },
state.connection.as_mut().unwrap(),
);
output.windows.insert(win_data.window);
let window = win_data.window;
output.windows.insert(window);
if self.window.is_some() && state.last_focused_toplevel == self.window {
let data = C::ExtraData::create(state);
let output = self.get_output_name(state);
let conn = state.connection.as_mut().unwrap();
debug!("focused window changed outputs - resetting primary output");
conn.focus_window(window, output, data);
}
}
self.server.enter(&output.server);
debug!("{} entered {}", self.server.id(), output.server.id());
}
Event::Leave { output } => {
let key: ObjectKey = output.data().copied().unwrap();
@ -132,6 +159,9 @@ impl SurfaceData {
};
let output: &mut Output = object.as_mut();
self.server.leave(&output.server);
if self.output_key == Some(key) {
self.output_key = None;
}
}
Event::PreferredBufferScale { factor } => self.server.preferred_buffer_scale(factor),
other => warn!("unhandled surface request: {other:?}"),
@ -522,7 +552,11 @@ impl HandleEvent for Keyboard {
.map(|o| <_ as AsRef<SurfaceData>>::as_ref(o))
{
state.last_kb_serial = Some(serial);
state.to_focus = Some(data.window.unwrap());
let output_name = data.get_output_name(state);
state.to_focus = Some(FocusData {
window: data.window.unwrap(),
output_name,
});
self.server.enter(serial, &data.server, keys);
}
}
@ -536,7 +570,7 @@ impl HandleEvent for Keyboard {
.get(key)
.map(|o| <_ as AsRef<SurfaceData>>::as_ref(o))
{
if state.to_focus == Some(data.window.unwrap()) {
if state.to_focus.as_ref().map(|d| d.window) == Some(data.window.unwrap()) {
state.to_focus.take();
} else {
state.unfocus = true;
@ -637,6 +671,7 @@ pub struct Output {
pub xdg: Option<XdgOutput>,
windows: HashSet<x::Window>,
position: OutputPosition,
name: String,
}
impl Output {
@ -647,6 +682,7 @@ impl Output {
xdg: None,
windows: HashSet::new(),
position: OutputPosition::Wl { x: 0, y: 0 },
name: "<unknown>".to_string(),
}
}
}
@ -723,7 +759,12 @@ impl Output {
simple_event_shunt! {
self.server, event: client::wl_output::Event => [
Name { name },
Name {
|name| {
self.name = name.clone();
name
}
},
Description { description },
Mode {
|flags| convert_wenum(flags),

View file

@ -179,6 +179,7 @@ pub struct SurfaceData {
role: Option<SurfaceRole>,
xwl: Option<XwaylandSurfaceV1>,
window: Option<x::Window>,
output_key: Option<ObjectKey>,
}
impl SurfaceData {
@ -468,6 +469,12 @@ fn handle_globals<'a, C: XConnection>(
new_key_type! {
pub struct ObjectKey;
}
struct FocusData {
window: x::Window,
output_name: Option<String>,
}
pub struct ServerState<C: XConnection> {
pub atoms: Option<Atoms>,
dh: DisplayHandle,
@ -478,11 +485,11 @@ pub struct ServerState<C: XConnection> {
qh: ClientQueueHandle,
client: Option<Client>,
to_focus: Option<x::Window>,
to_focus: Option<FocusData>,
unfocus: bool,
last_focused_toplevel: Option<x::Window>,
last_hovered: Option<x::Window>,
connection: Option<C>,
pub connection: Option<C>,
xdg_wm_base: XdgWmBase,
clipboard_data: Option<ClipboardData<C::MimeTypeData>>,
@ -846,16 +853,20 @@ impl<C: XConnection> ServerState<C> {
}
{
if let Some(win) = self.to_focus.take() {
if let Some(FocusData {
window,
output_name,
}) = self.to_focus.take()
{
let data = C::ExtraData::create(self);
let conn = self.connection.as_mut().unwrap();
debug!("focusing window {win:?}");
conn.focus_window(win, data);
self.last_focused_toplevel = Some(win);
debug!("focusing window {window:?}");
conn.focus_window(window, output_name, data);
self.last_focused_toplevel = Some(window);
} else if self.unfocus {
let data = C::ExtraData::create(self);
let conn = self.connection.as_mut().unwrap();
conn.focus_window(x::WINDOW_NONE, data);
conn.focus_window(x::WINDOW_NONE, None, data);
}
self.unfocus = false;
}

View file

@ -221,7 +221,7 @@ impl super::XConnection for FakeXConnection {
}
#[track_caller]
fn focus_window(&mut self, window: Window, _: ()) {
fn focus_window(&mut self, window: Window, _output_name: Option<String>, _: ()) {
assert!(
self.windows.contains_key(&window),
"Unknown window: {window:?}"

View file

@ -1,12 +1,13 @@
mod selection;
use selection::{SelectionData, SelectionTarget};
use crate::server::WindowAttributes;
use crate::{server::WindowAttributes, XConnection};
use bitflags::bitflags;
use log::{debug, trace, warn};
use std::collections::HashMap;
use std::ffi::CString;
use std::os::fd::{AsRawFd, BorrowedFd};
use std::sync::Arc;
use std::rc::Rc;
use xcb::{x, Xid, XidNew};
use xcb_util_cursor::{Cursor, CursorContext};
@ -103,8 +104,8 @@ impl WmName {
}
pub struct XState {
pub connection: Arc<xcb::Connection>,
pub atoms: Atoms,
connection: Rc<xcb::Connection>,
atoms: Atoms,
root: x::Window,
wm_window: x::Window,
selection_data: SelectionData,
@ -112,7 +113,15 @@ pub struct XState {
impl XState {
pub fn new(fd: BorrowedFd) -> Self {
let connection = Arc::new(xcb::Connection::connect_to_fd(fd.as_raw_fd(), None).unwrap());
let connection = Rc::new(
xcb::Connection::connect_to_fd_with_extensions(
fd.as_raw_fd(),
None,
&[xcb::Extension::Composite, xcb::Extension::RandR],
&[],
)
.unwrap(),
);
let setup = connection.get_setup();
let screen = setup.roots().next().unwrap();
let root = screen.root();
@ -139,6 +148,14 @@ impl XState {
})
.unwrap();
// Track RandR output changes
connection
.send_and_check_request(&xcb::randr::SelectInput {
window: root,
enable: xcb::randr::NotifyMask::RESOURCE_CHANGE,
})
.unwrap();
{
// Setup default cursor theme
let ctx = CursorContext::new(&connection, screen).unwrap();
@ -164,6 +181,13 @@ impl XState {
r
}
pub fn server_state_setup(&self, server_state: &mut super::RealServerState) {
let mut c = RealConnection::new(self.connection.clone());
c.update_outputs(self.root);
server_state.set_x_connection(c);
server_state.atoms = Some(self.atoms.clone());
}
fn set_root_property<P: x::PropEl>(&self, property: x::Atom, r#type: x::Atom, data: &[P]) {
self.connection
.send_and_check_request(&x::ChangeProperty {
@ -309,9 +333,10 @@ impl XState {
let active_win: &[x::Window] = active_win.value();
if active_win[0] == e.window() {
<_ as super::XConnection>::focus_window(
&mut self.connection,
// The connection on the server state stores state.
server_state.connection.as_mut().unwrap().focus_window(
x::Window::none(),
None,
self.atoms.clone(),
);
}
@ -401,6 +426,15 @@ impl XState {
t => warn!("unrecognized message: {t:?}"),
},
xcb::Event::X(x::Event::MappingNotify(_)) => {}
xcb::Event::RandR(xcb::randr::Event::Notify(e))
if matches!(e.u(), xcb::randr::NotifyData::Rc(_)) =>
{
server_state
.connection
.as_mut()
.unwrap()
.update_outputs(self.root);
}
other => {
warn!("unhandled event: {other:?}");
}
@ -774,17 +808,73 @@ impl TryFrom<u32> for SetState {
}
}
impl super::XConnection for Arc<xcb::Connection> {
pub struct RealConnection {
connection: Rc<xcb::Connection>,
outputs: HashMap<String, xcb::randr::Output>,
primary_output: xcb::randr::Output,
}
impl RealConnection {
fn new(connection: Rc<xcb::Connection>) -> Self {
Self {
connection,
outputs: Default::default(),
primary_output: Xid::none(),
}
}
fn update_outputs(&mut self, root: x::Window) {
self.outputs.clear();
let reply = self
.connection
.wait_for_reply(
self.connection
.send_request(&xcb::randr::GetScreenResources { window: root }),
)
.expect("Couldn't grab screen resources");
for output in reply.outputs().iter().copied() {
let reply = self
.connection
.wait_for_reply(self.connection.send_request(&xcb::randr::GetOutputInfo {
output,
config_timestamp: reply.config_timestamp(),
}))
.expect("Couldn't get output info");
let name = std::str::from_utf8(reply.name())
.unwrap_or_else(|_| panic!("couldn't parse output name: {:?}", reply.name()));
self.outputs.insert(name.to_string(), output);
}
self.primary_output = self
.connection
.wait_for_reply(
self.connection
.send_request(&xcb::randr::GetOutputPrimary { window: root }),
)
.expect("Couldn't get primary output")
.output();
debug!(
"new outputs: {:?} | primary: {:?}",
self.outputs, self.primary_output
);
}
}
impl XConnection for RealConnection {
type ExtraData = Atoms;
type MimeTypeData = SelectionTarget;
fn root_window(&self) -> x::Window {
self.get_setup().roots().next().unwrap().root()
self.connection.get_setup().roots().next().unwrap().root()
}
fn set_window_dims(&mut self, window: x::Window, dims: crate::server::PendingSurfaceState) {
trace!("set window dimensions {window:?} {dims:?}");
unwrap_or_skip_bad_window!(self.send_and_check_request(&x::ConfigureWindow {
unwrap_or_skip_bad_window!(self.connection.send_and_check_request(&x::ConfigureWindow {
window,
value_list: &[
x::ConfigWindow::X(dims.x),
@ -801,38 +891,74 @@ impl super::XConnection for Arc<xcb::Connection> {
} else {
&[]
};
self.send_and_check_request(&x::ChangeProperty::<x::Atom> {
mode: x::PropMode::Replace,
window,
property: atoms.net_wm_state,
r#type: x::ATOM_ATOM,
data,
})
.unwrap();
self.connection
.send_and_check_request(&x::ChangeProperty::<x::Atom> {
mode: x::PropMode::Replace,
window,
property: atoms.net_wm_state,
r#type: x::ATOM_ATOM,
data,
})
.unwrap();
}
fn focus_window(&mut self, window: x::Window, atoms: Self::ExtraData) {
if let Err(e) = self.send_and_check_request(&x::SetInputFocus {
fn focus_window(
&mut self,
window: x::Window,
output_name: Option<String>,
atoms: Self::ExtraData,
) {
trace!("{window:?} {output_name:?}");
if let Err(e) = self.connection.send_and_check_request(&x::SetInputFocus {
focus: window,
revert_to: x::InputFocus::None,
time: x::CURRENT_TIME,
}) {
log::debug!("SetInputFocus failed ({:?}: {:?})", window, e);
debug!("SetInputFocus failed ({:?}: {:?})", window, e);
return;
}
if let Err(e) = self.send_and_check_request(&x::ChangeProperty {
if let Err(e) = self.connection.send_and_check_request(&x::ChangeProperty {
mode: x::PropMode::Replace,
window: self.root_window(),
property: atoms.active_win,
r#type: x::ATOM_WINDOW,
data: &[window],
}) {
log::debug!("ChangeProperty failed ({:?}: {:?})", window, e);
debug!("ChangeProperty failed ({:?}: {:?})", window, e);
}
if let Some(name) = output_name {
let Some(output) = self.outputs.get(&name).copied() else {
warn!("Couldn't find output {name}, primary output will be wrong");
return;
};
if output == self.primary_output {
debug!("primary output is already {name}");
return;
}
if let Err(e) = self
.connection
.send_and_check_request(&xcb::randr::SetOutputPrimary { window, output })
{
warn!("Couldn't set output {name} as primary: {e:?}");
} else {
debug!("set {name} as primary output");
self.primary_output = output;
}
} else {
let _ = self
.connection
.send_and_check_request(&xcb::randr::SetOutputPrimary {
window,
output: Xid::none(),
});
self.primary_output = Xid::none();
}
}
fn close_window(&mut self, window: x::Window, atoms: Self::ExtraData) {
let cookie = self.send_request(&x::GetProperty {
let cookie = self.connection.send_request(&x::GetProperty {
window,
delete: false,
property: atoms.wm_protocols,
@ -840,7 +966,7 @@ impl super::XConnection for Arc<xcb::Connection> {
long_offset: 0,
long_length: 10,
});
let reply = unwrap_or_skip_bad_window!(self.wait_for_reply(cookie));
let reply = unwrap_or_skip_bad_window!(self.connection.wait_for_reply(cookie));
if reply.value::<x::Atom>().contains(&atoms.wm_delete_window) {
let data = [atoms.wm_delete_window.resource_id(), 0, 0, 0, 0];
@ -850,28 +976,28 @@ impl super::XConnection for Arc<xcb::Connection> {
x::ClientMessageData::Data32(data),
);
unwrap_or_skip_bad_window!(self.send_and_check_request(&x::SendEvent {
unwrap_or_skip_bad_window!(self.connection.send_and_check_request(&x::SendEvent {
destination: x::SendEventDest::Window(window),
propagate: false,
event_mask: x::EventMask::empty(),
event,
}));
} else {
unwrap_or_skip_bad_window!(self.send_and_check_request(&x::KillClient {
unwrap_or_skip_bad_window!(self.connection.send_and_check_request(&x::KillClient {
resource: window.resource_id()
}))
}
}
fn raise_to_top(&mut self, window: x::Window) {
unwrap_or_skip_bad_window!(self.send_and_check_request(&x::ConfigureWindow {
unwrap_or_skip_bad_window!(self.connection.send_and_check_request(&x::ConfigureWindow {
window,
value_list: &[x::ConfigWindow::StackMode(x::StackMode::Above)],
}));
}
}
impl super::FromServerState<Arc<xcb::Connection>> for Atoms {
impl super::FromServerState<RealConnection> for Atoms {
fn create(state: &super::RealServerState) -> Self {
state.atoms.as_ref().unwrap().clone()
}