api: Add output powered and enabled props

This commit is contained in:
Ottatop 2024-06-04 16:52:12 -05:00
parent 5fe5152b76
commit 1c55296d8f
15 changed files with 217 additions and 58 deletions

View file

@ -118,6 +118,8 @@ local pinnacle_output_v0alpha1_Transform = {
---@field transform pinnacle.output.v0alpha1.Transform?
---@field serial integer?
---@field keyboard_focus_stack_window_ids integer[]?
---@field enabled boolean?
---@field powered boolean?
-- Window

View file

@ -40,8 +40,6 @@ output.handle = output_handle
---
---@return OutputHandle[]
function output.get_all()
-- Not going to batch these because I doubt people would have that many monitors
local response = client.unary_request(output_service.Get, {})
---@type OutputHandle[]
@ -54,6 +52,27 @@ function output.get_all()
return handles
end
---Get all enabled outputs.
---
---### Example
---```lua
---local outputs = Output.get_all_enabled()
---```
---
---@return OutputHandle[]
function output.get_all_enabled()
local outputs = output.get_all()
local enabled_handles = {}
for _, handle in ipairs(outputs) do
if handle:enabled() then
table.insert(enabled_handles, handle)
end
end
return enabled_handles
end
---Get an output by its name (the connector it's plugged into).
---
---### Example
@ -421,7 +440,7 @@ function output.setup_locs(update_locs_on, locs)
end
local function layout_outputs()
local outputs = output.get_all()
local outputs = output.get_all_enabled()
---@type OutputHandle[]
local placed_outputs = {}
@ -948,6 +967,8 @@ end
---@field transform Transform?
---@field serial integer?
---@field keyboard_focus_stack WindowHandle[]
---@field enabled boolean?
---@field powered boolean?
---Get all properties of this output.
---
@ -1020,6 +1041,8 @@ end
---Get this output's logical width in pixels.
---
---If the output is disabled, this returns nil.
---
---Shorthand for `handle:props().logical_width`.
---
---@return integer?
@ -1029,6 +1052,8 @@ end
---Get this output's logical height in pixels.
---
---If the output is disabled, this returns nil.
---
---Shorthand for `handle:props().y`.
---
---@return integer?
@ -1142,6 +1167,25 @@ function OutputHandle:keyboard_focus_stack()
return self:props().keyboard_focus_stack
end
---Get whether this output is enabled.
---
---Disabled outputs are not mapped to the global space and cannot be used.
---
---@return boolean?
function OutputHandle:enabled()
return self:props().enabled
end
---Get whether this output is powered.
---
---Unpowered outputs that are enabled will be off, but they will still be
---mapped to the global space, meaning you can still interact with them.
---
---@return boolean?
function OutputHandle:powered()
return self:props().powered
end
---Get this output's keyboard focus stack.
---
---This only includes windows on active tags.

View file

@ -116,6 +116,8 @@ message GetPropertiesResponse {
optional uint32 serial = 16;
// Window ids of the keyboard focus stack for this output.
repeated uint32 keyboard_focus_stack_window_ids = 17;
optional bool enabled = 18;
optional bool powered = 19;
}
service OutputService {

View file

@ -60,7 +60,7 @@ impl Output {
}
}
/// Get a handle to all connected outputs.
/// Get handles to all connected outputs.
///
/// # Examples
///
@ -82,10 +82,35 @@ impl Output {
.into_inner()
.output_names
.into_iter()
.map(move |name| self.new_handle(name))
.map(|name| self.new_handle(name))
.collect()
}
/// Get handles to all outputs that are connected and enabled.
///
/// # Examples
///
/// ```
/// let enabled = output.get_all_enabled();
/// ```
pub fn get_all_enabled(&self) -> Vec<OutputHandle> {
block_on_tokio(self.get_all_enabled_async())
}
/// The async version of [`Output::get_all_enabled`].
pub async fn get_all_enabled_async(&self) -> Vec<OutputHandle> {
let outputs = self.get_all_async().await;
let mut enabled_outputs = Vec::new();
for output in outputs {
if output.enabled_async().await.unwrap_or_default() {
enabled_outputs.push(output);
}
}
enabled_outputs
}
/// Get a handle to the output with the given name.
///
/// By "name", we mean the name of the connector the output is connected to.
@ -272,7 +297,7 @@ impl Output {
let api = self.api.get().unwrap().clone();
let layout_outputs = move || {
let outputs = api.output.get_all();
let outputs = api.output.get_all_enabled();
let mut rightmost_output_and_x: Option<(OutputHandle, i32)> = None;
@ -1001,6 +1026,8 @@ impl OutputHandle {
.into_iter()
.map(|id| self.api.window.new_handle(id))
.collect(),
enabled: response.enabled,
powered: response.powered,
}
}
@ -1056,6 +1083,8 @@ impl OutputHandle {
/// Get this output's logical width in pixels.
///
/// If the output is disabled, this returns None.
///
/// Shorthand for `self.props().logical_width`.
pub fn logical_width(&self) -> Option<u32> {
self.props().logical_width
@ -1068,6 +1097,8 @@ impl OutputHandle {
/// Get this output's logical height in pixels.
///
/// If the output is disabled, this returns None.
///
/// Shorthand for `self.props().logical_height`.
pub fn logical_height(&self) -> Option<u32> {
self.props().logical_height
@ -1228,6 +1259,34 @@ impl OutputHandle {
.collect()
}
/// Get whether this output is enabled.
///
/// Disabled outputs act as if you unplugged them.
pub fn enabled(&self) -> Option<bool> {
self.props().enabled
}
/// The async version of [`OutputHandle::enabled`].
pub async fn enabled_async(&self) -> Option<bool> {
self.props_async().await.enabled
}
/// Get whether this output is powered.
///
/// Unpowered outputs will be turned off but you can still interact with them.
///
/// Outputs can be disabled but still powered; this just means
/// they will turn on when powered. Disabled and unpowered outputs
/// will not power on when enabled, but will still be interactable.
pub fn powered(&self) -> Option<bool> {
self.props().powered
}
/// The async version of [`OutputHandle::powered`].
pub async fn powered_async(&self) -> Option<bool> {
self.props_async().await.powered
}
/// Get this output's unique name (the name of its connector).
pub fn name(&self) -> String {
self.name.to_string()
@ -1292,6 +1351,15 @@ pub struct OutputProperties {
pub serial: Option<u32>,
/// This output's window keyboard focus stack.
pub keyboard_focus_stack: Vec<WindowHandle>,
/// Whether this output is enabled.
///
/// Enabled outputs are mapped in the global space and usable.
/// Disabled outputs function as if you unplugged them.
pub enabled: Option<bool>,
/// Whether this output is powered.
///
/// Unpowered outputs will be off but you can still interact with them.
pub powered: Option<bool>,
}
/// A custom modeline.

View file

@ -886,7 +886,7 @@ impl tag_service_server::TagService for TagService {
.flat_map(|id| id.tag(&state.pinnacle))
.collect::<Vec<_>>();
for output in state.pinnacle.space.outputs().cloned().collect::<Vec<_>>() {
for output in state.pinnacle.outputs.keys().cloned().collect::<Vec<_>>() {
// TODO: seriously, convert state.tags into a hashset
output.with_state_mut(|state| {
for tag_to_remove in tags_to_remove.iter() {
@ -916,8 +916,8 @@ impl tag_service_server::TagService for TagService {
run_unary(&self.sender, move |state| {
let tag_ids = state
.pinnacle
.space
.outputs()
.outputs
.keys()
.flat_map(|op| op.with_state(|state| state.tags.clone()))
.map(|tag| tag.id())
.map(|id| id.0)
@ -1272,8 +1272,8 @@ impl output_service_server::OutputService for OutputService {
run_unary(&self.sender, move |state| {
let output_names = state
.pinnacle
.space
.outputs()
.outputs
.keys()
.map(|output| output.name())
.collect::<Vec<_>>();
@ -1400,6 +1400,18 @@ impl output_service_server::OutputService for OutputService {
})
.unwrap_or_default();
let enabled = output.as_ref().map(|output| {
state
.pinnacle
.outputs
.get(output)
.is_some_and(|global| global.is_some())
});
let powered = output
.as_ref()
.map(|output| output.with_state(|state| state.powered));
output::v0alpha1::GetPropertiesResponse {
make,
model,
@ -1418,6 +1430,8 @@ impl output_service_server::OutputService for OutputService {
transform,
serial,
keyboard_focus_stack_window_ids,
enabled,
powered,
}
})
.await
@ -1453,7 +1467,7 @@ impl render_service_server::RenderService for RenderService {
run_unary_no_response(&self.sender, move |state| {
state.backend.set_upscale_filter(filter);
for output in state.pinnacle.space.outputs().cloned().collect::<Vec<_>>() {
for output in state.pinnacle.outputs.keys().cloned().collect::<Vec<_>>() {
state.backend.reset_buffers(&output);
state.schedule_render(&output);
}
@ -1478,7 +1492,7 @@ impl render_service_server::RenderService for RenderService {
run_unary_no_response(&self.sender, move |state| {
state.backend.set_downscale_filter(filter);
for output in state.pinnacle.space.outputs().cloned().collect::<Vec<_>>() {
for output in state.pinnacle.outputs.keys().cloned().collect::<Vec<_>>() {
state.backend.reset_buffers(&output);
state.schedule_render(&output);
}

View file

@ -95,10 +95,12 @@ impl Dummy {
UninitBackend {
seat_name: dummy.seat_name(),
init: Box::new(move |pinnacle| {
output.create_global::<State>(&display_handle);
let global = output.create_global::<State>(&display_handle);
pinnacle.output_focus_stack.set_focus(output.clone());
pinnacle.outputs.insert(output.clone(), Some(global));
pinnacle
.shm_state
.update_formats(dummy.renderer.shm_formats());
@ -136,7 +138,9 @@ impl Pinnacle {
output.set_preferred(mode);
output.with_state_mut(|state| state.modes = vec![mode]);
output.create_global::<State>(&self.display_handle);
let global = output.create_global::<State>(&self.display_handle);
self.outputs.insert(output.clone(), Some(global));
self.space.map_output(&output, (0, 0));

View file

@ -1037,7 +1037,7 @@ impl Udev {
let (phys_w, phys_h) = connector.size().unwrap_or((0, 0));
if pinnacle.space.outputs().any(|op| {
if pinnacle.outputs.keys().any(|op| {
op.user_data()
.get::<UdevOutputData>()
.is_some_and(|op_id| op_id.crtc == crtc)
@ -1056,11 +1056,10 @@ impl Udev {
);
let global = output.create_global::<State>(&self.display_handle);
pinnacle.outputs.insert(output.clone(), global);
pinnacle.outputs.insert(output.clone(), Some(global));
output.with_state_mut(|state| {
state.serial = serial;
state.powered = true;
});
output.set_preferred(wl_mode);
@ -1198,8 +1197,8 @@ impl Udev {
device.surfaces.remove(&crtc);
let output = pinnacle
.space
.outputs()
.outputs
.keys()
.find(|o| {
o.user_data()
.get::<UdevOutputData>()
@ -1285,15 +1284,11 @@ impl Udev {
return;
};
let output = if let Some(output) = pinnacle
.outputs
.keys()
.chain(pinnacle.unmapped_outputs.iter())
.find(|o| {
let udev_op_data = o.user_data().get::<UdevOutputData>();
udev_op_data
.is_some_and(|data| data.device_id == surface.device_id && data.crtc == crtc)
}) {
let output = if let Some(output) = pinnacle.outputs.keys().find(|o| {
let udev_op_data = o.user_data().get::<UdevOutputData>();
udev_op_data
.is_some_and(|data| data.device_id == surface.device_id && data.crtc == crtc)
}) {
output.clone()
} else {
// somehow we got called with an invalid output

View file

@ -185,10 +185,12 @@ impl Winit {
let init = Box::new(move |pinnacle: &mut Pinnacle| {
let output = winit.output.clone();
output.create_global::<State>(&display_handle);
let global = output.create_global::<State>(&display_handle);
pinnacle.output_focus_stack.set_focus(output.clone());
pinnacle.outputs.insert(output.clone(), Some(global));
pinnacle
.shm_state
.update_formats(winit.backend.renderer().shm_formats());

View file

@ -381,7 +381,7 @@ impl Pinnacle {
// Clear state
debug!("Clearing tags");
for output in self.space.outputs() {
for output in self.outputs.keys() {
output.with_state_mut(|state| state.tags.clear());
}

View file

@ -158,11 +158,19 @@ impl LayoutState {
}
impl Pinnacle {
pub fn request_layout(&mut self, output: &Output) -> Option<LayoutRequestId> {
pub fn request_layout(&mut self, output: &Output) {
if self
.outputs
.get(output)
.is_some_and(|global| global.is_none())
{
return;
}
let id = self.layout_state.next_id();
let Some(sender) = self.layout_state.layout_request_sender.as_ref() else {
warn!("Layout requested but no client has connected to the layout service");
return None;
return;
};
let windows_on_foc_tags = output.with_state(|state| {
@ -213,8 +221,6 @@ impl Pinnacle {
output_width: Some(output_width as u32),
output_height: Some(output_height as u32),
}));
Some(id)
}
}

View file

@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-3.0-or-later
use std::{cell::RefCell, num::NonZeroU32};
use std::{cell::RefCell, collections::hash_map::Entry, num::NonZeroU32};
use pinnacle_api_defs::pinnacle::signal::v0alpha1::{
OutputDisconnectResponse, OutputMoveResponse, OutputResizeResponse,
@ -35,8 +35,8 @@ impl OutputName {
/// Get the output with this name.
pub fn output(&self, pinnacle: &Pinnacle) -> Option<Output> {
pinnacle
.space
.outputs()
.outputs
.keys()
.find(|output| output.name() == self.0)
.cloned()
}
@ -55,7 +55,7 @@ pub enum BlankingState {
}
/// The state of an output
#[derive(Debug, Default)]
#[derive(Debug)]
pub struct OutputState {
pub tags: Vec<Tag>,
pub focus_stack: WindowKeyboardFocusStack,
@ -73,6 +73,22 @@ pub struct OutputState {
pub powered: bool,
}
impl Default for OutputState {
fn default() -> Self {
Self {
tags: Default::default(),
focus_stack: Default::default(),
screencopy: Default::default(),
serial: Default::default(),
modes: Default::default(),
lock_surface: Default::default(),
blanking_state: Default::default(),
layout_transaction: Default::default(),
powered: true,
}
}
}
impl WithState for Output {
type State = OutputState;
@ -226,21 +242,29 @@ impl Pinnacle {
pub fn set_output_enabled(&mut self, output: &Output, enabled: bool) {
if enabled {
self.unmapped_outputs.remove(output);
if !self.outputs.contains_key(output) {
let global = output.create_global::<State>(&self.display_handle);
self.outputs.insert(output.clone(), global);
match self.outputs.entry(output.clone()) {
Entry::Occupied(entry) => {
let global = entry.into_mut();
if global.is_none() {
*global = Some(output.create_global::<State>(&self.display_handle));
}
}
Entry::Vacant(entry) => {
let global = output.create_global::<State>(&self.display_handle);
entry.insert(Some(global));
}
}
self.space.map_output(output, output.current_location());
// TODO: output connect?
} else {
let global = self.outputs.remove(output);
let global = self.outputs.get_mut(output);
if let Some(global) = global {
self.display_handle.remove_global::<State>(global);
if let Some(global) = global.take() {
self.display_handle.remove_global::<State>(global);
}
}
self.space.unmap_output(output);
self.unmapped_outputs.insert(output.clone());
// TODO: should this trigger the signal?
self.signal_state.output_disconnect.signal(|buffer| {
@ -267,11 +291,12 @@ impl Pinnacle {
}
pub fn remove_output(&mut self, output: &Output) {
let global = self.outputs.remove(output);
let global = self.outputs.get_mut(output);
if let Some(global) = global {
self.display_handle.remove_global::<State>(global);
if let Some(global) = global.take() {
self.display_handle.remove_global::<State>(global);
}
}
self.unmapped_outputs.remove(output);
for layer in layer_map_for_output(output).layers() {
layer.layer_surface().send_close();

View file

@ -3,4 +3,3 @@ pub mod gamma_control;
pub mod output_management;
pub mod output_power_management;
pub mod screencopy;

View file

@ -153,8 +153,7 @@ pub struct Pinnacle {
/// WlSurfaces with an attached idle inhibitor.
pub idle_inhibiting_surfaces: HashSet<WlSurface>,
pub outputs: HashMap<Output, GlobalId>,
pub unmapped_outputs: HashSet<Output>,
pub outputs: HashMap<Output, Option<GlobalId>>,
}
impl State {
@ -355,7 +354,6 @@ impl Pinnacle {
idle_inhibiting_surfaces: HashSet::new(),
outputs: HashMap::new(),
unmapped_outputs: HashSet::new(),
};
Ok(pinnacle)

View file

@ -26,8 +26,8 @@ impl TagId {
/// Get the tag associated with this id.
pub fn tag(&self, pinnacle: &Pinnacle) -> Option<Tag> {
pinnacle
.space
.outputs()
.outputs
.keys()
.flat_map(|op| op.with_state(|state| state.tags.clone()))
.find(|tag| &tag.id() == self)
}
@ -118,8 +118,8 @@ impl Tag {
/// RefCell Safety: This uses RefCells on every mapped output.
pub fn output(&self, pinnacle: &Pinnacle) -> Option<Output> {
pinnacle
.space
.outputs()
.outputs
.keys()
.find(|output| output.with_state(|state| state.tags.iter().any(|tg| tg == self)))
.cloned()
}

View file

@ -308,7 +308,7 @@ impl Pinnacle {
self.z_index_stack.retain(|win| win != window);
for output in self.space.outputs() {
for output in self.outputs.keys() {
output.with_state_mut(|state| state.focus_stack.stack.retain(|win| win != window));
}
}