virtual-output: add support for virtual outputs
This commit is contained in:
parent
c25d17514d
commit
530e66ef78
27 changed files with 1480 additions and 9 deletions
|
|
@ -84,6 +84,10 @@ impl Mode {
|
|||
n => 1_000_000_000_000 / (n as u64),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn size(&self) -> (i32, i32) {
|
||||
(self.width, self.height)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Mode {
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ use {
|
|||
BackendColorSpace, BackendConnectorState, BackendEotfs, Connector, ConnectorId,
|
||||
ConnectorKernelId, Mode,
|
||||
},
|
||||
backends::metal::MetalError,
|
||||
state::State,
|
||||
utils::{errorfmt::ErrorFmt, hash_map_ext::HashMapExt},
|
||||
video::drm::DrmError,
|
||||
|
|
@ -14,6 +13,7 @@ use {
|
|||
any::{Any, TypeId},
|
||||
cell::{Cell, RefCell},
|
||||
collections::hash_map::Entry,
|
||||
error::Error,
|
||||
hash::{Hash, Hasher},
|
||||
rc::Rc,
|
||||
},
|
||||
|
|
@ -119,13 +119,17 @@ pub enum BackendConnectorTransactionError {
|
|||
#[error("Could not create a mode blob")]
|
||||
CreateModeBlob(#[source] DrmError),
|
||||
#[error("Could not allocate buffers for connector {}", .0)]
|
||||
AllocateScanoutBuffers(ConnectorKernelId, #[source] Box<MetalError>),
|
||||
AllocateScanoutBuffers(ConnectorKernelId, #[source] Box<dyn Error>),
|
||||
#[error("Test commit failed")]
|
||||
AtomicTestFailed(#[source] DrmError),
|
||||
#[error("Commit failed")]
|
||||
AtomicCommitFailed(#[source] DrmError),
|
||||
#[error("Could not create a gamma lut blob")]
|
||||
CreateGammaLutBlob(#[source] DrmError),
|
||||
#[error("Connector {} does not support gamma lut", .0)]
|
||||
GammaLutNotSupported(ConnectorKernelId),
|
||||
#[error("There is no render context")]
|
||||
NoRenderContext,
|
||||
}
|
||||
|
||||
pub trait BackendConnectorTransaction {
|
||||
|
|
|
|||
|
|
@ -42,6 +42,8 @@ pub enum RandrCmd {
|
|||
Card(CardArgs),
|
||||
/// Modify the settings of an output.
|
||||
Output(OutputArgs),
|
||||
/// Modify virtual outputs.
|
||||
VirtualOutput(VirtualOutputArgs),
|
||||
}
|
||||
|
||||
impl Default for RandrCmd {
|
||||
|
|
@ -465,6 +467,32 @@ fn blend_space_possible_values() -> Vec<PossibleValue> {
|
|||
res
|
||||
}
|
||||
|
||||
#[derive(Args, Debug)]
|
||||
pub struct VirtualOutputArgs {
|
||||
#[clap(subcommand)]
|
||||
pub command: VirtualOutputCommand,
|
||||
}
|
||||
|
||||
#[derive(Subcommand, Debug, Clone)]
|
||||
pub enum VirtualOutputCommand {
|
||||
/// Create a virtual output.
|
||||
Create(CreateVirtualOutputArgs),
|
||||
/// Remove a virtual output.
|
||||
Remove(RemoveVirtualOutputArgs),
|
||||
}
|
||||
|
||||
#[derive(Args, Debug, Clone)]
|
||||
pub struct CreateVirtualOutputArgs {
|
||||
/// The name of the virtual output.
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
#[derive(Args, Debug, Clone)]
|
||||
pub struct RemoveVirtualOutputArgs {
|
||||
/// The name of the virtual output.
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
pub fn main(global: GlobalArgs, args: RandrArgs) {
|
||||
with_tool_client(global.log_level, |tc| async move {
|
||||
let idle = Rc::new(Randr { tc: tc.clone() });
|
||||
|
|
@ -580,6 +608,7 @@ impl Randr {
|
|||
RandrCmd::Show(args) => self.show(randr, args).await,
|
||||
RandrCmd::Card(args) => self.card(randr, args).await,
|
||||
RandrCmd::Output(args) => self.output(randr, args).await,
|
||||
RandrCmd::VirtualOutput(args) => self.virtual_output(randr, args).await,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -848,6 +877,31 @@ impl Randr {
|
|||
tc.round_trip().await;
|
||||
}
|
||||
|
||||
async fn virtual_output(self: &Rc<Self>, randr: JayRandrId, args: VirtualOutputArgs) {
|
||||
let tc = &self.tc;
|
||||
match args.command {
|
||||
VirtualOutputCommand::Create(t) => {
|
||||
self.handle_error(randr, |msg| {
|
||||
eprintln!("Could not create a virtual output: {}", msg);
|
||||
});
|
||||
tc.send(jay_randr::CreateVirtualOutput {
|
||||
self_id: randr,
|
||||
name: &t.name,
|
||||
});
|
||||
}
|
||||
VirtualOutputCommand::Remove(t) => {
|
||||
self.handle_error(randr, |msg| {
|
||||
eprintln!("Could not remove a virtual output: {}", msg);
|
||||
});
|
||||
tc.send(jay_randr::RemoveVirtualOutput {
|
||||
self_id: randr,
|
||||
name: &t.name,
|
||||
});
|
||||
}
|
||||
}
|
||||
tc.round_trip().await;
|
||||
}
|
||||
|
||||
async fn card(self: &Rc<Self>, randr: JayRandrId, args: CardArgs) {
|
||||
let tc = &self.tc;
|
||||
match args.command {
|
||||
|
|
|
|||
|
|
@ -395,6 +395,7 @@ fn start_compositor2(
|
|||
bo_drop_queue: Rc::new(ObjectDropQueue::new(&ring)),
|
||||
egg_state: Default::default(),
|
||||
control_centers: Default::default(),
|
||||
virtual_outputs: Default::default(),
|
||||
});
|
||||
state.tracker.register(ClientId::from_raw(0));
|
||||
create_dummy_output(&state);
|
||||
|
|
|
|||
|
|
@ -1605,6 +1605,14 @@ impl ConfigProxyHandler {
|
|||
self.respond(Response::GetConnector { connector });
|
||||
}
|
||||
|
||||
fn handle_create_virtual_output(&self, name: &str) {
|
||||
self.state.virtual_outputs.get_or_create(&self.state, name);
|
||||
}
|
||||
|
||||
fn handle_remove_virtual_output(&self, name: &str) {
|
||||
self.state.virtual_outputs.remove_output(&self.state, name);
|
||||
}
|
||||
|
||||
fn handle_get_connector_active_workspace(&self, connector: Connector) -> Result<(), CphError> {
|
||||
let output = self.get_output_node(connector)?;
|
||||
let workspace = output
|
||||
|
|
@ -3357,6 +3365,8 @@ impl ConfigProxyHandler {
|
|||
.handle_connector_supports_arbitrary_modes(connector)
|
||||
.wrn("connector_supports_arbitrary_modes")?,
|
||||
ClientMessage::GetConnectorByName { name } => self.handle_get_connector_by_name(name),
|
||||
ClientMessage::CreateVirtualOutput { name } => self.handle_create_virtual_output(name),
|
||||
ClientMessage::RemoveVirtualOutput { name } => self.handle_remove_virtual_output(name),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ use {
|
|||
cc_input::InputPane,
|
||||
cc_look_and_feel::LookAndFeelPane,
|
||||
cc_outputs::OutputsPane,
|
||||
cc_virtual_outputs::VirtualOutputsPane,
|
||||
cc_window::{WindowPane, WindowSearchPane},
|
||||
cc_xwayland::XwaylandPane,
|
||||
},
|
||||
|
|
@ -51,6 +52,7 @@ mod cc_input;
|
|||
mod cc_look_and_feel;
|
||||
mod cc_outputs;
|
||||
mod cc_sidebar;
|
||||
mod cc_virtual_outputs;
|
||||
mod cc_window;
|
||||
mod cc_xwayland;
|
||||
|
||||
|
|
@ -93,6 +95,7 @@ bitflags! {
|
|||
CCI_GPUS,
|
||||
CCI_INPUT,
|
||||
CCI_LOOK_AND_FEEL,
|
||||
CCI_VIRTUAL_OUTPUTS,
|
||||
}
|
||||
|
||||
pub struct ControlCenter {
|
||||
|
|
@ -145,6 +148,7 @@ enum PaneType {
|
|||
Client(ClientPane),
|
||||
WindowSearch(WindowSearchPane),
|
||||
Window(WindowPane),
|
||||
VirtualOutputs(VirtualOutputsPane),
|
||||
}
|
||||
|
||||
struct CcBehavior<'a> {
|
||||
|
|
@ -174,6 +178,7 @@ impl Pane {
|
|||
PaneType::Client(v) => v.title(res),
|
||||
PaneType::WindowSearch(v) => v.title(res),
|
||||
PaneType::Window(v) => v.title(res),
|
||||
PaneType::VirtualOutputs(v) => v.title(res),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -191,6 +196,7 @@ impl Pane {
|
|||
PaneType::Client(p) => p.show(behavior, ui),
|
||||
PaneType::WindowSearch(p) => p.show(behavior, ui),
|
||||
PaneType::Window(p) => p.show(behavior, ui),
|
||||
PaneType::VirtualOutputs(p) => p.show(ui),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -210,6 +216,7 @@ impl PaneType {
|
|||
PaneType::Client(_) => ControlCenterInterest::none(),
|
||||
PaneType::WindowSearch(_) => ControlCenterInterest::none(),
|
||||
PaneType::Window(_) => ControlCenterInterest::none(),
|
||||
PaneType::VirtualOutputs(_) => CCI_VIRTUAL_OUTPUTS,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ enum PaneName {
|
|||
LookAndFeel,
|
||||
Clients,
|
||||
WindowSearch,
|
||||
VirtualOutputs,
|
||||
}
|
||||
|
||||
impl PaneName {
|
||||
|
|
@ -33,6 +34,7 @@ impl PaneName {
|
|||
PaneName::LookAndFeel => "Look and Feel",
|
||||
PaneName::Clients => "Clients",
|
||||
PaneName::WindowSearch => "Window Search",
|
||||
PaneName::VirtualOutputs => "Virtual Outputs",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -79,6 +81,9 @@ impl ControlCenterInner {
|
|||
PaneName::WindowSearch => {
|
||||
PaneType::WindowSearch(self.create_window_search_pane())
|
||||
}
|
||||
PaneName::VirtualOutputs => {
|
||||
PaneType::VirtualOutputs(self.create_virtual_outputs_pane())
|
||||
}
|
||||
};
|
||||
self.open(tree, ty);
|
||||
ui.ctx().request_repaint();
|
||||
|
|
|
|||
49
src/control_center/cc_virtual_outputs.rs
Normal file
49
src/control_center/cc_virtual_outputs.rs
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
use {
|
||||
crate::{
|
||||
control_center::ControlCenterInner, egui_adapter::egui_platform::icons::ICON_CLOSE,
|
||||
state::State,
|
||||
},
|
||||
egui::Ui,
|
||||
std::rc::Rc,
|
||||
};
|
||||
|
||||
pub struct VirtualOutputsPane {
|
||||
state: Rc<State>,
|
||||
new: String,
|
||||
}
|
||||
|
||||
impl ControlCenterInner {
|
||||
pub fn create_virtual_outputs_pane(self: &Rc<Self>) -> VirtualOutputsPane {
|
||||
VirtualOutputsPane {
|
||||
state: self.state.clone(),
|
||||
new: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl VirtualOutputsPane {
|
||||
pub fn title(&self, res: &mut String) {
|
||||
res.push_str("Virtual Outputs");
|
||||
}
|
||||
|
||||
pub fn show(&mut self, ui: &mut Ui) {
|
||||
let s = &self.state;
|
||||
let mut outputs: Vec<_> = s.virtual_outputs.outputs.lock().keys().cloned().collect();
|
||||
outputs.sort();
|
||||
for o in &outputs {
|
||||
ui.horizontal(|ui| {
|
||||
if ui.button(ICON_CLOSE).clicked() {
|
||||
s.virtual_outputs.remove_output(s, o);
|
||||
}
|
||||
ui.label(o);
|
||||
});
|
||||
}
|
||||
ui.horizontal(|ui| {
|
||||
ui.text_edit_singleline(&mut self.new);
|
||||
if ui.button("Add").clicked() {
|
||||
s.virtual_outputs.get_or_create(s, &self.new);
|
||||
ui.ctx().request_repaint();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -78,7 +78,7 @@ global_base!(JayCompositorGlobal, JayCompositor, JayCompositorError);
|
|||
|
||||
impl Global for JayCompositorGlobal {
|
||||
fn version(&self) -> u32 {
|
||||
29
|
||||
30
|
||||
}
|
||||
|
||||
fn required_caps(&self) -> ClientCaps {
|
||||
|
|
|
|||
|
|
@ -270,6 +270,11 @@ impl JayRandr {
|
|||
}
|
||||
|
||||
fn get_connector(&self, name: &str) -> Option<Rc<ConnectorData>> {
|
||||
for c in self.client.state.connectors.lock().values() {
|
||||
if *c.name == name {
|
||||
return Some(c.clone());
|
||||
}
|
||||
}
|
||||
let namelc = name.to_ascii_lowercase();
|
||||
for c in self.client.state.connectors.lock().values() {
|
||||
if c.name.to_ascii_lowercase() == namelc {
|
||||
|
|
@ -281,6 +286,11 @@ impl JayRandr {
|
|||
}
|
||||
|
||||
fn get_output(&self, name: &str) -> Option<Rc<OutputData>> {
|
||||
for c in self.client.state.outputs.lock().values() {
|
||||
if *c.connector.name == name {
|
||||
return Some(c.clone());
|
||||
}
|
||||
}
|
||||
let namelc = name.to_ascii_lowercase();
|
||||
for c in self.client.state.outputs.lock().values() {
|
||||
if c.connector.name.to_ascii_lowercase() == namelc {
|
||||
|
|
@ -588,6 +598,28 @@ impl JayRandrRequestHandler for JayRandr {
|
|||
c.set_use_native_gamut(req.use_native_gamut != 0);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn create_virtual_output(
|
||||
&self,
|
||||
req: CreateVirtualOutput<'_>,
|
||||
_slf: &Rc<Self>,
|
||||
) -> Result<(), Self::Error> {
|
||||
self.state
|
||||
.virtual_outputs
|
||||
.get_or_create(&self.state, req.name);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn remove_virtual_output(
|
||||
&self,
|
||||
req: RemoveVirtualOutput<'_>,
|
||||
_slf: &Rc<Self>,
|
||||
) -> Result<(), Self::Error> {
|
||||
self.state
|
||||
.virtual_outputs
|
||||
.remove_output(&self.state, req.name);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
object_base! {
|
||||
|
|
|
|||
|
|
@ -62,7 +62,6 @@ pub struct WpPresentationFeedback {
|
|||
}
|
||||
|
||||
pub const KIND_VSYNC: u32 = 0x1;
|
||||
#[expect(dead_code)]
|
||||
pub const KIND_HW_CLOCK: u32 = 0x2;
|
||||
pub const KIND_HW_COMPLETION: u32 = 0x4;
|
||||
pub const KIND_ZERO_COPY: u32 = 0x8;
|
||||
|
|
|
|||
|
|
@ -114,6 +114,7 @@ mod user_session;
|
|||
mod utils;
|
||||
mod version;
|
||||
mod video;
|
||||
mod virtual_output;
|
||||
mod vulkan_core;
|
||||
mod wheel;
|
||||
mod wire;
|
||||
|
|
|
|||
|
|
@ -131,6 +131,7 @@ use {
|
|||
dmabuf::DmaBufIds,
|
||||
drm::{Drm, wait_for_syncobj::WaitForSyncobj},
|
||||
},
|
||||
virtual_output::VirtualOutputs,
|
||||
wheel::Wheel,
|
||||
wire::{
|
||||
ExtForeignToplevelListV1Id, ExtIdleNotificationV1Id, JayHeadManagerSessionV1Id,
|
||||
|
|
@ -302,6 +303,7 @@ pub struct State {
|
|||
pub bo_drop_queue: Rc<ObjectDropQueue<Rc<dyn BufferObject>>>,
|
||||
pub egg_state: EggState,
|
||||
pub control_centers: ControlCenters,
|
||||
pub virtual_outputs: VirtualOutputs,
|
||||
}
|
||||
|
||||
// impl Drop for State {
|
||||
|
|
@ -674,6 +676,7 @@ impl State {
|
|||
self.icons.clear();
|
||||
self.wait_for_syncobj
|
||||
.set_ctx(ctx.as_ref().and_then(|c| c.syncobj_ctx().cloned()));
|
||||
self.virtual_outputs.handle_render_ctx_change(self);
|
||||
|
||||
'handle_new_feedback: {
|
||||
if let Some(ctx) = &ctx {
|
||||
|
|
@ -1184,6 +1187,7 @@ impl State {
|
|||
self.bo_drop_queue.kill();
|
||||
self.egg_state.clear();
|
||||
self.control_centers.clear();
|
||||
self.virtual_outputs.clear();
|
||||
}
|
||||
|
||||
pub fn remove_toplevel_id(&self, id: ToplevelIdentifier) {
|
||||
|
|
|
|||
|
|
@ -19,7 +19,9 @@ use {
|
|||
},
|
||||
std::{rc::Rc, time::Duration},
|
||||
};
|
||||
pub use {hardware_cursor::handle_hardware_cursor_tick, idle::idle};
|
||||
pub use {
|
||||
connector::handle as handle_connector, hardware_cursor::handle_hardware_cursor_tick, idle::idle,
|
||||
};
|
||||
|
||||
pub async fn handle_backend_events(state: Rc<State>) {
|
||||
let mut beh = BackendEventHandler { state };
|
||||
|
|
|
|||
|
|
@ -334,7 +334,7 @@ impl ToolClient {
|
|||
self_id: s.registry,
|
||||
name: s.jay_compositor.0,
|
||||
interface: JayCompositor.name(),
|
||||
version: s.jay_compositor.1.min(29),
|
||||
version: s.jay_compositor.1.min(30),
|
||||
id: id.into(),
|
||||
});
|
||||
self.jay_compositor.set(Some(id));
|
||||
|
|
|
|||
|
|
@ -1146,6 +1146,7 @@ pub enum ConnectorType {
|
|||
SPI,
|
||||
USB,
|
||||
EmbeddedWindow,
|
||||
VirtualOutput,
|
||||
}
|
||||
|
||||
impl ConnectorType {
|
||||
|
|
@ -1200,6 +1201,7 @@ impl ConnectorType {
|
|||
Self::SPI => sys::DRM_MODE_CONNECTOR_SPI,
|
||||
Self::USB => sys::DRM_MODE_CONNECTOR_USB,
|
||||
Self::EmbeddedWindow => sys::DRM_MODE_CONNECTOR_Unknown,
|
||||
Self::VirtualOutput => sys::DRM_MODE_CONNECTOR_Unknown,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1228,6 +1230,7 @@ impl ConnectorType {
|
|||
Self::SPI => CON_SPI,
|
||||
Self::USB => CON_USB,
|
||||
Self::EmbeddedWindow => CON_EMBEDDED_WINDOW,
|
||||
Self::VirtualOutput => CON_VIRTUAL_OUTPUT,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1257,6 +1260,7 @@ impl Display for ConnectorType {
|
|||
Self::SPI => "SPI",
|
||||
Self::USB => "USB",
|
||||
Self::EmbeddedWindow => "EmbeddedWindow",
|
||||
Self::VirtualOutput => "VO",
|
||||
};
|
||||
f.write_str(s)
|
||||
}
|
||||
|
|
|
|||
1105
src/virtual_output.rs
Normal file
1105
src/virtual_output.rs
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue