use crate::core::{
bindings::{KeyCode, MouseEvent},
data_types::{Point, Region},
hooks::HookName,
manager::state::WmState,
xconnection::{
Atom, ClientMessage, ConfigureEvent, PointerChange, PropertyEvent, XAtomQuerier, XEvent,
Xid,
},
};
use std::str::FromStr;
#[non_exhaustive]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[must_use = "Generated event actions must be handled"]
#[derive(Debug, PartialEq, Eq)]
pub enum EventAction {
ClientFocusLost(Xid),
ClientFocusGained(Xid),
ClientNameChanged(Xid, bool),
ClientToWorkspace(Xid, usize),
DestroyClient(Xid),
DetectScreens,
FocusIn(Xid),
LayoutVisible,
LayoutWorkspace(usize),
MapWindow(Xid),
MoveClientIfFloating(Xid, Region),
RunHook(HookName),
RunKeyBinding(KeyCode),
RunMouseBinding(MouseEvent),
SetActiveClient(Xid),
SetActiveWorkspace(usize),
SetScreenFromPoint(Option<Point>),
ToggleClientFullScreen(Xid, bool),
UnknownPropertyChange(Xid, String, bool),
Unmap(Xid),
}
pub(super) fn process_next_event<X>(event: XEvent, state: &WmState, conn: &X) -> Vec<EventAction>
where
X: XAtomQuerier,
{
match event {
XEvent::Destroy(id) => vec![EventAction::DestroyClient(id)],
XEvent::Expose(_) => vec![], XEvent::FocusIn(id) => vec![EventAction::FocusIn(id)],
XEvent::KeyPress(code) => vec![EventAction::RunKeyBinding(code)],
XEvent::Leave(p) => vec![
EventAction::ClientFocusLost(p.id),
EventAction::SetScreenFromPoint(Some(p.abs)),
],
XEvent::MouseEvent(evt) => vec![EventAction::RunMouseBinding(evt)],
XEvent::RandrNotify => vec![EventAction::DetectScreens],
XEvent::ScreenChange => vec![EventAction::SetScreenFromPoint(None)],
XEvent::UnmapNotify(id) => vec![EventAction::Unmap(id)],
XEvent::ClientMessage(msg) => process_client_message(state, conn, msg),
XEvent::ConfigureNotify(evt) => process_configure_notify(evt),
XEvent::ConfigureRequest(evt) => process_configure_request(evt),
XEvent::Enter(p) => process_enter_notify(state, p),
XEvent::MapRequest(id, override_redirect) => {
process_map_request(state, id, override_redirect)
}
XEvent::PropertyNotify(evt) => process_property_notify(evt),
}
}
fn process_client_message<X>(_state: &WmState, conn: &X, msg: ClientMessage) -> Vec<EventAction>
where
X: XAtomQuerier,
{
let data = msg.data();
trace!(id = msg.id, dtype = ?msg.dtype, ?data, "got client message");
let is_fullscreen = |data: &[u32]| {
data.iter()
.map(|&a| conn.atom_name(a))
.flatten()
.any(|s| s == Atom::NetWmStateFullscreen.as_ref())
};
match Atom::from_str(&msg.dtype) {
Ok(Atom::NetActiveWindow) => vec![EventAction::SetActiveClient(msg.id)],
Ok(Atom::NetCurrentDesktop) => vec![EventAction::SetActiveWorkspace(data.as_usize()[0])],
Ok(Atom::NetWmDesktop) => vec![EventAction::ClientToWorkspace(msg.id, data.as_usize()[0])],
Ok(Atom::NetWmState) if is_fullscreen(&data.as_u32()[1..3]) => {
let should_fullscreen = [1, 2].contains(&data.as_usize()[0]);
vec![EventAction::ToggleClientFullScreen(
msg.id,
should_fullscreen,
)]
}
_ => vec![],
}
}
fn process_configure_notify(evt: ConfigureEvent) -> Vec<EventAction> {
if evt.is_root {
vec![EventAction::DetectScreens]
} else {
vec![]
}
}
fn process_configure_request(evt: ConfigureEvent) -> Vec<EventAction> {
if !evt.is_root {
vec![EventAction::MoveClientIfFloating(evt.id, evt.r)]
} else {
vec![]
}
}
fn process_enter_notify(state: &WmState, p: PointerChange) -> Vec<EventAction> {
let mut actions = vec![
EventAction::ClientFocusGained(p.id),
EventAction::SetScreenFromPoint(Some(p.abs)),
];
if let Some(current) = state.clients.focused_client_id() {
if current != p.id {
actions.insert(0, EventAction::ClientFocusLost(current));
}
}
actions
}
fn process_map_request(state: &WmState, id: Xid, override_redirect: bool) -> Vec<EventAction> {
if override_redirect || state.clients.is_known(id) {
vec![]
} else {
vec![EventAction::MapWindow(id)]
}
}
fn process_property_notify(evt: PropertyEvent) -> Vec<EventAction> {
match Atom::from_str(&evt.atom) {
Ok(a) if a == Atom::WmName || a == Atom::NetWmName => {
vec![EventAction::ClientNameChanged(evt.id, evt.is_root)]
}
_ => vec![EventAction::UnknownPropertyChange(
evt.id,
evt.atom,
evt.is_root,
)],
}
}