1
0
Fork 0
forked from wry/wry

config: add focus-below and focus-above actions

This commit is contained in:
Julian Orth 2025-07-19 22:01:50 +02:00
parent c034ea7604
commit bd85db5b59
12 changed files with 211 additions and 19 deletions

View file

@ -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 });
}

View file

@ -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)]

View file

@ -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)

View file

@ -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(())
}

View file

@ -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<LI, SI>(
self: &Rc<Self>,
next_layer: impl Fn(NodeLayer) -> NodeLayer,
layer_node_next: impl Fn(
&NodeRef<Rc<ZwlrLayerSurfaceV1>>,
) -> Option<NodeRef<Rc<ZwlrLayerSurfaceV1>>>,
stacked_node_next: impl Fn(
&NodeRef<Rc<dyn StackedNode>>,
) -> Option<NodeRef<Rc<dyn StackedNode>>>,
layer_list_iter: impl Fn(&LinkedList<Rc<ZwlrLayerSurfaceV1>>) -> LI,
stacked_list_iter: impl Fn(&LinkedList<Rc<dyn StackedNode>>) -> SI,
) where
LI: Iterator<Item = NodeRef<Rc<ZwlrLayerSurfaceV1>>>,
SI: Iterator<Item = NodeRef<Rc<dyn StackedNode>>>,
{
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 &current_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<Rc<ZwlrLayerSurfaceV1>>| {
for n in layer_list_iter(l) {
if node_viable(&**n) {
return Some(n.deref().clone() as Rc<dyn Node>);
}
}
None
};
let handle_stacked = |l: &LinkedList<Rc<dyn StackedNode>>| {
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<dyn Node>);
}
}
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<dyn Node>),
NodeLayer::Fullscreen => ws
.as_ref()
.and_then(|w| w.fullscreen.get())
.map(|n| n as Rc<dyn Node>),
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>) {
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>) {
self.focus_layer_rel(
|l| l.next(),
|n| n.next(),
|n| n.next(),
|l| l.iter(),
|l| l.iter(),
);
}
fn set_selection_<T, X, S>(
self: &Rc<Self>,
field: &CloneCell<Option<Rc<dyn DynDataSource>>>,

View file

@ -141,22 +141,21 @@ pub enum NodeLayer {
pub enum NodeLayerLink {
Display,
Layer0(#[expect(dead_code)] NodeRef<Rc<ZwlrLayerSurfaceV1>>),
Layer1(#[expect(dead_code)] NodeRef<Rc<ZwlrLayerSurfaceV1>>),
Layer0(NodeRef<Rc<ZwlrLayerSurfaceV1>>),
Layer1(NodeRef<Rc<ZwlrLayerSurfaceV1>>),
Output,
Workspace,
Tiled,
Fullscreen,
Stacked(#[expect(dead_code)] NodeRef<Rc<dyn StackedNode>>),
Layer2(#[expect(dead_code)] NodeRef<Rc<ZwlrLayerSurfaceV1>>),
Layer3(#[expect(dead_code)] NodeRef<Rc<ZwlrLayerSurfaceV1>>),
StackedAboveLayers(#[expect(dead_code)] NodeRef<Rc<dyn StackedNode>>),
Stacked(NodeRef<Rc<dyn StackedNode>>),
Layer2(NodeRef<Rc<ZwlrLayerSurfaceV1>>),
Layer3(NodeRef<Rc<ZwlrLayerSurfaceV1>>),
StackedAboveLayers(NodeRef<Rc<dyn StackedNode>>),
Lock,
InputMethod,
}
impl NodeLayerLink {
#[expect(dead_code)]
pub fn layer(&self) -> NodeLayer {
macro_rules! map {
($($id:ident,)*) => {
@ -186,7 +185,6 @@ impl NodeLayerLink {
}
impl NodeLayer {
#[expect(dead_code)]
pub fn prev(self) -> Self {
if self == NodeLayer::Display {
return NodeLayer::InputMethod;
@ -194,7 +192,6 @@ impl NodeLayer {
Self::from_linear(self.linearize() - 1).unwrap_or(NodeLayer::InputMethod)
}
#[expect(dead_code)]
pub fn next(self) -> Self {
Self::from_linear(self.linearize() + 1).unwrap_or(NodeLayer::Display)
}
@ -216,7 +213,6 @@ pub trait Node: 'static {
let _ = title;
}
#[expect(dead_code)]
fn node_accepts_focus(&self) -> bool {
true
}

View file

@ -23,7 +23,10 @@ use {
ahash::AHashMap,
jay_config::{
Axis, Direction, Workspace,
input::{SwitchEvent, Timeline, acceleration::AccelProfile, clickmethod::ClickMethod},
input::{
LayerDirection, SwitchEvent, Timeline, acceleration::AccelProfile,
clickmethod::ClickMethod,
},
keyboard::{Keymap, ModifiedKeySym, mods::Modifiers, syms::KeySym},
logging::LogLevel,
status::MessageFormat,
@ -72,6 +75,7 @@ pub enum SimpleCommand {
ShowBar(bool),
ToggleBar,
FocusHistory(Timeline),
FocusLayerRel(LayerDirection),
}
#[derive(Debug, Clone)]

View file

@ -34,7 +34,7 @@ use {
jay_config::{
Axis::{Horizontal, Vertical},
get_workspace,
input::Timeline,
input::{LayerDirection, Timeline},
},
thiserror::Error,
};
@ -139,6 +139,8 @@ impl ActionParser<'_> {
"toggle-bar" => ToggleBar,
"focus-prev" => FocusHistory(Timeline::Older),
"focus-next" => FocusHistory(Timeline::Newer),
"focus-below" => FocusLayerRel(LayerDirection::Below),
"focus-above" => FocusLayerRel(LayerDirection::Above),
_ => {
return Err(
ActionParserError::UnknownSimpleAction(string.to_string()).spanned(span)

View file

@ -160,6 +160,10 @@ impl Action {
let persistent = state.persistent.clone();
B::new(move || persistent.seat.focus_history(timeline))
}
SimpleCommand::FocusLayerRel(direction) => {
let persistent = state.persistent.clone();
B::new(move || persistent.seat.focus_layer_rel(direction))
}
},
Action::Multi { actions } => {
let actions: Vec<_> = actions.into_iter().map(|a| a.into_fn(state)).collect();

View file

@ -1614,7 +1614,9 @@
"hide-bar",
"toggle-bar",
"focus-prev",
"focus-next"
"focus-next",
"focus-below",
"focus-above"
]
},
"Status": {

View file

@ -3675,6 +3675,14 @@ The string should have one of the following values:
Focuses the next window in the focus history.
- `focus-below`:
Focuses the layer below the currently focused layer.
- `focus-above`:
Focuses the layer above the currently focused layer.
<a name="types-Status"></a>

View file

@ -864,6 +864,10 @@ SimpleActionName:
description: Focuses the previous window in the focus history.
- value: focus-next
description: Focuses the next window in the focus history.
- value: focus-below
description: Focuses the layer below the currently focused layer.
- value: focus-above
description: Focuses the layer above the currently focused layer.
Color: