1
0
Fork 0
forked from wry/wry

config: add Seat::show_workspace_on

This commit is contained in:
Julian Orth 2025-10-07 05:02:07 +02:00
parent d2ce140f54
commit d320d2f3c1
12 changed files with 131 additions and 13 deletions

View file

@ -122,6 +122,7 @@ pub(crate) struct ConfigClient {
window_match_handlers: RefCell<HashMap<WindowMatcher, WindowMatchHandler>>, window_match_handlers: RefCell<HashMap<WindowMatcher, WindowMatchHandler>>,
feat_mod_mask: Cell<bool>, feat_mod_mask: Cell<bool>,
feat_show_workspace_on: Cell<bool>,
} }
struct ClientMatchHandler { struct ClientMatchHandler {
@ -266,6 +267,7 @@ pub unsafe extern "C" fn init(
client_match_handlers: Default::default(), client_match_handlers: Default::default(),
window_match_handlers: Default::default(), window_match_handlers: Default::default(),
feat_mod_mask: Cell::new(false), feat_mod_mask: Cell::new(false),
feat_show_workspace_on: Cell::new(false),
}); });
let init = unsafe { slice::from_raw_parts(init, size) }; let init = unsafe { slice::from_raw_parts(init, size) };
client.handle_init_msg(init); client.handle_init_msg(init);
@ -591,6 +593,18 @@ impl ConfigClient {
self.send(&ClientMessage::ShowWorkspace { seat, workspace }); self.send(&ClientMessage::ShowWorkspace { seat, workspace });
} }
pub fn show_workspace_on(&self, seat: Seat, workspace: Workspace, connector: Connector) {
if self.feat_show_workspace_on.get() && connector.connected() {
self.send(&ClientMessage::ShowWorkspaceOn {
seat,
workspace,
connector,
});
} else {
self.show_workspace(seat, workspace);
}
}
pub fn set_seat_workspace(&self, seat: Seat, workspace: Workspace) { pub fn set_seat_workspace(&self, seat: Seat, workspace: Workspace) {
self.send(&ClientMessage::SetSeatWorkspace { seat, workspace }); self.send(&ClientMessage::SetSeatWorkspace { seat, workspace });
} }
@ -2086,6 +2100,7 @@ impl ConfigClient {
match feat { match feat {
ServerFeature::NONE => {} ServerFeature::NONE => {}
ServerFeature::MOD_MASK => self.feat_mod_mask.set(true), ServerFeature::MOD_MASK => self.feat_mod_mask.set(true),
ServerFeature::SHOW_WORKSPACE_ON => self.feat_show_workspace_on.set(true),
_ => {} _ => {}
} }
} }

View file

@ -30,6 +30,7 @@ pub struct ServerFeature(u16);
impl ServerFeature { impl ServerFeature {
pub const NONE: Self = Self(0); pub const NONE: Self = Self(0);
pub const MOD_MASK: Self = Self(1); pub const MOD_MASK: Self = Self(1);
pub const SHOW_WORKSPACE_ON: Self = Self(2);
} }
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
@ -782,6 +783,11 @@ pub enum ClientMessage<'a> {
matcher: ClientMatcher, matcher: ClientMatcher,
caps: ClientCapabilities, caps: ClientCapabilities,
}, },
ShowWorkspaceOn {
seat: Seat,
workspace: Workspace,
connector: Connector,
},
} }
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]

View file

@ -439,6 +439,15 @@ impl Seat {
get!().show_workspace(self, workspace) get!().show_workspace(self, workspace)
} }
/// Shows the workspace and sets the keyboard focus of the seat to that workspace.
///
/// If the workspace doesn't currently exist and the connector is connected, the
/// workspace is created on the given connector. If the connector is not connected,
/// the workspace is created on the output that contains the seat's cursor.
pub fn show_workspace_on(self, workspace: Workspace, connector: Connector) {
get!().show_workspace_on(self, workspace, connector)
}
/// Moves the currently focused window to the workspace. /// Moves the currently focused window to the workspace.
pub fn set_workspace(self, workspace: Workspace) { pub fn set_workspace(self, workspace: Workspace) {
get!().set_seat_workspace(self, workspace) get!().set_seat_workspace(self, workspace)

View file

@ -280,7 +280,7 @@ impl ConfigProxy {
pub fn configure(&self, reload: bool) { pub fn configure(&self, reload: bool) {
self.send(&ServerMessage::Features { self.send(&ServerMessage::Features {
features: vec![ServerFeature::MOD_MASK], features: vec![ServerFeature::MOD_MASK, ServerFeature::SHOW_WORKSPACE_ON],
}); });
self.send(&ServerMessage::Configure { reload }); self.send(&ServerMessage::Configure { reload });
} }

View file

@ -1006,10 +1006,16 @@ impl ConfigProxyHandler {
Ok(()) Ok(())
} }
fn handle_show_workspace(&self, seat: Seat, ws: Workspace) -> Result<(), CphError> { fn handle_show_workspace(
&self,
seat: Seat,
ws: Workspace,
output: Option<Connector>,
) -> Result<(), CphError> {
let seat = self.get_seat(seat)?; let seat = self.get_seat(seat)?;
let name = self.get_workspace(ws)?; let name = self.get_workspace(ws)?;
self.state.show_workspace(&seat, &name); let output = output.map(|o| self.get_output_node(o)).transpose()?;
self.state.show_workspace(&seat, &name, output);
Ok(()) Ok(())
} }
@ -2725,7 +2731,7 @@ impl ConfigProxyHandler {
} }
ClientMessage::GetWorkspace { name } => self.handle_get_workspace(name), ClientMessage::GetWorkspace { name } => self.handle_get_workspace(name),
ClientMessage::ShowWorkspace { seat, workspace } => self ClientMessage::ShowWorkspace { seat, workspace } => self
.handle_show_workspace(seat, workspace) .handle_show_workspace(seat, workspace, None)
.wrn("show_workspace")?, .wrn("show_workspace")?,
ClientMessage::SetSeatWorkspace { seat, workspace } => self ClientMessage::SetSeatWorkspace { seat, workspace } => self
.handle_set_seat_workspace(seat, workspace) .handle_set_seat_workspace(seat, workspace)
@ -3203,6 +3209,13 @@ impl ConfigProxyHandler {
ClientMessage::SetClientMatcherBoundingCapabilities { matcher, caps } => self ClientMessage::SetClientMatcherBoundingCapabilities { matcher, caps } => self
.handle_set_client_matcher_bounding_capabilities(matcher, caps) .handle_set_client_matcher_bounding_capabilities(matcher, caps)
.wrn("set_client_matcher_bounding_capabilities")?, .wrn("set_client_matcher_bounding_capabilities")?,
ClientMessage::ShowWorkspaceOn {
seat,
workspace,
connector,
} => self
.handle_show_workspace(seat, workspace, Some(connector))
.wrn("show_workspace_on")?,
} }
Ok(()) Ok(())
} }

View file

@ -906,11 +906,16 @@ impl State {
} }
} }
pub fn show_workspace(&self, seat: &Rc<WlSeatGlobal>, name: &str) { pub fn show_workspace(
&self,
seat: &Rc<WlSeatGlobal>,
name: &str,
output: Option<Rc<OutputNode>>,
) {
let ws = match self.workspaces.get(name) { let ws = match self.workspaces.get(name) {
Some(ws) => ws, Some(ws) => ws,
_ => { _ => {
let output = seat.get_output(); let output = output.unwrap_or_else(|| seat.get_output());
if output.is_dummy { if output.is_dummy {
log::warn!("Not showing workspace because seat is on dummy output"); log::warn!("Not showing workspace because seat is on dummy output");
return; return;

View file

@ -141,6 +141,7 @@ pub enum Action {
}, },
ShowWorkspace { ShowWorkspace {
name: String, name: String,
output: Option<OutputMatch>,
}, },
SimpleCommand { SimpleCommand {
cmd: SimpleCommand, cmd: SimpleCommand,

View file

@ -88,6 +88,8 @@ pub enum ActionParserError {
JumpToMark(#[source] MarkIdParserError), JumpToMark(#[source] MarkIdParserError),
#[error("Could not parse a copy-mark action")] #[error("Could not parse a copy-mark action")]
CopyMark(#[source] MarkIdParserError), CopyMark(#[source] MarkIdParserError),
#[error("Could not parse a show-workspace action")]
ShowWorkspace(#[source] OutputMatchParserError),
} }
pub struct ActionParser<'a>(pub &'a Context<'a>); pub struct ActionParser<'a>(pub &'a Context<'a>);
@ -184,8 +186,15 @@ impl ActionParser<'_> {
} }
fn parse_show_workspace(&mut self, ext: &mut Extractor<'_>) -> ParseResult<Self> { fn parse_show_workspace(&mut self, ext: &mut Extractor<'_>) -> ParseResult<Self> {
let name = ext.extract(str("name"))?.value.to_string(); let (name, output) = ext.extract((str("name"), opt(val("output"))))?;
Ok(Action::ShowWorkspace { name }) let name = name.value.to_string();
let output = output
.map(|o| {
o.parse_map(&mut OutputMatchParser(self.0))
.map_spanned_err(ActionParserError::ShowWorkspace)
})
.transpose()?;
Ok(Action::ShowWorkspace { name, output })
} }
fn parse_move_to_workspace(&mut self, ext: &mut Extractor<'_>) -> ParseResult<Self> { fn parse_move_to_workspace(&mut self, ext: &mut Extractor<'_>) -> ParseResult<Self> {

View file

@ -235,9 +235,26 @@ impl Action {
} }
Action::Exec { exec } => b.new(move || create_command(&exec).spawn()), Action::Exec { exec } => b.new(move || create_command(&exec).spawn()),
Action::SwitchToVt { num } => b.new(move || switch_to_vt(num)), Action::SwitchToVt { num } => b.new(move || switch_to_vt(num)),
Action::ShowWorkspace { name } => { Action::ShowWorkspace { name, output } => {
let workspace = get_workspace(&name); let workspace = get_workspace(&name);
b.new(move || s.show_workspace(workspace)) let state = state.clone();
b.new(move || {
let output = 'get_output: {
let Some(output) = &output else {
break 'get_output None;
};
for connector in connectors() {
if connector.connected() && output.matches(connector, &state) {
break 'get_output Some(connector);
}
}
None
};
match output {
Some(o) => s.show_workspace_on(workspace, o),
_ => s.show_workspace(workspace),
}
})
} }
Action::MoveToWorkspace { name } => { Action::MoveToWorkspace { name } => {
let workspace = get_workspace(&name); let workspace = get_workspace(&name);

View file

@ -125,7 +125,7 @@
] ]
}, },
{ {
"description": "Switches to a workspace.\n\n- Example:\n\n ```toml\n [shortcuts]\n alt-F1 = { type = \"show-workspace\", name = \"1\" }\n ```\n", "description": "Switches to a workspace.\n\n- Example 1:\n\n ```toml\n [shortcuts]\n alt-F1 = { type = \"show-workspace\", name = \"1\" }\n ```\n\n- Example 2:\n\n ```toml\n [shortcuts]\n alt-F1 = { type = \"show-workspace\", name = \"1\", output.name = \"left\" }\n ```\n",
"type": "object", "type": "object",
"properties": { "properties": {
"type": { "type": {
@ -134,6 +134,10 @@
"name": { "name": {
"type": "string", "type": "string",
"description": "The name of the workspace." "description": "The name of the workspace."
},
"output": {
"description": "The output to show a newly created workspace on. This has no effect on\nworkspaces that already exist.\n\nIf this is not set, then a new workspace is shown on the output that\ncontains the cursor.\n\nIf multiple outputs match, the workspace is shown on the first matching\noutput.\n",
"$ref": "#/$defs/OutputMatch"
} }
}, },
"required": [ "required": [

View file

@ -232,12 +232,19 @@ This table is a tagged union. The variant is determined by the `type` field. It
Switches to a workspace. Switches to a workspace.
- Example: - Example 1:
```toml ```toml
[shortcuts] [shortcuts]
alt-F1 = { type = "show-workspace", name = "1" } alt-F1 = { type = "show-workspace", name = "1" }
``` ```
- Example 2:
```toml
[shortcuts]
alt-F1 = { type = "show-workspace", name = "1", output.name = "left" }
```
The table has the following fields: The table has the following fields:
@ -247,6 +254,19 @@ This table is a tagged union. The variant is determined by the `type` field. It
The value of this field should be a string. The value of this field should be a string.
- `output` (optional):
The output to show a newly created workspace on. This has no effect on
workspaces that already exist.
If this is not set, then a new workspace is shown on the output that
contains the cursor.
If multiple outputs match, the workspace is shown on the first matching
output.
The value of this field should be a [OutputMatch](#types-OutputMatch).
- `move-to-workspace`: - `move-to-workspace`:
Moves the currently focused window to a workspace. Moves the currently focused window to a workspace.

View file

@ -239,17 +239,36 @@ Action:
description: | description: |
Switches to a workspace. Switches to a workspace.
- Example: - Example 1:
```toml ```toml
[shortcuts] [shortcuts]
alt-F1 = { type = "show-workspace", name = "1" } alt-F1 = { type = "show-workspace", name = "1" }
``` ```
- Example 2:
```toml
[shortcuts]
alt-F1 = { type = "show-workspace", name = "1", output.name = "left" }
```
fields: fields:
name: name:
description: The name of the workspace. description: The name of the workspace.
required: true required: true
kind: string kind: string
output:
description: |
The output to show a newly created workspace on. This has no effect on
workspaces that already exist.
If this is not set, then a new workspace is shown on the output that
contains the cursor.
If multiple outputs match, the workspace is shown on the first matching
output.
required: false
ref: OutputMatch
move-to-workspace: move-to-workspace:
description: | description: |
Moves the currently focused window to a workspace. Moves the currently focused window to a workspace.