From fecfd24ba078776b62a2ab24f8801ae9859d6f13 Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Sun, 17 Mar 2024 13:37:00 +0100 Subject: [PATCH] config: add move-to-output action --- .github/workflows/toml-spec.yml | 2 +- jay-config/src/_private/client.rs | 9 +- jay-config/src/_private/ipc.rs | 10 ++ jay-config/src/input.rs | 8 +- jay-config/src/lib.rs | 9 +- src/compositor.rs | 5 +- src/config/handler.rs | 52 ++++++++- src/tree/output.rs | 5 +- src/tree/workspace.rs | 11 +- toml-config/src/config.rs | 86 +++++++++++---- toml-config/src/config/parsers/action.rs | 128 ++++++++++++++++------- toml-config/src/lib.rs | 17 +++ toml-spec/spec/spec.generated.json | 21 ++++ toml-spec/spec/spec.generated.md | 37 +++++++ toml-spec/spec/spec.yaml | 33 ++++++ 15 files changed, 357 insertions(+), 76 deletions(-) diff --git a/.github/workflows/toml-spec.yml b/.github/workflows/toml-spec.yml index 3a82660d..a00fa052 100644 --- a/.github/workflows/toml-spec.yml +++ b/.github/workflows/toml-spec.yml @@ -11,7 +11,7 @@ env: CARGO_TERM_COLOR: always jobs: - rustfmt: + toml-spec: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 diff --git a/jay-config/src/_private/client.rs b/jay-config/src/_private/client.rs index 22fd8561..6915f056 100644 --- a/jay-config/src/_private/client.rs +++ b/jay-config/src/_private/client.rs @@ -4,7 +4,7 @@ use { crate::{ _private::{ bincode_ops, - ipc::{ClientMessage, InitMessage, Response, ServerMessage}, + ipc::{ClientMessage, InitMessage, Response, ServerMessage, WorkspaceSource}, logging, Config, ConfigEntry, ConfigEntryGen, PollableId, WireMode, VERSION, }, exec::Command, @@ -421,6 +421,13 @@ impl Client { self.send(&ClientMessage::DisablePointerConstraint { seat }); } + pub fn move_to_output(&self, workspace: WorkspaceSource, connector: Connector) { + self.send(&ClientMessage::MoveToOutput { + workspace, + connector, + }); + } + pub fn set_fullscreen(&self, seat: Seat, fullscreen: bool) { self.send(&ClientMessage::SetFullscreen { seat, fullscreen }); } diff --git a/jay-config/src/_private/ipc.rs b/jay-config/src/_private/ipc.rs index 377bd3bc..07c1f7ed 100644 --- a/jay-config/src/_private/ipc.rs +++ b/jay-config/src/_private/ipc.rs @@ -424,6 +424,16 @@ pub enum ClientMessage<'a> { SetIdle { timeout: Duration, }, + MoveToOutput { + workspace: WorkspaceSource, + connector: Connector, + }, +} + +#[derive(Serialize, Deserialize, Debug)] +pub enum WorkspaceSource { + Seat(Seat), + Explicit(Workspace), } #[derive(Serialize, Deserialize, Debug)] diff --git a/jay-config/src/input.rs b/jay-config/src/input.rs index 58666aee..b0db1cdd 100644 --- a/jay-config/src/input.rs +++ b/jay-config/src/input.rs @@ -8,7 +8,8 @@ use { input::{acceleration::AccelProfile, capability::Capability}, keyboard::Keymap, Axis, Direction, ModifiedKeySym, Workspace, - _private::DEFAULT_SEAT_NAME, + _private::{ipc::WorkspaceSource, DEFAULT_SEAT_NAME}, + video::Connector, }, serde::{Deserialize, Serialize}, std::time::Duration, @@ -319,6 +320,11 @@ impl Seat { pub fn disable_pointer_constraint(self) { get!().disable_pointer_constraint(self) } + + /// Moves the currently focused workspace to another output. + pub fn move_to_output(self, connector: Connector) { + get!().move_to_output(WorkspaceSource::Seat(self), connector); + } } /// Returns all seats. diff --git a/jay-config/src/lib.rs b/jay-config/src/lib.rs index 5881821c..83543401 100644 --- a/jay-config/src/lib.rs +++ b/jay-config/src/lib.rs @@ -40,7 +40,7 @@ )] use { - crate::keyboard::ModifiedKeySym, + crate::{_private::ipc::WorkspaceSource, keyboard::ModifiedKeySym, video::Connector}, serde::{Deserialize, Serialize}, std::{ fmt::{Debug, Display, Formatter}, @@ -159,6 +159,13 @@ impl Workspace { let get = get!(); get.set_workspace_capture(self, !get.get_workspace_capture(self)); } + + /// Moves this workspace to another output. + /// + /// This has no effect if the workspace is not currently being shown. + pub fn move_to_output(self, output: Connector) { + get!().move_to_output(WorkspaceSource::Explicit(self), output); + } } /// Returns the workspace with the given name. diff --git a/src/compositor.rs b/src/compositor.rs index 8f48948c..8e4e5fd6 100644 --- a/src/compositor.rs +++ b/src/compositor.rs @@ -440,9 +440,8 @@ fn create_dummy_output(state: &Rc) { title_texture: Cell::new(None), attention_requests: Default::default(), }); - dummy_workspace.output_link.set(Some( - dummy_output.workspaces.add_last(dummy_workspace.clone()), - )); + *dummy_workspace.output_link.borrow_mut() = + Some(dummy_output.workspaces.add_last(dummy_workspace.clone())); dummy_output.show_workspace(&dummy_workspace); state.dummy_output.set(Some(dummy_output)); } diff --git a/src/config/handler.rs b/src/config/handler.rs index fe76c274..117938c2 100644 --- a/src/config/handler.rs +++ b/src/config/handler.rs @@ -12,7 +12,10 @@ use { scale::Scale, state::{ConnectorData, DeviceHandlerData, DrmDevData, OutputData, State}, theme::{Color, ThemeSized, DEFAULT_FONT}, - tree::{ContainerNode, ContainerSplit, FloatNode, Node, NodeVisitorBase, OutputNode}, + tree::{ + move_ws_to_output, ContainerNode, ContainerSplit, FloatNode, Node, NodeVisitorBase, + OutputNode, WsMoveConfig, + }, utils::{ asyncevent::AsyncEvent, copyhashmap::CopyHashMap, @@ -29,7 +32,7 @@ use { jay_config::{ _private::{ bincode_ops, - ipc::{ClientMessage, Response, ServerMessage}, + ipc::{ClientMessage, Response, ServerMessage, WorkspaceSource}, PollableId, WireMode, }, input::{ @@ -753,6 +756,45 @@ impl ConfigProxyHandler { Ok(()) } + fn handle_move_to_output( + &self, + workspace: WorkspaceSource, + connector: Connector, + ) -> Result<(), CphError> { + let output = self.get_output(connector)?; + let ws = match workspace { + WorkspaceSource::Explicit(ws) => { + let name = self.get_workspace(ws)?; + match self.state.workspaces.get(name.as_str()) { + Some(ws) => ws, + _ => return Ok(()), + } + } + WorkspaceSource::Seat(s) => match self.get_seat(s)?.get_output().workspace.get() { + Some(ws) => ws, + _ => return Ok(()), + }, + }; + if ws.is_dummy || output.node.is_dummy { + return Ok(()); + } + if ws.output.get().id == output.node.id { + return Ok(()); + } + let link = match &*ws.output_link.borrow() { + None => return Ok(()), + Some(l) => l.to_ref(), + }; + let config = WsMoveConfig { + make_visible_if_empty: true, + source_is_destroyed: false, + }; + move_ws_to_output(&link, &output.node, config); + self.state.tree_changed(); + self.state.damage(); + Ok(()) + } + fn handle_set_idle(&self, timeout: Duration) { self.state.idle.set_timeout(timeout); } @@ -1676,6 +1718,12 @@ impl ConfigProxyHandler { .handle_get_input_device_devnode(device) .wrn("get_input_device_devnode")?, ClientMessage::SetIdle { timeout } => self.handle_set_idle(timeout), + ClientMessage::MoveToOutput { + workspace, + connector, + } => self + .handle_move_to_output(workspace, connector) + .wrn("move_to_output")?, } Ok(()) } diff --git a/src/tree/output.rs b/src/tree/output.rs index b1f0c7d8..b0e274fe 100644 --- a/src/tree/output.rs +++ b/src/tree/output.rs @@ -337,7 +337,7 @@ impl OutputNode { stacked: Default::default(), seat_state: Default::default(), name: name.to_string(), - output_link: Cell::new(None), + output_link: Default::default(), visible: Cell::new(false), fullscreen: Default::default(), visible_on_desired_output: Cell::new(false), @@ -347,8 +347,7 @@ impl OutputNode { title_texture: Default::default(), attention_requests: Default::default(), }); - ws.output_link - .set(Some(self.workspaces.add_last(ws.clone()))); + *ws.output_link.borrow_mut() = Some(self.workspaces.add_last(ws.clone())); self.state.workspaces.set(name.to_string(), ws.clone()); if self.workspace.is_none() { self.show_workspace(&ws); diff --git a/src/tree/workspace.rs b/src/tree/workspace.rs index a58bc789..9471576a 100644 --- a/src/tree/workspace.rs +++ b/src/tree/workspace.rs @@ -24,7 +24,12 @@ use { }, wire::JayWorkspaceId, }, - std::{cell::Cell, fmt::Debug, ops::Deref, rc::Rc}, + std::{ + cell::{Cell, RefCell}, + fmt::Debug, + ops::Deref, + rc::Rc, + }, }; tree_id!(WorkspaceNodeId); @@ -38,7 +43,7 @@ pub struct WorkspaceNode { pub stacked: LinkedList>, pub seat_state: NodeSeatState, pub name: String, - pub output_link: Cell>>>, + pub output_link: RefCell>>>, pub visible: Cell, pub fullscreen: CloneCell>>, pub visible_on_desired_output: Cell, @@ -52,7 +57,7 @@ pub struct WorkspaceNode { impl WorkspaceNode { pub fn clear(&self) { self.container.set(None); - self.output_link.set(None); + *self.output_link.borrow_mut() = None; self.fullscreen.set(None); self.jay_workspaces.clear(); } diff --git a/toml-config/src/config.rs b/toml-config/src/config.rs index d56817a2..efdcceef 100644 --- a/toml-config/src/config.rs +++ b/toml-config/src/config.rs @@ -22,7 +22,7 @@ use { status::MessageFormat, theme::Color, video::{GfxApi, Transform}, - Axis, Direction, + Axis, Direction, Workspace, }, std::{ error::Error, @@ -53,26 +53,70 @@ pub enum SimpleCommand { #[derive(Debug, Clone)] pub enum Action { - ConfigureConnector { con: ConfigConnector }, - ConfigureDirectScanout { enabled: bool }, - ConfigureDrmDevice { dev: ConfigDrmDevice }, - ConfigureIdle { idle: Duration }, - ConfigureInput { input: Input }, - ConfigureOutput { out: Output }, - Exec { exec: Exec }, - MoveToWorkspace { name: String }, - Multi { actions: Vec }, - SetEnv { env: Vec<(String, String)> }, - SetGfxApi { api: GfxApi }, - SetKeymap { map: ConfigKeymap }, - SetLogLevel { level: LogLevel }, - SetRenderDevice { dev: DrmDeviceMatch }, - SetStatus { status: Option }, - SetTheme { theme: Box }, - ShowWorkspace { name: String }, - SimpleCommand { cmd: SimpleCommand }, - SwitchToVt { num: u32 }, - UnsetEnv { env: Vec }, + ConfigureConnector { + con: ConfigConnector, + }, + ConfigureDirectScanout { + enabled: bool, + }, + ConfigureDrmDevice { + dev: ConfigDrmDevice, + }, + ConfigureIdle { + idle: Duration, + }, + ConfigureInput { + input: Input, + }, + ConfigureOutput { + out: Output, + }, + Exec { + exec: Exec, + }, + MoveToWorkspace { + name: String, + }, + Multi { + actions: Vec, + }, + SetEnv { + env: Vec<(String, String)>, + }, + SetGfxApi { + api: GfxApi, + }, + SetKeymap { + map: ConfigKeymap, + }, + SetLogLevel { + level: LogLevel, + }, + SetRenderDevice { + dev: DrmDeviceMatch, + }, + SetStatus { + status: Option, + }, + SetTheme { + theme: Box, + }, + ShowWorkspace { + name: String, + }, + SimpleCommand { + cmd: SimpleCommand, + }, + SwitchToVt { + num: u32, + }, + UnsetEnv { + env: Vec, + }, + MoveToOutput { + workspace: Option, + output: OutputMatch, + }, } #[derive(Debug, Clone, Default)] diff --git a/toml-config/src/config/parsers/action.rs b/toml-config/src/config/parsers/action.rs index 4c5f4d2a..16ca5083 100644 --- a/toml-config/src/config/parsers/action.rs +++ b/toml-config/src/config/parsers/action.rs @@ -16,19 +16,24 @@ use { keymap::{KeymapParser, KeymapParserError}, log_level::{LogLevelParser, LogLevelParserError}, output::{OutputParser, OutputParserError}, + output_match::{OutputMatchParser, OutputMatchParserError}, status::{StatusParser, StatusParserError}, theme::{ThemeParser, ThemeParserError}, StringParser, StringParserError, }, + spanned::SpannedErrorExt, Action, }, toml::{ - toml_span::{Span, Spanned, SpannedExt}, + toml_span::{DespanExt, Span, Spanned, SpannedExt}, toml_value::Value, }, }, indexmap::IndexMap, - jay_config::Axis::{Horizontal, Vertical}, + jay_config::{ + get_workspace, + Axis::{Horizontal, Vertical}, + }, thiserror::Error, }; @@ -45,31 +50,33 @@ pub enum ActionParserError { #[error(transparent)] Extract(#[from] ExtractorError), #[error("Could not parse the exec action")] - Exec(#[from] ExecParserError), + Exec(#[source] ExecParserError), #[error("Could not parse the configure-connector action")] - ConfigureConnector(#[from] ConnectorParserError), + ConfigureConnector(#[source] ConnectorParserError), #[error("Could not parse the configure-input action")] - ConfigureInput(#[from] InputParserError), + ConfigureInput(#[source] InputParserError), #[error("Could not parse the configure-output action")] - ConfigureOutput(#[from] OutputParserError), + ConfigureOutput(#[source] OutputParserError), #[error("Could not parse the environment variables")] - Env(#[from] EnvParserError), + Env(#[source] EnvParserError), #[error("Could not parse a set-keymap action")] - SetKeymap(#[from] KeymapParserError), + SetKeymap(#[source] KeymapParserError), #[error("Could not parse a set-status action")] - Status(#[from] StatusParserError), + Status(#[source] StatusParserError), #[error("Could not parse a set-theme action")] - Theme(#[from] ThemeParserError), + Theme(#[source] ThemeParserError), #[error("Could not parse a set-log-level action")] - SetLogLevel(#[from] LogLevelParserError), + SetLogLevel(#[source] LogLevelParserError), #[error("Could not parse a set-gfx-api action")] - GfxApi(#[from] GfxApiParserError), + GfxApi(#[source] GfxApiParserError), #[error("Could not parse a configure-drm-device action")] - DrmDevice(#[from] DrmDeviceParserError), + DrmDevice(#[source] DrmDeviceParserError), #[error("Could not parse a set-render-device action")] - SetRenderDevice(#[from] DrmDeviceMatchParserError), + SetRenderDevice(#[source] DrmDeviceMatchParserError), #[error("Could not parse a configure-idle action")] - ConfigureIdle(#[from] IdleParserError), + ConfigureIdle(#[source] IdleParserError), + #[error("Could not parse a move-to-output action")] + MoveToOutput(#[source] OutputMatchParserError), } pub struct ActionParser<'a>(pub &'a Context<'a>); @@ -117,7 +124,8 @@ impl ActionParser<'_> { fn parse_exec(&mut self, ext: &mut Extractor<'_>) -> ParseResult { let exec = ext .extract(val("exec"))? - .parse_map(&mut ExecParser(self.0))?; + .parse_map(&mut ExecParser(self.0)) + .map_spanned_err(ActionParserError::Exec)?; Ok(Action::Exec { exec }) } @@ -139,35 +147,46 @@ impl ActionParser<'_> { fn parse_configure_connector(&mut self, ext: &mut Extractor<'_>) -> ParseResult { let con = ext .extract(val("connector"))? - .parse_map(&mut ConnectorParser(self.0))?; + .parse_map(&mut ConnectorParser(self.0)) + .map_spanned_err(ActionParserError::ConfigureConnector)?; Ok(Action::ConfigureConnector { con }) } fn parse_configure_input(&mut self, ext: &mut Extractor<'_>) -> ParseResult { - let input = ext.extract(val("input"))?.parse_map(&mut InputParser { - cx: self.0, - tag_ok: false, - })?; + let input = ext + .extract(val("input"))? + .parse_map(&mut InputParser { + cx: self.0, + tag_ok: false, + }) + .map_spanned_err(ActionParserError::ConfigureInput)?; Ok(Action::ConfigureInput { input }) } fn parse_configure_idle(&mut self, ext: &mut Extractor<'_>) -> ParseResult { let idle = ext .extract(val("idle"))? - .parse_map(&mut IdleParser(self.0))?; + .parse_map(&mut IdleParser(self.0)) + .map_spanned_err(ActionParserError::ConfigureIdle)?; Ok(Action::ConfigureIdle { idle }) } fn parse_configure_output(&mut self, ext: &mut Extractor<'_>) -> ParseResult { - let out = ext.extract(val("output"))?.parse_map(&mut OutputParser { - cx: self.0, - name_ok: false, - })?; + let out = ext + .extract(val("output"))? + .parse_map(&mut OutputParser { + cx: self.0, + name_ok: false, + }) + .map_spanned_err(ActionParserError::ConfigureOutput)?; Ok(Action::ConfigureOutput { out }) } fn parse_set_env(&mut self, ext: &mut Extractor<'_>) -> ParseResult { - let env = ext.extract(val("env"))?.parse_map(&mut EnvParser)?; + let env = ext + .extract(val("env"))? + .parse_map(&mut EnvParser) + .map_spanned_err(ActionParserError::Env)?; Ok(Action::SetEnv { env }) } @@ -195,17 +214,23 @@ impl ActionParser<'_> { } fn parse_set_keymap(&mut self, ext: &mut Extractor<'_>) -> ParseResult { - let map = ext.extract(val("map"))?.parse_map(&mut KeymapParser { - cx: self.0, - definition: false, - })?; + let map = ext + .extract(val("map"))? + .parse_map(&mut KeymapParser { + cx: self.0, + definition: false, + }) + .map_spanned_err(ActionParserError::SetKeymap)?; Ok(Action::SetKeymap { map }) } fn parse_set_status(&mut self, ext: &mut Extractor<'_>) -> ParseResult { let status = match ext.extract(opt(val("status")))? { None => None, - Some(v) => Some(v.parse_map(&mut StatusParser(self.0))?), + Some(v) => Some( + v.parse_map(&mut StatusParser(self.0)) + .map_spanned_err(ActionParserError::Status)?, + ), }; Ok(Action::SetStatus { status }) } @@ -213,26 +238,34 @@ impl ActionParser<'_> { fn parse_set_theme(&mut self, ext: &mut Extractor<'_>) -> ParseResult { let theme = ext .extract(val("theme"))? - .parse_map(&mut ThemeParser(self.0))?; + .parse_map(&mut ThemeParser(self.0)) + .map_spanned_err(ActionParserError::Theme)?; Ok(Action::SetTheme { theme: Box::new(theme), }) } fn parse_set_log_level(&mut self, ext: &mut Extractor<'_>) -> ParseResult { - let level = ext.extract(val("level"))?.parse_map(&mut LogLevelParser)?; + let level = ext + .extract(val("level"))? + .parse_map(&mut LogLevelParser) + .map_spanned_err(ActionParserError::SetLogLevel)?; Ok(Action::SetLogLevel { level }) } fn parse_set_gfx_api(&mut self, ext: &mut Extractor<'_>) -> ParseResult { - let api = ext.extract(val("api"))?.parse_map(&mut GfxApiParser)?; + let api = ext + .extract(val("api"))? + .parse_map(&mut GfxApiParser) + .map_spanned_err(ActionParserError::GfxApi)?; Ok(Action::SetGfxApi { api }) } fn parse_set_render_device(&mut self, ext: &mut Extractor<'_>) -> ParseResult { let dev = ext .extract(val("dev"))? - .parse_map(&mut DrmDeviceMatchParser(self.0))?; + .parse_map(&mut DrmDeviceMatchParser(self.0)) + .map_spanned_err(ActionParserError::SetRenderDevice)?; Ok(Action::SetRenderDevice { dev }) } @@ -242,12 +275,26 @@ impl ActionParser<'_> { } fn parse_configure_drm_device(&mut self, ext: &mut Extractor<'_>) -> ParseResult { - let dev = ext.extract(val("dev"))?.parse_map(&mut DrmDeviceParser { - cx: self.0, - name_ok: false, - })?; + let dev = ext + .extract(val("dev"))? + .parse_map(&mut DrmDeviceParser { + cx: self.0, + name_ok: false, + }) + .map_spanned_err(ActionParserError::DrmDevice)?; Ok(Action::ConfigureDrmDevice { dev }) } + + fn parse_move_to_output(&mut self, ext: &mut Extractor<'_>) -> ParseResult { + let (ws, output) = ext.extract((opt(str("workspace")), val("output")))?; + let output = output + .parse_map(&mut OutputMatchParser(self.0)) + .map_spanned_err(ActionParserError::MoveToOutput)?; + Ok(Action::MoveToOutput { + workspace: ws.despan().map(get_workspace), + output, + }) + } } impl<'a> Parser for ActionParser<'a> { @@ -297,6 +344,7 @@ impl<'a> Parser for ActionParser<'a> { "configure-drm-device" => self.parse_configure_drm_device(&mut ext), "set-render-device" => self.parse_set_render_device(&mut ext), "configure-idle" => self.parse_configure_idle(&mut ext), + "move-to-output" => self.parse_move_to_output(&mut ext), v => { ext.ignore_unused(); return Err(ActionParserError::UnknownType(v.to_string()).spanned(ty.span)); diff --git a/toml-config/src/lib.rs b/toml-config/src/lib.rs index c3fffb05..edfd840d 100644 --- a/toml-config/src/lib.rs +++ b/toml-config/src/lib.rs @@ -154,6 +154,23 @@ impl Action { }) } Action::ConfigureIdle { idle } => Box::new(move || set_idle(Some(idle))), + Action::MoveToOutput { output, workspace } => { + let state = state.clone(); + Box::new(move || { + let output = 'get_output: { + for connector in connectors() { + if connector.connected() && output.matches(connector, &state) { + break 'get_output connector; + } + } + return; + }; + match workspace { + Some(ws) => ws.move_to_output(output), + None => s.move_to_output(output), + } + }) + } } } } diff --git a/toml-spec/spec/spec.generated.json b/toml-spec/spec/spec.generated.json index 9301b6bd..cdf97b93 100644 --- a/toml-spec/spec/spec.generated.json +++ b/toml-spec/spec/spec.generated.json @@ -136,6 +136,27 @@ "name" ] }, + { + "description": "Moves a workspace to a different output.\n\n- Example 1:\n\n ```toml\n [shortcuts]\n alt-F1 = { type = \"move-to-output\", workspace = \"1\", output.name = \"right\" }\n ```\n\n- Example 2:\n\n ```toml\n [shortcuts]\n alt-F1 = { type = \"move-to-output\", output.name = \"right\" }\n ```\n", + "type": "object", + "properties": { + "type": { + "const": "move-to-output" + }, + "workspace": { + "type": "string", + "description": "The name of the workspace.\n\nIf this is omitted, the currently active workspace is moved.\n" + }, + "output": { + "description": "The output to move to.\n\nIf multiple outputs match, the workspace is moved to the first matching\noutput.\n", + "$ref": "#/$defs/OutputMatch" + } + }, + "required": [ + "type", + "output" + ] + }, { "description": "Applies a configuration to connectors.\n\n- Example:\n\n ```toml\n [shortcuts]\n alt-j = { type = \"configure-connector\", connector = { match.name = \"eDP-1\", enabled = false } }\n alt-k = { type = \"configure-connector\", connector = { match.name = \"eDP-1\", enabled = true } }\n ```\n", "type": "object", diff --git a/toml-spec/spec/spec.generated.md b/toml-spec/spec/spec.generated.md index 28bc1d07..45c6201e 100644 --- a/toml-spec/spec/spec.generated.md +++ b/toml-spec/spec/spec.generated.md @@ -214,6 +214,43 @@ This table is a tagged union. The variant is determined by the `type` field. It The value of this field should be a string. +- `move-to-output`: + + Moves a workspace to a different output. + + - Example 1: + + ```toml + [shortcuts] + alt-F1 = { type = "move-to-output", workspace = "1", output.name = "right" } + ``` + + - Example 2: + + ```toml + [shortcuts] + alt-F1 = { type = "move-to-output", output.name = "right" } + ``` + + The table has the following fields: + + - `workspace` (optional): + + The name of the workspace. + + If this is omitted, the currently active workspace is moved. + + The value of this field should be a string. + + - `output` (required): + + The output to move to. + + If multiple outputs match, the workspace is moved to the first matching + output. + + The value of this field should be a [OutputMatch](#types-OutputMatch). + - `configure-connector`: Applies a configuration to connectors. diff --git a/toml-spec/spec/spec.yaml b/toml-spec/spec/spec.yaml index 75822054..458a05d6 100644 --- a/toml-spec/spec/spec.yaml +++ b/toml-spec/spec/spec.yaml @@ -219,6 +219,39 @@ Action: description: The name of the workspace. required: true kind: string + move-to-output: + description: | + Moves a workspace to a different output. + + - Example 1: + + ```toml + [shortcuts] + alt-F1 = { type = "move-to-output", workspace = "1", output.name = "right" } + ``` + + - Example 2: + + ```toml + [shortcuts] + alt-F1 = { type = "move-to-output", output.name = "right" } + ``` + fields: + workspace: + description: | + The name of the workspace. + + If this is omitted, the currently active workspace is moved. + required: false + kind: string + output: + description: | + The output to move to. + + If multiple outputs match, the workspace is moved to the first matching + output. + required: true + ref: OutputMatch configure-connector: description: | Applies a configuration to connectors.