xwayland: Handle _NET_ACTIVE_WINDOW client messages

Allow X11 clients to activate a window.

This shares the logic with xdg-activation. It might make sense to handle
the urgent hint on an X11 Window natively, but for now this just marks a
workspace as urgent on activation in the same way xdg-activation does.
This commit is contained in:
Ian Douglas Scott 2025-11-25 14:37:48 -08:00 committed by Victoria Brekenfeld
parent 8fc7f0809f
commit cf55b6c899
2 changed files with 139 additions and 114 deletions

View file

@ -106,132 +106,145 @@ impl XdgActivationHandler for State {
let Some(context) = token_data.user_data.get::<ActivationContext>() else {
return;
};
let mut shell = self.common.shell.write();
match context {
ActivationContext::UrgentOnly => {
let shell = self.common.shell.write();
if let Some((workspace, _output)) = shell.workspace_for_surface(&surface) {
let mut workspace_guard = self.common.workspace_state.update();
workspace_guard.add_workspace_state(&workspace, WState::Urgent);
}
}
ActivationContext::Workspace(_) => {
let seat = shell.seats.last_active().clone();
let current_output = seat.active_output();
if let Some(element) = shell.element_for_surface(&surface).cloned() {
if element.is_minimized() {
shell.unminimize_request(&surface, &seat, &self.common.event_loop_handle);
}
let Some((element_output, element_workspace)) = shell
.space_for(&element)
.map(|w| (w.output.clone(), w.handle))
else {
return;
};
let in_current_workspace =
element_workspace == shell.active_space(&current_output).unwrap().handle;
if !in_current_workspace {
let Some(idx) = shell
.workspaces
.idx_for_handle(&element_output, &element_workspace)
else {
warn!("Couldn't determine idx for elements workspace?");
return;
};
if let Err(err) = shell.activate(
&element_output,
idx,
WorkspaceDelta::new_shortcut(),
&mut self.common.workspace_state.update(),
) {
warn!("Failed to activate the workspace: {err:?}");
}
}
let current_workspace = shell.active_space_mut(&current_output).unwrap();
current_workspace
.floating_layer
.space
.raise_element(&element, true);
if element.is_stack() {
if let Some((window, _)) = element.windows().find(|(window, _)| {
let mut found = false;
window.with_surfaces(|wl_surface, _| {
if wl_surface == &surface {
found = true;
}
});
found
}) {
element.set_active(&window);
} else {
warn!("Failed to find activated window in the stack");
return;
}
}
if seat.get_keyboard().unwrap().current_focus() != Some(element.clone().into())
&& current_workspace.is_tiled(&surface)
{
for mapped in current_workspace
.mapped()
.filter(|m| m.maximized_state.lock().unwrap().is_some())
.cloned()
.collect::<Vec<_>>()
.into_iter()
{
current_workspace.unmaximize_request(&mapped);
}
}
std::mem::drop(shell);
Shell::set_focus(
self,
Some(&KeyboardFocusTarget::Element(element.clone())),
&seat,
None,
false,
);
} else if let Some((workspace, _)) = shell.workspace_for_surface(&surface) {
let current_workspace = shell.active_space(&current_output).unwrap();
if workspace == current_workspace.handle {
let Some(target) = shell
.workspaces
.space_for_handle(&workspace)
.unwrap()
.get_fullscreen()
.cloned()
.map(KeyboardFocusTarget::Fullscreen)
else {
return;
};
std::mem::drop(shell);
Shell::set_focus(self, Some(&target), &seat, None, false);
} else {
if let Some(surface) = shell
.workspaces
.space_for_handle(&workspace)
.and_then(|w| w.get_fullscreen())
.cloned()
{
shell.append_focus_stack(surface, &seat)
}
let mut workspace_guard = self.common.workspace_state.update();
workspace_guard.add_workspace_state(&workspace, WState::Urgent);
}
} else {
shell
.pending_activations
.insert(ActivationKey::Wayland(surface), *context);
};
self.activate_surface(
&surface,
Some((ActivationKey::Wayland(surface.clone()), *context)),
);
}
}
}
}
impl State {
pub fn activate_surface(
&mut self,
surface: &WlSurface,
pending_activation: Option<(ActivationKey, ActivationContext)>,
) {
let mut shell = self.common.shell.write();
let seat = shell.seats.last_active().clone();
let current_output = seat.active_output();
if let Some(element) = shell.element_for_surface(surface).cloned() {
if element.is_minimized() {
shell.unminimize_request(surface, &seat, &self.common.event_loop_handle);
}
let Some((element_output, element_workspace)) = shell
.space_for(&element)
.map(|w| (w.output.clone(), w.handle))
else {
return;
};
let in_current_workspace =
element_workspace == shell.active_space(&current_output).unwrap().handle;
if !in_current_workspace {
let Some(idx) = shell
.workspaces
.idx_for_handle(&element_output, &element_workspace)
else {
warn!("Couldn't determine idx for elements workspace?");
return;
};
if let Err(err) = shell.activate(
&element_output,
idx,
WorkspaceDelta::new_shortcut(),
&mut self.common.workspace_state.update(),
) {
warn!("Failed to activate the workspace: {err:?}");
}
}
let current_workspace = shell.active_space_mut(&current_output).unwrap();
current_workspace
.floating_layer
.space
.raise_element(&element, true);
if element.is_stack() {
if let Some((window, _)) = element.windows().find(|(window, _)| {
let mut found = false;
window.with_surfaces(|wl_surface, _| {
if wl_surface == surface {
found = true;
}
});
found
}) {
element.set_active(&window);
} else {
warn!("Failed to find activated window in the stack");
return;
}
}
if seat.get_keyboard().unwrap().current_focus() != Some(element.clone().into())
&& current_workspace.is_tiled(surface)
{
for mapped in current_workspace
.mapped()
.filter(|m| m.maximized_state.lock().unwrap().is_some())
.cloned()
.collect::<Vec<_>>()
.into_iter()
{
current_workspace.unmaximize_request(&mapped);
}
}
std::mem::drop(shell);
Shell::set_focus(
self,
Some(&KeyboardFocusTarget::Element(element.clone())),
&seat,
None,
false,
);
} else if let Some((workspace, _)) = shell.workspace_for_surface(&surface) {
let current_workspace = shell.active_space(&current_output).unwrap();
if workspace == current_workspace.handle {
let Some(target) = shell
.workspaces
.space_for_handle(&workspace)
.unwrap()
.get_fullscreen()
.cloned()
.map(KeyboardFocusTarget::Fullscreen)
else {
return;
};
std::mem::drop(shell);
Shell::set_focus(self, Some(&target), &seat, None, false);
} else {
if let Some(surface) = shell
.workspaces
.space_for_handle(&workspace)
.and_then(|w| w.get_fullscreen())
.cloned()
{
shell.append_focus_stack(surface, &seat)
}
let mut workspace_guard = self.common.workspace_state.update();
workspace_guard.add_workspace_state(&workspace, WState::Urgent);
}
} else if let Some((activation_key, context)) = pending_activation {
shell.pending_activations.insert(activation_key, context);
};
}
}
delegate_xdg_activation!(State);

View file

@ -1110,6 +1110,18 @@ impl XwmHandler for State {
}
}
fn active_window_request(
&mut self,
_xwm: XwmId,
window: X11Surface,
_timestamp: u32,
_currently_active_window: Option<X11Surface>,
) {
if let Some(surface) = window.wl_surface() {
self.activate_surface(&surface, None);
}
}
fn send_selection(
&mut self,
_xwm: XwmId,