1
0
Fork 0
forked from wry/wry

Merge pull request #631 from mahkoh/jorth/show-workspace-on

config: add Seat::show_workspace_on
This commit is contained in:
mahkoh 2025-10-07 06:17:54 +02:00 committed by GitHub
commit f17800517e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
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>>,
feat_mod_mask: Cell<bool>,
feat_show_workspace_on: Cell<bool>,
}
struct ClientMatchHandler {
@ -266,6 +267,7 @@ pub unsafe extern "C" fn init(
client_match_handlers: Default::default(),
window_match_handlers: Default::default(),
feat_mod_mask: Cell::new(false),
feat_show_workspace_on: Cell::new(false),
});
let init = unsafe { slice::from_raw_parts(init, size) };
client.handle_init_msg(init);
@ -591,6 +593,18 @@ impl ConfigClient {
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) {
self.send(&ClientMessage::SetSeatWorkspace { seat, workspace });
}
@ -2086,6 +2100,7 @@ impl ConfigClient {
match feat {
ServerFeature::NONE => {}
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 {
pub const NONE: Self = Self(0);
pub const MOD_MASK: Self = Self(1);
pub const SHOW_WORKSPACE_ON: Self = Self(2);
}
#[derive(Serialize, Deserialize, Debug)]
@ -782,6 +783,11 @@ pub enum ClientMessage<'a> {
matcher: ClientMatcher,
caps: ClientCapabilities,
},
ShowWorkspaceOn {
seat: Seat,
workspace: Workspace,
connector: Connector,
},
}
#[derive(Serialize, Deserialize, Debug)]

View file

@ -439,6 +439,15 @@ impl Seat {
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.
pub fn set_workspace(self, workspace: Workspace) {
get!().set_seat_workspace(self, workspace)

View file

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

View file

@ -1006,10 +1006,16 @@ impl ConfigProxyHandler {
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 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(())
}
@ -2725,7 +2731,7 @@ impl ConfigProxyHandler {
}
ClientMessage::GetWorkspace { name } => self.handle_get_workspace(name),
ClientMessage::ShowWorkspace { seat, workspace } => self
.handle_show_workspace(seat, workspace)
.handle_show_workspace(seat, workspace, None)
.wrn("show_workspace")?,
ClientMessage::SetSeatWorkspace { seat, workspace } => self
.handle_set_seat_workspace(seat, workspace)
@ -3203,6 +3209,13 @@ impl ConfigProxyHandler {
ClientMessage::SetClientMatcherBoundingCapabilities { matcher, caps } => self
.handle_set_client_matcher_bounding_capabilities(matcher, caps)
.wrn("set_client_matcher_bounding_capabilities")?,
ClientMessage::ShowWorkspaceOn {
seat,
workspace,
connector,
} => self
.handle_show_workspace(seat, workspace, Some(connector))
.wrn("show_workspace_on")?,
}
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) {
Some(ws) => ws,
_ => {
let output = seat.get_output();
let output = output.unwrap_or_else(|| seat.get_output());
if output.is_dummy {
log::warn!("Not showing workspace because seat is on dummy output");
return;

View file

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

View file

@ -88,6 +88,8 @@ pub enum ActionParserError {
JumpToMark(#[source] MarkIdParserError),
#[error("Could not parse a copy-mark action")]
CopyMark(#[source] MarkIdParserError),
#[error("Could not parse a show-workspace action")]
ShowWorkspace(#[source] OutputMatchParserError),
}
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> {
let name = ext.extract(str("name"))?.value.to_string();
Ok(Action::ShowWorkspace { name })
let (name, output) = ext.extract((str("name"), opt(val("output"))))?;
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> {

View file

@ -235,9 +235,26 @@ impl Action {
}
Action::Exec { exec } => b.new(move || create_command(&exec).spawn()),
Action::SwitchToVt { num } => b.new(move || switch_to_vt(num)),
Action::ShowWorkspace { name } => {
Action::ShowWorkspace { name, output } => {
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 } => {
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",
"properties": {
"type": {
@ -134,6 +134,10 @@
"name": {
"type": "string",
"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": [

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.
- Example:
- Example 1:
```toml
[shortcuts]
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:
@ -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.
- `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`:
Moves the currently focused window to a workspace.

View file

@ -239,17 +239,36 @@ Action:
description: |
Switches to a workspace.
- Example:
- Example 1:
```toml
[shortcuts]
alt-F1 = { type = "show-workspace", name = "1" }
```
- Example 2:
```toml
[shortcuts]
alt-F1 = { type = "show-workspace", name = "1", output.name = "left" }
```
fields:
name:
description: The name of the workspace.
required: true
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:
description: |
Moves the currently focused window to a workspace.