diff --git a/api/lua/pinnacle/signal.lua b/api/lua/pinnacle/signal.lua index 69a5029..857bd27 100644 --- a/api/lua/pinnacle/signal.lua +++ b/api/lua/pinnacle/signal.lua @@ -23,12 +23,17 @@ local rpc_types = { OutputMove = { response_type = "OutputMoveResponse", }, + WindowPointerEnter = { response_type = "WindowPointerEnterResponse", }, WindowPointerLeave = { response_type = "WindowPointerLeaveResponse", }, + + TagActive = { + response_type = "TagActiveResponse", + }, } ---Build GrpcRequestParams @@ -126,6 +131,17 @@ local signals = { ---@type fun(response: table) 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) @@ -178,6 +194,15 @@ signals.WindowPointerLeave.on_response = function(response) 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 diff --git a/api/lua/pinnacle/tag.lua b/api/lua/pinnacle/tag.lua index 357eb66..a4541f1 100644 --- a/api/lua/pinnacle/tag.lua +++ b/api/lua/pinnacle/tag.lua @@ -210,6 +210,52 @@ function tag.remove(tags) client.unary_request(build_grpc_request_params("Remove", { tag_ids = ids })) end +---@type table +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. --- ---### Example diff --git a/api/protocol/pinnacle/signal/v0alpha1/signal.proto b/api/protocol/pinnacle/signal/v0alpha1/signal.proto index e922ab4..603dd04 100644 --- a/api/protocol/pinnacle/signal/v0alpha1/signal.proto +++ b/api/protocol/pinnacle/signal/v0alpha1/signal.proto @@ -26,6 +26,7 @@ message OutputDisconnectResponse { message OutputResizeRequest { optional StreamControl control = 1; } + // An output's logical size changed message OutputResizeResponse { optional string output_name = 1; @@ -36,6 +37,7 @@ message OutputResizeResponse { message OutputMoveRequest { optional StreamControl control = 1; } + // An output's location in the global space changed message OutputMoveResponse { optional string output_name = 1; @@ -59,11 +61,23 @@ message WindowPointerLeaveResponse { 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 { rpc OutputConnect(stream OutputConnectRequest) returns (stream OutputConnectResponse); rpc OutputDisconnect(stream OutputDisconnectRequest) returns (stream OutputDisconnectResponse); rpc OutputResize(stream OutputResizeRequest) returns (stream OutputResizeResponse); rpc OutputMove(stream OutputMoveRequest) returns (stream OutputMoveResponse); + rpc WindowPointerEnter(stream WindowPointerEnterRequest) returns (stream WindowPointerEnterResponse); rpc WindowPointerLeave(stream WindowPointerLeaveRequest) returns (stream WindowPointerLeaveResponse); + + rpc TagActive(stream TagActiveRequest) returns (stream TagActiveResponse); } diff --git a/api/rust/src/signal.rs b/api/rust/src/signal.rs index 7081c4c..c2ca2d3 100644 --- a/api/rust/src/signal.rs +++ b/api/rust/src/signal.rs @@ -27,7 +27,9 @@ use tokio::sync::{ use tokio_stream::{wrappers::UnboundedReceiverStream, StreamExt}; 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 { 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, + 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; @@ -239,8 +259,11 @@ pub(crate) struct SignalState { pub(crate) output_disconnect: SignalData, pub(crate) output_resize: SignalData, pub(crate) output_move: SignalData, + pub(crate) window_pointer_enter: SignalData, pub(crate) window_pointer_leave: SignalData, + + pub(crate) tag_active: SignalData, } impl std::fmt::Debug for SignalState { @@ -262,6 +285,7 @@ impl SignalState { output_move: 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()), + tag_active: SignalData::new(client.clone(), fut_sender.clone()), } } @@ -272,6 +296,7 @@ impl SignalState { self.output_move.api.set(api.clone()).unwrap(); self.window_pointer_enter.api.set(api.clone()).unwrap(); self.window_pointer_leave.api.set(api.clone()).unwrap(); + self.tag_active.api.set(api.clone()).unwrap(); } } diff --git a/api/rust/src/tag.rs b/api/rust/src/tag.rs index 5fad16b..2f53b0d 100644 --- a/api/rust/src/tag.rs +++ b/api/rust/src/tag.rs @@ -44,7 +44,14 @@ use pinnacle_api_defs::pinnacle::{ }; 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. #[derive(Clone, Debug)] @@ -227,6 +234,19 @@ impl Tag { 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. diff --git a/pinnacle-api-defs/src/lib.rs b/pinnacle-api-defs/src/lib.rs index a1f4a2b..271f160 100644 --- a/pinnacle-api-defs/src/lib.rs +++ b/pinnacle-api-defs/src/lib.rs @@ -67,7 +67,8 @@ pub mod pinnacle { OutputResizeRequest, OutputMoveRequest, WindowPointerEnterRequest, - WindowPointerLeaveRequest + WindowPointerLeaveRequest, + TagActiveRequest ); } } diff --git a/src/api.rs b/src/api.rs index e2d2bf8..f2bee98 100644 --- a/src/api.rs +++ b/src/api.rs @@ -706,9 +706,9 @@ impl tag_service_server::TagService for TagService { }; match set_or_toggle { - SetOrToggle::Set => tag.set_active(true), - SetOrToggle::Unset => tag.set_active(false), - SetOrToggle::Toggle => tag.set_active(!tag.active()), + SetOrToggle::Set => tag.set_active(true, state), + SetOrToggle::Unset => tag.set_active(false, state), + SetOrToggle::Toggle => tag.set_active(!tag.active(), state), 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(output) = tag.output(state) else { return }; - output.with_state_mut(|state| { - for op_tag in state.tags.iter_mut() { - op_tag.set_active(false); + output.with_state_mut(|op_state| { + for op_tag in op_state.tags.iter_mut() { + op_tag.set_active(false, state); } - tag.set_active(true); + tag.set_active(true, state); }); state.request_layout(&output); diff --git a/src/api/signal.rs b/src/api/signal.rs index 09e19ae..1fea7df 100644 --- a/src/api/signal.rs +++ b/src/api/signal.rs @@ -3,8 +3,9 @@ use std::collections::VecDeque; use pinnacle_api_defs::pinnacle::signal::v0alpha1::{ signal_service_server, OutputConnectRequest, OutputConnectResponse, OutputDisconnectRequest, OutputDisconnectResponse, OutputMoveRequest, OutputMoveResponse, OutputResizeRequest, - OutputResizeResponse, SignalRequest, StreamControl, WindowPointerEnterRequest, - WindowPointerEnterResponse, WindowPointerLeaveRequest, WindowPointerLeaveResponse, + OutputResizeResponse, SignalRequest, StreamControl, TagActiveRequest, TagActiveResponse, + WindowPointerEnterRequest, WindowPointerEnterResponse, WindowPointerLeaveRequest, + WindowPointerLeaveResponse, }; use tokio::{sync::mpsc::UnboundedSender, task::JoinHandle}; use tonic::{Request, Response, Status, Streaming}; @@ -27,6 +28,9 @@ pub struct SignalState { SignalData>, pub window_pointer_leave: SignalData>, + + // Tag + pub tag_active: SignalData>, } impl SignalState { @@ -184,9 +188,12 @@ impl signal_service_server::SignalService for SignalService { type OutputDisconnectStream = ResponseStream; type OutputResizeStream = ResponseStream; type OutputMoveStream = ResponseStream; + type WindowPointerEnterStream = ResponseStream; type WindowPointerLeaveStream = ResponseStream; + type TagActiveStream = ResponseStream; + async fn output_connect( &self, request: Request>, @@ -252,4 +259,15 @@ impl signal_service_server::SignalService for SignalService { &mut state.signal_state.window_pointer_leave }) } + + async fn tag_active( + &self, + request: Request>, + ) -> Result, Status> { + let in_stream = request.into_inner(); + + start_signal_stream(self.sender.clone(), in_stream, |state| { + &mut state.signal_state.tag_active + }) + } } diff --git a/src/tag.rs b/src/tag.rs index cc9d1f2..f4b3490 100644 --- a/src/tag.rs +++ b/src/tag.rs @@ -87,8 +87,17 @@ impl Tag { 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; + + 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()), + }, + ); + }) } }