Add tag active signal

This commit is contained in:
Ottatop 2024-04-18 14:42:49 -05:00
parent c0aa9067f3
commit 1c2f639a56
9 changed files with 171 additions and 13 deletions

View file

@ -23,12 +23,17 @@ local rpc_types = {
OutputMove = { OutputMove = {
response_type = "OutputMoveResponse", response_type = "OutputMoveResponse",
}, },
WindowPointerEnter = { WindowPointerEnter = {
response_type = "WindowPointerEnterResponse", response_type = "WindowPointerEnterResponse",
}, },
WindowPointerLeave = { WindowPointerLeave = {
response_type = "WindowPointerLeaveResponse", response_type = "WindowPointerLeaveResponse",
}, },
TagActive = {
response_type = "TagActiveResponse",
},
} }
---Build GrpcRequestParams ---Build GrpcRequestParams
@ -126,6 +131,17 @@ local signals = {
---@type fun(response: table) ---@type fun(response: table)
on_response = nil, on_response = nil,
}, },
TagActive = {
---@nodoc
---@type H2Stream?
sender = nil,
---@nodoc
---@type (fun(tag: TagHandle, active: boolean))[]
callbacks = {},
---@nodoc
---@type fun(response: table)
on_response = nil,
},
} }
signals.OutputConnect.on_response = function(response) signals.OutputConnect.on_response = function(response)
@ -178,6 +194,15 @@ signals.WindowPointerLeave.on_response = function(response)
end end
end end
signals.TagActive.on_response = function(response)
---@diagnostic disable-next-line: invisible
local tag_handle = require("pinnacle.tag").handle.new(response.tag_id)
for _, callback in ipairs(signals.TagActive.callbacks) do
callback(tag_handle, response.active)
end
end
----------------------------------------------------------------------------- -----------------------------------------------------------------------------
---@nodoc ---@nodoc

View file

@ -210,6 +210,52 @@ function tag.remove(tags)
client.unary_request(build_grpc_request_params("Remove", { tag_ids = ids })) client.unary_request(build_grpc_request_params("Remove", { tag_ids = ids }))
end end
---@type table<string, SignalServiceMethod>
local signal_name_to_SignalName = {
active = "TagActive",
}
---@class TagSignal Signals related to tag events.
---@field active fun(tag: TagHandle, active: boolean)? A tag was set to active or not active.
---Connect to a tag signal.
---
---The compositor sends signals about various events. Use this function to run a callback when
---some tag signal occurs.
---
---This function returns a table of signal handles with each handle stored at the same key used
---to connect to the signal. See `SignalHandles` for more information.
---
---# Example
---```lua
---Tag.connect_signal({
--- active = function(tag, active)
--- print("Activity for " .. tag:name() .. " was set to", active)
--- end
---})
---```
---
---@param signals TagSignal The signal you want to connect to
---
---@return SignalHandles signal_handles Handles to every signal you connected to wrapped in a table, with keys being the same as the connected signal.
---
---@see SignalHandles.disconnect_all - To disconnect from these signals
function tag.connect_signal(signals)
---@diagnostic disable-next-line: invisible
local handles = require("pinnacle.signal").handles.new({})
for signal, callback in pairs(signals) do
require("pinnacle.signal").add_callback(signal_name_to_SignalName[signal], callback)
---@diagnostic disable-next-line: invisible
local handle = require("pinnacle.signal").handle.new(signal_name_to_SignalName[signal], callback)
handles[signal] = handle
end
return handles
end
--------------------------------------------------------------
---Remove this tag. ---Remove this tag.
--- ---
---### Example ---### Example

View file

