1
0
Fork 0
forked from wry/wry

Merge pull request #449 from khyperia/relative-behavior-mode

implement new setting: fallback output mode
This commit is contained in:
mahkoh 2026-01-18 12:39:17 +01:00 committed by GitHub
commit 2f4543912b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
29 changed files with 306 additions and 55 deletions

View file

@ -728,6 +728,7 @@ fn create_dummy_output(state: &Rc<State>) {
bar_rect: Default::default(),
bar_rect_rel: Default::default(),
bar_rect_with_separator: Default::default(),
bar_separator_rect: Default::default(),
bar_separator_rect_rel: Default::default(),
non_exclusive_rect: Default::default(),
render_data: Default::default(),

View file

@ -54,7 +54,7 @@ use {
Axis, Direction, Workspace,
client::{Client as ConfigClient, ClientCapabilities, ClientMatcher},
input::{
FocusFollowsMouseMode, InputDevice, LayerDirection, Seat, Timeline,
FallbackOutputMode, 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,
@ -518,6 +518,16 @@ impl ConfigProxyHandler {
Ok(())
}
fn handle_set_fallback_output_mode(
&self,
seat: Seat,
mode: FallbackOutputMode,
) -> Result<(), CphError> {
let seat = self.get_seat(seat)?;
seat.set_fallback_output_mode(mode);
Ok(())
}
fn handle_set_window_management_enabled(
&self,
seat: Seat,
@ -1012,16 +1022,16 @@ impl ConfigProxyHandler {
self.state.double_click_distance.set(dist);
}
fn handle_get_seat_workspace(&self, seat: Seat) -> Result<(), CphError> {
fn handle_get_seat_cursor_workspace(&self, seat: Seat) -> Result<(), CphError> {
let seat = self.get_seat(seat)?;
let output = seat.get_output();
let output = seat.get_cursor_output();
let mut workspace = Workspace(0);
if !output.is_dummy
&& let Some(ws) = output.workspace.get()
{
workspace = self.get_workspace_by_name(&ws.name);
}
self.respond(Response::GetSeatWorkspace { workspace });
self.respond(Response::GetSeatCursorWorkspace { workspace });
Ok(())
}
@ -1056,7 +1066,7 @@ impl ConfigProxyHandler {
let name = self.get_workspace(ws)?;
let workspace = match self.state.workspaces.get(name.deref()) {
Some(ws) => ws,
_ => seat.get_output().create_workspace(name.deref()),
_ => seat.get_fallback_output().create_workspace(name.deref()),
};
seat.set_workspace(&workspace);
Ok(())
@ -1112,10 +1122,12 @@ impl ConfigProxyHandler {
Some(ws) => ws,
_ => return Ok(()),
},
WorkspaceSource::Seat(s) => match self.get_seat(s)?.get_output().workspace.get() {
Some(ws) => ws,
_ => return Ok(()),
},
WorkspaceSource::Seat(s) => {
match self.get_seat(s)?.get_fallback_output().workspace.get() {
Some(ws) => ws,
_ => return Ok(()),
}
}
};
self.state.move_ws_to_output(&ws, &output);
Ok(())
@ -2935,9 +2947,9 @@ impl ConfigProxyHandler {
ClientMessage::MakeRenderDevice { device } => self
.handle_make_render_device(device)
.wrn("make_render_device")?,
ClientMessage::GetSeatWorkspace { seat } => self
.handle_get_seat_workspace(seat)
.wrn("get_seat_workspace")?,
ClientMessage::GetSeatCursorWorkspace { seat } => self
.handle_get_seat_cursor_workspace(seat)
.wrn("get_seat_cursor_workspace")?,
ClientMessage::GetSeatKeyboardWorkspace { seat } => self
.handle_get_seat_keyboard_workspace(seat)
.wrn("get_seat_keyboard_workspace")?,
@ -3354,6 +3366,9 @@ impl ConfigProxyHandler {
} => self
.handle_keymap_from_names(rules, model, groups, options)
.wrn("keymap_from_names")?,
ClientMessage::SetFallbackOutputMode { seat, mode } => self
.handle_set_fallback_output_mode(seat, mode)
.wrn("set_fallback_output_mode")?,
}
Ok(())
}

View file

@ -106,7 +106,10 @@ use {
wire_ei::EiSeatId,
},
ahash::AHashMap,
jay_config::keyboard::syms::{KeySym, SYM_Escape},
jay_config::{
input::FallbackOutputMode,
keyboard::syms::{KeySym, SYM_Escape},
},
kbvm::Keycode,
smallvec::SmallVec,
std::{
@ -226,6 +229,7 @@ pub struct WlSeatGlobal {
input_method_grab: CloneCell<Option<Rc<dyn InputMethodKeyboardGrab>>>,
forward: Cell<bool>,
focus_follows_mouse: Cell<bool>,
fallback_output_mode: Cell<FallbackOutputMode>,
swipe_bindings: PerClientBindings<ZwpPointerGestureSwipeV1>,
pinch_bindings: PerClientBindings<ZwpPointerGesturePinchV1>,
hold_bindings: PerClientBindings<ZwpPointerGestureHoldV1>,
@ -325,6 +329,7 @@ impl WlSeatGlobal {
input_method_grab: Default::default(),
forward: Cell::new(false),
focus_follows_mouse: Cell::new(true),
fallback_output_mode: Cell::new(FallbackOutputMode::Cursor),
swipe_bindings: Default::default(),
pinch_bindings: Default::default(),
hold_bindings: Default::default(),
@ -457,7 +462,7 @@ impl WlSeatGlobal {
self.data_control_devices.remove(&device.id());
}
pub fn get_output(&self) -> Rc<OutputNode> {
pub fn get_cursor_output(&self) -> Rc<OutputNode> {
self.cursor_user_group.latest_output()
}
@ -469,6 +474,15 @@ impl WlSeatGlobal {
self.keyboard_node.get().node_output()
}
pub fn get_fallback_output(&self) -> Rc<OutputNode> {
if self.fallback_output_mode.get() == FallbackOutputMode::Focus
&& let Some(output) = self.get_keyboard_output()
{
return output;
}
self.get_cursor_output()
}
pub fn set_workspace(&self, ws: &Rc<WorkspaceNode>) {
let tl = match self.keyboard_node.get().node_toplevel() {
Some(tl) => tl,
@ -726,7 +740,16 @@ impl WlSeatGlobal {
pub fn move_focus(self: &Rc<Self>, direction: Direction) {
let tl = match self.keyboard_node.get().node_toplevel() {
Some(tl) => tl,
_ => return,
_ => {
if let Some(ws) = self.keyboard_node.get().node_into_workspace()
&& let Some(target) = self
.state
.find_output_in_direction(&ws.output.get(), direction)
{
target.take_keyboard_navigation_focus(self, direction);
}
return;
}
};
if direction == Direction::Down && tl.node_is_container() {
tl.node_do_focus(self, direction);
@ -748,6 +771,13 @@ impl WlSeatGlobal {
pub fn move_focused(self: &Rc<Self>, direction: Direction) {
let kb_node = self.keyboard_node.get();
let Some(tl) = kb_node.node_toplevel() else {
if let Some(ws) = self.keyboard_node.get().node_into_workspace()
&& let Some(target) = self
.state
.find_output_in_direction(&ws.output.get(), direction)
{
self.state.move_ws_to_output(&ws, &target);
}
return;
};
let data = tl.tl_data();
@ -981,7 +1011,15 @@ impl WlSeatGlobal {
NodeLayer::Layer0 => handle_layer_shell(&output.layers[0]),
NodeLayer::Layer1 => handle_layer_shell(&output.layers[1]),
NodeLayer::Output => None,
NodeLayer::Workspace => None,
NodeLayer::Workspace => {
if let Some(ws) = &ws
&& ws.container_visible()
{
self.focus_node(ws.clone());
return;
}
None
}
NodeLayer::Tiled => ws
.as_ref()
.and_then(|w| w.container.get())
@ -1393,6 +1431,10 @@ impl WlSeatGlobal {
self.focus_follows_mouse.set(focus_follows_mouse);
}
pub fn set_fallback_output_mode(&self, fallback_output_mode: FallbackOutputMode) {
self.fallback_output_mode.set(fallback_output_mode);
}
pub fn set_window_management_enabled(self: &Rc<Self>, enabled: bool) {
self.pointer_owner
.set_window_management_enabled(self, enabled);

View file

@ -213,12 +213,12 @@ impl NodeSeatState {
fn release_kb_focus2(&self, focus_last: bool) {
self.release_kb_grab();
while let Some((_, seat)) = self.kb_foci.pop() {
let output = seat.get_fallback_output();
seat.kb_owner
.set_kb_node(&seat, seat.state.root.clone(), seat.state.next_serial(None));
// log::info!("keyboard_node = root");
if focus_last {
seat.get_output()
.node_do_focus(&seat, Direction::Unspecified);
output.node_do_focus(&seat, Direction::Unspecified);
}
}
}

View file

@ -486,7 +486,7 @@ impl XdgToplevel {
if should_be_mapped {
if !self.is_mapped.replace(true) {
if let Some(seat) = drag.source.data.seat.get() {
self.xdg.set_output(&seat.get_output());
self.xdg.set_output(&seat.get_cursor_output());
}
self.toplevel_data.broadcast(self.clone());
self.tl_set_visible(self.state.root_visible());

View file

@ -136,7 +136,7 @@ impl XdgToplevelDragV1 {
if self.source.data.was_used()
&& let Some(tl) = self.toplevel.get()
{
let output = seat.get_output();
let output = seat.get_cursor_output();
let (x, y) = seat.pointer_cursor().position();
tl.drag.take();
tl.after_toplevel_drag(

View file

@ -60,7 +60,7 @@ impl ZwlrLayerShellV1RequestHandler for ZwlrLayerShellV1 {
self.client.lookup(req.output)?.global.clone()
} else {
for seat in self.client.state.seat_queue.rev_iter() {
let output = seat.get_output();
let output = seat.get_fallback_output();
if !output.is_dummy {
break 'get_output output.global.opt.clone();
}

View file

@ -18,6 +18,7 @@ async fn test(run: Rc<TestRun>) -> TestResult {
win1.set_color(255, 0, 0, 255);
win1.map2().await?;
run.cfg.set_floating(ds.seat.id(), true)?;
client.sync().await;
for i in ["1", "2"] {
let (x, y) = win1.tl.server.node_absolute_position().position();

View file

@ -85,6 +85,7 @@ impl Renderer<'_> {
} else {
render_layer!(output.layers[0]);
render_layer!(output.layers[1]);
let ws = output.workspace.get();
if self.state.show_bar.get() {
let non_exclusive_rect_rel = output.non_exclusive_rect_rel.get();
let (mut x, mut y) = non_exclusive_rect_rel.translate_inv(x, y);
@ -109,7 +110,12 @@ impl Renderer<'_> {
self.base
.fill_boxes2(slice::from_ref(&aw.rect), &c, srgb, x, y);
}
let c = theme.colors.separator.get();
let mut c = theme.colors.separator.get();
if let Some(ws) = &ws
&& ws.seat_state.is_active()
{
c = theme.colors.focused_title_background.get();
}
self.base
.fill_boxes2(slice::from_ref(&rd.bar_separator), &c, srgb, x, y);
let c = theme.colors.unfocused_title_background.get();
@ -172,7 +178,7 @@ impl Renderer<'_> {
}
}
}
if let Some(ws) = output.workspace.get() {
if let Some(ws) = &ws {
let ws_rect = output.workspace_rect_rel.get();
let (x, y) = ws_rect.translate_inv(x, y);
self.render_workspace(&ws, x, y);

View file

@ -780,7 +780,7 @@ impl State {
pub fn ensure_map_workspace(&self, seat: Option<&Rc<WlSeatGlobal>>) -> Rc<WorkspaceNode> {
seat.cloned()
.or_else(|| self.seat_queue.last().map(|s| s.deref().clone()))
.map(|s| s.get_output())
.map(|s| s.get_fallback_output())
.or_else(|| self.root.outputs.lock().values().next().cloned())
.or_else(|| self.dummy_output.get())
.unwrap()
@ -916,7 +916,7 @@ impl State {
let ws = match self.workspaces.get(name) {
Some(ws) => ws,
_ => {
let output = output.unwrap_or_else(|| seat.get_output());
let output = output.unwrap_or_else(|| seat.get_fallback_output());
if output.is_dummy {
log::warn!("Not showing workspace because seat is on dummy output");
return;
@ -929,7 +929,7 @@ impl State {
pub fn float_map_ws(&self) -> Rc<WorkspaceNode> {
if let Some(seat) = self.seat_queue.last() {
let output = seat.get_output();
let output = seat.get_fallback_output();
if !output.is_dummy {
return output.ensure_workspace();
}

View file

@ -235,6 +235,7 @@ impl ConnectorHandler {
bar_rect: Default::default(),
bar_rect_rel: Default::default(),
bar_rect_with_separator: Default::default(),
bar_separator_rect: Default::default(),
bar_separator_rect_rel: Default::default(),
render_data: Default::default(),
state: self.state.clone(),

View file

@ -96,6 +96,7 @@ pub struct OutputNode {
pub bar_rect: Cell<Rect>,
pub bar_rect_rel: Cell<Rect>,
pub bar_rect_with_separator: Cell<Rect>,
pub bar_separator_rect: Cell<Rect>,
pub bar_separator_rect_rel: Cell<Rect>,
pub render_data: RefCell<OutputRenderData>,
pub state: Rc<State>,
@ -775,11 +776,11 @@ impl OutputNode {
let mut bar_rect = Rect::default();
let mut bar_rect_rel = Rect::default();
let mut bar_rect_with_separator = Rect::default();
let mut bar_separator_rect = Rect::default();
let mut bar_separator_rect_rel = Rect::default();
let mut workspace_rect = non_exclusive_rect;
let mut workspace_rect_rel = non_exclusive_rect_rel;
if self.state.show_bar.get() {
let bar_separator_rect;
match self.state.theme.bar_position.get() {
BarPosition::Bottom => {
workspace_rect = Rect::new_sized_saturating(x1, y1, width, height - bh - bsw);
@ -806,6 +807,7 @@ impl OutputNode {
self.bar_rect.set(bar_rect);
self.bar_rect_rel.set(bar_rect_rel);
self.bar_rect_with_separator.set(bar_rect_with_separator);
self.bar_separator_rect.set(bar_separator_rect);
self.bar_separator_rect_rel.set(bar_separator_rect_rel);
self.workspace_rect.set(workspace_rect);
self.workspace_rect_rel.set(workspace_rect_rel);
@ -1148,20 +1150,13 @@ impl OutputNode {
set_layer_visible!(self.layers[3], visible);
}
fn button(self: Rc<Self>, seat: &Rc<WlSeatGlobal>, id: PointerType) {
fn bar_button(self: &Rc<Self>, seat: &Rc<WlSeatGlobal>, x: i32, y: i32) -> bool {
if !self.state.show_bar.get() {
return;
}
let (x, y) = match self.pointer_positions.get(&id) {
Some(p) => p,
_ => return,
};
if let PointerType::Seat(s) = id {
self.pointer_down.set(s, (x, y));
return false;
}
let bar_rect_rel = self.bar_rect_rel.get();
if bar_rect_rel.not_contains(x, y) {
return;
return false;
}
let (x, _) = bar_rect_rel.translate(x, y);
let ws = 'ws: {
@ -1171,9 +1166,25 @@ impl OutputNode {
break 'ws title.ws.clone();
}
}
return;
return true;
};
self.state.show_workspace2(Some(seat), &self, &ws);
self.state.show_workspace2(Some(seat), self, &ws);
true
}
fn button(self: Rc<Self>, seat: &Rc<WlSeatGlobal>, id: PointerType) {
let (x, y) = match self.pointer_positions.get(&id) {
Some(p) => p,
_ => return,
};
if let PointerType::Seat(s) = id {
self.pointer_down.set(s, (x, y));
}
if self.bar_button(seat, x, y) {
return;
}
let ws = self.ensure_workspace();
seat.focus_node(ws);
}
pub fn update_presentation_type(&self) {
@ -1490,6 +1501,10 @@ impl OutputNode {
if c.node_visible() {
c.node_do_focus(seat, direction);
}
} else {
if ws.node_visible() {
seat.focus_node(ws);
}
}
}
}

View file

@ -366,17 +366,23 @@ impl Node for WorkspaceNode {
seat.focus_node(last);
} else if let Some(container) = self.container.get() {
container.node_do_focus(seat, direction);
} else if let Some(float) = self
} else if let Some(child) = self
.stacked
.rev_iter()
.find_map(|node| (*node).clone().node_into_float())
.filter_map(|node| (*node).clone().node_into_float())
.find_map(|float| float.child.get())
{
if let Some(child) = float.child.get() {
child.node_do_focus(seat, direction);
}
child.node_do_focus(seat, direction);
} else {
seat.focus_node(self);
}
}
fn node_active_changed(&self, _active: bool) {
let output = self.output.get();
self.state.damage(output.bar_separator_rect.get());
}
fn node_find_tree_at(
&self,
x: i32,