Merge pull request #812 from mahkoh/jorth/vo-preparation
Preparations for virtual outputs
This commit is contained in:
commit
7e479490c9
30 changed files with 620 additions and 357 deletions
|
|
@ -560,6 +560,12 @@ impl ConfigClient {
|
|||
connector
|
||||
}
|
||||
|
||||
pub fn get_connector_by_name(&self, name: &str) -> Connector {
|
||||
let res = self.send_with_response(&ClientMessage::GetConnectorByName { name });
|
||||
get_response!(res, Connector(0), GetConnector { connector });
|
||||
connector
|
||||
}
|
||||
|
||||
pub fn get_seat_cursor_workspace(&self, seat: Seat) -> Workspace {
|
||||
let res = self.send_with_response(&ClientMessage::GetSeatCursorWorkspace { seat });
|
||||
get_response!(res, Workspace(0), GetSeatCursorWorkspace { workspace });
|
||||
|
|
@ -1191,6 +1197,19 @@ impl ConfigClient {
|
|||
modes.into_iter().map(WireMode::to_mode).collect()
|
||||
}
|
||||
|
||||
pub fn connector_supports_arbitrary_modes(&self, connector: Connector) -> bool {
|
||||
let res =
|
||||
self.send_with_response(&ClientMessage::ConnectorSupportsArbitraryModes { connector });
|
||||
get_response!(
|
||||
res,
|
||||
false,
|
||||
ConnectorSupportsArbitraryModes {
|
||||
supports_arbitrary_modes
|
||||
}
|
||||
);
|
||||
supports_arbitrary_modes
|
||||
}
|
||||
|
||||
pub fn connector_size(&self, connector: Connector) -> (i32, i32) {
|
||||
let res = self.send_with_response(&ClientMessage::ConnectorSize { connector });
|
||||
get_response!(res, (0, 0), ConnectorSize { width, height });
|
||||
|
|
|
|||
|
|
@ -846,6 +846,12 @@ pub enum ClientMessage<'a> {
|
|||
monospace: Option<Vec<&'a str>>,
|
||||
},
|
||||
OpenControlCenter,
|
||||
ConnectorSupportsArbitraryModes {
|
||||
connector: Connector,
|
||||
},
|
||||
GetConnectorByName {
|
||||
name: &'a str,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
|
|
@ -1096,6 +1102,9 @@ pub enum Response {
|
|||
KeymapFromNames {
|
||||
keymap: Keymap,
|
||||
},
|
||||
ConnectorSupportsArbitraryModes {
|
||||
supports_arbitrary_modes: bool,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
|
|
|
|||
|
|
@ -157,6 +157,14 @@ impl Connector {
|
|||
get!(Vec::new()).connector_modes(self)
|
||||
}
|
||||
|
||||
/// Returns whether this connector supports arbitrary modes.
|
||||
pub fn supports_arbitrary_modes(self) -> bool {
|
||||
if !self.exists() {
|
||||
return false;
|
||||
}
|
||||
get!(false).connector_supports_arbitrary_modes(self)
|
||||
}
|
||||
|
||||
/// Returns the logical width of the connector.
|
||||
///
|
||||
/// The returned value will be different from `mode().width()` if the scale is not 1.
|
||||
|
|
@ -446,6 +454,14 @@ pub fn get_connector(id: impl ToConnectorId) -> Connector {
|
|||
get!(Connector(0)).get_connector(ty, idx)
|
||||
}
|
||||
|
||||
/// Returns the connector with the given name.
|
||||
///
|
||||
/// Unlike [`get_connector`], this function can also be used for connectors whose names
|
||||
/// don't follow the `<type>-<id>` pattern.
|
||||
pub fn get_connector_by_name(name: &str) -> Connector {
|
||||
get!(Connector(0)).get_connector_by_name(name)
|
||||
}
|
||||
|
||||
/// A type that can be converted to a `(ConnectorType, idx)` tuple.
|
||||
pub trait ToConnectorId {
|
||||
fn to_connector_id(&self) -> Result<(ConnectorType, u32), String>;
|
||||
|
|
|
|||
|
|
@ -100,7 +100,7 @@ impl Display for Mode {
|
|||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct MonitorInfo {
|
||||
pub modes: Vec<Mode>,
|
||||
pub modes: Option<Vec<Mode>>,
|
||||
pub output_id: Rc<OutputId>,
|
||||
pub width_mm: i32,
|
||||
pub height_mm: i32,
|
||||
|
|
@ -141,6 +141,7 @@ pub trait Connector: Any {
|
|||
fn damage(&self);
|
||||
fn drm_dev(&self) -> Option<DrmDeviceId>;
|
||||
fn effectively_locked(&self) -> bool;
|
||||
fn state(&self) -> BackendConnectorState;
|
||||
fn caps(&self) -> ConnectorCaps {
|
||||
ConnectorCaps::none()
|
||||
}
|
||||
|
|
@ -169,6 +170,9 @@ pub trait Connector: Any {
|
|||
fn gamma_lut_size(&self) -> Option<u32> {
|
||||
None
|
||||
}
|
||||
fn name(&self) -> String {
|
||||
self.kernel_id().to_string()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
|
|
|||
|
|
@ -2,8 +2,10 @@ use {
|
|||
crate::{
|
||||
async_engine::SpawnedFuture,
|
||||
backend::{
|
||||
Backend, Connector, ConnectorEvent, ConnectorId, ConnectorKernelId, DrmDeviceId,
|
||||
self, Backend, BackendConnectorState, BackendConnectorStateSerial, Connector,
|
||||
ConnectorEvent, ConnectorId, ConnectorKernelId, DrmDeviceId,
|
||||
},
|
||||
format::XRGB8888,
|
||||
video::drm::ConnectorType,
|
||||
},
|
||||
std::{error::Error, rc::Rc},
|
||||
|
|
@ -52,4 +54,25 @@ impl Connector for DummyOutput {
|
|||
fn effectively_locked(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn state(&self) -> BackendConnectorState {
|
||||
let mode = backend::Mode {
|
||||
width: 0,
|
||||
height: 0,
|
||||
refresh_rate_millihz: 40_000,
|
||||
};
|
||||
BackendConnectorState {
|
||||
serial: BackendConnectorStateSerial::from_raw(0),
|
||||
enabled: true,
|
||||
active: false,
|
||||
mode,
|
||||
non_desktop_override: None,
|
||||
vrr: false,
|
||||
tearing: false,
|
||||
format: XRGB8888,
|
||||
color_space: Default::default(),
|
||||
eotf: Default::default(),
|
||||
gamma_lut: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,12 +9,11 @@ use {
|
|||
},
|
||||
cmm::cmm_description::ColorDescription,
|
||||
gfx_api::{
|
||||
AcquireSync, AlphaMode, BufferResv, GfxApiOpt, GfxRenderPass, GfxTexture, ReleaseSync,
|
||||
AcquireSync, BufferResv, DirectScanoutPosition, GfxRenderPass, GfxTexture, ReleaseSync,
|
||||
SyncFile, create_render_pass,
|
||||
},
|
||||
ifs::wl_output::BlendSpace,
|
||||
rect::Region,
|
||||
theme::Color,
|
||||
time::Time,
|
||||
tracy::FrameName,
|
||||
tree::OutputNode,
|
||||
|
|
@ -56,16 +55,6 @@ pub struct DirectScanoutData {
|
|||
position: DirectScanoutPosition,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct DirectScanoutPosition {
|
||||
pub src_width: i32,
|
||||
pub src_height: i32,
|
||||
pub crtc_x: i32,
|
||||
pub crtc_y: i32,
|
||||
pub crtc_width: i32,
|
||||
pub crtc_height: i32,
|
||||
}
|
||||
|
||||
pub struct PresentFb {
|
||||
fb: Rc<DrmFramebuffer>,
|
||||
tex: Rc<dyn GfxTexture>,
|
||||
|
|
@ -643,122 +632,17 @@ impl MetalConnector {
|
|||
blend_cd: &Rc<ColorDescription>,
|
||||
cd: &Rc<ColorDescription>,
|
||||
) -> Option<DirectScanoutData> {
|
||||
let ct = 'ct: {
|
||||
let mut ops = pass.ops.iter().rev();
|
||||
let ct = 'ct2: {
|
||||
for opt in &mut ops {
|
||||
match opt {
|
||||
GfxApiOpt::Sync => {}
|
||||
GfxApiOpt::FillRect(_) => {
|
||||
// Top-most layer must be a texture.
|
||||
return None;
|
||||
}
|
||||
GfxApiOpt::CopyTexture(ct) => break 'ct2 ct,
|
||||
}
|
||||
}
|
||||
return None;
|
||||
};
|
||||
if ct.alpha_mode != AlphaMode::PremultipliedElectrical {
|
||||
// Direct scanout requires premultiplied electrical alpha.
|
||||
return None;
|
||||
}
|
||||
if !ct.cd.embeds_into(cd) {
|
||||
// Direct scanout requires embeddable color descriptions.
|
||||
return None;
|
||||
}
|
||||
if !ct.opaque && !ct.cd.embeds_into(blend_cd) {
|
||||
// Blending changes the appearance of translucent buffers.
|
||||
return None;
|
||||
}
|
||||
if ct.alpha.is_some() {
|
||||
// Direct scanout with alpha factor is not supported.
|
||||
return None;
|
||||
}
|
||||
if !ct.tex.format().has_alpha && ct.target.is_covering() {
|
||||
// Texture covers the entire screen and is opaque.
|
||||
break 'ct ct;
|
||||
}
|
||||
for opt in ops {
|
||||
match opt {
|
||||
GfxApiOpt::Sync => {}
|
||||
GfxApiOpt::FillRect(fr) => {
|
||||
if fr.effective_color() == Color::SOLID_BLACK {
|
||||
// Black fills can be ignored because this is the CRTC background color.
|
||||
if fr.rect.is_covering() {
|
||||
// If fill covers the entire screen, we don't have to look further.
|
||||
break 'ct ct;
|
||||
}
|
||||
} else {
|
||||
// Fill could be visible.
|
||||
return None;
|
||||
}
|
||||
}
|
||||
GfxApiOpt::CopyTexture(_) => {
|
||||
// Texture could be visible.
|
||||
return None;
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(clear) = pass.clear
|
||||
&& clear != Color::SOLID_BLACK
|
||||
{
|
||||
// Background could be visible.
|
||||
return None;
|
||||
}
|
||||
ct
|
||||
};
|
||||
if let AcquireSync::None | AcquireSync::Implicit = ct.acquire_sync {
|
||||
// Cannot perform scanout without explicit sync.
|
||||
return None;
|
||||
}
|
||||
if ct.source.buffer_transform != ct.target.output_transform {
|
||||
// Rotations and mirroring are not supported.
|
||||
return None;
|
||||
}
|
||||
if !ct.source.is_covering() {
|
||||
// Viewports are not supported.
|
||||
return None;
|
||||
}
|
||||
if ct.target.x1 < -1.0 || ct.target.y1 < -1.0 || ct.target.x2 > 1.0 || ct.target.y2 > 1.0 {
|
||||
// Rendering outside the screen is not supported.
|
||||
return None;
|
||||
}
|
||||
let (tex_w, tex_h) = ct.tex.size();
|
||||
let (x1, x2, y1, y2) = {
|
||||
let plane_w = plane.mode_w.get() as f32;
|
||||
let plane_h = plane.mode_h.get() as f32;
|
||||
let ((x1, x2), (y1, y2)) = ct
|
||||
.target
|
||||
.output_transform
|
||||
.maybe_swap(((ct.target.x1, ct.target.x2), (ct.target.y1, ct.target.y2)));
|
||||
(
|
||||
(x1 + 1.0) * plane_w / 2.0,
|
||||
(x2 + 1.0) * plane_w / 2.0,
|
||||
(y1 + 1.0) * plane_h / 2.0,
|
||||
(y2 + 1.0) * plane_h / 2.0,
|
||||
)
|
||||
};
|
||||
let (crtc_w, crtc_h) = (x2 - x1, y2 - y1);
|
||||
if crtc_w < 0.0 || crtc_h < 0.0 {
|
||||
// Flipping x or y axis is not supported.
|
||||
return None;
|
||||
}
|
||||
if self.cursor_enabled.get() && (tex_w as f32, tex_h as f32) != (crtc_w, crtc_h) {
|
||||
// If hardware cursors are used, we cannot scale the texture.
|
||||
return None;
|
||||
}
|
||||
let (ct, position) = pass.prepare_direct_scanout(
|
||||
plane.mode_w.get(),
|
||||
plane.mode_h.get(),
|
||||
blend_cd,
|
||||
cd,
|
||||
self.cursor_enabled.get(),
|
||||
)?;
|
||||
let Some(dmabuf) = ct.tex.dmabuf() else {
|
||||
// Shm buffers cannot be scanned out.
|
||||
return None;
|
||||
};
|
||||
let position = DirectScanoutPosition {
|
||||
src_width: tex_w,
|
||||
src_height: tex_h,
|
||||
crtc_x: x1 as _,
|
||||
crtc_y: y1 as _,
|
||||
crtc_width: crtc_w as _,
|
||||
crtc_height: crtc_h as _,
|
||||
};
|
||||
let mut cache = self.scanout_buffers.borrow_mut();
|
||||
if let Some(buffer) = cache.get(&dmabuf.id) {
|
||||
return buffer.fb.as_ref().map(|fb| DirectScanoutData {
|
||||
|
|
|
|||
|
|
@ -867,6 +867,10 @@ impl Connector for MetalConnector {
|
|||
fb.locked
|
||||
}
|
||||
|
||||
fn state(&self) -> BackendConnectorState {
|
||||
self.display.borrow().persistent.state.borrow().clone()
|
||||
}
|
||||
|
||||
fn caps(&self) -> ConnectorCaps {
|
||||
CONCAP_CONNECTOR | CONCAP_MODE_SETTING | CONCAP_PHYSICAL_DISPLAY
|
||||
}
|
||||
|
|
@ -1959,7 +1963,7 @@ impl MetalBackend {
|
|||
let mut state = dd.persistent.state.borrow().clone();
|
||||
state.serial = self.state.backend_connector_state_serials.next();
|
||||
connector.send_event(ConnectorEvent::Connected(MonitorInfo {
|
||||
modes,
|
||||
modes: Some(modes),
|
||||
output_id: dd.output_id.clone(),
|
||||
width_mm: dd.mm_width as _,
|
||||
height_mm: dd.mm_height as _,
|
||||
|
|
|
|||
|
|
@ -590,7 +590,7 @@ impl XBackend {
|
|||
.backend_events
|
||||
.push(BackendEvent::NewConnector(output.clone()));
|
||||
output.events.push(ConnectorEvent::Connected(MonitorInfo {
|
||||
modes: vec![],
|
||||
modes: Some(vec![]),
|
||||
output_id: Rc::new(OutputId::new(
|
||||
String::new(),
|
||||
"X.Org Foundation".to_string(),
|
||||
|
|
@ -1113,6 +1113,10 @@ impl Connector for XOutput {
|
|||
true
|
||||
}
|
||||
|
||||
fn state(&self) -> BackendConnectorState {
|
||||
self.state.borrow().clone()
|
||||
}
|
||||
|
||||
fn transaction_type(&self) -> Box<dyn BackendConnectorTransactionTypeDyn> {
|
||||
Box::new(XTransactionType)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -527,6 +527,7 @@ struct Output {
|
|||
pub blend_space: Option<String>,
|
||||
pub native_gamut: Option<Primaries>,
|
||||
pub use_native_gamut: bool,
|
||||
pub arbitrary_modes: bool,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
|
|
@ -641,9 +642,22 @@ impl Randr {
|
|||
log::error!("Connector {} is not connected", connector.name);
|
||||
return;
|
||||
};
|
||||
let Some(mode) = output.modes.iter().find(|m| {
|
||||
m.width == t.width && m.height == t.height && m.refresh_rate() == t.refresh_rate
|
||||
}) else {
|
||||
let mode = 'mode: {
|
||||
if let Some(mode) = output.modes.iter().find(|m| {
|
||||
m.width == t.width
|
||||
&& m.height == t.height
|
||||
&& m.refresh_rate() == t.refresh_rate
|
||||
}) {
|
||||
break 'mode *mode;
|
||||
}
|
||||
if output.arbitrary_modes {
|
||||
break 'mode Mode {
|
||||
width: t.width,
|
||||
height: t.height,
|
||||
refresh_rate_millihz: (t.refresh_rate * 1_000.0).round() as u32,
|
||||
current: false,
|
||||
};
|
||||
}
|
||||
log::error!(
|
||||
"Output {} does not support this refresh rate",
|
||||
connector.name
|
||||
|
|
@ -1082,6 +1096,9 @@ impl Randr {
|
|||
p.b.0.0, p.b.1.0, p.wp.0.0, p.wp.1.0
|
||||
);
|
||||
}
|
||||
if o.arbitrary_modes {
|
||||
println!(" supports arbitrary modes");
|
||||
}
|
||||
if o.modes.is_not_empty() && modes {
|
||||
println!(" modes:");
|
||||
for mode in &o.modes {
|
||||
|
|
@ -1280,6 +1297,12 @@ impl Randr {
|
|||
let output = c.output.as_mut().unwrap();
|
||||
output.use_native_gamut = true;
|
||||
});
|
||||
jay_randr::ArbitraryModes::handle(tc, randr, data.clone(), |data, _| {
|
||||
let mut data = data.borrow_mut();
|
||||
let c = data.connectors.last_mut().unwrap();
|
||||
let output = c.output.as_mut().unwrap();
|
||||
output.arbitrary_modes = true;
|
||||
});
|
||||
tc.round_trip().await;
|
||||
data.borrow_mut().clone()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ use {
|
|||
crate::{
|
||||
acceptor::{Acceptor, AcceptorError},
|
||||
async_engine::{AsyncEngine, Phase, SpawnedFuture},
|
||||
backend::{self, Backend, BackendConnectorState, BackendConnectorStateSerial, Connector},
|
||||
backend::{Backend, Connector},
|
||||
backends::{
|
||||
dummy::{DummyBackend, DummyOutput},
|
||||
metal, x,
|
||||
|
|
@ -675,29 +675,13 @@ fn create_dummy_output(state: &Rc<State>) {
|
|||
serial_number: "".to_string(),
|
||||
});
|
||||
let persistent_state = Rc::new(PersistentOutputState::default());
|
||||
let mode = backend::Mode {
|
||||
width: 0,
|
||||
height: 0,
|
||||
refresh_rate_millihz: 40_000,
|
||||
};
|
||||
let backend_state = BackendConnectorState {
|
||||
serial: BackendConnectorStateSerial::from_raw(0),
|
||||
enabled: true,
|
||||
active: false,
|
||||
mode,
|
||||
non_desktop_override: None,
|
||||
vrr: false,
|
||||
tearing: false,
|
||||
format: XRGB8888,
|
||||
color_space: Default::default(),
|
||||
eotf: Default::default(),
|
||||
gamma_lut: Default::default(),
|
||||
};
|
||||
let id = state.connector_ids.next();
|
||||
let connector = Rc::new(DummyOutput { id }) as Rc<dyn Connector>;
|
||||
let backend_state = connector.state();
|
||||
let name = Rc::new("Dummy".to_string());
|
||||
let head_name = state.head_names.next();
|
||||
let head_state = HeadState {
|
||||
connector_id: id,
|
||||
name: RcEq(name.clone()),
|
||||
position: (0, 0),
|
||||
size: (0, 0),
|
||||
|
|
@ -725,6 +709,7 @@ fn create_dummy_output(state: &Rc<State>) {
|
|||
blend_space: BlendSpace::Srgb,
|
||||
use_native_gamut: false,
|
||||
vrr_cursor_hz: None,
|
||||
persistent_state: Some(RcEq(persistent_state.clone())),
|
||||
};
|
||||
let connector_data = Rc::new(ConnectorData {
|
||||
id,
|
||||
|
|
@ -754,7 +739,7 @@ fn create_dummy_output(state: &Rc<State>) {
|
|||
state.globals.name(),
|
||||
state,
|
||||
&connector_data,
|
||||
Vec::new(),
|
||||
Some(Vec::new()),
|
||||
0,
|
||||
0,
|
||||
&output_id,
|
||||
|
|
|
|||
|
|
@ -1205,6 +1205,7 @@ impl ConfigProxyHandler {
|
|||
.global
|
||||
.modes
|
||||
.iter()
|
||||
.flatten()
|
||||
.map(|m| WireMode {
|
||||
width: m.width,
|
||||
height: m.height,
|
||||
|
|
@ -1215,6 +1216,17 @@ impl ConfigProxyHandler {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_connector_supports_arbitrary_modes(
|
||||
&self,
|
||||
connector: Connector,
|
||||
) -> Result<(), CphError> {
|
||||
let connector = self.get_output_node(connector)?;
|
||||
self.respond(Response::ConnectorSupportsArbitraryModes {
|
||||
supports_arbitrary_modes: connector.global.modes.is_none(),
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_connector_name(&self, connector: Connector) -> Result<(), CphError> {
|
||||
let connector = self.get_connector(connector)?;
|
||||
self.respond(Response::GetConnectorName {
|
||||
|
|
@ -1580,6 +1592,19 @@ impl ConfigProxyHandler {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_get_connector_by_name(&self, name: &str) {
|
||||
let connector = self
|
||||
.state
|
||||
.connectors
|
||||
.lock()
|
||||
.values()
|
||||
.find(|c| *c.name == name)
|
||||
.map(|c| c.connector.id().raw() as _)
|
||||
.map(Connector)
|
||||
.unwrap_or(Connector(0));
|
||||
self.respond(Response::GetConnector { connector });
|
||||
}
|
||||
|
||||
fn handle_get_connector_active_workspace(&self, connector: Connector) -> Result<(), CphError> {
|
||||
let output = self.get_output_node(connector)?;
|
||||
let workspace = output
|
||||
|
|
@ -3328,6 +3353,10 @@ impl ConfigProxyHandler {
|
|||
monospace,
|
||||
} => self.handle_set_egui_fonts(proportional, monospace),
|
||||
ClientMessage::OpenControlCenter => self.handle_open_control_center(),
|
||||
ClientMessage::ConnectorSupportsArbitraryModes { connector } => self
|
||||
.handle_connector_supports_arbitrary_modes(connector)
|
||||
.wrn("connector_supports_arbitrary_modes")?,
|
||||
ClientMessage::GetConnectorByName { name } => self.handle_get_connector_by_name(name),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -134,11 +134,11 @@ impl GpusPane {
|
|||
.connectors
|
||||
.lock()
|
||||
.values()
|
||||
.map(|v| v.connector.kernel_id().to_string())
|
||||
.map(|v| v.name.clone())
|
||||
.collect::<Vec<_>>();
|
||||
cs.sort();
|
||||
for c in cs {
|
||||
ui.label(c);
|
||||
ui.label(&**c);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -25,9 +25,10 @@ use {
|
|||
},
|
||||
ahash::AHashMap,
|
||||
egui::{
|
||||
Align, Button, Checkbox, Color32, ComboBox, DragValue, EventFilter, FontId, Frame, Grid,
|
||||
Id, Key, Layout, PointerButton, Rect, ScrollArea, Sense, Shadow, Stroke, StrokeKind, Style,
|
||||
TextFormat, Ui, UiBuilder, Vec2, Widget, WidgetText, pos2, text::LayoutJob, vec2,
|
||||
Align, Button, Checkbox, CollapsingHeader, Color32, ComboBox, DragValue, EventFilter,
|
||||
FontId, Frame, Grid, Id, Key, Layout, PointerButton, Rect, ScrollArea, Sense, Shadow,
|
||||
Stroke, StrokeKind, Style, TextFormat, Ui, UiBuilder, Vec2, Widget, WidgetText, emath,
|
||||
pos2, text::LayoutJob, vec2,
|
||||
},
|
||||
egui_tiles::{
|
||||
Behavior, Container, Linear, LinearDir, ResizeState, SimplificationOptions, Tile, TileId,
|
||||
|
|
@ -68,6 +69,7 @@ enum Pane {
|
|||
struct CompleteHead {
|
||||
id: ConnectorId,
|
||||
name: HeadName,
|
||||
pretty_name: Rc<String>,
|
||||
live_state: ReadOnlyHeadState,
|
||||
changed_state: Option<HeadState>,
|
||||
z: u64,
|
||||
|
|
@ -123,9 +125,9 @@ pub enum View {
|
|||
#[derive(Error, Debug)]
|
||||
enum HeadTransactionError {
|
||||
#[error("The connector {} has been removed", .0)]
|
||||
HeadRemoved(ConnectorId),
|
||||
HeadRemoved(Rc<String>),
|
||||
#[error("The display connected to connector {} has changed", .0)]
|
||||
MonitorChanged(ConnectorId),
|
||||
MonitorChanged(Rc<String>),
|
||||
#[error(transparent)]
|
||||
Backend(#[from] BackendConnectorTransactionError),
|
||||
}
|
||||
|
|
@ -811,10 +813,12 @@ impl OutputsPaneInner {
|
|||
continue;
|
||||
};
|
||||
let Some(connector) = self.state.connectors.get(&head.id) else {
|
||||
return Err(HeadTransactionError::HeadRemoved(head.id));
|
||||
return Err(HeadTransactionError::HeadRemoved(head.pretty_name.clone()));
|
||||
};
|
||||
if head.live_state.borrow().monitor_info != desired.monitor_info {
|
||||
return Err(HeadTransactionError::MonitorChanged(head.id));
|
||||
return Err(HeadTransactionError::MonitorChanged(
|
||||
head.pretty_name.clone(),
|
||||
));
|
||||
}
|
||||
let old = connector.state.borrow().clone();
|
||||
let mut new = old.clone();
|
||||
|
|
@ -838,6 +842,7 @@ impl OutputsPaneInner {
|
|||
let Some(desired) = &head.changed_state else {
|
||||
continue;
|
||||
};
|
||||
desired.flush_persistent_state(&self.state);
|
||||
if let Some(output) = self.state.outputs.get(&head.id)
|
||||
&& let Some(node) = &output.node
|
||||
{
|
||||
|
|
@ -894,6 +899,7 @@ impl OutputsPaneInner {
|
|||
self.heads.entry(mgrs.name).or_insert_with(|| CompleteHead {
|
||||
id: connector.id,
|
||||
name: mgrs.name,
|
||||
pretty_name: connector.name.clone(),
|
||||
live_state: mgrs.state(),
|
||||
changed_state: None,
|
||||
z: 0,
|
||||
|
|
@ -946,36 +952,38 @@ fn show_connector(state: &State, settings: &Settings, head: &mut CompleteHead, u
|
|||
..Default::default()
|
||||
},
|
||||
);
|
||||
ui.collapsing(layout_job, |ui| {
|
||||
grid(ui, ("settings", head.name), |ui| {
|
||||
let mut diff = false;
|
||||
show_serial_number(ui, m);
|
||||
diff |= show_enablement(ui, m, t);
|
||||
diff |= show_position(ui, m, t);
|
||||
diff |= show_scale(ui, m, t);
|
||||
diff |= show_mode(ui, m, t);
|
||||
diff |= show_size(ui, m, t);
|
||||
diff |= show_transform(ui, m, t);
|
||||
diff |= show_brightness(ui, m, t);
|
||||
diff |= show_color_space(ui, m, t);
|
||||
diff |= show_eotf(ui, m, t);
|
||||
diff |= show_format(ui, m, t);
|
||||
diff |= show_tearing(ui, m, t);
|
||||
diff |= show_vrr(ui, m, t);
|
||||
diff |= show_non_desktop(ui, m, t);
|
||||
diff |= show_blend_space(ui, m, t);
|
||||
diff |= show_use_native_gamut(ui, m, t);
|
||||
show_native_gamut(ui, m);
|
||||
diff |= show_cursor_hz(ui, m, t);
|
||||
show_flip_margin(state, ui, m, t, head.id);
|
||||
if diff {
|
||||
let ui = &mut *ui.row();
|
||||
ui.label("");
|
||||
ui.label("");
|
||||
ui.label("^ current");
|
||||
}
|
||||
CollapsingHeader::new(layout_job)
|
||||
.id_salt(("connector", head.name))
|
||||
.show(ui, |ui| {
|
||||
grid(ui, ("settings", head.name), |ui| {
|
||||
let mut diff = false;
|
||||
show_serial_number(ui, m);
|
||||
diff |= show_enablement(state, ui, m, t);
|
||||
diff |= show_position(ui, m, t);
|
||||
diff |= show_scale(ui, m, t);
|
||||
diff |= show_mode(ui, m, t);
|
||||
diff |= show_size(ui, m, t);
|
||||
diff |= show_transform(ui, m, t);
|
||||
diff |= show_brightness(ui, m, t);
|
||||
diff |= show_color_space(ui, m, t);
|
||||
diff |= show_eotf(ui, m, t);
|
||||
diff |= show_format(ui, m, t);
|
||||
diff |= show_tearing(ui, m, t);
|
||||
diff |= show_vrr(ui, m, t);
|
||||
diff |= show_non_desktop(state, ui, m, t);
|
||||
diff |= show_blend_space(ui, m, t);
|
||||
diff |= show_use_native_gamut(ui, m, t);
|
||||
show_native_gamut(ui, m);
|
||||
diff |= show_cursor_hz(ui, m, t);
|
||||
show_flip_margin(state, ui, m, t, head.id);
|
||||
if diff {
|
||||
let ui = &mut *ui.row();
|
||||
ui.label("");
|
||||
ui.label("");
|
||||
ui.label("^ current");
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
fn show_serial_number(ui: &mut Ui, m: &HeadState) {
|
||||
|
|
@ -986,7 +994,7 @@ fn show_serial_number(ui: &mut Ui, m: &HeadState) {
|
|||
}
|
||||
}
|
||||
|
||||
fn show_enablement(ui: &mut Ui, m: &HeadState, t: &mut Option<HeadState>) -> bool {
|
||||
fn show_enablement(state: &State, ui: &mut Ui, m: &HeadState, t: &mut Option<HeadState>) -> bool {
|
||||
let ui = &mut *ui.row();
|
||||
grid_label(ui, "Enabled");
|
||||
let mut v = effective!(m, t).connector_enabled;
|
||||
|
|
@ -994,7 +1002,7 @@ fn show_enablement(ui: &mut Ui, m: &HeadState, t: &mut Option<HeadState>) -> boo
|
|||
if changed {
|
||||
let t = modify!(m, t);
|
||||
t.connector_enabled = v;
|
||||
t.update_in_compositor_space(m.wl_output);
|
||||
t.update_in_compositor_space(state, m.wl_output);
|
||||
}
|
||||
let diff = v != m.connector_enabled;
|
||||
if diff {
|
||||
|
|
@ -1087,15 +1095,33 @@ fn show_mode(ui: &mut Ui, m: &HeadState, t: &mut Option<HeadState>) -> bool {
|
|||
)
|
||||
};
|
||||
if let Some(monitor_info) = &m.monitor_info
|
||||
&& monitor_info.modes.len() > 1
|
||||
&& let Some(modes) = &monitor_info.modes
|
||||
&& modes.len() > 1
|
||||
{
|
||||
ComboBox::from_id_salt("modes")
|
||||
.selected_text(mode_text(mode))
|
||||
.show_ui(ui, |ui| {
|
||||
for v in &monitor_info.modes {
|
||||
for v in modes {
|
||||
ui.selectable_value(&mut mode, *v, mode_text(*v));
|
||||
}
|
||||
});
|
||||
} else if let Some(monitor_info) = &m.monitor_info
|
||||
&& monitor_info.modes.is_none()
|
||||
{
|
||||
ui.horizontal(|ui| {
|
||||
fn value<T: emath::Numeric>(ui: &mut Ui, v: &mut T, min: T, max: T) -> bool {
|
||||
let res = DragValue::new(v).range(min..=max).speed(1.0).ui(ui);
|
||||
res.changed()
|
||||
}
|
||||
value(ui, &mut mode.width, 1, u16::MAX as i32);
|
||||
ui.label("x");
|
||||
value(ui, &mut mode.height, 1, u16::MAX as i32);
|
||||
ui.label("@");
|
||||
let mut hz = mode.refresh_rate_millihz as f64 / 1_000.0;
|
||||
if value(ui, &mut hz, 0.0, 1_000_000.0) {
|
||||
mode.refresh_rate_millihz = (hz * 1_000.0).round() as u32;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
ui.label(mode_text(mode));
|
||||
}
|
||||
|
|
@ -1525,7 +1551,7 @@ fn show_vrr(ui: &mut Ui, m: &HeadState, t: &mut Option<HeadState>) -> bool {
|
|||
diff
|
||||
}
|
||||
|
||||
fn show_non_desktop(ui: &mut Ui, m: &HeadState, t: &mut Option<HeadState>) -> bool {
|
||||
fn show_non_desktop(state: &State, ui: &mut Ui, m: &HeadState, t: &mut Option<HeadState>) -> bool {
|
||||
{
|
||||
let ui = &mut *ui.row();
|
||||
grid_label(ui, "Non-desktop");
|
||||
|
|
@ -1555,7 +1581,7 @@ fn show_non_desktop(ui: &mut Ui, m: &HeadState, t: &mut Option<HeadState>) -> bo
|
|||
if changed {
|
||||
let t = modify!(m, t);
|
||||
t.override_non_desktop = v;
|
||||
t.update_in_compositor_space(m.wl_output);
|
||||
t.update_in_compositor_space(state, m.wl_output);
|
||||
}
|
||||
let diff = v != m.override_non_desktop;
|
||||
if diff {
|
||||
|
|
|
|||
135
src/gfx_api.rs
135
src/gfx_api.rs
|
|
@ -1214,3 +1214,138 @@ impl FdSync {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct DirectScanoutPosition {
|
||||
pub src_width: i32,
|
||||
pub src_height: i32,
|
||||
pub crtc_x: i32,
|
||||
pub crtc_y: i32,
|
||||
pub crtc_width: i32,
|
||||
pub crtc_height: i32,
|
||||
}
|
||||
|
||||
impl GfxRenderPass {
|
||||
pub fn prepare_direct_scanout(
|
||||
&self,
|
||||
mode_w: i32,
|
||||
mode_h: i32,
|
||||
blend_cd: &Rc<ColorDescription>,
|
||||
cd: &Rc<ColorDescription>,
|
||||
no_scaling: bool,
|
||||
) -> Option<(&CopyTexture, DirectScanoutPosition)> {
|
||||
let ct = 'ct: {
|
||||
let mut ops = self.ops.iter().rev();
|
||||
let ct = 'ct2: {
|
||||
for opt in &mut ops {
|
||||
match opt {
|
||||
GfxApiOpt::Sync => {}
|
||||
GfxApiOpt::FillRect(_) => {
|
||||
// Top-most layer must be a texture.
|
||||
return None;
|
||||
}
|
||||
GfxApiOpt::CopyTexture(ct) => break 'ct2 ct,
|
||||
}
|
||||
}
|
||||
return None;
|
||||
};
|
||||
if ct.alpha_mode != AlphaMode::PremultipliedElectrical {
|
||||
// Direct scanout requires premultiplied electrical alpha.
|
||||
return None;
|
||||
}
|
||||
if !ct.cd.embeds_into(cd) {
|
||||
// Direct scanout requires embeddable color descriptions.
|
||||
return None;
|
||||
}
|
||||
if !ct.opaque && !ct.cd.embeds_into(blend_cd) {
|
||||
// Blending changes the appearance of translucent buffers.
|
||||
return None;
|
||||
}
|
||||
if ct.alpha.is_some() {
|
||||
// Direct scanout with alpha factor is not supported.
|
||||
return None;
|
||||
}
|
||||
if !ct.tex.format().has_alpha && ct.target.is_covering() {
|
||||
// Texture covers the entire screen and is opaque.
|
||||
break 'ct ct;
|
||||
}
|
||||
for opt in ops {
|
||||
match opt {
|
||||
GfxApiOpt::Sync => {}
|
||||
GfxApiOpt::FillRect(fr) => {
|
||||
if fr.effective_color() == Color::SOLID_BLACK {
|
||||
// Black fills can be ignored because this is the CRTC background color.
|
||||
if fr.rect.is_covering() {
|
||||
// If fill covers the entire screen, we don't have to look further.
|
||||
break 'ct ct;
|
||||
}
|
||||
} else {
|
||||
// Fill could be visible.
|
||||
return None;
|
||||
}
|
||||
}
|
||||
GfxApiOpt::CopyTexture(_) => {
|
||||
// Texture could be visible.
|
||||
return None;
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(clear) = self.clear
|
||||
&& clear != Color::SOLID_BLACK
|
||||
{
|
||||
// Background could be visible.
|
||||
return None;
|
||||
}
|
||||
ct
|
||||
};
|
||||
if let AcquireSync::None | AcquireSync::Implicit = ct.acquire_sync {
|
||||
// Cannot perform scanout without explicit sync.
|
||||
return None;
|
||||
}
|
||||
if ct.source.buffer_transform != ct.target.output_transform {
|
||||
// Rotations and mirroring are not supported.
|
||||
return None;
|
||||
}
|
||||
if !ct.source.is_covering() {
|
||||
// Viewports are not supported.
|
||||
return None;
|
||||
}
|
||||
if ct.target.x1 < -1.0 || ct.target.y1 < -1.0 || ct.target.x2 > 1.0 || ct.target.y2 > 1.0 {
|
||||
// Rendering outside the screen is not supported.
|
||||
return None;
|
||||
}
|
||||
let (tex_w, tex_h) = ct.tex.size();
|
||||
let (x1, x2, y1, y2) = {
|
||||
let plane_w = mode_w as f32;
|
||||
let plane_h = mode_h as f32;
|
||||
let ((x1, x2), (y1, y2)) = ct
|
||||
.target
|
||||
.output_transform
|
||||
.maybe_swap(((ct.target.x1, ct.target.x2), (ct.target.y1, ct.target.y2)));
|
||||
(
|
||||
(x1 + 1.0) * plane_w / 2.0,
|
||||
(x2 + 1.0) * plane_w / 2.0,
|
||||
(y1 + 1.0) * plane_h / 2.0,
|
||||
(y2 + 1.0) * plane_h / 2.0,
|
||||
)
|
||||
};
|
||||
let (crtc_w, crtc_h) = (x2 - x1, y2 - y1);
|
||||
if crtc_w < 0.0 || crtc_h < 0.0 {
|
||||
// Flipping x or y axis is not supported.
|
||||
return None;
|
||||
}
|
||||
if no_scaling && (tex_w as f32, tex_h as f32) != (crtc_w, crtc_h) {
|
||||
// If scaling is not supported, we cannot scale the texture.
|
||||
return None;
|
||||
}
|
||||
let position = DirectScanoutPosition {
|
||||
src_width: tex_w,
|
||||
src_height: tex_h,
|
||||
crtc_x: x1 as _,
|
||||
crtc_y: y1 as _,
|
||||
crtc_width: crtc_w as _,
|
||||
crtc_height: crtc_h as _,
|
||||
};
|
||||
Some((ct, position))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,16 +12,17 @@ use {
|
|||
head_management_macros::HeadExts,
|
||||
jay_head_manager_session_v1::JayHeadManagerSessionV1, jay_head_v1::JayHeadV1,
|
||||
},
|
||||
wl_output::BlendSpace,
|
||||
wl_output::{BlendSpace, PersistentOutputState},
|
||||
},
|
||||
scale::Scale,
|
||||
state::OutputData,
|
||||
state::{OutputData, State},
|
||||
tree::{OutputNode, TearingMode, Transform, VrrMode},
|
||||
utils::{copyhashmap::CopyHashMap, hash_map_ext::HashMapExt, rc_eq::RcEq},
|
||||
wire::JayHeadManagerSessionV1Id,
|
||||
},
|
||||
std::{
|
||||
cell::{Cell, Ref, RefCell},
|
||||
collections::hash_map::Entry,
|
||||
rc::Rc,
|
||||
},
|
||||
thiserror::Error,
|
||||
|
|
@ -71,6 +72,7 @@ struct HeadCommon {
|
|||
|
||||
#[derive(Clone, PartialEq)]
|
||||
pub struct HeadState {
|
||||
pub connector_id: ConnectorId,
|
||||
pub name: RcEq<String>,
|
||||
pub wl_output: Option<GlobalName>,
|
||||
pub connector_enabled: bool,
|
||||
|
|
@ -98,6 +100,7 @@ pub struct HeadState {
|
|||
pub blend_space: BlendSpace,
|
||||
pub use_native_gamut: bool,
|
||||
pub vrr_cursor_hz: Option<f64>,
|
||||
pub persistent_state: Option<RcEq<PersistentOutputState>>,
|
||||
}
|
||||
|
||||
pub struct ReadOnlyHeadState {
|
||||
|
|
@ -111,7 +114,7 @@ impl ReadOnlyHeadState {
|
|||
}
|
||||
|
||||
impl HeadState {
|
||||
pub fn update_in_compositor_space(&mut self, wl_output: Option<GlobalName>) {
|
||||
pub fn update_in_compositor_space(&mut self, state: &State, wl_output: Option<GlobalName>) {
|
||||
self.in_compositor_space = false;
|
||||
self.wl_output = None;
|
||||
if !self.connector_enabled {
|
||||
|
|
@ -128,6 +131,26 @@ impl HeadState {
|
|||
}
|
||||
self.in_compositor_space = true;
|
||||
self.wl_output = wl_output;
|
||||
if self.persistent_state.is_none() {
|
||||
let ds = state
|
||||
.persistent_output_states
|
||||
.get(&mi.output_id)
|
||||
.unwrap_or_else(|| state.new_persistent_output_state());
|
||||
self.position = ds.pos.get();
|
||||
self.transform = ds.transform.get();
|
||||
self.vrr_mode = ds.vrr_mode.get();
|
||||
self.tearing_mode = ds.tearing_mode.get();
|
||||
self.brightness = ds.brightness.get();
|
||||
self.blend_space = ds.blend_space.get();
|
||||
self.use_native_gamut = ds.use_native_gamut.get();
|
||||
self.vrr_cursor_hz = ds.vrr_cursor_hz.get();
|
||||
self.scale = ds.scale.get();
|
||||
self.persistent_state = Some(RcEq(ds));
|
||||
if let Some(c) = state.connectors.get(&self.connector_id) {
|
||||
self.mode = c.state.borrow().mode;
|
||||
}
|
||||
self.update_size();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_size(&mut self) {
|
||||
|
|
@ -135,6 +158,18 @@ impl HeadState {
|
|||
OutputNode::calculate_extents_(self.mode, self.transform, self.scale, self.position)
|
||||
.size();
|
||||
}
|
||||
|
||||
pub fn flush_persistent_state(&self, state: &State) {
|
||||
if let Some(mi) = &self.monitor_info
|
||||
&& let Some(ds) = &self.persistent_state
|
||||
&& let Entry::Vacant(v) = state
|
||||
.persistent_output_states
|
||||
.lock()
|
||||
.entry(mi.output_id.clone())
|
||||
{
|
||||
v.insert(ds.0.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum HeadOp {
|
||||
|
|
@ -249,24 +284,13 @@ impl HeadManagers {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn handle_output_connected(&self, output: &OutputData) {
|
||||
pub fn handle_output_connected(&self, s: &State, output: &OutputData) {
|
||||
let state = &mut *self.state.borrow_mut();
|
||||
state.connected = true;
|
||||
state.monitor_info = Some(RcEq(output.monitor_info.clone()));
|
||||
state.persistent_state = None;
|
||||
state.inherent_non_desktop = output.monitor_info.non_desktop;
|
||||
state.update_in_compositor_space(output.node.as_ref().map(|n| n.global.name));
|
||||
if let Some(n) = &output.node {
|
||||
state.position = n.global.pos.get().position();
|
||||
state.size = n.global.pos.get().size();
|
||||
state.mode = n.global.mode.get();
|
||||
state.transform = n.global.persistent.transform.get();
|
||||
state.vrr_mode = n.global.persistent.vrr_mode.get();
|
||||
state.tearing_mode = n.global.persistent.tearing_mode.get();
|
||||
state.brightness = n.global.persistent.brightness.get();
|
||||
state.blend_space = n.global.persistent.blend_space.get();
|
||||
state.use_native_gamut = n.global.persistent.use_native_gamut.get();
|
||||
state.vrr_cursor_hz = n.global.persistent.vrr_cursor_hz.get();
|
||||
}
|
||||
state.update_in_compositor_space(s, output.node.as_ref().map(|n| n.global.name));
|
||||
for head in self.managers.lock().values() {
|
||||
skip_in_transaction!(head);
|
||||
if let Some(ext) = &head.ext.connector_info_v1 {
|
||||
|
|
@ -321,11 +345,12 @@ impl HeadManagers {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn handle_output_disconnected(&self) {
|
||||
pub fn handle_output_disconnected(&self, s: &State) {
|
||||
let state = &mut *self.state.borrow_mut();
|
||||
state.connected = false;
|
||||
state.monitor_info = None;
|
||||
state.update_in_compositor_space(None);
|
||||
state.persistent_state = None;
|
||||
state.update_in_compositor_space(s, None);
|
||||
for head in self.managers.lock().values() {
|
||||
skip_in_transaction!(head);
|
||||
if let Some(ext) = &head.ext.compositor_space_info_v1 {
|
||||
|
|
@ -406,10 +431,10 @@ impl HeadManagers {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn handle_enabled_change(&self, enabled: bool) {
|
||||
pub fn handle_enabled_change(&self, s: &State, enabled: bool) {
|
||||
let state = &mut *self.state.borrow_mut();
|
||||
state.connector_enabled = enabled;
|
||||
state.update_in_compositor_space(state.wl_output);
|
||||
state.update_in_compositor_space(s, state.wl_output);
|
||||
for head in self.managers.lock().values() {
|
||||
skip_in_transaction!(head);
|
||||
if let Some(ext) = &head.ext.compositor_space_info_v1 {
|
||||
|
|
|
|||
|
|
@ -41,8 +41,10 @@ impl HeadName {
|
|||
|
||||
pub(in super::super) fn send_modes(&self, state: &HeadState) {
|
||||
self.client.event(Reset { self_id: self.id });
|
||||
if let Some(mi) = &state.monitor_info {
|
||||
for mode in &mi.modes {
|
||||
if let Some(mi) = &state.monitor_info
|
||||
&& let Some(modes) = &mi.modes
|
||||
{
|
||||
for mode in modes {
|
||||
self.client.event(Mode {
|
||||
self_id: self.id,
|
||||
width: mode.width,
|
||||
|
|
@ -73,7 +75,8 @@ impl JayHeadExtModeSetterV1RequestHandler for HeadName {
|
|||
.borrow()
|
||||
.monitor_info
|
||||
.as_deref()
|
||||
.map(|i| i.modes.len())
|
||||
.and_then(|i| i.modes.as_ref())
|
||||
.map(|m| m.len())
|
||||
.unwrap_or(0);
|
||||
let idx = req.idx as usize;
|
||||
if idx >= num_modes {
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ impl HeadName {
|
|||
pub(in super::super) fn send_info(&self, state: &HeadState) {
|
||||
self.send_reset();
|
||||
if let Some(mi) = &state.monitor_info {
|
||||
for mode in &mi.modes {
|
||||
for mode in mi.modes.iter().flatten() {
|
||||
self.send_mode(mode);
|
||||
}
|
||||
self.send_manufacturer(&mi.output_id.manufacturer);
|
||||
|
|
|
|||
|
|
@ -404,7 +404,7 @@ impl JayHeadManagerSessionV1RequestHandler for JayHeadManagerSessionV1 {
|
|||
}
|
||||
HeadOp::SetConnectorEnabled(enabled) => {
|
||||
state.connector_enabled = enabled;
|
||||
state.update_in_compositor_space(snapshot.wl_output);
|
||||
state.update_in_compositor_space(&self.client.state, snapshot.wl_output);
|
||||
to_send |= COMPOSITOR_SPACE_INFO_FULL;
|
||||
to_send |= COMPOSITOR_SPACE_INFO_ENABLED;
|
||||
to_send |= CORE_INFO;
|
||||
|
|
@ -422,14 +422,20 @@ impl JayHeadManagerSessionV1RequestHandler for JayHeadManagerSessionV1 {
|
|||
to_send |= COMPOSITOR_SPACE_INFO_SIZE;
|
||||
}
|
||||
HeadOp::SetMode(i) => {
|
||||
state.mode = snapshot.monitor_info.as_deref().unwrap().modes[i];
|
||||
state.mode = snapshot
|
||||
.monitor_info
|
||||
.as_deref()
|
||||
.unwrap()
|
||||
.modes
|
||||
.as_ref()
|
||||
.unwrap()[i];
|
||||
state.update_size();
|
||||
to_send |= MODE_INFO;
|
||||
to_send |= COMPOSITOR_SPACE_INFO_SIZE;
|
||||
}
|
||||
HeadOp::SetNonDesktopOverride(m) => {
|
||||
state.override_non_desktop = m;
|
||||
state.update_in_compositor_space(snapshot.wl_output);
|
||||
state.update_in_compositor_space(&self.client.state, snapshot.wl_output);
|
||||
to_send |= COMPOSITOR_SPACE_INFO_FULL;
|
||||
to_send |= CORE_INFO;
|
||||
to_send |= NON_DESKTOP_INFO;
|
||||
|
|
@ -551,6 +557,7 @@ impl JayHeadManagerSessionV1RequestHandler for JayHeadManagerSessionV1 {
|
|||
}
|
||||
for head in self.heads.lock().values() {
|
||||
let desired = &*head.common.transaction_state.borrow();
|
||||
desired.flush_persistent_state(&self.client.state);
|
||||
if let Some(output) = self.client.state.outputs.get(&head.common.id)
|
||||
&& let Some(node) = &output.node
|
||||
{
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@ global_base!(JayCompositorGlobal, JayCompositor, JayCompositorError);
|
|||
|
||||
impl Global for JayCompositorGlobal {
|
||||
fn version(&self) -> u32 {
|
||||
28
|
||||
29
|
||||
}
|
||||
|
||||
fn required_caps(&self) -> ClientCaps {
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ use {
|
|||
},
|
||||
jay_config::video::{TearingMode as ConfigTearingMode, VrrMode as ConfigVrrMode},
|
||||
linearize::LinearizeExt,
|
||||
std::rc::Rc,
|
||||
std::{rc::Rc, slice},
|
||||
thiserror::Error,
|
||||
};
|
||||
|
||||
|
|
@ -36,6 +36,7 @@ const COLORIMETRY_SINCE: Version = Version(15);
|
|||
const BRIGHTNESS_SINCE: Version = Version(16);
|
||||
const BLEND_SPACE_SINCE: Version = Version(21);
|
||||
const NATIVE_GAMUT_SINCE: Version = Version(23);
|
||||
const ARBITRARY_MODES_SINCE: Version = Version(29);
|
||||
|
||||
impl JayRandr {
|
||||
pub fn new(id: JayRandrId, client: &Rc<Client>, version: Version) -> Self {
|
||||
|
|
@ -162,7 +163,11 @@ impl JayRandr {
|
|||
}
|
||||
}
|
||||
let current_mode = global.mode.get();
|
||||
for mode in &global.modes {
|
||||
for mode in global
|
||||
.modes
|
||||
.as_deref()
|
||||
.unwrap_or(slice::from_ref(¤t_mode))
|
||||
{
|
||||
self.client.event(Mode {
|
||||
self_id: self.id,
|
||||
width: mode.width,
|
||||
|
|
@ -232,6 +237,9 @@ impl JayRandr {
|
|||
self.client.event(UseNativeGamut { self_id: self.id });
|
||||
}
|
||||
}
|
||||
if self.version >= ARBITRARY_MODES_SINCE && global.modes.is_none() {
|
||||
self.client.event(ArbitraryModes { self_id: self.id });
|
||||
}
|
||||
}
|
||||
|
||||
fn send_error(&self, msg: &str) {
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@ pub struct WlOutputGlobal {
|
|||
pub output_id: Rc<OutputId>,
|
||||
pub mode: Cell<backend::Mode>,
|
||||
pub refresh_nsec: Cell<u64>,
|
||||
pub modes: Vec<backend::Mode>,
|
||||
pub modes: Option<Vec<backend::Mode>>,
|
||||
pub formats: CloneCell<Rc<Vec<&'static Format>>>,
|
||||
pub format: Cell<&'static Format>,
|
||||
pub width_mm: i32,
|
||||
|
|
@ -199,7 +199,7 @@ impl WlOutputGlobal {
|
|||
name: GlobalName,
|
||||
state: &Rc<State>,
|
||||
connector: &Rc<ConnectorData>,
|
||||
modes: Vec<backend::Mode>,
|
||||
modes: Option<Vec<backend::Mode>>,
|
||||
width_mm: i32,
|
||||
height_mm: i32,
|
||||
output_id: &Rc<OutputId>,
|
||||
|
|
|
|||
|
|
@ -12,9 +12,9 @@ use {
|
|||
scale,
|
||||
state::OutputData,
|
||||
tree::{self, VrrMode},
|
||||
utils::copyhashmap::CopyHashMap,
|
||||
wire::{ZwlrOutputHeadV1Id, zwlr_output_head_v1::*},
|
||||
},
|
||||
ahash::AHashMap,
|
||||
std::rc::Rc,
|
||||
thiserror::Error,
|
||||
};
|
||||
|
|
@ -44,7 +44,7 @@ pub struct ZwlrOutputHeadV1 {
|
|||
pub(super) manager: Rc<ZwlrOutputManagerV1>,
|
||||
pub(super) head_id: WlrOutputHeadId,
|
||||
pub(super) connector_id: ConnectorId,
|
||||
pub(super) modes: AHashMap<backend::Mode, Rc<ZwlrOutputModeV1>>,
|
||||
pub(super) modes: CopyHashMap<backend::Mode, Rc<ZwlrOutputModeV1>>,
|
||||
}
|
||||
|
||||
impl ZwlrOutputHeadV1 {
|
||||
|
|
@ -177,13 +177,21 @@ impl ZwlrOutputHeadV1 {
|
|||
}
|
||||
|
||||
pub fn handle_mode_change(&self, new: backend::Mode) {
|
||||
let Some(mode) = self.modes.get(&new) else {
|
||||
let Some(mode) = self.modes.get(&new).or_else(|| {
|
||||
self.manager
|
||||
.create_mode(self.head_id, &new, false, false)
|
||||
.inspect(|mode| {
|
||||
self.modes.set(new, mode.clone());
|
||||
self.send_mode(mode);
|
||||
mode.send();
|
||||
})
|
||||
}) else {
|
||||
return;
|
||||
};
|
||||
if mode.destroyed.get() {
|
||||
return;
|
||||
}
|
||||
self.send_current_mode(mode);
|
||||
self.send_current_mode(&mode);
|
||||
self.manager.schedule_done();
|
||||
}
|
||||
|
||||
|
|
@ -207,7 +215,7 @@ impl ZwlrOutputHeadV1 {
|
|||
|
||||
pub fn handle_disconnected(&self) {
|
||||
self.send_finished();
|
||||
for mode in self.modes.values() {
|
||||
for mode in self.modes.lock().values() {
|
||||
if !mode.destroyed.get() {
|
||||
mode.send_finished();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,23 +1,24 @@
|
|||
use {
|
||||
crate::{
|
||||
backend::Mode,
|
||||
client::{CAP_HEAD_MANAGER, Client, ClientCaps, ClientError},
|
||||
globals::{Global, GlobalName},
|
||||
ifs::wlr_output_manager::{
|
||||
zwlr_output_configuration_v1::ZwlrOutputConfigurationV1,
|
||||
zwlr_output_head_v1::{
|
||||
ADAPTIVE_SYNC_SINCE, MAKE_SINCE, MODEL_SINCE, SERIAL_NUMBER_SINCE, ZwlrOutputHeadV1,
|
||||
ADAPTIVE_SYNC_SINCE, MAKE_SINCE, MODEL_SINCE, SERIAL_NUMBER_SINCE, WlrOutputHeadId,
|
||||
ZwlrOutputHeadV1,
|
||||
},
|
||||
zwlr_output_mode_v1::ZwlrOutputModeV1,
|
||||
},
|
||||
leaks::Tracker,
|
||||
object::{Object, Version},
|
||||
state::OutputData,
|
||||
utils::numcell::NumCell,
|
||||
utils::{copyhashmap::CopyHashMap, numcell::NumCell},
|
||||
wire::{ZwlrOutputManagerV1Id, zwlr_output_manager_v1::*},
|
||||
},
|
||||
ahash::AHashMap,
|
||||
isnt::std_1::string::IsntStringExt,
|
||||
std::{cell::Cell, rc::Rc},
|
||||
std::{cell::Cell, rc::Rc, slice},
|
||||
thiserror::Error,
|
||||
};
|
||||
|
||||
|
|
@ -134,38 +135,27 @@ impl ZwlrOutputManagerV1 {
|
|||
let state_mode = output.connector.state.borrow().mode;
|
||||
let head_id = self.client.state.wlr_output_managers.head_ids.next();
|
||||
let mut modes_list = vec![];
|
||||
let mut modes = AHashMap::new();
|
||||
let modes = CopyHashMap::new();
|
||||
let mut have_current = false;
|
||||
for (idx, mode) in mi.modes.iter().enumerate() {
|
||||
if modes.contains_key(mode) {
|
||||
for (idx, mode) in mi
|
||||
.modes
|
||||
.as_deref()
|
||||
.unwrap_or(slice::from_ref(&state_mode))
|
||||
.iter()
|
||||
.enumerate()
|
||||
{
|
||||
if modes.contains(mode) {
|
||||
continue;
|
||||
}
|
||||
let current = !have_current && *mode == state_mode;
|
||||
if current {
|
||||
have_current = true;
|
||||
}
|
||||
let id = match self.client.new_id() {
|
||||
Ok(id) => id,
|
||||
Err(e) => {
|
||||
self.client.error(e);
|
||||
return;
|
||||
}
|
||||
let Some(output_mode) = self.create_mode(head_id, mode, idx == 0, current) else {
|
||||
return;
|
||||
};
|
||||
let output_mode = Rc::new(ZwlrOutputModeV1 {
|
||||
id,
|
||||
head_id,
|
||||
client: self.client.clone(),
|
||||
tracker: Default::default(),
|
||||
version: self.version,
|
||||
mode: *mode,
|
||||
preferred: idx == 0,
|
||||
initial_current: current,
|
||||
destroyed: Cell::new(false),
|
||||
});
|
||||
track!(self.client, output_mode);
|
||||
self.client.add_server_obj(&output_mode);
|
||||
modes_list.push(output_mode.clone());
|
||||
modes.insert(*mode, output_mode);
|
||||
modes.set(*mode, output_mode);
|
||||
}
|
||||
let head = Rc::new(ZwlrOutputHeadV1 {
|
||||
id,
|
||||
|
|
@ -244,6 +234,36 @@ impl ZwlrOutputManagerV1 {
|
|||
.queue
|
||||
.push(self.clone());
|
||||
}
|
||||
|
||||
pub(super) fn create_mode(
|
||||
self: &Rc<Self>,
|
||||
head_id: WlrOutputHeadId,
|
||||
mode: &Mode,
|
||||
preferred: bool,
|
||||
initial_current: bool,
|
||||
) -> Option<Rc<ZwlrOutputModeV1>> {
|
||||
let id = match self.client.new_id() {
|
||||
Ok(id) => id,
|
||||
Err(e) => {
|
||||
self.client.error(e);
|
||||
return None;
|
||||
}
|
||||
};
|
||||
let output_mode = Rc::new(ZwlrOutputModeV1 {
|
||||
id,
|
||||
head_id,
|
||||
client: self.client.clone(),
|
||||
tracker: Default::default(),
|
||||
version: self.version,
|
||||
mode: *mode,
|
||||
preferred,
|
||||
initial_current,
|
||||
destroyed: Cell::new(false),
|
||||
});
|
||||
track!(self.client, output_mode);
|
||||
self.client.add_server_obj(&output_mode);
|
||||
Some(output_mode)
|
||||
}
|
||||
}
|
||||
|
||||
global_base!(
|
||||
|
|
|
|||
|
|
@ -37,7 +37,15 @@ use {
|
|||
},
|
||||
ahash::AHashMap,
|
||||
bstr::ByteSlice,
|
||||
std::{any::Any, cell::Cell, error::Error, io, os::unix::ffi::OsStrExt, pin::Pin, rc::Rc},
|
||||
std::{
|
||||
any::Any,
|
||||
cell::{Cell, RefCell},
|
||||
error::Error,
|
||||
io,
|
||||
os::unix::ffi::OsStrExt,
|
||||
pin::Pin,
|
||||
rc::Rc,
|
||||
},
|
||||
thiserror::Error,
|
||||
uapi::c,
|
||||
};
|
||||
|
|
@ -75,6 +83,24 @@ pub struct TestBackend {
|
|||
impl TestBackend {
|
||||
pub fn new(state: &Rc<State>, future: TestFuture) -> Self {
|
||||
state.set_backend_idle(false);
|
||||
let mode = Mode {
|
||||
width: 800,
|
||||
height: 600,
|
||||
refresh_rate_millihz: 60_000,
|
||||
};
|
||||
let bcs = BackendConnectorState {
|
||||
serial: state.backend_connector_state_serials.next(),
|
||||
enabled: true,
|
||||
active: true,
|
||||
mode,
|
||||
non_desktop_override: None,
|
||||
vrr: false,
|
||||
tearing: false,
|
||||
format: XRGB8888,
|
||||
color_space: Default::default(),
|
||||
eotf: Default::default(),
|
||||
gamma_lut: Default::default(),
|
||||
};
|
||||
let default_connector = Rc::new(TestConnector {
|
||||
id: state.connector_ids.next(),
|
||||
kernel_id: ConnectorKernelId {
|
||||
|
|
@ -85,6 +111,7 @@ impl TestBackend {
|
|||
feedback: Default::default(),
|
||||
idle: Default::default(),
|
||||
damage_calls: NumCell::new(0),
|
||||
state: RefCell::new(bcs.clone()),
|
||||
});
|
||||
let default_mouse = Rc::new(TestBackendMouse {
|
||||
common: TestInputDeviceCommon {
|
||||
|
|
@ -120,13 +147,8 @@ impl TestBackend {
|
|||
state: state.clone(),
|
||||
},
|
||||
});
|
||||
let mode = Mode {
|
||||
width: 800,
|
||||
height: 600,
|
||||
refresh_rate_millihz: 60_000,
|
||||
};
|
||||
let default_monitor_info = MonitorInfo {
|
||||
modes: vec![mode],
|
||||
modes: Some(vec![mode]),
|
||||
output_id: Rc::new(OutputId {
|
||||
connector: None,
|
||||
manufacturer: "jay".to_string(),
|
||||
|
|
@ -142,19 +164,7 @@ impl TestBackend {
|
|||
color_spaces: vec![],
|
||||
primaries: Primaries::SRGB,
|
||||
luminance: None,
|
||||
state: BackendConnectorState {
|
||||
serial: state.backend_connector_state_serials.next(),
|
||||
enabled: true,
|
||||
active: true,
|
||||
mode,
|
||||
non_desktop_override: None,
|
||||
vrr: false,
|
||||
tearing: false,
|
||||
format: XRGB8888,
|
||||
color_space: Default::default(),
|
||||
eotf: Default::default(),
|
||||
gamma_lut: Default::default(),
|
||||
},
|
||||
state: bcs,
|
||||
};
|
||||
Self {
|
||||
state: state.clone(),
|
||||
|
|
@ -325,6 +335,7 @@ pub struct TestConnector {
|
|||
pub feedback: CloneCell<Option<Rc<DrmFeedback>>>,
|
||||
pub idle: TEEH<bool>,
|
||||
pub damage_calls: NumCell<u32>,
|
||||
pub state: RefCell<BackendConnectorState>,
|
||||
}
|
||||
|
||||
impl Connector for TestConnector {
|
||||
|
|
@ -357,6 +368,10 @@ impl Connector for TestConnector {
|
|||
true
|
||||
}
|
||||
|
||||
fn state(&self) -> BackendConnectorState {
|
||||
self.state.borrow().clone()
|
||||
}
|
||||
|
||||
fn drm_feedback(&self) -> Option<Rc<DrmFeedback>> {
|
||||
self.feedback.get()
|
||||
}
|
||||
|
|
@ -404,6 +419,7 @@ impl BackendPreparedConnectorTransaction for TestBackendTransaction {
|
|||
self: Box<Self>,
|
||||
) -> Result<Box<dyn BackendAppliedConnectorTransaction>, BackendConnectorTransactionError> {
|
||||
for (c, s) in self.connectors.values() {
|
||||
*c.state.borrow_mut() = s.clone();
|
||||
c.idle.push(!s.active);
|
||||
}
|
||||
Ok(self)
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ use {
|
|||
utils::numcell::NumCell,
|
||||
video::drm::ConnectorType,
|
||||
},
|
||||
std::rc::Rc,
|
||||
std::{cell::RefCell, rc::Rc},
|
||||
};
|
||||
|
||||
testcase!();
|
||||
|
|
@ -27,6 +27,19 @@ async fn test(run: Rc<TestRun>) -> TestResult {
|
|||
bail!("no dummy output");
|
||||
};
|
||||
|
||||
let bcs = BackendConnectorState {
|
||||
serial: run.state.backend_connector_state_serials.next(),
|
||||
enabled: true,
|
||||
active: true,
|
||||
mode: Default::default(),
|
||||
non_desktop_override: None,
|
||||
vrr: false,
|
||||
tearing: false,
|
||||
format: XRGB8888,
|
||||
color_space: Default::default(),
|
||||
eotf: Default::default(),
|
||||
gamma_lut: Default::default(),
|
||||
};
|
||||
let new_connector = Rc::new(TestConnector {
|
||||
id: run.state.connector_ids.next(),
|
||||
kernel_id: ConnectorKernelId {
|
||||
|
|
@ -37,9 +50,10 @@ async fn test(run: Rc<TestRun>) -> TestResult {
|
|||
feedback: Default::default(),
|
||||
idle: Default::default(),
|
||||
damage_calls: NumCell::new(0),
|
||||
state: RefCell::new(bcs.clone()),
|
||||
});
|
||||
let new_monitor_info = MonitorInfo {
|
||||
modes: vec![],
|
||||
modes: Some(vec![]),
|
||||
output_id: Rc::new(OutputId {
|
||||
connector: None,
|
||||
manufacturer: "jay".to_string(),
|
||||
|
|
@ -55,19 +69,7 @@ async fn test(run: Rc<TestRun>) -> TestResult {
|
|||
color_spaces: vec![],
|
||||
primaries: Primaries::SRGB,
|
||||
luminance: None,
|
||||
state: BackendConnectorState {
|
||||
serial: run.state.backend_connector_state_serials.next(),
|
||||
enabled: true,
|
||||
active: true,
|
||||
mode: Default::default(),
|
||||
non_desktop_override: None,
|
||||
vrr: false,
|
||||
tearing: false,
|
||||
format: XRGB8888,
|
||||
color_space: Default::default(),
|
||||
eotf: Default::default(),
|
||||
gamma_lut: Default::default(),
|
||||
},
|
||||
state: bcs,
|
||||
};
|
||||
run.backend
|
||||
.state
|
||||
|
|
|
|||
41
src/state.rs
41
src/state.rs
|
|
@ -62,7 +62,7 @@ use {
|
|||
jay_seat_events::JaySeatEvents,
|
||||
jay_workspace_watcher::JayWorkspaceWatcher,
|
||||
wl_buffer::WlBuffer,
|
||||
wl_output::{OutputGlobalOpt, OutputId, PersistentOutputState},
|
||||
wl_output::{BlendSpace, OutputGlobalOpt, OutputId, PersistentOutputState},
|
||||
wl_seat::{
|
||||
PhysicalKeyboardId, PhysicalKeyboardIds, PositionHintRequest, SeatIds,
|
||||
WlSeatGlobal,
|
||||
|
|
@ -504,7 +504,7 @@ impl ConnectorData {
|
|||
}};
|
||||
}
|
||||
if b!(old.enabled != s.enabled) {
|
||||
self.head_managers.handle_enabled_change(s.enabled);
|
||||
self.head_managers.handle_enabled_change(state, s.enabled);
|
||||
}
|
||||
if b!(old.active != s.active) {
|
||||
self.head_managers.handle_active_change(s.active);
|
||||
|
|
@ -1961,6 +1961,43 @@ impl State {
|
|||
colored.field(&self.theme).set(v);
|
||||
self.colors_changed();
|
||||
}
|
||||
|
||||
pub fn ensure_persistent_output_state(
|
||||
&self,
|
||||
output_id: &Rc<OutputId>,
|
||||
) -> Rc<PersistentOutputState> {
|
||||
match self.persistent_output_states.get(output_id) {
|
||||
Some(ds) => ds,
|
||||
_ => {
|
||||
let ds = self.new_persistent_output_state();
|
||||
self.persistent_output_states
|
||||
.set(output_id.clone(), ds.clone());
|
||||
ds
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_persistent_output_state(&self) -> Rc<PersistentOutputState> {
|
||||
let x1 = self
|
||||
.root
|
||||
.outputs
|
||||
.lock()
|
||||
.values()
|
||||
.map(|o| o.global.pos.get().x2())
|
||||
.max()
|
||||
.unwrap_or(0);
|
||||
Rc::new(PersistentOutputState {
|
||||
transform: Default::default(),
|
||||
scale: Default::default(),
|
||||
pos: Cell::new((x1, 0)),
|
||||
vrr_mode: Cell::new(self.default_vrr_mode.get()),
|
||||
vrr_cursor_hz: Cell::new(self.default_vrr_cursor_hz.get()),
|
||||
tearing_mode: Cell::new(self.default_tearing_mode.get()),
|
||||
brightness: Cell::new(None),
|
||||
blend_space: Cell::new(BlendSpace::Srgb),
|
||||
use_native_gamut: Cell::new(false),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
|
|
|
|||
|
|
@ -1,16 +1,12 @@
|
|||
use {
|
||||
crate::{
|
||||
backend::{
|
||||
BackendConnectorState, BackendConnectorStateSerial, Connector, ConnectorEvent,
|
||||
ConnectorId, MonitorInfo,
|
||||
},
|
||||
backend::{Connector, ConnectorEvent, ConnectorId, MonitorInfo},
|
||||
control_center::CCI_OUTPUTS,
|
||||
format::XRGB8888,
|
||||
globals::GlobalName,
|
||||
ifs::{
|
||||
head_management::{HeadManagers, HeadState},
|
||||
jay_tray_v1::JayTrayV1Global,
|
||||
wl_output::{BlendSpace, PersistentOutputState, WlOutputGlobal},
|
||||
wl_output::{BlendSpace, WlOutputGlobal},
|
||||
},
|
||||
output_schedule::OutputSchedule,
|
||||
state::{ConnectorData, OutputData, State},
|
||||
|
|
@ -35,22 +31,11 @@ pub fn handle(state: &Rc<State>, connector: &Rc<dyn Connector>) {
|
|||
_ => panic!("connector's drm device does not exist"),
|
||||
};
|
||||
}
|
||||
let backend_state = BackendConnectorState {
|
||||
serial: BackendConnectorStateSerial::from_raw(0),
|
||||
enabled: true,
|
||||
active: false,
|
||||
mode: Default::default(),
|
||||
non_desktop_override: None,
|
||||
vrr: false,
|
||||
tearing: false,
|
||||
format: XRGB8888,
|
||||
color_space: Default::default(),
|
||||
eotf: Default::default(),
|
||||
gamma_lut: None,
|
||||
};
|
||||
let backend_state = connector.state();
|
||||
let id = connector.id();
|
||||
let name = Rc::new(connector.kernel_id().to_string());
|
||||
let name = Rc::new(connector.name());
|
||||
let head_state = HeadState {
|
||||
connector_id: id,
|
||||
name: RcEq(name.clone()),
|
||||
position: (0, 0),
|
||||
size: (0, 0),
|
||||
|
|
@ -61,7 +46,7 @@ pub fn handle(state: &Rc<State>, connector: &Rc<dyn Connector>) {
|
|||
wl_output: None,
|
||||
connector_enabled: backend_state.enabled,
|
||||
in_compositor_space: false,
|
||||
mode: Default::default(),
|
||||
mode: backend_state.mode,
|
||||
monitor_info: None,
|
||||
inherent_non_desktop: false,
|
||||
override_non_desktop: backend_state.non_desktop_override,
|
||||
|
|
@ -78,6 +63,7 @@ pub fn handle(state: &Rc<State>, connector: &Rc<dyn Connector>) {
|
|||
blend_space: BlendSpace::Srgb,
|
||||
use_native_gamut: false,
|
||||
vrr_cursor_hz: None,
|
||||
persistent_state: None,
|
||||
};
|
||||
let data = Rc::new(ConnectorData {
|
||||
id,
|
||||
|
|
@ -153,7 +139,11 @@ impl ConnectorHandler {
|
|||
}
|
||||
|
||||
async fn handle_connected(&self, info: MonitorInfo) {
|
||||
log::info!("Connector {} connected", self.data.connector.kernel_id());
|
||||
log::info!(
|
||||
"Connector {} connected ({})",
|
||||
self.data.name,
|
||||
self.data.connector.kernel_id(),
|
||||
);
|
||||
self.data.connected.set(true);
|
||||
self.data.set_state(&self.state, info.state.clone());
|
||||
*self.data.description.borrow_mut() = create_description(&info);
|
||||
|
|
@ -164,45 +154,19 @@ impl ConnectorHandler {
|
|||
self.handle_desktop_connected(info, name).await;
|
||||
}
|
||||
self.data.connected.set(false);
|
||||
self.data.head_managers.handle_output_disconnected();
|
||||
self.data
|
||||
.head_managers
|
||||
.handle_output_disconnected(&self.state);
|
||||
self.state.trigger_cci(CCI_OUTPUTS);
|
||||
for head in self.data.wlr_output_heads.lock().drain_values() {
|
||||
head.handle_disconnected();
|
||||
}
|
||||
log::info!("Connector {} disconnected", self.data.connector.kernel_id());
|
||||
log::info!("Connector {} disconnected", self.data.name);
|
||||
}
|
||||
|
||||
async fn handle_desktop_connected(&self, info: MonitorInfo, name: GlobalName) {
|
||||
let output_id = info.output_id.clone();
|
||||
let desired_state = match self.state.persistent_output_states.get(&output_id) {
|
||||
Some(ds) => ds,
|
||||
_ => {
|
||||
let x1 = self
|
||||
.state
|
||||
.root
|
||||
.outputs
|
||||
.lock()
|
||||
.values()
|
||||
.map(|o| o.global.pos.get().x2())
|
||||
.max()
|
||||
.unwrap_or(0);
|
||||
let ds = Rc::new(PersistentOutputState {
|
||||
transform: Default::default(),
|
||||
scale: Default::default(),
|
||||
pos: Cell::new((x1, 0)),
|
||||
vrr_mode: Cell::new(self.state.default_vrr_mode.get()),
|
||||
vrr_cursor_hz: Cell::new(self.state.default_vrr_cursor_hz.get()),
|
||||
tearing_mode: Cell::new(self.state.default_tearing_mode.get()),
|
||||
brightness: Cell::new(None),
|
||||
blend_space: Cell::new(BlendSpace::Srgb),
|
||||
use_native_gamut: Cell::new(false),
|
||||
});
|
||||
self.state
|
||||
.persistent_output_states
|
||||
.set(output_id.clone(), ds.clone());
|
||||
ds
|
||||
}
|
||||
};
|
||||
let desired_state = self.state.ensure_persistent_output_state(&output_id);
|
||||
let global = Rc::new(WlOutputGlobal::new(
|
||||
name,
|
||||
&self.state,
|
||||
|
|
@ -339,7 +303,7 @@ impl ConnectorHandler {
|
|||
self.state.workspace_managers.announce_output(&on);
|
||||
self.data
|
||||
.head_managers
|
||||
.handle_output_connected(&output_data);
|
||||
.handle_output_connected(&self.state, &output_data);
|
||||
self.state.trigger_cci(CCI_OUTPUTS);
|
||||
self.state.wlr_output_managers.announce_head(&output_data);
|
||||
'outer: loop {
|
||||
|
|
@ -466,7 +430,7 @@ impl ConnectorHandler {
|
|||
}
|
||||
self.data
|
||||
.head_managers
|
||||
.handle_output_connected(&output_data);
|
||||
.handle_output_connected(&self.state, &output_data);
|
||||
self.state.trigger_cci(CCI_OUTPUTS);
|
||||
self.state.wlr_output_managers.announce_head(&output_data);
|
||||
'outer: loop {
|
||||
|
|
|
|||
|
|
@ -334,7 +334,7 @@ impl ToolClient {
|
|||
self_id: s.registry,
|
||||
name: s.jay_compositor.0,
|
||||
interface: JayCompositor.name(),
|
||||
version: s.jay_compositor.1.min(28),
|
||||
version: s.jay_compositor.1.min(29),
|
||||
id: id.into(),
|
||||
});
|
||||
self.jay_compositor.set(Some(id));
|
||||
|
|
|
|||
|
|
@ -830,11 +830,20 @@ impl Output {
|
|||
Some(rr) => m.refresh_rate() as f64 / 1000.0 == rr,
|
||||
}
|
||||
});
|
||||
match m {
|
||||
None => {
|
||||
'set_mode: {
|
||||
let (w, h, mhz) = 'mode: {
|
||||
if let Some(m) = m {
|
||||
break 'mode (m.width(), m.height(), m.refresh_rate());
|
||||
}
|
||||
if c.supports_arbitrary_modes()
|
||||
&& let Some(refresh) = mode.refresh_rate
|
||||
{
|
||||
break 'mode (mode.width, mode.height, (refresh * 1_000.0).round() as u32);
|
||||
}
|
||||
log::warn!("Output {} does not support mode {mode}", c.name());
|
||||
}
|
||||
Some(m) => c.set_mode(m.width(), m.height(), Some(m.refresh_rate())),
|
||||
break 'set_mode;
|
||||
};
|
||||
c.set_mode(w, h, Some(mhz));
|
||||
}
|
||||
}
|
||||
if let Some(vrr) = &self.vrr {
|
||||
|
|
|
|||
|
|
@ -229,3 +229,6 @@ event native_gamut (since = 23) {
|
|||
|
||||
event use_native_gamut (since = 23) {
|
||||
}
|
||||
|
||||
event arbitrary_modes (since = 29) {
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue