Add output resize and move signals

This commit is contained in:
Ottatop 2024-04-12 11:46:52 -05:00
parent 6d78436d18
commit 023ebe8a2d
11 changed files with 207 additions and 18 deletions

View file

@ -166,12 +166,17 @@ function output.connect_for_all(callback)
})
end
---@type table<string, SignalServiceMethod>
local signal_name_to_SignalName = {
connect = "OutputConnect",
resize = "OutputResize",
move = "OutputMove",
}
---@class OutputSignal Signals related to output events.
---@field connect fun(output: OutputHandle)? An output was connected. FIXME: This currently does not fire for outputs that have been previously connected and disconnected.
---@field resize fun(output: OutputHandle, logical_width: integer, logical_height: integer)? An output's logical size changed.
---@field move fun(output: OutputHandle, x: integer, y: integer)? An output moved.
---Connect to an output signal.
---

View file

@ -14,6 +14,12 @@ local rpc_types = {
OutputConnect = {
response_type = "OutputConnectResponse",
},
OutputResize = {
response_type = "OutputResizeResponse",
},
OutputMove = {
response_type = "OutputMoveResponse",
},
WindowPointerEnter = {
response_type = "WindowPointerEnterResponse",
},
@ -62,6 +68,28 @@ local signals = {
---@type fun(response: table)
on_response = nil,
},
OutputResize = {
---@nodoc
---@type H2Stream?
sender = nil,
---@nodoc
---@type (fun(output: OutputHandle, logical_width: integer, logical_height: integer))[]
callbacks = {},
---@nodoc
---@type fun(response: table)
on_response = nil,
},
OutputMove = {
---@nodoc
---@type H2Stream?
sender = nil,
---@nodoc
---@type (fun(output: OutputHandle, x: integer, y: integer))[]
callbacks = {},
---@nodoc
---@type fun(response: table)
on_response = nil,
},
WindowPointerEnter = {
---@nodoc
---@type H2Stream?
@ -94,6 +122,22 @@ signals.OutputConnect.on_response = function(response)
end
end
signals.OutputResize.on_response = function(response)
---@diagnostic disable-next-line: invisible
local handle = require("pinnacle.output").handle.new(response.output_name)
for _, callback in ipairs(signals.OutputResize.callbacks) do
callback(handle, response.logical_width, response.logical_height)
end
end
signals.OutputMove.on_response = function(response)
---@diagnostic disable-next-line: invisible
local handle = require("pinnacle.output").handle.new(response.output_name)
for _, callback in ipairs(signals.OutputMove.callbacks) do
callback(handle, response.x, response.y)
end
end
signals.WindowPointerEnter.on_response = function(response)
---@diagnostic disable-next-line: invisible
local window_handle = require("pinnacle.window").handle.new(response.window_id)

View file

@ -17,6 +17,26 @@ message OutputConnectResponse {
optional string output_name = 1;
}
message OutputResizeRequest {
optional StreamControl control = 1;
}
// An output's logical size changed
message OutputResizeResponse {
optional string output_name = 1;
optional uint32 logical_width = 2;
optional uint32 logical_height = 3;
}
message OutputMoveRequest {
optional StreamControl control = 1;
}
// An output's location in the global space changed
message OutputMoveResponse {
optional string output_name = 1;
optional int32 x = 2;
optional int32 y = 3;
}
message WindowPointerEnterRequest {
optional StreamControl control = 1;
}
@ -35,6 +55,8 @@ message WindowPointerLeaveResponse {
service SignalService {
rpc OutputConnect(stream OutputConnectRequest) returns (stream OutputConnectResponse);
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);
}

View file

@ -159,6 +159,8 @@ impl Output {
match signal {
OutputSignal::Connect(f) => signal_state.output_connect.add_callback(f),
OutputSignal::Resize(f) => signal_state.output_resize.add_callback(f),
OutputSignal::Move(f) => signal_state.output_move.add_callback(f),
}
}
}

View file

@ -6,6 +6,8 @@
//! Some of the other modules have a `connect_signal` method that will allow you to pass in
//! callbacks to run on each signal. Use them to connect to the signals defined here.
#![allow(clippy::type_complexity)]
use std::{
collections::{btree_map, BTreeMap},
sync::{
@ -138,6 +140,42 @@ signals! {
}
},
}
/// An output's logical size changed.
///
/// Callbacks receive the output and new width and height.
OutputResize = {
enum_name = Resize,
callback_type = Box<dyn FnMut(&OutputHandle, u32, u32) + Send + 'static>,
client_request = output_resize,
on_response = |response, callbacks| {
if let Some(output_name) = &response.output_name {
let output = OUTPUT.get().expect("OUTPUT doesn't exist");
let handle = output.new_handle(output_name);
for callback in callbacks {
callback(&handle, response.logical_width(), response.logical_height())
}
}
},
}
/// An output's location in the global space changed.
///
/// Callbacks receive the output and new x and y.
OutputMove = {
enum_name = Move,
callback_type = Box<dyn FnMut(&OutputHandle, i32, i32) + Send + 'static>,
client_request = output_move,
on_response = |response, callbacks| {
if let Some(output_name) = &response.output_name {
let output = OUTPUT.get().expect("OUTPUT doesn't exist");
let handle = output.new_handle(output_name);
for callback in callbacks {
callback(&handle, response.x(), response.y())
}
}
},
}
}
/// Signals relating to window events.
WindowSignal => {
@ -185,6 +223,8 @@ pub(crate) type SingleWindowFn = Box<dyn FnMut(&WindowHandle) + Send + 'static>;
pub(crate) struct SignalState {
pub(crate) output_connect: SignalData<OutputConnect>,
pub(crate) output_resize: SignalData<OutputResize>,
pub(crate) output_move: SignalData<OutputMove>,
pub(crate) window_pointer_enter: SignalData<WindowPointerEnter>,
pub(crate) window_pointer_leave: SignalData<WindowPointerLeave>,
}
@ -197,6 +237,8 @@ impl SignalState {
let client = SignalServiceClient::new(channel);
Self {
output_connect: SignalData::new(client.clone(), fut_sender.clone()),
output_resize: 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_leave: SignalData::new(client.clone(), fut_sender.clone()),
}

View file

@ -63,6 +63,8 @@ pub mod pinnacle {
impl_signal_request!(
OutputConnectRequest,
OutputResizeRequest,
OutputMoveRequest,
WindowPointerEnterRequest,
WindowPointerLeaveRequest
);

View file

@ -955,8 +955,7 @@ impl output_service_server::OutputService for OutputService {
if let Some(y) = y {
loc.y = y;
}
output.change_current_state(None, None, None, Some(loc));
state.space.map_output(&output, loc);
state.change_output_state(&output, None, None, None, Some(loc));
debug!("Mapping output {} to {loc:?}", output.name());
state.request_layout(&output);
})
@ -1016,7 +1015,13 @@ impl output_service_server::OutputService for OutputService {
current_scale = f64::max(current_scale, 0.25);
output.change_current_state(None, None, Some(Scale::Fractional(current_scale)), None);
state.change_output_state(
&output,
None,
None,
Some(Scale::Fractional(current_scale)),
None,
);
layer_map_for_output(&output).arrange();
state.request_layout(&output);
state.schedule_render(&output);

View file

@ -1,9 +1,10 @@
use std::collections::VecDeque;
use pinnacle_api_defs::pinnacle::signal::v0alpha1::{
signal_service_server, OutputConnectRequest, OutputConnectResponse, SignalRequest,
StreamControl, WindowPointerEnterRequest, WindowPointerEnterResponse,
WindowPointerLeaveRequest, WindowPointerLeaveResponse,
signal_service_server, OutputConnectRequest, OutputConnectResponse, OutputMoveRequest,
OutputMoveResponse, OutputResizeRequest, OutputResizeResponse, SignalRequest, StreamControl,
WindowPointerEnterRequest, WindowPointerEnterResponse, WindowPointerLeaveRequest,
WindowPointerLeaveResponse,
};
use tokio::{sync::mpsc::UnboundedSender, task::JoinHandle};
use tonic::{Request, Response, Status, Streaming};
@ -15,7 +16,12 @@ use super::{run_bidirectional_streaming, ResponseStream, StateFnSender};
#[derive(Debug, Default)]
pub struct SignalState {
// Output
pub output_connect: SignalData<OutputConnectResponse, VecDeque<OutputConnectResponse>>,
pub output_resize: SignalData<OutputResizeResponse, VecDeque<OutputResizeResponse>>,
pub output_move: SignalData<OutputMoveResponse, VecDeque<OutputMoveResponse>>,
// Window
pub window_pointer_enter:
SignalData<WindowPointerEnterResponse, VecDeque<WindowPointerEnterResponse>>,
pub window_pointer_leave:
@ -171,6 +177,8 @@ impl SignalService {
#[tonic::async_trait]
impl signal_service_server::SignalService for SignalService {
type OutputConnectStream = ResponseStream<OutputConnectResponse>;
type OutputResizeStream = ResponseStream<OutputResizeResponse>;
type OutputMoveStream = ResponseStream<OutputMoveResponse>;
type WindowPointerEnterStream = ResponseStream<WindowPointerEnterResponse>;
type WindowPointerLeaveStream = ResponseStream<WindowPointerLeaveResponse>;
@ -185,6 +193,28 @@ impl signal_service_server::SignalService for SignalService {
})
}
async fn output_resize(
&self,
request: Request<Streaming<OutputResizeRequest>>,
) -> Result<Response<Self::OutputResizeStream>, Status> {
let in_stream = request.into_inner();
start_signal_stream(self.sender.clone(), in_stream, |state| {
&mut state.signal_state.output_resize
})
}
async fn output_move(
&self,
request: Request<Streaming<OutputMoveRequest>>,
) -> Result<Response<Self::OutputMoveStream>, Status> {
let in_stream = request.into_inner();
start_signal_stream(self.sender.clone(), in_stream, |state| {
&mut state.signal_state.output_move
})
}
async fn window_pointer_enter(
&self,
request: Request<Streaming<WindowPointerEnterRequest>>,

View file

@ -272,7 +272,7 @@ impl State {
{
match render_surface.compositor.use_mode(drm_mode) {
Ok(()) => {
output.change_current_state(Some(mode), None, None, None);
self.change_output_state(output, Some(mode), None, None, None);
layer_map_for_output(output).arrange();
}
Err(err) => error!("Failed to resize output: {err}"),
@ -280,7 +280,7 @@ impl State {
}
}
} else {
output.change_current_state(Some(mode), None, None, None);
self.change_output_state(output, Some(mode), None, None, None);
layer_map_for_output(output).arrange();
}
@ -1042,6 +1042,8 @@ impl State {
output.add_mode(mode);
}
output.set_preferred(wl_mode);
self.output_focus_stack.set_focus(output.clone());
let x = self.space.outputs().fold(0, |acc, o| {
@ -1052,10 +1054,6 @@ impl State {
});
let position = (x, 0).into();
output.set_preferred(wl_mode);
output.change_current_state(Some(wl_mode), None, None, Some(position));
self.space.map_output(&output, position);
output.user_data().insert_if_missing(|| UdevOutputData {
crtc,
device_id: node,
@ -1122,6 +1120,8 @@ impl State {
device.surfaces.insert(crtc, surface);
self.change_output_state(&output, Some(wl_mode), None, None, Some(position));
// If there is saved connector state, the connector was previously plugged in.
// In this case, restore its tags and location.
// TODO: instead of checking the connector, check the monitor's edid info instead
@ -1131,11 +1131,8 @@ impl State {
.get(&OutputName(output.name()))
{
let ConnectorSavedState { loc, tags, scale } = saved_state;
output.change_current_state(None, None, *scale, Some(*loc));
self.space.map_output(&output, *loc);
output.with_state_mut(|state| state.tags = tags.clone());
self.change_output_state(&output, None, None, *scale, Some(*loc));
} else {
self.signal_state.output_connect.signal(|buffer| {
buffer.push_back(OutputConnectResponse {

View file

@ -221,7 +221,8 @@ pub fn setup_winit(
size,
refresh: 144_000,
};
output.change_current_state(
state.change_output_state(
&output,
Some(mode),
None,
Some(Scale::Fractional(scale_factor)),

View file

@ -2,7 +2,11 @@
use std::cell::RefCell;
use smithay::output::Output;
use pinnacle_api_defs::pinnacle::signal::v0alpha1::{OutputMoveResponse, OutputResizeResponse};
use smithay::{
output::{Mode, Output, Scale},
utils::{Logical, Point, Transform},
};
use crate::{
focus::WindowKeyboardFocusStack,
@ -68,3 +72,38 @@ impl OutputState {
self.tags.iter().filter(|tag| tag.active())
}
}
impl State {
/// A wrapper around [`Output::change_current_state`] that additionally sends an output
/// geometry signal.
pub fn change_output_state(
&mut self,
output: &Output,
mode: Option<Mode>,
transform: Option<Transform>,
scale: Option<Scale>,
location: Option<Point<i32, Logical>>,
) {
output.change_current_state(mode, transform, scale, location);
if let Some(location) = location {
self.space.map_output(output, location);
self.signal_state.output_move.signal(|buf| {
buf.push_back(OutputMoveResponse {
output_name: Some(output.name()),
x: Some(location.x),
y: Some(location.y),
});
});
}
if mode.is_some() || transform.is_some() || scale.is_some() {
self.signal_state.output_resize.signal(|buf| {
let geo = self.space.output_geometry(output);
buf.push_back(OutputResizeResponse {
output_name: Some(output.name()),
logical_width: geo.map(|geo| geo.size.w as u32),
logical_height: geo.map(|geo| geo.size.h as u32),
});
});
}
}
}