1
0
Fork 0
forked from wry/wry

config: add fallback output mode

This commit is contained in:
khyperia 2025-12-23 14:57:02 +01:00 committed by Julian Orth
parent a975e3b25a
commit dd3f8bad40
16 changed files with 215 additions and 19 deletions

View file

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

View file

@ -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,
@ -826,6 +827,10 @@ pub enum ClientMessage<'a> {
groups: Option<Vec<Group<'a>>>,
options: Option<Vec<&'a str>>,
},
SetFallbackOutputMode {
seat: Seat,
mode: FallbackOutputMode,
},
}
#[derive(Serialize, Deserialize, Debug)]

View file

@ -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<Seat> {
get!().seats()

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,
@ -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_cursor_output().create_workspace(name.deref()),
_ => seat.get_fallback_output().create_workspace(name.deref()),
};
seat.set_workspace(&workspace);
Ok(())
@ -1112,11 +1122,12 @@ impl ConfigProxyHandler {
Some(ws) => ws,
_ => return Ok(()),
},
WorkspaceSource::Seat(s) => match self.get_seat(s)?.get_cursor_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(())
@ -3355,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(),
@ -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,
@ -1393,6 +1407,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_cursor_output()
.node_do_focus(&seat, Direction::Unspecified);
output.node_do_focus(&seat, Direction::Unspecified);
}
}
}

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_cursor_output();
let output = seat.get_fallback_output();
if !output.is_dummy {
break 'get_output output.global.opt.clone();
}

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_cursor_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_cursor_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_cursor_output();
let output = seat.get_fallback_output();
if !output.is_dummy {
return output.ensure_workspace();
}

View file

@ -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<String, InputMode>,
pub workspace_display_order: Option<WorkspaceDisplayOrder>,
pub simple_im: Option<SimpleIm>,
pub fallback_output_mode: Option<FallbackOutputMode>,
}
#[derive(Debug, Error)]

View file

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

View file

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

View file

@ -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<Self> {
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)
}
}

View file

@ -1627,6 +1627,9 @@ fn load_config(initial_load: bool, auto_reload: bool, persistent: &Rc<Persistent
persistent.seat.set_simple_im_enabled(enabled);
}
}
if let Some(v) = config.fallback_output_mode {
persistent.seat.set_fallback_output_mode(v);
}
}
fn create_command(exec: &Exec) -> Command {

View file

@ -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",

View file

@ -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).
<a name="types-Connector"></a>
### `Connector`
@ -2694,6 +2708,28 @@ The table has the following fields:
The value of this field should be a boolean.
<a name="types-FallbackOutputMode"></a>
### `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).
<a name="types-Float"></a>
### `Float`

View file

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