@ -26,6 +26,7 @@ message OutputDisconnectResponse {
message OutputResizeRequest { message OutputResizeRequest {
optional StreamControl control = 1; optional StreamControl control = 1;
} }
// An output's logical size changed // An output's logical size changed
message OutputResizeResponse { message OutputResizeResponse {
optional string output_name = 1; optional string output_name = 1;
@ -36,6 +37,7 @@ message OutputResizeResponse {
message OutputMoveRequest { message OutputMoveRequest {
optional StreamControl control = 1; optional StreamControl control = 1;
} }
// An output's location in the global space changed // An output's location in the global space changed
message OutputMoveResponse { message OutputMoveResponse {
optional string output_name = 1; optional string output_name = 1;
@ -59,11 +61,23 @@ message WindowPointerLeaveResponse {
optional uint32 window_id = 1; optional uint32 window_id = 1;
} }
message TagActiveRequest {
optional StreamControl control = 1;
}
message TagActiveResponse {
optional uint32 tag_id = 1;
// The tag was set to active or inactive.
optional bool active = 2;
}
service SignalService { service SignalService {
rpc OutputConnect(stream OutputConnectRequest) returns (stream OutputConnectResponse); rpc OutputConnect(stream OutputConnectRequest) returns (stream OutputConnectResponse);
rpc OutputDisconnect(stream OutputDisconnectRequest) returns (stream OutputDisconnectResponse); rpc OutputDisconnect(stream OutputDisconnectRequest) returns (stream OutputDisconnectResponse);
rpc OutputResize(stream OutputResizeRequest) returns (stream OutputResizeResponse); rpc OutputResize(stream OutputResizeRequest) returns (stream OutputResizeResponse);
rpc OutputMove(stream OutputMoveRequest) returns (stream OutputMoveResponse); rpc OutputMove(stream OutputMoveRequest) returns (stream OutputMoveResponse);
rpc WindowPointerEnter(stream WindowPointerEnterRequest) returns (stream WindowPointerEnterResponse); rpc WindowPointerEnter(stream WindowPointerEnterRequest) returns (stream WindowPointerEnterResponse);
rpc WindowPointerLeave(stream WindowPointerLeaveRequest) returns (stream WindowPointerLeaveResponse); rpc WindowPointerLeave(stream WindowPointerLeaveRequest) returns (stream WindowPointerLeaveResponse);
rpc TagActive(stream TagActiveRequest) returns (stream TagActiveResponse);
} }

View file

@ -27,7 +27,9 @@ use tokio::sync::{
use tokio_stream::{wrappers::UnboundedReceiverStream, StreamExt}; use tokio_stream::{wrappers::UnboundedReceiverStream, StreamExt};
use tonic::{transport::Channel, Streaming}; use tonic::{transport::Channel, Streaming};
use crate::{block_on_tokio, output::OutputHandle, window::WindowHandle, ApiModules}; use crate::{
block_on_tokio, output::OutputHandle, tag::TagHandle, window::WindowHandle, ApiModules,
};
pub(crate) trait Signal { pub(crate) trait Signal {
type Callback; type Callback;
@ -229,6 +231,24 @@ signals! {
}, },
} }
} }
/// Signals relating to tag events.
TagSignal => {
/// A tag was set to active or not active.
TagActive = {
enum_name = Active,
callback_type = Box<dyn FnMut(&TagHandle, bool) + Send + 'static>,
client_request = tag_active,
on_response = |response, callbacks, api| {
if let Some(tag_id) = response.tag_id {
let handle = api.tag.new_handle(tag_id);
for callback in callbacks {
callback(&handle, response.active.unwrap());
}
}
},
}
}
} }
pub(crate) type SingleOutputFn = Box<dyn FnMut(&OutputHandle) + Send + 'static>; pub(crate) type SingleOutputFn = Box<dyn FnMut(&OutputHandle) + Send + 'static>;
@ -239,8 +259,11 @@ pub(crate) struct SignalState {
pub(crate) output_disconnect: SignalData<OutputDisconnect>, pub(crate) output_disconnect: SignalData<OutputDisconnect>,
pub(crate) output_resize: SignalData<OutputResize>, pub(crate) output_resize: SignalData<OutputResize>,
pub(crate) output_move: SignalData<OutputMove>, pub(crate) output_move: SignalData<OutputMove>,
pub(crate) window_pointer_enter: SignalData<WindowPointerEnter>, pub(crate) window_pointer_enter: SignalData<WindowPointerEnter>,
pub(crate) window_pointer_leave: SignalData<WindowPointerLeave>, pub(crate) window_pointer_leave: SignalData<WindowPointerLeave>,
pub(crate) tag_active: SignalData<TagActive>,
} }
impl std::fmt::Debug for SignalState { impl std::fmt::Debug for SignalState {
@ -262,6 +285,7 @@ impl SignalState {
output_move: SignalData::new(client.clone(), fut_sender.clone()), output_move: SignalData::new(client.clone(), fut_sender.clone()),
window_pointer_enter: SignalData::new(client.clone(), fut_sender.clone()), window_pointer_enter: SignalData::new(client.clone(), fut_sender.clone()),
window_pointer_leave: SignalData::new(client.clone(), fut_sender.clone()), window_pointer_leave: SignalData::new(client.clone(), fut_sender.clone()),
tag_active: SignalData::new(client.clone(), fut_sender.clone()),
} }
} }
@ -272,6 +296,7 @@ impl SignalState {
self.output_move.api.set(api.clone()).unwrap(); self.output_move.api.set(api.clone()).unwrap();
self.window_pointer_enter.api.set(api.clone()).unwrap(); self.window_pointer_enter.api.set(api.clone()).unwrap();
self.window_pointer_leave.api.set(api.clone()).unwrap(); self.window_pointer_leave.api.set(api.clone()).unwrap();
self.tag_active.api.set(api.clone()).unwrap();
} }
} }

