diff --git a/jay-config/src/_private/client.rs b/jay-config/src/_private/client.rs index 31172667..a9196cf8 100644 --- a/jay-config/src/_private/client.rs +++ b/jay-config/src/_private/client.rs @@ -15,8 +15,9 @@ use { client::{Client, ClientCapabilities, ClientCriterion, ClientMatcher, MatchedClient}, exec::Command, input::{ - FocusFollowsMouseMode, InputDevice, LayerDirection, Seat, SwitchEvent, Timeline, - acceleration::AccelProfile, capability::Capability, clickmethod::ClickMethod, + FallbackOutputMode, FocusFollowsMouseMode, InputDevice, LayerDirection, Seat, + SwitchEvent, Timeline, acceleration::AccelProfile, capability::Capability, + clickmethod::ClickMethod, }, keyboard::{ Group, Keymap, @@ -551,9 +552,9 @@ impl ConfigClient { connector } - pub fn get_seat_workspace(&self, seat: Seat) -> Workspace { - let res = self.send_with_response(&ClientMessage::GetSeatWorkspace { seat }); - get_response!(res, Workspace(0), GetSeatWorkspace { workspace }); + pub fn get_seat_cursor_workspace(&self, seat: Seat) -> Workspace { + let res = self.send_with_response(&ClientMessage::GetSeatCursorWorkspace { seat }); + get_response!(res, Workspace(0), GetSeatCursorWorkspace { workspace }); workspace } @@ -1364,6 +1365,10 @@ impl ConfigClient { self.send(&ClientMessage::SetFocusFollowsMouseMode { seat, mode }) } + pub fn set_fallback_output_mode(&self, seat: Seat, mode: FallbackOutputMode) { + self.send(&ClientMessage::SetFallbackOutputMode { seat, mode }) + } + pub fn set_window_management_enabled(&self, seat: Seat, enabled: bool) { self.send(&ClientMessage::SetWindowManagementEnabled { seat, enabled }) } diff --git a/jay-config/src/_private/ipc.rs b/jay-config/src/_private/ipc.rs index 1b89c8b9..91d53955 100644 --- a/jay-config/src/_private/ipc.rs +++ b/jay-config/src/_private/ipc.rs @@ -4,8 +4,9 @@ use { Axis, Direction, PciId, Workspace, client::{Client, ClientCapabilities, ClientMatcher}, input::{ - FocusFollowsMouseMode, InputDevice, LayerDirection, Seat, SwitchEvent, Timeline, - acceleration::AccelProfile, capability::Capability, clickmethod::ClickMethod, + FallbackOutputMode, FocusFollowsMouseMode, InputDevice, LayerDirection, Seat, + SwitchEvent, Timeline, acceleration::AccelProfile, capability::Capability, + clickmethod::ClickMethod, }, keyboard::{Group, Keymap, mods::Modifiers, syms::KeySym}, logging::LogLevel, @@ -372,7 +373,7 @@ pub enum ClientMessage<'a> { MakeRenderDevice { device: DrmDevice, }, - GetSeatWorkspace { + GetSeatCursorWorkspace { seat: Seat, }, SetDefaultWorkspaceCapture { @@ -826,6 +827,10 @@ pub enum ClientMessage<'a> { groups: Option>>, options: Option>, }, + SetFallbackOutputMode { + seat: Seat, + mode: FallbackOutputMode, + }, } #[derive(Serialize, Deserialize, Debug)] @@ -925,7 +930,7 @@ pub enum Response { width: i32, height: i32, }, - GetSeatWorkspace { + GetSeatCursorWorkspace { workspace: Workspace, }, GetDefaultWorkspaceCapture { diff --git a/jay-config/src/input.rs b/jay-config/src/input.rs index 4672663c..2e985766 100644 --- a/jay-config/src/input.rs +++ b/jay-config/src/input.rs @@ -420,7 +420,7 @@ impl Seat { /// /// If no such workspace exists, `exists` returns `false` for the returned workspace. pub fn get_workspace(self) -> Workspace { - get!(Workspace(0)).get_seat_workspace(self) + get!(Workspace(0)).get_seat_cursor_workspace(self) } /// Returns the workspace that is currently active on the output that contains the seat's @@ -503,6 +503,13 @@ impl Seat { get!().set_focus_follows_mouse_mode(self, mode); } + /// Sets the fallback output mode. + /// + /// The default is `Cursor`. + pub fn set_fallback_output_mode(self, mode: FallbackOutputMode) { + get!().set_fallback_output_mode(self, mode); + } + /// Enables or disable window management mode. /// /// In window management mode, floating windows can be moved by pressing the left @@ -650,6 +657,19 @@ pub enum FocusFollowsMouseMode { False, } +/// Defines which output is used when no particular output is specified. +/// +/// This configures where to place a newly opened window or workspace, what window to focus when a +/// window is closed, which workspace is moved with [`Seat::move_to_output`], and similar actions. +#[derive(Serialize, Deserialize, Copy, Clone, Debug, Hash, Eq, PartialEq)] +#[non_exhaustive] +pub enum FallbackOutputMode { + /// Use the output the cursor is on. + Cursor, + /// Use the output the focus is on (highlighted window). + Focus, +} + /// Returns all seats. pub fn get_seats() -> Vec { get!().seats() diff --git a/src/compositor.rs b/src/compositor.rs index 8cb3ae8b..0e333e56 100644 --- a/src/compositor.rs +++ b/src/compositor.rs @@ -728,6 +728,7 @@ fn create_dummy_output(state: &Rc) { 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(), diff --git a/src/config/handler.rs b/src/config/handler.rs index cc5f1eb5..d32f608b 100644 --- a/src/config/handler.rs +++ b/src/config/handler.rs @@ -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(()) } diff --git a/src/ifs/wl_seat.rs b/src/ifs/wl_seat.rs index b746e3dc..27ced5da 100644 --- a/src/ifs/wl_seat.rs +++ b/src/ifs/wl_seat.rs @@ -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>>, forward: Cell, focus_follows_mouse: Cell, + fallback_output_mode: Cell, swipe_bindings: PerClientBindings, pinch_bindings: PerClientBindings, hold_bindings: PerClientBindings, @@ -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 { + pub fn get_cursor_output(&self) -> Rc { 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 { + 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) { let tl = match self.keyboard_node.get().node_toplevel() { Some(tl) => tl, @@ -726,7 +740,16 @@ impl WlSeatGlobal { pub fn move_focus(self: &Rc, 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, 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, enabled: bool) { self.pointer_owner .set_window_management_enabled(self, enabled); diff --git a/src/ifs/wl_seat/event_handling.rs b/src/ifs/wl_seat/event_handling.rs index ddec3ad8..9764c496 100644 --- a/src/ifs/wl_seat/event_handling.rs +++ b/src/ifs/wl_seat/event_handling.rs @@ -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); } } } diff --git a/src/ifs/wl_surface/xdg_surface/xdg_toplevel.rs b/src/ifs/wl_surface/xdg_surface/xdg_toplevel.rs index 8a2808e7..8a74a8c2 100644 --- a/src/ifs/wl_surface/xdg_surface/xdg_toplevel.rs +++ b/src/ifs/wl_surface/xdg_surface/xdg_toplevel.rs @@ -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()); diff --git a/src/ifs/xdg_toplevel_drag_v1.rs b/src/ifs/xdg_toplevel_drag_v1.rs index 61d4590c..3234be47 100644 --- a/src/ifs/xdg_toplevel_drag_v1.rs +++ b/src/ifs/xdg_toplevel_drag_v1.rs @@ -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( diff --git a/src/ifs/zwlr_layer_shell_v1.rs b/src/ifs/zwlr_layer_shell_v1.rs index 53e63178..f1e59041 100644 --- a/src/ifs/zwlr_layer_shell_v1.rs +++ b/src/ifs/zwlr_layer_shell_v1.rs @@ -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(); } diff --git a/src/it/tests/t0007_subsurface/screenshot_1.qoi b/src/it/tests/t0007_subsurface/screenshot_1.qoi index eca5ddef..230c0408 100644 Binary files a/src/it/tests/t0007_subsurface/screenshot_1.qoi and b/src/it/tests/t0007_subsurface/screenshot_1.qoi differ diff --git a/src/it/tests/t0007_subsurface/screenshot_2.qoi b/src/it/tests/t0007_subsurface/screenshot_2.qoi index ea65888d..722271f6 100644 Binary files a/src/it/tests/t0007_subsurface/screenshot_2.qoi and b/src/it/tests/t0007_subsurface/screenshot_2.qoi differ diff --git a/src/it/tests/t0029_double_click_float.rs b/src/it/tests/t0029_double_click_float.rs index dbce7c08..a2c71b92 100644 --- a/src/it/tests/t0029_double_click_float.rs +++ b/src/it/tests/t0029_double_click_float.rs @@ -18,6 +18,7 @@ async fn test(run: Rc) -> 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(); diff --git a/src/it/tests/t0029_double_click_float/screenshot_1.qoi b/src/it/tests/t0029_double_click_float/screenshot_1.qoi index f49edd4d..dd974ccf 100644 Binary files a/src/it/tests/t0029_double_click_float/screenshot_1.qoi and b/src/it/tests/t0029_double_click_float/screenshot_1.qoi differ diff --git a/src/it/tests/t0029_double_click_float/screenshot_2.qoi b/src/it/tests/t0029_double_click_float/screenshot_2.qoi index dd974ccf..f49edd4d 100644 Binary files a/src/it/tests/t0029_double_click_float/screenshot_2.qoi and b/src/it/tests/t0029_double_click_float/screenshot_2.qoi differ diff --git a/src/it/tests/t0037_toplevel_drag/screenshot_1.qoi b/src/it/tests/t0037_toplevel_drag/screenshot_1.qoi index 65d5cd35..9ec55fdc 100644 Binary files a/src/it/tests/t0037_toplevel_drag/screenshot_1.qoi and b/src/it/tests/t0037_toplevel_drag/screenshot_1.qoi differ diff --git a/src/renderer.rs b/src/renderer.rs index 6f7efd46..2c94020f 100644 --- a/src/renderer.rs +++ b/src/renderer.rs @@ -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); diff --git a/src/state.rs b/src/state.rs index 94f5f8d7..85cdba2b 100644 --- a/src/state.rs +++ b/src/state.rs @@ -780,7 +780,7 @@ impl State { pub fn ensure_map_workspace(&self, seat: Option<&Rc>) -> Rc { 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 { 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(); } diff --git a/src/tasks/connector.rs b/src/tasks/connector.rs index 4b24cba5..85753771 100644 --- a/src/tasks/connector.rs +++ b/src/tasks/connector.rs @@ -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(), diff --git a/src/tree/output.rs b/src/tree/output.rs index 8e7565ff..b2dee56e 100644 --- a/src/tree/output.rs +++ b/src/tree/output.rs @@ -96,6 +96,7 @@ pub struct OutputNode { pub bar_rect: Cell, pub bar_rect_rel: Cell, pub bar_rect_with_separator: Cell, + pub bar_separator_rect: Cell, pub bar_separator_rect_rel: Cell, pub render_data: RefCell, pub state: Rc, @@ -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, seat: &Rc, id: PointerType) { + fn bar_button(self: &Rc, seat: &Rc, 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, seat: &Rc, 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); + } } } } diff --git a/src/tree/workspace.rs b/src/tree/workspace.rs index df0e43cf..eb308c08 100644 --- a/src/tree/workspace.rs +++ b/src/tree/workspace.rs @@ -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, diff --git a/toml-config/src/config.rs b/toml-config/src/config.rs index 182ebaf4..9dc6daa0 100644 --- a/toml-config/src/config.rs +++ b/toml-config/src/config.rs @@ -27,7 +27,7 @@ use { Axis, Direction, Workspace, client::ClientCapabilities, input::{ - LayerDirection, SwitchEvent, Timeline, acceleration::AccelProfile, + FallbackOutputMode, LayerDirection, SwitchEvent, Timeline, acceleration::AccelProfile, clickmethod::ClickMethod, }, keyboard::{Keymap, ModifiedKeySym, mods::Modifiers, syms::KeySym}, @@ -537,6 +537,7 @@ pub struct Config { pub input_modes: AHashMap, pub workspace_display_order: Option, pub simple_im: Option, + pub fallback_output_mode: Option, } #[derive(Debug, Error)] diff --git a/toml-config/src/config/parsers.rs b/toml-config/src/config/parsers.rs index c786c71d..1a4319c3 100644 --- a/toml-config/src/config/parsers.rs +++ b/toml-config/src/config/parsers.rs @@ -21,6 +21,7 @@ mod drm_device; mod drm_device_match; mod env; pub mod exec; +mod fallback_output_mode; pub mod float; pub mod focus_history; mod format; diff --git a/toml-config/src/config/parsers/config.rs b/toml-config/src/config/parsers/config.rs index 92e2c067..3ab5f655 100644 --- a/toml-config/src/config/parsers/config.rs +++ b/toml-config/src/config/parsers/config.rs @@ -15,6 +15,7 @@ use { drm_device::DrmDevicesParser, drm_device_match::DrmDeviceMatchParser, env::EnvParser, + fallback_output_mode::FallbackOutputModeParser, float::FloatParser, focus_history::FocusHistoryParser, gfx_api::GfxApiParser, @@ -147,6 +148,7 @@ impl Parser for ConfigParser<'_> { auto_reload, simple_im_val, show_titles, + fallback_output_mode_val, ), ) = ext.extract(( ( @@ -204,6 +206,7 @@ impl Parser for ConfigParser<'_> { recover(opt(bol("auto-reload"))), opt(val("simple-im")), recover(opt(bol("show-titles"))), + opt(val("fallback-output-mode")), ), ))?; let mut keymap = None; @@ -524,6 +527,18 @@ impl Parser for ConfigParser<'_> { } } } + let mut fallback_output_mode = None; + if let Some(value) = fallback_output_mode_val { + match value.parse(&mut FallbackOutputModeParser) { + Ok(v) => fallback_output_mode = Some(v), + Err(e) => { + log::warn!( + "Could not parse the fallback output mode: {}", + self.0.error(e) + ); + } + } + } Ok(Config { keymap, repeat_rate, @@ -570,6 +585,7 @@ impl Parser for ConfigParser<'_> { input_modes, workspace_display_order, simple_im, + fallback_output_mode, }) } } diff --git a/toml-config/src/config/parsers/fallback_output_mode.rs b/toml-config/src/config/parsers/fallback_output_mode.rs new file mode 100644 index 00000000..3918a5db --- /dev/null +++ b/toml-config/src/config/parsers/fallback_output_mode.rs @@ -0,0 +1,38 @@ +use { + crate::{ + config::parser::{DataType, ParseResult, Parser, UnexpectedDataType}, + toml::toml_span::{Span, SpannedExt}, + }, + jay_config::input::FallbackOutputMode, + thiserror::Error, +}; + +pub struct FallbackOutputModeParser; + +#[derive(Debug, Error)] +pub enum FallbackOutputModeParserError { + #[error(transparent)] + DataType(#[from] UnexpectedDataType), + #[error("Unknown mode {0}")] + Unknown(String), +} + +impl Parser for FallbackOutputModeParser { + type Value = FallbackOutputMode; + type Error = FallbackOutputModeParserError; + const EXPECTED: &'static [DataType] = &[DataType::String]; + + fn parse_string(&mut self, span: Span, string: &str) -> ParseResult { + use FallbackOutputMode::*; + let api = match string.to_ascii_lowercase().as_str() { + "cursor" => Cursor, + "focus" => Focus, + _ => { + return Err( + FallbackOutputModeParserError::Unknown(string.to_string()).spanned(span) + ); + } + }; + Ok(api) + } +} diff --git a/toml-config/src/lib.rs b/toml-config/src/lib.rs index 5138c8c8..77420126 100644 --- a/toml-config/src/lib.rs +++ b/toml-config/src/lib.rs @@ -1627,6 +1627,9 @@ fn load_config(initial_load: bool, auto_reload: bool, persistent: &Rc Command { diff --git a/toml-spec/spec/spec.generated.json b/toml-spec/spec/spec.generated.json index 7b9ee650..c855205a 100644 --- a/toml-spec/spec/spec.generated.json +++ b/toml-spec/spec/spec.generated.json @@ -1068,6 +1068,10 @@ "simple-im": { "description": "Configures the simple, XCompose based input method.\n\nBy default, the input method is enabled. \n\nEven if the input method is enabled, it will only be used if there is no\nrunning external IM.\n\n- Example:\n\n ```toml\n [simple-im]\n enabled = false\n ```\n", "$ref": "#/$defs/SimpleIm" + }, + "fallback-output-mode": { + "description": "Sets the fallback output mode.\n\nThe default is `cursor`.\n\n- Example:\n\n ```toml\n fallback-output-mode = \"focus\"\n ```\n", + "$ref": "#/$defs/FallbackOutputMode" } }, "required": [] @@ -1284,6 +1288,14 @@ } ] }, + "FallbackOutputMode": { + "type": "string", + "description": "Defines which output is used when no particular output is specified.\n\nThis configures where to place a newly opened window or workspace, what window to focus when a\nwindow is closed, which workspace is moved with move-to-output, and similar actions.\n", + "enum": [ + "cursor", + "focus" + ] + }, "Float": { "description": "Describes settings of floating windows.\n\n- Example:\n\n ```toml\n [float]\n show-pin-icon = true\n ```\n", "type": "object", diff --git a/toml-spec/spec/spec.generated.md b/toml-spec/spec/spec.generated.md index fbbf6236..369cb4c7 100644 --- a/toml-spec/spec/spec.generated.md +++ b/toml-spec/spec/spec.generated.md @@ -2218,6 +2218,20 @@ The table has the following fields: The value of this field should be a [SimpleIm](#types-SimpleIm). +- `fallback-output-mode` (optional): + + Sets the fallback output mode. + + The default is `cursor`. + + - Example: + + ```toml + fallback-output-mode = "focus" + ``` + + The value of this field should be a [FallbackOutputMode](#types-FallbackOutputMode). + ### `Connector` @@ -2694,6 +2708,28 @@ The table has the following fields: The value of this field should be a boolean. + +### `FallbackOutputMode` + +Defines which output is used when no particular output is specified. + +This configures where to place a newly opened window or workspace, what window to focus when a +window is closed, which workspace is moved with move-to-output, and similar actions. + +Values of this type should be strings. + +The string should have one of the following values: + +- `cursor`: + + Use the output the cursor is on. + +- `focus`: + + Use the output the focus is on (highlighted window). + + + ### `Float` diff --git a/toml-spec/spec/spec.yaml b/toml-spec/spec/spec.yaml index 9e0296bb..14d3ec17 100644 --- a/toml-spec/spec/spec.yaml +++ b/toml-spec/spec/spec.yaml @@ -2986,6 +2986,19 @@ Config: [simple-im] enabled = false ``` + fallback-output-mode: + ref: FallbackOutputMode + required: false + description: | + Sets the fallback output mode. + + The default is `cursor`. + + - Example: + + ```toml + fallback-output-mode = "focus" + ``` Idle: @@ -4351,3 +4364,17 @@ BarPosition: description: The bar is at the top of the output. - value: bottom description: The bar is at the bottom of the output. + + +FallbackOutputMode: + kind: string + description: | + Defines which output is used when no particular output is specified. + + This configures where to place a newly opened window or workspace, what window to focus when a + window is closed, which workspace is moved with move-to-output, and similar actions. + values: + - value: cursor + description: Use the output the cursor is on. + - value: focus + description: Use the output the focus is on (highlighted window).