mirror of
https://github.com/pinnacle-comp/pinnacle.git
synced 2024-12-25 09:59:21 +01:00
Use drm-extras for monitor info
And also remove matching outputs by serial. TODO: add matching by the new serial string
This commit is contained in:
parent
f33c7bbd78
commit
5cdf9769de
16 changed files with 61 additions and 2762 deletions
4
.github/workflows/ci.pinnacle.yml
vendored
4
.github/workflows/ci.pinnacle.yml
vendored
|
@ -59,10 +59,10 @@ jobs:
|
||||||
uses: extractions/setup-just@v1
|
uses: extractions/setup-just@v1
|
||||||
- name: Test
|
- name: Test
|
||||||
if: ${{ runner.debug != '1' }}
|
if: ${{ runner.debug != '1' }}
|
||||||
run: just install test --no-default-features -- --test-threads=1
|
run: just install test
|
||||||
- name: Test (debug)
|
- name: Test (debug)
|
||||||
if: ${{ runner.debug == '1' }}
|
if: ${{ runner.debug == '1' }}
|
||||||
run: RUST_LOG=debug RUST_BACKTRACE=1 just install test --no-default-features -- --nocapture --test-threads=1
|
run: RUST_LOG=debug RUST_BACKTRACE=1 just install test -- --nocapture
|
||||||
check-format:
|
check-format:
|
||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
name: Check formatting
|
name: Check formatting
|
||||||
|
|
9
Cargo.lock
generated
9
Cargo.lock
generated
|
@ -2302,7 +2302,7 @@ dependencies = [
|
||||||
"bitflags 2.6.0",
|
"bitflags 2.6.0",
|
||||||
"libc",
|
"libc",
|
||||||
"libdisplay-info-derive",
|
"libdisplay-info-derive",
|
||||||
"libdisplay-info-sys 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"libdisplay-info-sys",
|
||||||
"thiserror 1.0.69",
|
"thiserror 1.0.69",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -2323,11 +2323,6 @@ version = "0.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ea8cec1fa7872b621f40c756bc1304b1a975461282e250b0e76737b037c0c236"
|
checksum = "ea8cec1fa7872b621f40c756bc1304b1a975461282e250b0e76737b037c0c236"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "libdisplay-info-sys"
|
|
||||||
version = "0.1.0"
|
|
||||||
source = "git+https://github.com/Smithay/libdisplay-info-rs?rev=a482d0d#a482d0d4b71762c13d40fa394efe04473916f31c"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libloading"
|
name = "libloading"
|
||||||
version = "0.7.4"
|
version = "0.7.4"
|
||||||
|
@ -3197,7 +3192,7 @@ dependencies = [
|
||||||
"drm-sys 0.8.0",
|
"drm-sys 0.8.0",
|
||||||
"gag",
|
"gag",
|
||||||
"indexmap 2.7.0",
|
"indexmap 2.7.0",
|
||||||
"libdisplay-info-sys 0.1.0 (git+https://github.com/Smithay/libdisplay-info-rs?rev=a482d0d)",
|
"libdisplay-info",
|
||||||
"pinnacle",
|
"pinnacle",
|
||||||
"pinnacle-api",
|
"pinnacle-api",
|
||||||
"pinnacle-api-defs",
|
"pinnacle-api-defs",
|
||||||
|
|
|
@ -135,8 +135,8 @@ chrono = "0.4.39"
|
||||||
bytemuck = "1.20.0"
|
bytemuck = "1.20.0"
|
||||||
pinnacle-api = { path = "./api/rust", default-features = false }
|
pinnacle-api = { path = "./api/rust", default-features = false }
|
||||||
gag = "1.0.0"
|
gag = "1.0.0"
|
||||||
drm-sys = "0.8.0" # TODO: remove and use libdisplay-info
|
drm-sys = "0.8.0"
|
||||||
libdisplay-info-sys = { git = "https://github.com/Smithay/libdisplay-info-rs", rev = "a482d0d" }
|
libdisplay-info = "0.1.0"
|
||||||
indexmap = { workspace = true }
|
indexmap = { workspace = true }
|
||||||
snowcap = { path = "./snowcap", optional = true }
|
snowcap = { path = "./snowcap", optional = true }
|
||||||
snowcap-api = { path = "./snowcap/api/rust", optional = true }
|
snowcap-api = { path = "./snowcap/api/rust", optional = true }
|
||||||
|
|
|
@ -401,6 +401,7 @@ local pinnacle_signal_v0alpha1_StreamControl = {
|
||||||
---@field keyboard_focus_stack_window_ids integer[]?
|
---@field keyboard_focus_stack_window_ids integer[]?
|
||||||
---@field enabled boolean?
|
---@field enabled boolean?
|
||||||
---@field powered boolean?
|
---@field powered boolean?
|
||||||
|
---@field serial_str string?
|
||||||
|
|
||||||
---@class pinnacle.render.v0alpha1.SetUpscaleFilterRequest
|
---@class pinnacle.render.v0alpha1.SetUpscaleFilterRequest
|
||||||
---@field filter pinnacle.render.v0alpha1.Filter?
|
---@field filter pinnacle.render.v0alpha1.Filter?
|
||||||
|
|
|
@ -162,13 +162,8 @@ end
|
||||||
---
|
---
|
||||||
---@return boolean
|
---@return boolean
|
||||||
local function output_id_matches(id_str, op)
|
local function output_id_matches(id_str, op)
|
||||||
if id_str:match("^serial:") then
|
|
||||||
local serial = tonumber(id_str:sub(8))
|
|
||||||
return serial and serial == op:serial() or false
|
|
||||||
else
|
|
||||||
return id_str == op.name
|
return id_str == op.name
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|
||||||
---@class OutputSetup
|
---@class OutputSetup
|
||||||
---@field filter (fun(output: OutputHandle): boolean)? -- A filter for wildcard matches that should return true if this setup should apply to the passed in output.
|
---@field filter (fun(output: OutputHandle): boolean)? -- A filter for wildcard matches that should return true if this setup should apply to the passed in output.
|
||||||
|
@ -195,11 +190,6 @@ end
|
||||||
---
|
---
|
||||||
---Otherwise, keys will attempt to match the exact name of an output.
|
---Otherwise, keys will attempt to match the exact name of an output.
|
||||||
---
|
---
|
||||||
---Use `"serial:<number>"` to match outputs by their EDID serial. For example, `"serial:143256"`.
|
|
||||||
---Note that not all displays have EDID serials. Also, serials are not guaranteed to be unique.
|
|
||||||
---If you're unlucky enough to have two displays with the same serial, you'll have to use their names
|
|
||||||
---or filter with wildcards instead.
|
|
||||||
---
|
|
||||||
---##### Setups
|
---##### Setups
|
||||||
---
|
---
|
||||||
---If an output is matched, the corresponding `OutputSetup` entry will be applied to it.
|
---If an output is matched, the corresponding `OutputSetup` entry will be applied to it.
|
||||||
|
@ -233,8 +223,6 @@ end
|
||||||
--- ["eDP-1"] = {
|
--- ["eDP-1"] = {
|
||||||
--- tags = { "6", "7" },
|
--- tags = { "6", "7" },
|
||||||
--- },
|
--- },
|
||||||
--- -- Match an output by its EDID serial number
|
|
||||||
--- ["serial:235987"] = { ... }
|
|
||||||
---})
|
---})
|
||||||
---```
|
---```
|
||||||
---
|
---
|
||||||
|
@ -362,9 +350,6 @@ end
|
||||||
---
|
---
|
||||||
---Keys for `locs` should be output identifiers. These are strings of
|
---Keys for `locs` should be output identifiers. These are strings of
|
||||||
---the name of the output, for example "eDP-1" or "HDMI-A-1".
|
---the name of the output, for example "eDP-1" or "HDMI-A-1".
|
||||||
---Additionally, if you want to match the EDID serial of an output,
|
|
||||||
---prepend the serial with "serial:", for example "serial:174652".
|
|
||||||
---You can find this by doing `get-edid | edid-decode`.
|
|
||||||
---
|
---
|
||||||
---#### Fallback relative-tos
|
---#### Fallback relative-tos
|
||||||
---
|
---
|
||||||
|
@ -400,16 +385,6 @@ end
|
||||||
---
|
---
|
||||||
--- -- Only relayout on output connect and resize
|
--- -- Only relayout on output connect and resize
|
||||||
---Output.setup_locs({ "connect", "resize" }, { ... })
|
---Output.setup_locs({ "connect", "resize" }, { ... })
|
||||||
---
|
|
||||||
--- -- Use EDID serials for identification.
|
|
||||||
--- -- You can run
|
|
||||||
--- -- require("pinnacle").run(function(Pinnacle)
|
|
||||||
--- -- print(Pinnacle.output.get_focused():serial())
|
|
||||||
--- -- end)
|
|
||||||
--- -- in a Lua repl to find the EDID serial of the focused output.
|
|
||||||
---Output.setup_locs("all" {
|
|
||||||
--- ["serial:139487"] = { ... },
|
|
||||||
---})
|
|
||||||
---```
|
---```
|
||||||
---
|
---
|
||||||
---@param update_locs_on (UpdateLocsOn)[] | "all"
|
---@param update_locs_on (UpdateLocsOn)[] | "all"
|
||||||
|
@ -999,7 +974,7 @@ end
|
||||||
---@field tags TagHandle[]
|
---@field tags TagHandle[]
|
||||||
---@field scale number?
|
---@field scale number?
|
||||||
---@field transform Transform?
|
---@field transform Transform?
|
||||||
---@field serial integer?
|
---@field serial string?
|
||||||
---@field keyboard_focus_stack WindowHandle[]
|
---@field keyboard_focus_stack WindowHandle[]
|
||||||
---@field enabled boolean?
|
---@field enabled boolean?
|
||||||
---@field powered boolean?
|
---@field powered boolean?
|
||||||
|
@ -1043,7 +1018,7 @@ function OutputHandle:props()
|
||||||
tags = tag_handles,
|
tags = tag_handles,
|
||||||
scale = response.scale,
|
scale = response.scale,
|
||||||
transform = transform_name_to_code[response.transform] --[[@as Transform?]],
|
transform = transform_name_to_code[response.transform] --[[@as Transform?]],
|
||||||
serial = response.serial,
|
serial = response.serial_str,
|
||||||
keyboard_focus_stack = keyboard_focus_stack_handles,
|
keyboard_focus_stack = keyboard_focus_stack_handles,
|
||||||
enabled = response.enabled,
|
enabled = response.enabled,
|
||||||
powered = response.powered,
|
powered = response.powered,
|
||||||
|
@ -1197,11 +1172,11 @@ function OutputHandle:transform()
|
||||||
return self:props().transform
|
return self:props().transform
|
||||||
end
|
end
|
||||||
|
|
||||||
---Get this output's EDID serial number.
|
---Get this output's EDID serial.
|
||||||
---
|
---
|
||||||
---Shorthand for `handle:props().serial`.
|
---Shorthand for `handle:props().serial`.
|
||||||
---
|
---
|
||||||
---@return integer?
|
---@return string?
|
||||||
function OutputHandle:serial()
|
function OutputHandle:serial()
|
||||||
return self:props().serial
|
return self:props().serial
|
||||||
end
|
end
|
||||||
|
|
|
@ -118,6 +118,7 @@ message GetPropertiesResponse {
|
||||||
repeated uint32 keyboard_focus_stack_window_ids = 17;
|
repeated uint32 keyboard_focus_stack_window_ids = 17;
|
||||||
optional bool enabled = 18;
|
optional bool enabled = 18;
|
||||||
optional bool powered = 19;
|
optional bool powered = 19;
|
||||||
|
optional string serial_str = 20;
|
||||||
}
|
}
|
||||||
|
|
||||||
service OutputService {
|
service OutputService {
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
//! This module provides [`Output`], which allows you to get [`OutputHandle`]s for different
|
//! This module provides [`Output`], which allows you to get [`OutputHandle`]s for different
|
||||||
//! connected monitors and set them up.
|
//! connected monitors and set them up.
|
||||||
|
|
||||||
use std::{num::NonZeroU32, str::FromStr};
|
use std::str::FromStr;
|
||||||
|
|
||||||
use futures::FutureExt;
|
use futures::FutureExt;
|
||||||
use pinnacle_api_defs::pinnacle::output::{
|
use pinnacle_api_defs::pinnacle::output::{
|
||||||
|
@ -555,13 +555,6 @@ pub enum OutputLoc {
|
||||||
pub enum OutputId {
|
pub enum OutputId {
|
||||||
/// Identify using the output's name.
|
/// Identify using the output's name.
|
||||||
Name(String),
|
Name(String),
|
||||||
/// Identify using the output's EDID serial number.
|
|
||||||
///
|
|
||||||
/// Note: some displays (like laptop screens) don't have a serial number, in which case this won't match it.
|
|
||||||
/// Additionally the Rust API assumes monitor serial numbers are unique.
|
|
||||||
/// If you're unlucky enough to have two monitors with the same serial number,
|
|
||||||
/// use [`OutputId::Name`] instead.
|
|
||||||
Serial(NonZeroU32),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl OutputId {
|
impl OutputId {
|
||||||
|
@ -577,7 +570,6 @@ impl OutputId {
|
||||||
pub fn matches(&self, output: &OutputHandle) -> bool {
|
pub fn matches(&self, output: &OutputHandle) -> bool {
|
||||||
match self {
|
match self {
|
||||||
OutputId::Name(name) => *name == output.name(),
|
OutputId::Name(name) => *name == output.name(),
|
||||||
OutputId::Serial(serial) => Some(serial.get()) == output.serial(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1028,7 +1020,7 @@ impl OutputHandle {
|
||||||
.collect(),
|
.collect(),
|
||||||
scale: response.scale,
|
scale: response.scale,
|
||||||
transform: response.transform.and_then(|tf| tf.try_into().ok()),
|
transform: response.transform.and_then(|tf| tf.try_into().ok()),
|
||||||
serial: response.serial,
|
serial: response.serial_str,
|
||||||
keyboard_focus_stack: response
|
keyboard_focus_stack: response
|
||||||
.keyboard_focus_stack_window_ids
|
.keyboard_focus_stack_window_ids
|
||||||
.into_iter()
|
.into_iter()
|
||||||
|
@ -1227,15 +1219,15 @@ impl OutputHandle {
|
||||||
self.props_async().await.transform
|
self.props_async().await.transform
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get this output's EDID serial number.
|
/// Get this output's EDID serial.
|
||||||
///
|
///
|
||||||
/// Shorthand for `self.props().serial`
|
/// Shorthand for `self.props().serial`
|
||||||
pub fn serial(&self) -> Option<u32> {
|
pub fn serial(&self) -> Option<String> {
|
||||||
self.props().serial
|
self.props().serial
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The async version of [`OutputHandle::serial`].
|
/// The async version of [`OutputHandle::serial`].
|
||||||
pub async fn serial_async(&self) -> Option<u32> {
|
pub async fn serial_async(&self) -> Option<String> {
|
||||||
self.props_async().await.serial
|
self.props_async().await.serial
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1355,8 +1347,8 @@ pub struct OutputProperties {
|
||||||
pub scale: Option<f32>,
|
pub scale: Option<f32>,
|
||||||
/// This output's transform.
|
/// This output's transform.
|
||||||
pub transform: Option<Transform>,
|
pub transform: Option<Transform>,
|
||||||
/// This output's EDID serial number.
|
/// This output's EDID serial.
|
||||||
pub serial: Option<u32>,
|
pub serial: Option<String>,
|
||||||
/// This output's window keyboard focus stack.
|
/// This output's window keyboard focus stack.
|
||||||
pub keyboard_focus_stack: Vec<WindowHandle>,
|
pub keyboard_focus_stack: Vec<WindowHandle>,
|
||||||
/// Whether this output is enabled.
|
/// Whether this output is enabled.
|
||||||
|
|
2
justfile
2
justfile
|
@ -101,7 +101,7 @@ run *args: gen-lua-pb-defs
|
||||||
|
|
||||||
# Run `cargo test`
|
# Run `cargo test`
|
||||||
test *args: gen-lua-pb-defs
|
test *args: gen-lua-pb-defs
|
||||||
cargo test {{args}}
|
cargo test --no-default-features {{args}}
|
||||||
|
|
||||||
compile-wlcs:
|
compile-wlcs:
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
|
@ -1477,9 +1477,11 @@ impl output_service_server::OutputService for OutputService {
|
||||||
}) as i32
|
}) as i32
|
||||||
});
|
});
|
||||||
|
|
||||||
let serial = output.as_ref().and_then(|output| {
|
let serial = Some(0);
|
||||||
output.with_state(|state| state.serial.map(|serial| serial.get()))
|
|
||||||
});
|
let serial_str = output
|
||||||
|
.as_ref()
|
||||||
|
.map(|output| output.with_state(|state| state.serial.clone()));
|
||||||
|
|
||||||
let keyboard_focus_stack_window_ids = output
|
let keyboard_focus_stack_window_ids = output
|
||||||
.as_ref()
|
.as_ref()
|
||||||
|
@ -1527,6 +1529,7 @@ impl output_service_server::OutputService for OutputService {
|
||||||
keyboard_focus_stack_window_ids,
|
keyboard_focus_stack_window_ids,
|
||||||
enabled,
|
enabled,
|
||||||
powered,
|
powered,
|
||||||
|
serial_str,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
|
|
|
@ -91,8 +91,6 @@ use crate::{
|
||||||
state::{FrameCallbackSequence, Pinnacle, State, WithState},
|
state::{FrameCallbackSequence, Pinnacle, State, WithState},
|
||||||
};
|
};
|
||||||
|
|
||||||
use self::drm::util::EdidInfo;
|
|
||||||
|
|
||||||
use super::{BackendData, UninitBackend};
|
use super::{BackendData, UninitBackend};
|
||||||
|
|
||||||
const SUPPORTED_FORMATS: &[Fourcc] = &[
|
const SUPPORTED_FORMATS: &[Fourcc] = &[
|
||||||
|
@ -915,14 +913,20 @@ impl Udev {
|
||||||
connector.interface_id()
|
connector.interface_id()
|
||||||
);
|
);
|
||||||
|
|
||||||
let (make, model, serial) = EdidInfo::try_from_connector(&device.drm, connector.handle())
|
let display_info =
|
||||||
.map(|info| (info.manufacturer, info.model, info.serial))
|
smithay_drm_extras::display_info::for_connector(&device.drm, connector.handle());
|
||||||
.unwrap_or_else(|err| {
|
|
||||||
warn!("Failed to parse EDID info: {err}");
|
|
||||||
("Unknown".into(), "Unknown".into(), None)
|
|
||||||
});
|
|
||||||
|
|
||||||
let (phys_w, phys_h) = connector.size().unwrap_or((0, 0));
|
let (make, model, serial) = display_info
|
||||||
|
.map(|info| {
|
||||||
|
(
|
||||||
|
info.make().unwrap_or("Unknown".into()),
|
||||||
|
info.model().unwrap_or("Unknown".into()),
|
||||||
|
info.serial().unwrap_or("Unknown".into()),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.unwrap_or_else(|| ("Unknown".into(), "Unknown".into(), "Unknown".into()));
|
||||||
|
|
||||||
|
let (phys_w, phys_h) = connector.size().unwrap_or_default();
|
||||||
|
|
||||||
if pinnacle.outputs.keys().any(|op| {
|
if pinnacle.outputs.keys().any(|op| {
|
||||||
op.user_data()
|
op.user_data()
|
||||||
|
|
|
@ -3,7 +3,6 @@ use smithay::{backend::drm::DrmDevice, reexports::drm::control::crtc};
|
||||||
use tracing::warn;
|
use tracing::warn;
|
||||||
use util::get_drm_property;
|
use util::get_drm_property;
|
||||||
|
|
||||||
pub mod edid_manus;
|
|
||||||
pub mod util;
|
pub mod util;
|
||||||
|
|
||||||
const DRM_CRTC_ACTIVE: &str = "ACTIVE";
|
const DRM_CRTC_ACTIVE: &str = "ACTIVE";
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,129 +1,19 @@
|
||||||
use std::{ffi::CString, io::Write, mem::MaybeUninit, num::NonZeroU32, time::Duration};
|
use std::{ffi::CString, io::Write, time::Duration};
|
||||||
|
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use drm_sys::{
|
use drm_sys::{
|
||||||
drm_mode_modeinfo, DRM_MODE_FLAG_NHSYNC, DRM_MODE_FLAG_NVSYNC, DRM_MODE_FLAG_PHSYNC,
|
drm_mode_modeinfo, DRM_MODE_FLAG_NHSYNC, DRM_MODE_FLAG_NVSYNC, DRM_MODE_FLAG_PHSYNC,
|
||||||
DRM_MODE_FLAG_PVSYNC, DRM_MODE_TYPE_USERDEF,
|
DRM_MODE_FLAG_PVSYNC, DRM_MODE_TYPE_USERDEF,
|
||||||
};
|
};
|
||||||
use libdisplay_info_sys::cvt::{
|
use libdisplay_info::cvt::{self, ReducedBlankingVersion};
|
||||||
di_cvt_compute, di_cvt_options, di_cvt_reduced_blanking_version_DI_CVT_REDUCED_BLANKING_NONE,
|
|
||||||
di_cvt_timing,
|
|
||||||
};
|
|
||||||
use pinnacle_api_defs::pinnacle::output::v0alpha1::SetModelineRequest;
|
use pinnacle_api_defs::pinnacle::output::v0alpha1::SetModelineRequest;
|
||||||
use smithay::reexports::drm::{
|
use smithay::reexports::drm::{
|
||||||
self,
|
self,
|
||||||
control::{connector, property, Device, ModeFlags, ResourceHandle},
|
control::{property, Device, ModeFlags, ResourceHandle},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::edid_manus::get_manufacturer;
|
|
||||||
|
|
||||||
// A bunch of this stuff is from cosmic-comp
|
// A bunch of this stuff is from cosmic-comp
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct EdidInfo {
|
|
||||||
pub model: String,
|
|
||||||
pub manufacturer: String,
|
|
||||||
pub serial: Option<NonZeroU32>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl EdidInfo {
|
|
||||||
pub fn try_from_connector(
|
|
||||||
device: &impl Device,
|
|
||||||
connector: connector::Handle,
|
|
||||||
) -> anyhow::Result<Self> {
|
|
||||||
let edid_prop = get_drm_property(device, connector, "EDID")?;
|
|
||||||
let edid_info = device.get_property(edid_prop)?;
|
|
||||||
|
|
||||||
let mut info = Err(anyhow::anyhow!("No info"));
|
|
||||||
|
|
||||||
let props = device.get_properties(connector)?;
|
|
||||||
let (ids, vals) = props.as_props_and_values();
|
|
||||||
for (&id, &val) in ids.iter().zip(vals.iter()) {
|
|
||||||
if id == edid_prop {
|
|
||||||
if let property::Value::Blob(edid_blob) = edid_info.value_type().convert_value(val)
|
|
||||||
{
|
|
||||||
let blob = device.get_property_blob(edid_blob)?;
|
|
||||||
info = parse_edid(&blob);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
info
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Minimally parse the model and manufacturer from the given EDID data buffer.
|
|
||||||
///
|
|
||||||
/// `edid-rs` does not properly parse manufacturer ids (it has the order of the id bytes reversed
|
|
||||||
/// and doesn't add 64 to map the byte to a character), and it additionally
|
|
||||||
/// fails to parse detailed timing descriptors with an hactive that's divisible by 256
|
|
||||||
/// (see https://github.com/tuomas56/edid-rs/pull/1).
|
|
||||||
///
|
|
||||||
/// Because of this, we're just rolling our own minimal parser instead.
|
|
||||||
fn parse_edid(buffer: &[u8]) -> anyhow::Result<EdidInfo> {
|
|
||||||
// Manufacterer id is bytes 8-9, big endian
|
|
||||||
let manu_id = u16::from_be_bytes(buffer[8..=9].try_into()?);
|
|
||||||
|
|
||||||
// Characters are bits 14-10, 9-5, and 4-0.
|
|
||||||
// They also map 0b00001..=0b11010 to A..=Z, so add 64 to get the character.
|
|
||||||
let char1 = ((manu_id & 0b0111110000000000) >> 10) as u8 + 64;
|
|
||||||
let char2 = ((manu_id & 0b0000001111100000) >> 5) as u8 + 64;
|
|
||||||
let char3 = (manu_id & 0b0000000000011111) as u8 + 64;
|
|
||||||
|
|
||||||
let manufacturer = get_manufacturer([char1 as char, char2 as char, char3 as char]);
|
|
||||||
|
|
||||||
// INFO: This probably *isn't* completely unique between all monitors
|
|
||||||
let serial = u32::from_le_bytes(buffer[12..=15].try_into()?);
|
|
||||||
|
|
||||||
// Monitor names are inside of these display/monitor descriptors at bytes 72..=125.
|
|
||||||
// Each descriptor is 18 bytes long.
|
|
||||||
let descriptor1 = &buffer[72..=89];
|
|
||||||
let descriptor2 = &buffer[90..=107];
|
|
||||||
let descriptor3 = &buffer[108..=125];
|
|
||||||
|
|
||||||
let descriptors = [descriptor1, descriptor2, descriptor3];
|
|
||||||
|
|
||||||
let model = descriptors
|
|
||||||
.into_iter()
|
|
||||||
.find_map(|desc| {
|
|
||||||
// The descriptor is a monitor descriptor if its first 2 bytes are 0.
|
|
||||||
let is_monitor_descriptor = desc[0..=1] == [0, 0];
|
|
||||||
// The descriptor describes a monitor name if it has the tag 0xfc at byte 3.
|
|
||||||
let is_monitor_name = desc[3] == 0xfc;
|
|
||||||
|
|
||||||
if is_monitor_descriptor && is_monitor_name {
|
|
||||||
// Name is up to 13 bytes at bytes 5..=17 within the descriptor.
|
|
||||||
let monitor_name = desc[5..=17]
|
|
||||||
.iter()
|
|
||||||
// Names are terminated with a newline if shorter than 13 bytes.
|
|
||||||
.take_while(|&&byte| byte != b'\n')
|
|
||||||
.map(|&byte| byte as char)
|
|
||||||
.collect::<String>();
|
|
||||||
|
|
||||||
// NOTE: The EDID spec mandates that bytes after the newline are padded with
|
|
||||||
// | spaces (0x20), but we're just gonna ignore that haha
|
|
||||||
|
|
||||||
Some(monitor_name)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.or_else(|| {
|
|
||||||
// Get the product code instead.
|
|
||||||
// It's at bytes 10..=11, little-endian.
|
|
||||||
let product_code = u16::from_le_bytes(buffer[10..=11].try_into().ok()?);
|
|
||||||
Some(format!("{product_code:x}"))
|
|
||||||
})
|
|
||||||
.unwrap_or("Unknown".to_string());
|
|
||||||
|
|
||||||
Ok(EdidInfo {
|
|
||||||
model,
|
|
||||||
manufacturer,
|
|
||||||
serial: NonZeroU32::new(serial),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(super) fn get_drm_property(
|
pub(super) fn get_drm_property(
|
||||||
device: &impl Device,
|
device: &impl Device,
|
||||||
handle: impl ResourceHandle,
|
handle: impl ResourceHandle,
|
||||||
|
@ -221,8 +111,8 @@ pub fn create_drm_mode(width: i32, height: i32, refresh_mhz: Option<u32>) -> drm
|
||||||
|
|
||||||
// From https://gitlab.freedesktop.org/wlroots/wlroots/-/blob/95ac3e99242b4e7f59f00dd073ede405ff8e9e26/backend/drm/util.c#L247
|
// From https://gitlab.freedesktop.org/wlroots/wlroots/-/blob/95ac3e99242b4e7f59f00dd073ede405ff8e9e26/backend/drm/util.c#L247
|
||||||
fn generate_cvt_mode(hdisplay: i32, vdisplay: i32, vrefresh: Option<f64>) -> drm_mode_modeinfo {
|
fn generate_cvt_mode(hdisplay: i32, vdisplay: i32, vrefresh: Option<f64>) -> drm_mode_modeinfo {
|
||||||
let options: di_cvt_options = di_cvt_options {
|
let options = cvt::Options {
|
||||||
red_blank_ver: di_cvt_reduced_blanking_version_DI_CVT_REDUCED_BLANKING_NONE,
|
red_blank_ver: ReducedBlankingVersion::None,
|
||||||
h_pixels: hdisplay,
|
h_pixels: hdisplay,
|
||||||
v_lines: vdisplay,
|
v_lines: vdisplay,
|
||||||
ip_freq_rqd: vrefresh.unwrap_or(60.0),
|
ip_freq_rqd: vrefresh.unwrap_or(60.0),
|
||||||
|
@ -234,12 +124,7 @@ fn generate_cvt_mode(hdisplay: i32, vdisplay: i32, vrefresh: Option<f64>) -> drm
|
||||||
margins_rqd: false,
|
margins_rqd: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut timing = MaybeUninit::<di_cvt_timing>::zeroed();
|
let timing = cvt::Timing::compute(options);
|
||||||
// SAFETY: is an ffi function
|
|
||||||
unsafe { di_cvt_compute(timing.as_mut_ptr(), &options as *const _) };
|
|
||||||
|
|
||||||
// SAFETY: Initialized in the function above
|
|
||||||
let timing = unsafe { timing.assume_init() };
|
|
||||||
|
|
||||||
let hsync_start = (hdisplay + timing.h_front_porch as i32) as u16;
|
let hsync_start = (hdisplay + timing.h_front_porch as i32) as u16;
|
||||||
let vsync_start = (timing.v_lines_rnd + timing.v_front_porch) as u16;
|
let vsync_start = (timing.v_lines_rnd + timing.v_front_porch) as u16;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
use std::{cell::RefCell, num::NonZeroU32};
|
use std::cell::RefCell;
|
||||||
|
|
||||||
use indexmap::IndexSet;
|
use indexmap::IndexSet;
|
||||||
use pinnacle_api_defs::pinnacle::signal::v0alpha1::{
|
use pinnacle_api_defs::pinnacle::signal::v0alpha1::{
|
||||||
|
@ -64,7 +64,8 @@ pub struct OutputState {
|
||||||
|
|
||||||
pub focus_stack: WindowKeyboardFocusStack,
|
pub focus_stack: WindowKeyboardFocusStack,
|
||||||
pub screencopy: Option<Screencopy>,
|
pub screencopy: Option<Screencopy>,
|
||||||
pub serial: Option<NonZeroU32>,
|
// This monitor's edid serial. "Unknown" if it doesn't have one.
|
||||||
|
pub serial: String,
|
||||||
pub modes: Vec<Mode>,
|
pub modes: Vec<Mode>,
|
||||||
pub lock_surface: Option<LockSurface>,
|
pub lock_surface: Option<LockSurface>,
|
||||||
pub blanking_state: BlankingState,
|
pub blanking_state: BlankingState,
|
||||||
|
|
|
@ -397,10 +397,7 @@ where
|
||||||
if head.version() >= zwlr_output_head_v1::EVT_MAKE_SINCE {
|
if head.version() >= zwlr_output_head_v1::EVT_MAKE_SINCE {
|
||||||
head.make(physical_props.make);
|
head.make(physical_props.make);
|
||||||
head.model(physical_props.model);
|
head.model(physical_props.model);
|
||||||
|
head.serial_number(output.with_state(|state| state.serial.clone()));
|
||||||
if let Some(serial_number) = output.with_state(|state| state.serial) {
|
|
||||||
head.serial_number(serial_number.to_string());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if head.version() >= zwlr_output_head_v1::EVT_ADAPTIVE_SYNC_SINCE {
|
if head.version() >= zwlr_output_head_v1::EVT_ADAPTIVE_SYNC_SINCE {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use std::{panic::UnwindSafe, path::PathBuf, time::Duration};
|
use std::{panic::UnwindSafe, path::PathBuf, sync::Mutex, time::Duration};
|
||||||
|
|
||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
use pinnacle::{state::State, tag::TagId};
|
use pinnacle::{state::State, tag::TagId};
|
||||||
|
@ -27,6 +27,8 @@ pub fn sleep_millis(millis: u64) {
|
||||||
std::thread::sleep(Duration::from_millis(millis));
|
std::thread::sleep(Duration::from_millis(millis));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static MUTEX: Mutex<()> = Mutex::new(());
|
||||||
|
|
||||||
pub fn test_api<F>(test: F) -> anyhow::Result<()>
|
pub fn test_api<F>(test: F) -> anyhow::Result<()>
|
||||||
where
|
where
|
||||||
F: FnOnce(Sender<Box<dyn FnOnce(&mut State) + Send>>) -> anyhow::Result<()>
|
F: FnOnce(Sender<Box<dyn FnOnce(&mut State) + Send>>) -> anyhow::Result<()>
|
||||||
|
@ -34,6 +36,14 @@ where
|
||||||
+ UnwindSafe
|
+ UnwindSafe
|
||||||
+ 'static,
|
+ 'static,
|
||||||
{
|
{
|
||||||
|
let _guard = match MUTEX.lock() {
|
||||||
|
Ok(guard) => guard,
|
||||||
|
Err(err) => {
|
||||||
|
MUTEX.clear_poison();
|
||||||
|
err.into_inner()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
let mut event_loop = EventLoop::<State>::try_new()?;
|
let mut event_loop = EventLoop::<State>::try_new()?;
|
||||||
let mut state = State::new(
|
let mut state = State::new(
|
||||||
pinnacle::cli::Backend::Dummy,
|
pinnacle::cli::Backend::Dummy,
|
||||||
|
|
Loading…
Reference in a new issue