View file

@ -44,7 +44,14 @@ use pinnacle_api_defs::pinnacle::{
}; };
use tonic::transport::Channel; use tonic::transport::Channel;
use crate::{block_on_tokio, output::OutputHandle, util::Batch, window::WindowHandle, ApiModules}; use crate::{
block_on_tokio,
output::OutputHandle,
signal::{SignalHandle, TagSignal},
util::Batch,
window::WindowHandle,
ApiModules,
};
/// A struct that allows you to add and remove tags and get [`TagHandle`]s. /// A struct that allows you to add and remove tags and get [`TagHandle`]s.
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
@ -227,6 +234,19 @@ impl Tag {
block_on_tokio(client.remove(RemoveRequest { tag_ids })).unwrap(); block_on_tokio(client.remove(RemoveRequest { tag_ids })).unwrap();
} }
/// Connect to a tag signal.
///
/// The compositor will fire off signals that your config can listen for and act upon.
/// You can pass in a [`TagSignal`] along with a callback and it will get run
/// with the necessary arguments every time a signal of that type is received.
pub fn connect_signal(&self, signal: TagSignal) -> SignalHandle {
let mut signal_state = block_on_tokio(self.api.get().unwrap().signal.write());
match signal {
TagSignal::Active(f) => signal_state.tag_active.add_callback(f),
}
}
} }
/// A handle to a tag. /// A handle to a tag.

View file

@ -67,7 +67,8 @@ pub mod pinnacle {
OutputResizeRequest, OutputResizeRequest,
OutputMoveRequest, OutputMoveRequest,
WindowPointerEnterRequest, WindowPointerEnterRequest,
WindowPointerLeaveRequest WindowPointerLeaveRequest,
TagActiveRequest
); );
} }
} }

View file

@ -706,9 +706,9 @@ impl tag_service_server::TagService for TagService {
}; };
match set_or_toggle { match set_or_toggle {
SetOrToggle::Set => tag.set_active(true), SetOrToggle::Set => tag.set_active(true, state),
SetOrToggle::Unset => tag.set_active(false), SetOrToggle::Unset => tag.set_active(false, state),
SetOrToggle::Toggle => tag.set_active(!tag.active()), SetOrToggle::Toggle => tag.set_active(!tag.active(), state),
SetOrToggle::Unspecified => unreachable!(), SetOrToggle::Unspecified => unreachable!(),
} }
@ -736,11 +736,11 @@ impl tag_service_server::TagService for TagService {
let Some(tag) = tag_id.tag(state) else { return }; let Some(tag) = tag_id.tag(state) else { return };
let Some(output) = tag.output(state) else { return }; let Some(output) = tag.output(state) else { return };
output.with_state_mut(|state| { output.with_state_mut(|op_state| {
for op_tag in state.tags.iter_mut() { for op_tag in op_state.tags.iter_mut() {
op_tag.set_active(false); op_tag.set_active(false, state);
} }
tag.set_active(true); tag.set_active(true, state);
}); });
state.request_layout(&output); state.request_layout(&output);

View file

@ -3,8 +3,9 @@ use std::collections::VecDeque;
use pinnacle_api_defs::pinnacle::signal::v0alpha1::{ use pinnacle_api_defs::pinnacle::signal::v0alpha1::{
signal_service_server, OutputConnectRequest, OutputConnectResponse, OutputDisconnectRequest, signal_service_server, OutputConnectRequest, OutputConnectResponse, OutputDisconnectRequest,
OutputDisconnectResponse, OutputMoveRequest, OutputMoveResponse, OutputResizeRequest, OutputDisconnectResponse, OutputMoveRequest, OutputMoveResponse, OutputResizeRequest,
OutputResizeResponse, SignalRequest, StreamControl, WindowPointerEnterRequest, OutputResizeResponse, SignalRequest, StreamControl, TagActiveRequest, TagActiveResponse,
WindowPointerEnterResponse, WindowPointerLeaveRequest, WindowPointerLeaveResponse, WindowPointerEnterRequest, WindowPointerEnterResponse, WindowPointerLeaveRequest,
WindowPointerLeaveResponse,
}; };
use tokio::{sync::mpsc::UnboundedSender, task::JoinHandle}; use tokio::{sync::mpsc::UnboundedSender, task::JoinHandle};
use tonic::{Request, Response, Status, Streaming}; use tonic::{Request, Response, Status, Streaming};
@ -27,6 +28,9 @@ pub struct SignalState {
SignalData<WindowPointerEnterResponse, VecDeque<WindowPointerEnterResponse>>, SignalData<WindowPointerEnterResponse, VecDeque<WindowPointerEnterResponse>>,
pub window_pointer_leave: pub window_pointer_leave:
SignalData<WindowPointerLeaveResponse, VecDeque<WindowPointerLeaveResponse>>, SignalData<WindowPointerLeaveResponse, VecDeque<WindowPointerLeaveResponse>>,
// Tag
pub tag_active: SignalData<TagActiveResponse, VecDeque<TagActiveResponse>>,
} }
impl SignalState { impl SignalState {
@ -184,9 +188,12 @@ impl signal_service_server::SignalService for SignalService {
type OutputDisconnectStream = ResponseStream<OutputDisconnectResponse>; type OutputDisconnectStream = ResponseStream<OutputDisconnectResponse>;
type OutputResizeStream = ResponseStream<OutputResizeResponse>; type OutputResizeStream = ResponseStream<OutputResizeResponse>;
type OutputMoveStream = ResponseStream<OutputMoveResponse>; type OutputMoveStream = ResponseStream<OutputMoveResponse>;
type WindowPointerEnterStream = ResponseStream<WindowPointerEnterResponse>; type WindowPointerEnterStream = ResponseStream<WindowPointerEnterResponse>;
type WindowPointerLeaveStream = ResponseStream<WindowPointerLeaveResponse>; type WindowPointerLeaveStream = ResponseStream<WindowPointerLeaveResponse>;
type TagActiveStream = ResponseStream<TagActiveResponse>;
async fn output_connect( async fn output_connect(
&self, &self,
request: Request<Streaming<OutputConnectRequest>>, request: Request<Streaming<OutputConnectRequest>>,
@ -252,4 +259,15 @@ impl signal_service_server::SignalService for SignalService {
&mut state.signal_state.window_pointer_leave &mut state.signal_state.window_pointer_leave
}) })
} }
async fn tag_active(
&self,
request: Request<Streaming<TagActiveRequest>>,
) -> Result<Response<Self::TagActiveStream>, Status> {
let in_stream = request.into_inner();
start_signal_stream(self.sender.clone(), in_stream, |state| {
&mut state.signal_state.tag_active
})
}
} }

View file

@ -87,8 +87,17 @@ impl Tag {
self.0.lock().expect("tag already locked").active self.0.lock().expect("tag already locked").active
} }
pub fn set_active(&self, active: bool) { pub fn set_active(&self, active: bool, state: &mut State) {
self.0.lock().expect("tag already locked").active = active; self.0.lock().expect("tag already locked").active = active;
state.signal_state.tag_active.signal(|buf| {
buf.push_back(
pinnacle_api_defs::pinnacle::signal::v0alpha1::TagActiveResponse {
tag_id: Some(self.id().0),
active: Some(self.active()),
},
);
})
} }
} }