feat(config): Add directional output selection via separate direction field
Add support for directional output selection in the move-to-output action
using a separate `direction` field instead of overloading OutputMatch.
API additions:
- Add Workspace::connector() to get the connector showing a workspace
- Add Connector::connector_in_direction() to find outputs directionally
Implementation:
- Move directional finding logic from toml-config to compositor
- Algorithm uses center-to-center distance with axis-aligned preference
- Add GetWorkspaceConnector and GetConnectorInDirection IPC messages
Configuration changes:
- Add optional `direction` field to move-to-output action
- Either `output` or `direction` must be specified (not both)
- Valid directions: "left", "right", "up", "down"
Example usage:
logo+control+shift+right = { type = "move-to-output", direction = "right" }
Signed-off-by: Arthur Heymans <arthur@aheymans.xyz>
This commit is contained in:
parent
e81b31b452
commit
5529306c67
13 changed files with 348 additions and 25 deletions
|
|
@ -160,7 +160,8 @@ pub enum Action {
|
|||
},
|
||||
MoveToOutput {
|
||||
workspace: Option<Workspace>,
|
||||
output: OutputMatch,
|
||||
output: Option<OutputMatch>,
|
||||
direction: Option<Direction>,
|
||||
},
|
||||
SetRepeatRate {
|
||||
rate: RepeatRate,
|
||||
|
|
|
|||
|
|
@ -47,6 +47,10 @@ impl<'v> Extractor<'v> {
|
|||
self.log_unused = false;
|
||||
}
|
||||
|
||||
pub fn span(&self) -> Span {
|
||||
self.span
|
||||
}
|
||||
|
||||
pub fn extract<E: Extractable<'v>, U>(&mut self, e: E) -> Result<E::Output, Spanned<U>>
|
||||
where
|
||||
ExtractorError: Into<U>,
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ use {
|
|||
indexmap::IndexMap,
|
||||
jay_config::{
|
||||
Axis::{Horizontal, Vertical},
|
||||
get_workspace,
|
||||
Direction, get_workspace,
|
||||
input::{LayerDirection, Timeline},
|
||||
},
|
||||
thiserror::Error,
|
||||
|
|
@ -90,6 +90,10 @@ pub enum ActionParserError {
|
|||
CopyMark(#[source] MarkIdParserError),
|
||||
#[error("Could not parse a show-workspace action")]
|
||||
ShowWorkspace(#[source] OutputMatchParserError),
|
||||
#[error("Unknown direction {0}")]
|
||||
UnknownDirection(String),
|
||||
#[error("Exactly one of `output` or `direction` must be specified")]
|
||||
OutputAndDirectionMutuallyExclusive,
|
||||
}
|
||||
|
||||
pub struct ActionParser<'a>(pub &'a Context<'a>);
|
||||
|
|
@ -356,14 +360,40 @@ impl ActionParser<'_> {
|
|||
Ok(Action::ConfigureDrmDevice { dev })
|
||||
}
|
||||
|
||||
fn parse_direction(v: Spanned<&str>) -> Result<Direction, Spanned<ActionParserError>> {
|
||||
use Direction::*;
|
||||
match v.value {
|
||||
"left" => Ok(Left),
|
||||
"right" => Ok(Right),
|
||||
"up" => Ok(Up),
|
||||
"down" => Ok(Down),
|
||||
_ => Err(ActionParserError::UnknownDirection(v.value.to_string()).spanned(v.span)),
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_move_to_output(&mut self, ext: &mut Extractor<'_>) -> ParseResult<Self> {
|
||||
let (ws, output) = ext.extract((opt(str("workspace")), val("output")))?;
|
||||
let output = output
|
||||
.parse_map(&mut OutputMatchParser(self.0))
|
||||
.map_spanned_err(ActionParserError::MoveToOutput)?;
|
||||
let (ws, output_val, direction_val) = ext.extract((
|
||||
opt(str("workspace")),
|
||||
opt(val("output")),
|
||||
opt(str("direction")),
|
||||
))?;
|
||||
|
||||
// Validate that exactly one of output or direction is specified
|
||||
if output_val.is_some() == direction_val.is_some() {
|
||||
return Err(ActionParserError::OutputAndDirectionMutuallyExclusive.spanned(ext.span()));
|
||||
}
|
||||
|
||||
let output = output_val
|
||||
.map(|v| {
|
||||
v.parse(&mut OutputMatchParser(self.0))
|
||||
.map_spanned_err(ActionParserError::MoveToOutput)
|
||||
})
|
||||
.transpose()?;
|
||||
let direction = direction_val.map(Self::parse_direction).transpose()?;
|
||||
Ok(Action::MoveToOutput {
|
||||
workspace: ws.despan().map(get_workspace),
|
||||
output,
|
||||
direction,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -360,20 +360,52 @@ impl Action {
|
|||
set_idle_grace_period(period)
|
||||
}
|
||||
}),
|
||||
Action::MoveToOutput { output, workspace } => {
|
||||
Action::MoveToOutput {
|
||||
output,
|
||||
workspace,
|
||||
direction,
|
||||
} => {
|
||||
let state = state.clone();
|
||||
b.new(move || {
|
||||
let output = 'get_output: {
|
||||
for connector in connectors() {
|
||||
if connector.connected() && output.matches(connector, &state) {
|
||||
break 'get_output connector;
|
||||
let target_output = {
|
||||
// Handle directional output selection
|
||||
if let Some(direction) = direction {
|
||||
// Get the current workspace to determine the source output
|
||||
let current_ws = match workspace {
|
||||
Some(ws) => ws,
|
||||
None => s.get_workspace(),
|
||||
};
|
||||
if !current_ws.exists() {
|
||||
return;
|
||||
}
|
||||
// Get the connector that currently has this workspace
|
||||
let source_connector = current_ws.connector();
|
||||
if !source_connector.exists() {
|
||||
return;
|
||||
}
|
||||
// Find the connector in the given direction
|
||||
let target = source_connector.connector_in_direction(direction);
|
||||
if !target.exists() {
|
||||
return;
|
||||
}
|
||||
target
|
||||
} else if let Some(output) = &output {
|
||||
// Handle normal output matching
|
||||
'match_output: {
|
||||
for connector in connectors() {
|
||||
if connector.connected() && output.matches(connector, &state) {
|
||||
break 'match_output connector;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
return;
|
||||
};
|
||||
match workspace {
|
||||
Some(ws) => ws.move_to_output(output),
|
||||
None => s.move_to_output(output),
|
||||
Some(ws) => ws.move_to_output(target_output),
|
||||
None => s.move_to_output(target_output),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue