diff --git a/jay-config/src/_private/client.rs b/jay-config/src/_private/client.rs
index 4c14d30e..45729b09 100644
--- a/jay-config/src/_private/client.rs
+++ b/jay-config/src/_private/client.rs
@@ -15,7 +15,7 @@ use {
client::{Client, ClientCriterion, ClientMatcher, MatchedClient},
exec::Command,
input::{
- FocusFollowsMouseMode, InputDevice, Seat, SwitchEvent, Timeline,
+ FocusFollowsMouseMode, InputDevice, LayerDirection, Seat, SwitchEvent, Timeline,
acceleration::AccelProfile, capability::Capability, clickmethod::ClickMethod,
},
keyboard::{
@@ -379,6 +379,10 @@ impl ConfigClient {
});
}
+ pub fn seat_focus_layer_rel(&self, seat: Seat, direction: LayerDirection) {
+ self.send(&ClientMessage::SeatFocusLayerRel { seat, direction });
+ }
+
pub fn seat_focus(&self, seat: Seat, direction: Direction) {
self.send(&ClientMessage::SeatFocus { seat, direction });
}
diff --git a/jay-config/src/_private/ipc.rs b/jay-config/src/_private/ipc.rs
index 9ad25abc..700335bc 100644
--- a/jay-config/src/_private/ipc.rs
+++ b/jay-config/src/_private/ipc.rs
@@ -4,7 +4,7 @@ use {
Axis, Direction, PciId, Workspace,
client::{Client, ClientMatcher},
input::{
- FocusFollowsMouseMode, InputDevice, Seat, SwitchEvent, Timeline,
+ FocusFollowsMouseMode, InputDevice, LayerDirection, Seat, SwitchEvent, Timeline,
acceleration::AccelProfile, capability::Capability, clickmethod::ClickMethod,
},
keyboard::{Keymap, mods::Modifiers, syms::KeySym},
@@ -737,6 +737,10 @@ pub enum ClientMessage<'a> {
seat: Seat,
same_workspace: bool,
},
+ SeatFocusLayerRel {
+ seat: Seat,
+ direction: LayerDirection,
+ },
}
#[derive(Serialize, Deserialize, Debug)]
diff --git a/jay-config/src/input.rs b/jay-config/src/input.rs
index c63cb809..e1f12633 100644
--- a/jay-config/src/input.rs
+++ b/jay-config/src/input.rs
@@ -189,6 +189,13 @@ pub enum Timeline {
Newer,
}
+/// A direction for layer traversal.
+#[derive(Serialize, Deserialize, Copy, Clone, Debug, Hash, Eq, PartialEq)]
+pub enum LayerDirection {
+ Below,
+ Above,
+}
+
/// A seat.
#[derive(Serialize, Deserialize, Copy, Clone, Debug, Hash, Eq, PartialEq)]
pub struct Seat(pub u64);
@@ -303,6 +310,12 @@ impl Seat {
get!().seat_focus_history_set_same_workspace(self, same_workspace)
}
+ /// Moves the keyboard focus of the seat to the layer above or below the current
+ /// layer.
+ pub fn focus_layer_rel(self, direction: LayerDirection) {
+ get!().seat_focus_layer_rel(self, direction)
+ }
+
/// Moves the keyboard focus of the seat in the specified direction.
pub fn focus(self, direction: Direction) {
get!().seat_focus(self, direction)
diff --git a/src/config/handler.rs b/src/config/handler.rs
index d26ed820..07e9bdf9 100644
--- a/src/config/handler.rs
+++ b/src/config/handler.rs
@@ -54,7 +54,7 @@ use {
Axis, Direction, Workspace,
client::{Client as ConfigClient, ClientMatcher},
input::{
- FocusFollowsMouseMode, InputDevice, Seat, Timeline,
+ FocusFollowsMouseMode, InputDevice, LayerDirection, Seat, Timeline,
acceleration::{ACCEL_PROFILE_ADAPTIVE, ACCEL_PROFILE_FLAT, AccelProfile},
capability::{
CAP_GESTURE, CAP_KEYBOARD, CAP_POINTER, CAP_SWITCH, CAP_TABLET_PAD,
@@ -2185,6 +2185,19 @@ impl ConfigProxyHandler {
Ok(())
}
+ fn handle_seat_focus_layer_rel(
+ &self,
+ seat: Seat,
+ direction: LayerDirection,
+ ) -> Result<(), CphError> {
+ let seat = self.get_seat(seat)?;
+ match direction {
+ LayerDirection::Below => seat.focus_layer_below(),
+ LayerDirection::Above => seat.focus_layer_above(),
+ }
+ Ok(())
+ }
+
fn spaces_change(&self) {
struct V;
impl NodeVisitorBase for V {
@@ -3039,6 +3052,9 @@ impl ConfigProxyHandler {
} => self
.handle_seat_focus_history_set_same_workspace(seat, same_workspace)
.wrn("seat_focus_history_set_same_workspace")?,
+ ClientMessage::SeatFocusLayerRel { seat, direction } => self
+ .handle_seat_focus_layer_rel(seat, direction)
+ .wrn("seat_focus_layer_rel")?,
}
Ok(())
}
diff --git a/src/ifs/wl_seat.rs b/src/ifs/wl_seat.rs
index 1f0a8f9b..4cc0058a 100644
--- a/src/ifs/wl_seat.rs
+++ b/src/ifs/wl_seat.rs
@@ -70,6 +70,7 @@ use {
dnd_icon::DndIcon,
tray::{DynTrayItem, TrayItemId},
xdg_surface::xdg_popup::XdgPopup,
+ zwlr_layer_surface_v1::ZwlrLayerSurfaceV1,
},
xdg_toplevel_drag_v1::XdgToplevelDragV1,
},
@@ -80,9 +81,10 @@ use {
rect::Rect,
state::{DeviceHandlerData, State},
tree::{
- ContainerNode, ContainerSplit, Direction, FoundNode, Node, NodeId, NodeLocation,
- OutputNode, ToplevelNode, WorkspaceNode, generic_node_visitor, toplevel_create_split,
- toplevel_parent_container, toplevel_set_floating, toplevel_set_workspace,
+ ContainerNode, ContainerSplit, Direction, FoundNode, Node, NodeId, NodeLayer,
+ NodeLayerLink, NodeLocation, OutputNode, StackedNode, ToplevelNode, WorkspaceNode,
+ generic_node_visitor, toplevel_create_split, toplevel_parent_container,
+ toplevel_set_floating, toplevel_set_workspace,
},
utils::{
asyncevent::AsyncEvent,
@@ -801,6 +803,139 @@ impl WlSeatGlobal {
self.focus_history_same_workspace.set(same_workspace);
}
+ fn focus_layer_rel
(
+ self: &Rc,
+ next_layer: impl Fn(NodeLayer) -> NodeLayer,
+ layer_node_next: impl Fn(
+ &NodeRef>,
+ ) -> Option>>,
+ stacked_node_next: impl Fn(
+ &NodeRef>,
+ ) -> Option>>,
+ layer_list_iter: impl Fn(&LinkedList>) -> LI,
+ stacked_list_iter: impl Fn(&LinkedList>) -> SI,
+ ) where
+ LI: Iterator- >>,
+ SI: Iterator
- >>,
+ {
+ fn node_viable(n: &(impl Node + ?Sized)) -> bool {
+ n.node_visible() && n.node_accepts_focus()
+ }
+
+ let current = self.keyboard_node.get();
+ let Some(output) = current.node_output() else {
+ return;
+ };
+ let current_layer = current.node_layer();
+ match ¤t_layer {
+ NodeLayerLink::Layer0(l)
+ | NodeLayerLink::Layer1(l)
+ | NodeLayerLink::Layer2(l)
+ | NodeLayerLink::Layer3(l) => {
+ if let Some(n) = layer_node_next(l)
+ && node_viable(&**n)
+ {
+ n.deref()
+ .clone()
+ .node_do_focus(self, Direction::Unspecified);
+ return;
+ }
+ }
+ NodeLayerLink::Stacked(l) | NodeLayerLink::StackedAboveLayers(l) => {
+ if let Some(n) = stacked_node_next(l)
+ && node_viable(&**n)
+ && n.node_output().map(|o| o.id) == Some(output.id)
+ {
+ n.deref()
+ .clone()
+ .node_do_focus(self, Direction::Unspecified);
+ return;
+ }
+ }
+ NodeLayerLink::Display => {}
+ NodeLayerLink::Output => {}
+ NodeLayerLink::Workspace => {}
+ NodeLayerLink::Tiled => {}
+ NodeLayerLink::Fullscreen => {}
+ NodeLayerLink::Lock => {}
+ NodeLayerLink::InputMethod => {}
+ }
+ let handle_layer_shell = |l: &LinkedList>| {
+ for n in layer_list_iter(l) {
+ if node_viable(&**n) {
+ return Some(n.deref().clone() as Rc);
+ }
+ }
+ None
+ };
+ let handle_stacked = |l: &LinkedList>| {
+ for n in stacked_list_iter(l) {
+ if node_viable(&**n) && n.node_output().map(|o| o.id) == Some(output.id) {
+ return Some(n.deref().clone() as Rc);
+ }
+ }
+ None
+ };
+ let ws = output.workspace.get();
+ let first = next_layer(current_layer.layer());
+ let mut layer = first;
+ loop {
+ let node = match layer {
+ NodeLayer::Display => None,
+ NodeLayer::Layer0 => handle_layer_shell(&output.layers[0]),
+ NodeLayer::Layer1 => handle_layer_shell(&output.layers[1]),
+ NodeLayer::Output => None,
+ NodeLayer::Workspace => None,
+ NodeLayer::Tiled => ws
+ .as_ref()
+ .and_then(|w| w.container.get())
+ .map(|n| n as Rc),
+ NodeLayer::Fullscreen => ws
+ .as_ref()
+ .and_then(|w| w.fullscreen.get())
+ .map(|n| n as Rc),
+ NodeLayer::Stacked => handle_stacked(&self.state.root.stacked),
+ NodeLayer::Layer2 => handle_layer_shell(&output.layers[2]),
+ NodeLayer::Layer3 => handle_layer_shell(&output.layers[3]),
+ NodeLayer::StackedAboveLayers => {
+ handle_stacked(&self.state.root.stacked_above_layers)
+ }
+ NodeLayer::Lock => None,
+ NodeLayer::InputMethod => None,
+ };
+ if let Some(n) = node {
+ if node_viable(&*n) {
+ n.node_do_focus(self, Direction::Unspecified);
+ return;
+ }
+ }
+ layer = next_layer(layer);
+ if layer == first {
+ return;
+ }
+ }
+ }
+
+ pub fn focus_layer_below(self: &Rc) {
+ self.focus_layer_rel(
+ |l| l.prev(),
+ |n| n.prev(),
+ |n| n.prev(),
+ |l| l.rev_iter(),
+ |l| l.rev_iter(),
+ );
+ }
+
+ pub fn focus_layer_above(self: &Rc) {
+ self.focus_layer_rel(
+ |l| l.next(),
+ |n| n.next(),
+ |n| n.next(),
+ |l| l.iter(),
+ |l| l.iter(),
+ );
+ }
+
fn set_selection_(
self: &Rc,
field: &CloneCell