Skip to content
Merged
35 changes: 35 additions & 0 deletions api/lua/pinnacle/grpc/defs.lua
Original file line number Diff line number Diff line change
Expand Up @@ -1338,6 +1338,20 @@ local pinnacle_v1_Backend = {
---@class pinnacle.tag.v1.RemoveRequest
---@field tag_ids integer[]?

---@class pinnacle.tag.v1.MoveToOutputRequest
---@field output_name string?
---@field tag_ids integer[]?

---@class pinnacle.tag.v1.MoveToOutputResponse
---@field error pinnacle.tag.v1.MoveToOutputResponse.Error?

---@class pinnacle.tag.v1.MoveToOutputResponse.Error
---@field output_does_not_exist google.protobuf.Empty?
---@field same_window_on_two_outputs pinnacle.tag.v1.MoveToOutputResponse.Error.SameWindowOnTwoOutputs?

---@class pinnacle.tag.v1.MoveToOutputResponse.Error.SameWindowOnTwoOutputs
---@field window_ids integer[]?

---@class pinnacle.tag.v1.GetActiveRequest
---@field tag_id integer?

Expand Down Expand Up @@ -1600,6 +1614,10 @@ pinnacle.tag.v1.GetResponse = {}
pinnacle.tag.v1.AddRequest = {}
pinnacle.tag.v1.AddResponse = {}
pinnacle.tag.v1.RemoveRequest = {}
pinnacle.tag.v1.MoveToOutputRequest = {}
pinnacle.tag.v1.MoveToOutputResponse = {}
pinnacle.tag.v1.MoveToOutputResponse.Error = {}
pinnacle.tag.v1.MoveToOutputResponse.Error.SameWindowOnTwoOutputs = {}
pinnacle.tag.v1.GetActiveRequest = {}
pinnacle.tag.v1.GetActiveResponse = {}
pinnacle.tag.v1.GetNameRequest = {}
Expand Down Expand Up @@ -3553,6 +3571,23 @@ pinnacle.tag.v1.TagService.SwitchTo.response = ".google.protobuf.Empty"
function Client:pinnacle_tag_v1_TagService_SwitchTo(data)
return self:unary_request(pinnacle.tag.v1.TagService.SwitchTo, data)
end
pinnacle.tag.v1.TagService.MoveToOutput = {}
pinnacle.tag.v1.TagService.MoveToOutput.service = "pinnacle.tag.v1.TagService"
pinnacle.tag.v1.TagService.MoveToOutput.method = "MoveToOutput"
pinnacle.tag.v1.TagService.MoveToOutput.request = ".pinnacle.tag.v1.MoveToOutputRequest"
pinnacle.tag.v1.TagService.MoveToOutput.response = ".pinnacle.tag.v1.MoveToOutputResponse"

---Performs a unary request.
---
---@nodiscard
---
---@param data pinnacle.tag.v1.MoveToOutputRequest
---
---@return pinnacle.tag.v1.MoveToOutputResponse | nil response
---@return string | nil error An error string, if any
function Client:pinnacle_tag_v1_TagService_MoveToOutput(data)
return self:unary_request(pinnacle.tag.v1.TagService.MoveToOutput, data)
end
pinnacle.v1.PinnacleService = {}
pinnacle.v1.PinnacleService.Quit = {}
pinnacle.v1.PinnacleService.Quit.service = "pinnacle.v1.PinnacleService"
Expand Down
79 changes: 79 additions & 0 deletions api/lua/pinnacle/tag.lua
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,68 @@ function tag.remove(tags)
end
end

---@param response pinnacle.tag.v1.MoveToOutputResponse?
---@return pinnacle.tag.MoveToOutputError|nil
local function move_to_output_error_from_response(response)
if not (response and response.error) then
return nil
end

---@type pinnacle.tag.MoveToOutputError
local move_error = {}

if response.error.output_does_not_exist then
move_error.output_does_not_exist = true
end

if response.error.same_window_on_two_outputs then
local window_ids = response.error.same_window_on_two_outputs.window_ids or {}
move_error.same_window_on_two_outputs =
require("pinnacle.window").handle.new_from_table(window_ids)
end

return move_error
end

---Moves existing tags to the specified output.
---
---#### Example
---```lua
---local tag_to_move = Tag.get("1")
---Tag.move_to_output(Output.get_by_name("HDMI-1"), { tag_to_move })
---```
---
---@param output pinnacle.output.OutputHandle The output to move tags to.
---@param tags pinnacle.tag.TagHandle[] The tags to move.
---
---@return boolean ok `true` on success.
---@return pinnacle.tag.MoveToOutputError|nil err Error on failure.
function tag.move_to_output(output, tags)
---@type integer[]
local ids = {}

for _, tg in ipairs(tags) do
table.insert(ids, tg.id)
end

local response, err = client:pinnacle_tag_v1_TagService_MoveToOutput({
output_name = output.name,
tag_ids = ids,
})

if err then
log.error(err)
return false, nil
end
Comment thread
Ottatop marked this conversation as resolved.

local move_error = move_to_output_error_from_response(response)
if move_error then
return false, move_error
end

return true, nil
end

local signal_name_to_SignalName = {
active = "TagActive",
created = "TagCreated",
Expand All @@ -205,6 +267,12 @@ local signal_name_to_SignalName = {
---@field created fun(tag: pinnacle.tag.TagHandle)? A tag was created.
---@field removed fun(tag: pinnacle.tag.TagHandle)? A tag was removed.

---@class pinnacle.tag.MoveToOutputError
---`true` if the output does not exist.
---@field output_does_not_exist boolean?
---This operation would cause the provided windows to be on multiple outputs.
---@field same_window_on_two_outputs pinnacle.window.WindowHandle[]?

---Connects to a tag signal.
---
---`signals` is a table containing the signal(s) you want to connect to along with
Expand Down Expand Up @@ -322,6 +390,17 @@ function TagHandle:toggle_active()
end
end

---Moves this tag to the specified output.
---
---@see pinnacle.tag.move_to_output - for further information
---@param output pinnacle.output.OutputHandle The output to move this tag to.
---
---@return boolean ok `true` on success.
---@return pinnacle.tag.MoveToOutputError|nil err Error on failure.
function TagHandle:move_to_output(output)
return tag.move_to_output(output, { self })
end

---Gets whether or not this tag is active.
---
---@return boolean
Expand Down
21 changes: 21 additions & 0 deletions api/protobuf/pinnacle/tag/v1/tag.proto
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,26 @@ message RemoveRequest {
repeated uint32 tag_ids = 1;
}

message MoveToOutputRequest {
string output_name = 1;
repeated uint32 tag_ids = 2;
}

message MoveToOutputResponse {
message Error {
message SameWindowOnTwoOutputs {
repeated uint32 window_ids = 1;
}

oneof kind {
google.protobuf.Empty output_does_not_exist = 1;
SameWindowOnTwoOutputs same_window_on_two_outputs = 2;
}
}

optional Error error = 1;
}

// Tag properties

message GetActiveRequest {
Expand Down Expand Up @@ -68,4 +88,5 @@ service TagService {

rpc SetActive(SetActiveRequest) returns (google.protobuf.Empty);
rpc SwitchTo(SwitchToRequest) returns (google.protobuf.Empty);
rpc MoveToOutput(MoveToOutputRequest) returns (MoveToOutputResponse);
}
68 changes: 67 additions & 1 deletion api/rust/src/tag.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ use futures::FutureExt;
use pinnacle_api_defs::pinnacle::{
tag::v1::{
AddRequest, GetActiveRequest, GetNameRequest, GetOutputNameRequest, GetRequest,
RemoveRequest, SetActiveRequest, SwitchToRequest,
MoveToOutputRequest, RemoveRequest, SetActiveRequest, SwitchToRequest,
move_to_output_response::error::Kind,
},
util::v1::SetOrToggle,
};
Expand Down Expand Up @@ -181,6 +182,64 @@ pub fn remove(tags: impl IntoIterator<Item = TagHandle>) {
.unwrap();
}

/// Error that happens when moving tags to a different output.
#[derive(Debug, PartialEq, Clone)]
pub enum MoveToOutputError {
/// The requested output to move the tag to, does not exist
OutputDoesNotExist,

/// Moving the Tag to another output would result in having the same window in multiple tags.
/// It contains a list of windows that would be on multiple outputs.
SameWindowOnTwoOutputs(Vec<WindowHandle>),
}

/// Moves existing tags to the specified output.
///
/// # Examples
///
/// ```no_run
/// # || {
/// # use pinnacle_api::output;
/// # use pinnacle_api::tag;
/// let output = output::get_by_name("eDP-1")?;
/// let tag_to_move = tag::get("1")?;
/// tag::move_to_output(&output, [tag_to_move]);
/// # Some(())
/// # };
/// ```
pub fn move_to_output<I>(output: &OutputHandle, tag_handles: I) -> Result<(), MoveToOutputError>
where
I: IntoIterator<Item = TagHandle>,
{
let output_name = output.name();
let tag_ids = tag_handles.into_iter().map(|h| h.id).collect();

let error = Client::tag()
.move_to_output(MoveToOutputRequest {
output_name,
tag_ids,
})
.block_on_tokio()
.unwrap()
.into_inner()
.error
.and_then(|error| error.kind);

match error {
None => Ok(()),
Some(Kind::OutputDoesNotExist(_)) => Err(MoveToOutputError::OutputDoesNotExist),
Some(Kind::SameWindowOnTwoOutputs(windows)) => {
Err(MoveToOutputError::SameWindowOnTwoOutputs(
windows
.window_ids
.into_iter()
.map(WindowHandle::from_id)
.collect(),
))
}
}
}

/// Connects to a [`TagSignal`].
///
/// # Examples
Expand Down Expand Up @@ -316,6 +375,13 @@ impl TagHandle {
.unwrap();
}

/// Moves this tag to the specified output.
///
/// See [tag::move_to_output][crate::tag::move_to_output] for more information.
pub fn move_to_output(&self, output: &OutputHandle) -> Result<(), MoveToOutputError> {
move_to_output(output, [self.clone()])
}

/// Removes this tag from its output.
///
/// # Examples
Expand Down
Loading
Loading