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 ---@enum (key) OutputServiceMethod
local rpc_types = { local rpc_types = {
SetLocation = {}, SetLocation = {},
SetMode = {},
ConnectForAll = { ConnectForAll = {
response_type = "ConnectForAllResponse", response_type = "ConnectForAllResponse",
}, },
@ -319,51 +320,65 @@ function OutputHandle:set_loc_adj_to(other, alignment)
---@type integer ---@type integer
local y 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" or dir == "bottom" then
if dir == "top" then if dir == "top" then
y = other_props.y - self_props.pixel_height y = other_props.y - self_height
else else
y = other_props.y + other_props.pixel_height y = other_props.y + other_height
end end
if align == "left" then if align == "left" then
x = other_props.x x = other_props.x
elseif align == "center" then 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 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 end
else else
if dir == "left" then if dir == "left" then
x = other_props.x - self_props.pixel_width x = other_props.x - self_width
else else
x = other_props.x + other_props.pixel_width x = other_props.x + other_width
end end
if align == "top" then if align == "top" then
y = other_props.y y = other_props.y
elseif align == "center" then 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 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
end end
self:set_location({ x = x, y = y }) self:set_location({ x = x, y = y })
end end
---@class Mode
---@field pixel_width integer
---@field pixel_height integer
---@field refresh_rate_millihz integer
---@class OutputProperties ---@class OutputProperties
---@field make string? ---@field make string?
---@field model string? ---@field model string?
---@field x integer? ---@field x integer?
---@field y integer? ---@field y integer?
---@field pixel_width integer? ---@field current_mode Mode?
---@field pixel_height integer? ---@field preferred_mode Mode?
---@field refresh_rate integer? ---@field modes Mode[]
---@field physical_width integer? ---@field physical_width integer?
---@field physical_height integer? ---@field physical_height integer?
---@field focused boolean? ---@field focused boolean?
---@field tags TagHandle[]? ---@field tags TagHandle[]
---Get all properties of this output. ---Get all properties of this output.
--- ---
@ -376,6 +391,7 @@ function OutputHandle:props()
response.tags = handles response.tags = handles
response.tag_ids = nil response.tag_ids = nil
response.modes = response.modes or {}
return response return response
end end
@ -420,33 +436,31 @@ function OutputHandle:y()
return self:props().y return self:props().y
end 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? ---@return Mode?
function OutputHandle:pixel_width() function OutputHandle:current_mode()
return self:props().pixel_width return self:props().current_mode
end 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? ---@return Mode?
function OutputHandle:pixel_height() function OutputHandle:preferred_mode()
return self:props().pixel_height return self:props().preferred_mode
end 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 Mode[]
--- function OutputHandle:modes()
---@return integer? return self:props().modes
function OutputHandle:refresh_rate()
return self:props().refresh_rate
end end
---Get this output's physical width in millimeters. ---Get this output's physical width in millimeters.

View file

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

View file

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

View file

@ -14,7 +14,7 @@ use pinnacle_api_defs::pinnacle::{
}, },
output::{ output::{
self, self,
v0alpha1::{output_service_server, SetLocationRequest}, v0alpha1::{output_service_server, SetLocationRequest, SetModeRequest},
}, },
process::v0alpha1::{process_service_server, SetEnvRequest, SpawnRequest, SpawnResponse}, process::v0alpha1::{process_service_server, SetEnvRequest, SpawnRequest, SpawnResponse},
tag::{ tag::{
@ -949,6 +949,30 @@ impl output_service_server::OutputService for OutputService {
.await .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( async fn get(
&self, &self,
_request: Request<output::v0alpha1::GetRequest>, _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"))?, .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| { run_unary(&self.sender, move |state| {
let output = output_name.output(state); let output = output_name.output(state);
let pixel_width = output let current_mode = output
.as_ref() .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() .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() .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 let model = output
.as_ref() .as_ref()
@ -1030,9 +1069,9 @@ impl output_service_server::OutputService for OutputService {
model, model,
x, x,
y, y,
pixel_width, current_mode,
pixel_height, preferred_mode,
refresh_rate, modes,
physical_width, physical_width,
physical_height, physical_height,
focused, focused,