Add output modes to API

This commit is contained in:
Ottatop 2024-03-22 18:37:27 -05:00
parent 869a2223f5
commit 2fd98301e6
4 changed files with 197 additions and 86 deletions

View file

@ -12,6 +12,7 @@ local service = prefix .. "OutputService"
---@enum (key) OutputServiceMethod
local rpc_types = {
SetLocation = {},
SetMode = {},
ConnectForAll = {
response_type = "ConnectForAllResponse",
},
@ -319,51 +320,65 @@ function OutputHandle:set_loc_adj_to(other, alignment)
---@type integer
local y
if not self_props.current_mode or not other_props.current_mode then
return
end
local self_width = self_props.current_mode.pixel_width
local self_height = self_props.current_mode.pixel_height
local other_width = other_props.current_mode.pixel_width
local other_height = other_props.current_mode.pixel_height
if dir == "top" or dir == "bottom" then
if dir == "top" then
y = other_props.y - self_props.pixel_height
y = other_props.y - self_height
else
y = other_props.y + other_props.pixel_height
y = other_props.y + other_height
end
if align == "left" then
x = other_props.x
elseif align == "center" then
x = other_props.x + math.floor((other_props.pixel_width - self_props.pixel_width) / 2)
x = other_props.x + math.floor((other_width - self_width) / 2)
elseif align == "bottom" then
x = other_props.x + (other_props.pixel_width - self_props.pixel_width)
x = other_props.x + (other_width - self_width)
end
else
if dir == "left" then
x = other_props.x - self_props.pixel_width
x = other_props.x - self_width
else
x = other_props.x + other_props.pixel_width
x = other_props.x + other_width
end
if align == "top" then
y = other_props.y
elseif align == "center" then
y = other_props.y + math.floor((other_props.pixel_height - self_props.pixel_height) / 2)
y = other_props.y + math.floor((other_height - self_height) / 2)
elseif align == "bottom" then
y = other_props.y + (other_props.pixel_height - self_props.pixel_height)
y = other_props.y + (other_height - self_height)
end
end
self:set_location({ x = x, y = y })
end
---@class Mode
---@field pixel_width integer
---@field pixel_height integer
---@field refresh_rate_millihz integer
---@class OutputProperties
---@field make string?
---@field model string?
---@field x integer?
---@field y integer?
---@field pixel_width integer?
---@field pixel_height integer?
---@field refresh_rate integer?
---@field current_mode Mode?
---@field preferred_mode Mode?
---@field modes Mode[]
---@field physical_width integer?
---@field physical_height integer?
---@field focused boolean?
---@field tags TagHandle[]?
---@field tags TagHandle[]
---Get all properties of this output.
---
@ -376,6 +391,7 @@ function OutputHandle:props()
response.tags = handles
response.tag_ids = nil
response.modes = response.modes or {}
return response
end
@ -420,33 +436,31 @@ function OutputHandle:y()
return self:props().y
end
---Get this output's width in pixels.
---Get this output's current mode.
---
---Shorthand for `handle:props().pixel_width`.
---Shorthand for `handle:props().current_mode`.
---
---@return integer?
function OutputHandle:pixel_width()
return self:props().pixel_width
---@return Mode?
function OutputHandle:current_mode()
return self:props().current_mode
end
---Get this output's height in pixels.
---Get this output's preferred mode.
---
---Shorthand for `handle:props().pixel_height`.
---Shorthand for `handle:props().preferred_mode`.
---
---@return integer?
function OutputHandle:pixel_height()
return self:props().pixel_height
---@return Mode?
function OutputHandle:preferred_mode()
return self:props().preferred_mode
end
---Get this output's refresh rate in millihertz.
---Get all of this output's available modes.
---
---For example, 144Hz is returned as 144000.
---Shorthand for `handle:props().modes`.
---
---Shorthand for `handle:props().refresh_rate`.
---
---@return integer?
function OutputHandle:refresh_rate()
return self:props().refresh_rate
---@return Mode[]
function OutputHandle:modes()
return self:props().modes
end
---Get this output's physical width in millimeters.

View file

@ -4,12 +4,23 @@ package pinnacle.output.v0alpha1;
import "google/protobuf/empty.proto";
message Mode {
optional uint32 pixel_width = 1;
optional uint32 pixel_height = 2;
optional uint32 refresh_rate_millihz = 3;
}
message SetLocationRequest {
optional string output_name = 1;
optional int32 x = 2;
optional int32 y = 3;
}
message SetModeRequest {
optional string output_name = 1;
optional Mode mode = 2;
}
message GetRequest {}
message GetResponse {
repeated string output_names = 1;
@ -19,13 +30,24 @@ message GetPropertiesRequest {
optional string output_name = 1;
}
message GetPropertiesResponse {
// The monitor's manufacturer
optional string make = 1;
// The model of the monitor
optional string model = 2;
// The x-coord of the output in the global space
optional int32 x = 3;
// The y coord of the output in the global space
optional int32 y = 4;
optional uint32 pixel_width = 5;
optional uint32 pixel_height = 6;
optional uint32 refresh_rate = 7;
// NULLABLE
//
// The current mode
optional Mode current_mode = 5;
// NULLABLE
//
// The preferred mode
optional Mode preferred_mode = 6;
// All available modes
repeated Mode modes = 7;
// In millimeters
optional uint32 physical_width = 8;
// In millimeters
@ -36,6 +58,7 @@ message GetPropertiesResponse {
service OutputService {
rpc SetLocation(SetLocationRequest) returns (google.protobuf.Empty);
rpc SetMode(SetModeRequest) returns (google.protobuf.Empty);
rpc Get(GetRequest) returns (GetResponse);
rpc GetProperties(GetPropertiesRequest) returns (GetPropertiesResponse);
}

View file

@ -300,11 +300,13 @@ impl OutputHandle {
let attempt_set_loc = || -> Option<()> {
let other_x = other_props.x?;
let other_y = other_props.y?;
let other_width = other_props.pixel_width? as i32;
let other_height = other_props.pixel_height? as i32;
let other_mode = other_props.current_mode?;
let other_width = other_mode.pixel_width as i32;
let other_height = other_mode.pixel_height as i32;
let self_width = self_props.pixel_width? as i32;
let self_height = self_props.pixel_height? as i32;
let self_mode = self_props.current_mode?;
let self_width = self_mode.pixel_width as i32;
let self_height = self_mode.pixel_height as i32;
use Alignment::*;
@ -399,9 +401,31 @@ impl OutputHandle {
model: response.model,
x: response.x,
y: response.y,
pixel_width: response.pixel_width,
pixel_height: response.pixel_height,
refresh_rate: response.refresh_rate,
current_mode: response.current_mode.and_then(|mode| {
Some(Mode {
pixel_width: mode.pixel_width?,
pixel_height: mode.pixel_height?,
refresh_rate_millihertz: mode.refresh_rate_millihz?,
})
}),
preferred_mode: response.preferred_mode.and_then(|mode| {
Some(Mode {
pixel_width: mode.pixel_width?,
pixel_height: mode.pixel_height?,
refresh_rate_millihertz: mode.refresh_rate_millihz?,
})
}),
modes: response
.modes
.into_iter()
.flat_map(|mode| {
Some(Mode {
pixel_width: mode.pixel_width?,
pixel_height: mode.pixel_height?,
refresh_rate_millihertz: mode.refresh_rate_millihz?,
})
})
.collect(),
physical_width: response.physical_width,
physical_height: response.physical_height,
focused: response.focused,
@ -463,42 +487,40 @@ impl OutputHandle {
self.props_async().await.y
}
/// Get this output's screen width in pixels.
/// Get this output's current mode.
///
/// Shorthand for `self.props().pixel_width`.
pub fn pixel_width(&self) -> Option<u32> {
self.props().pixel_width
/// Shorthand for `self.props().current_mode`.
pub fn current_mode(&self) -> Option<Mode> {
self.props().current_mode
}
/// The async version of [`OutputHandle::pixel_width`].
pub async fn pixel_width_async(&self) -> Option<u32> {
self.props_async().await.pixel_width
/// The async version of [`OutputHandle::current_mode`].
pub async fn current_mode_async(&self) -> Option<Mode> {
self.props_async().await.current_mode
}
/// Get this output's screen height in pixels.
/// Get this output's preferred mode.
///
/// Shorthand for `self.props().pixel_height`.
pub fn pixel_height(&self) -> Option<u32> {
self.props().pixel_height
/// Shorthand for `self.props().preferred_mode`.
pub fn preferred_mode(&self) -> Option<Mode> {
self.props().preferred_mode
}
/// The async version of [`OutputHandle::pixel_height`].
pub async fn pixel_height_async(&self) -> Option<u32> {
self.props_async().await.pixel_height
/// The async version of [`OutputHandle::preferred_mode`].
pub async fn preferred_mode_async(&self) -> Option<Mode> {
self.props_async().await.preferred_mode
}
/// Get this output's refresh rate in millihertz.
/// Get all available modes this output supports.
///
/// For example, 144Hz will be returned as 144000.
///
/// Shorthand for `self.props().refresh_rate`.
pub fn refresh_rate(&self) -> Option<u32> {
self.props().refresh_rate
/// Shorthand for `self.props().modes`.
pub fn modes(&self) -> Vec<Mode> {
self.props().modes
}
/// The async version of [`OutputHandle::refresh_rate`].
pub async fn refresh_rate_async(&self) -> Option<u32> {
self.props_async().await.refresh_rate
/// The async version of [`OutputHandle::modes`].
pub async fn modes_async(&self) -> Vec<Mode> {
self.props_async().await.modes
}
/// Get this output's physical width in millimeters.
@ -557,34 +579,47 @@ impl OutputHandle {
}
}
/// A possible output pixel dimension and refresh rate configuration.
#[derive(Clone, Debug, PartialEq, Eq, Hash, Default)]
pub struct Mode {
/// The width of the output, in pixels.
pub pixel_width: u32,
/// The height of the output, in pixels.
pub pixel_height: u32,
/// The output's refresh rate, in millihertz.
///
/// For example, 60Hz is returned as 60000.
pub refresh_rate_millihertz: u32,
}
/// The properties of an output.
#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
pub struct OutputProperties {
/// The make of the output
/// The make of the output.
pub make: Option<String>,
/// The model of the output
/// The model of the output.
///
/// This is something like "27GL83A" or whatever crap monitor manufacturers name their monitors
/// these days.
pub model: Option<String>,
/// The x position of the output in the global space
/// The x position of the output in the global space.
pub x: Option<i32>,
/// The y position of the output in the global space
/// The y position of the output in the global space.
pub y: Option<i32>,
/// The output's screen width in pixels
pub pixel_width: Option<u32>,
/// The output's screen height in pixels
pub pixel_height: Option<u32>,
/// The output's refresh rate in millihertz
pub refresh_rate: Option<u32>,
/// The output's physical width in millimeters
/// The output's current mode.
pub current_mode: Option<Mode>,
/// The output's preferred mode.
pub preferred_mode: Option<Mode>,
/// All available modes the output supports.
pub modes: Vec<Mode>,
/// The output's physical width in millimeters.
pub physical_width: Option<u32>,
/// The output's physical height in millimeters
/// The output's physical height in millimeters.
pub physical_height: Option<u32>,
/// Whether this output is focused or not
/// Whether this output is focused or not.
///
/// This is currently implemented as the output with the most recent pointer motion.
pub focused: Option<bool>,
/// The tags this output has
/// The tags this output has.
pub tags: Vec<TagHandle>,
}

View file

@ -14,7 +14,7 @@ use pinnacle_api_defs::pinnacle::{
},
output::{
self,
v0alpha1::{output_service_server, SetLocationRequest},
v0alpha1::{output_service_server, SetLocationRequest, SetModeRequest},
},
process::v0alpha1::{process_service_server, SetEnvRequest, SpawnRequest, SpawnResponse},
tag::{
@ -949,6 +949,30 @@ impl output_service_server::OutputService for OutputService {
.await
}
async fn set_mode(&self, request: Request<SetModeRequest>) -> Result<Response<()>, Status> {
let request = request.into_inner();
run_unary_no_response(&self.sender, |state| {
let Some(output) = request
.output_name
.map(OutputName)
.and_then(|name| name.output(state))
else {
return;
};
let mode = request.mode.and_then(|mode| {
Some(smithay::output::Mode {
size: (mode.pixel_width? as i32, mode.pixel_height? as i32).into(),
refresh: mode.refresh_rate_millihz? as i32,
})
});
output.change_current_state(mode, None, None, None);
})
.await
}
async fn get(
&self,
_request: Request<output::v0alpha1::GetRequest>,
@ -977,20 +1001,35 @@ impl output_service_server::OutputService for OutputService {
.ok_or_else(|| Status::invalid_argument("no output specified"))?,
);
let from_smithay_mode = |mode: smithay::output::Mode| -> output::v0alpha1::Mode {
output::v0alpha1::Mode {
pixel_width: Some(mode.size.w as u32),
pixel_height: Some(mode.size.h as u32),
refresh_rate_millihz: Some(mode.refresh as u32),
}
};
run_unary(&self.sender, move |state| {
let output = output_name.output(state);
let pixel_width = output
let current_mode = output
.as_ref()
.and_then(|output| output.current_mode().map(|mode| mode.size.w as u32));
.and_then(|output| output.current_mode().map(from_smithay_mode));
let pixel_height = output
let preferred_mode = output
.as_ref()
.and_then(|output| output.current_mode().map(|mode| mode.size.h as u32));
.and_then(|output| output.preferred_mode().map(from_smithay_mode));
let refresh_rate = output
let modes = output
.as_ref()
.and_then(|output| output.current_mode().map(|mode| mode.refresh as u32));
.map(|output| {
output
.modes()
.into_iter()
.map(from_smithay_mode)
.collect::<Vec<_>>()
})
.unwrap_or(Vec::new());
let model = output
.as_ref()
@ -1030,9 +1069,9 @@ impl output_service_server::OutputService for OutputService {
model,
x,
y,
pixel_width,
pixel_height,
refresh_rate,
current_mode,
preferred_mode,
modes,
physical_width,
physical_height,
focused,