Merge pull request #814 from mahkoh/jorth/virtual-outputs
virtual-output: add support for virtual outputs
This commit is contained in:
commit
942c090195
28 changed files with 1482 additions and 9 deletions
|
|
@ -1236,6 +1236,14 @@ impl ConfigClient {
|
||||||
self.send(&ClientMessage::SetTearingMode { connector, mode })
|
self.send(&ClientMessage::SetTearingMode { connector, mode })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn create_virtual_output(&self, name: &str) {
|
||||||
|
self.send(&ClientMessage::CreateVirtualOutput { name })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn remove_virtual_output(&self, name: &str) {
|
||||||
|
self.send(&ClientMessage::RemoveVirtualOutput { name })
|
||||||
|
}
|
||||||
|
|
||||||
pub fn drm_devices(&self) -> Vec<DrmDevice> {
|
pub fn drm_devices(&self) -> Vec<DrmDevice> {
|
||||||
let res = self.send_with_response(&ClientMessage::GetDrmDevices);
|
let res = self.send_with_response(&ClientMessage::GetDrmDevices);
|
||||||
get_response!(res, vec![], GetDrmDevices { devices });
|
get_response!(res, vec![], GetDrmDevices { devices });
|
||||||
|
|
|
||||||
|
|
@ -852,6 +852,12 @@ pub enum ClientMessage<'a> {
|
||||||
GetConnectorByName {
|
GetConnectorByName {
|
||||||
name: &'a str,
|
name: &'a str,
|
||||||
},
|
},
|
||||||
|
CreateVirtualOutput {
|
||||||
|
name: &'a str,
|
||||||
|
},
|
||||||
|
RemoveVirtualOutput {
|
||||||
|
name: &'a str,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
|
|
||||||
|
|
@ -539,6 +539,7 @@ pub mod connector_type {
|
||||||
pub const CON_SPI: ConnectorType = ConnectorType(19);
|
pub const CON_SPI: ConnectorType = ConnectorType(19);
|
||||||
pub const CON_USB: ConnectorType = ConnectorType(20);
|
pub const CON_USB: ConnectorType = ConnectorType(20);
|
||||||
pub const CON_EMBEDDED_WINDOW: ConnectorType = ConnectorType(u32::MAX);
|
pub const CON_EMBEDDED_WINDOW: ConnectorType = ConnectorType(u32::MAX);
|
||||||
|
pub const CON_VIRTUAL_OUTPUT: ConnectorType = ConnectorType(u32::MAX - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A *Direct Rendering Manager* (DRM) device.
|
/// A *Direct Rendering Manager* (DRM) device.
|
||||||
|
|
@ -730,6 +731,25 @@ pub fn set_tearing_mode(mode: TearingMode) {
|
||||||
get!().set_tearing_mode(None, mode)
|
get!().set_tearing_mode(None, mode)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Creates a virtual output with the given name.
|
||||||
|
///
|
||||||
|
/// This is a no-op if a virtual output with that name already exists.
|
||||||
|
///
|
||||||
|
/// The created connector can be accessed with [`get_connector_by_name("VO-{name}")`].
|
||||||
|
///
|
||||||
|
/// A newly created connector is initially disabled. When a connector is destroyed and
|
||||||
|
/// later recreated, its previous state is restored.
|
||||||
|
pub fn create_virtual_output(name: &str) {
|
||||||
|
get!().create_virtual_output(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Removes the virtual output with the given name.
|
||||||
|
///
|
||||||
|
/// This is a no-op if a virtual output with that name does not exist.
|
||||||
|
pub fn remove_virtual_output(name: &str) {
|
||||||
|
get!().remove_virtual_output(name);
|
||||||
|
}
|
||||||
|
|
||||||
/// A graphics format.
|
/// A graphics format.
|
||||||
#[derive(Serialize, Deserialize, Copy, Clone, Debug, Eq, PartialEq, Hash)]
|
#[derive(Serialize, Deserialize, Copy, Clone, Debug, Eq, PartialEq, Hash)]
|
||||||
pub struct Format(pub u32);
|
pub struct Format(pub u32);
|
||||||
|
|
|
||||||
|
|
@ -84,6 +84,10 @@ impl Mode {
|
||||||
n => 1_000_000_000_000 / (n as u64),
|
n => 1_000_000_000_000 / (n as u64),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn size(&self) -> (i32, i32) {
|
||||||
|
(self.width, self.height)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for Mode {
|
impl Display for Mode {
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@ use {
|
||||||
BackendColorSpace, BackendConnectorState, BackendEotfs, Connector, ConnectorId,
|
BackendColorSpace, BackendConnectorState, BackendEotfs, Connector, ConnectorId,
|
||||||
ConnectorKernelId, Mode,
|
ConnectorKernelId, Mode,
|
||||||
},
|
},
|
||||||
backends::metal::MetalError,
|
|
||||||
state::State,
|
state::State,
|
||||||
utils::{errorfmt::ErrorFmt, hash_map_ext::HashMapExt},
|
utils::{errorfmt::ErrorFmt, hash_map_ext::HashMapExt},
|
||||||
video::drm::DrmError,
|
video::drm::DrmError,
|
||||||
|
|
@ -14,6 +13,7 @@ use {
|
||||||
any::{Any, TypeId},
|
any::{Any, TypeId},
|
||||||
cell::{Cell, RefCell},
|
cell::{Cell, RefCell},
|
||||||
collections::hash_map::Entry,
|
collections::hash_map::Entry,
|
||||||
|
error::Error,
|
||||||
hash::{Hash, Hasher},
|
hash::{Hash, Hasher},
|
||||||
rc::Rc,
|
rc::Rc,
|
||||||
},
|
},
|
||||||
|
|
@ -119,13 +119,17 @@ pub enum BackendConnectorTransactionError {
|
||||||
#[error("Could not create a mode blob")]
|
#[error("Could not create a mode blob")]
|
||||||
CreateModeBlob(#[source] DrmError),
|
CreateModeBlob(#[source] DrmError),
|
||||||
#[error("Could not allocate buffers for connector {}", .0)]
|
#[error("Could not allocate buffers for connector {}", .0)]
|
||||||
AllocateScanoutBuffers(ConnectorKernelId, #[source] Box<MetalError>),
|
AllocateScanoutBuffers(ConnectorKernelId, #[source] Box<dyn Error>),
|
||||||
#[error("Test commit failed")]
|
#[error("Test commit failed")]
|
||||||
AtomicTestFailed(#[source] DrmError),
|
AtomicTestFailed(#[source] DrmError),
|
||||||
#[error("Commit failed")]
|
#[error("Commit failed")]
|
||||||
AtomicCommitFailed(#[source] DrmError),
|
AtomicCommitFailed(#[source] DrmError),
|
||||||
#[error("Could not create a gamma lut blob")]
|
#[error("Could not create a gamma lut blob")]
|
||||||
CreateGammaLutBlob(#[source] DrmError),
|
CreateGammaLutBlob(#[source] DrmError),
|
||||||
|
#[error("Connector {} does not support gamma lut", .0)]
|
||||||
|
GammaLutNotSupported(ConnectorKernelId),
|
||||||
|
#[error("There is no render context")]
|
||||||
|
NoRenderContext,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait BackendConnectorTransaction {
|
pub trait BackendConnectorTransaction {
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,8 @@ pub enum RandrCmd {
|
||||||
Card(CardArgs),
|
Card(CardArgs),
|
||||||
/// Modify the settings of an output.
|
/// Modify the settings of an output.
|
||||||
Output(OutputArgs),
|
Output(OutputArgs),
|
||||||
|
/// Modify virtual outputs.
|
||||||
|
VirtualOutput(VirtualOutputArgs),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for RandrCmd {
|
impl Default for RandrCmd {
|
||||||
|
|
@ -465,6 +467,32 @@ fn blend_space_possible_values() -> Vec<PossibleValue> {
|
||||||
res
|
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) {
|
pub fn main(global: GlobalArgs, args: RandrArgs) {
|
||||||
with_tool_client(global.log_level, |tc| async move {
|
with_tool_client(global.log_level, |tc| async move {
|
||||||
let idle = Rc::new(Randr { tc: tc.clone() });
|
let idle = Rc::new(Randr { tc: tc.clone() });
|
||||||
|
|
@ -580,6 +608,7 @@ impl Randr {
|
||||||
RandrCmd::Show(args) => self.show(randr, args).await,
|
RandrCmd::Show(args) => self.show(randr, args).await,
|
||||||
RandrCmd::Card(args) => self.card(randr, args).await,
|
RandrCmd::Card(args) => self.card(randr, args).await,
|
||||||
RandrCmd::Output(args) => self.output(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;
|
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) {
|
async fn card(self: &Rc<Self>, randr: JayRandrId, args: CardArgs) {
|
||||||
let tc = &self.tc;
|
let tc = &self.tc;
|
||||||
match args.command {
|
match args.command {
|
||||||
|
|
|
||||||
|
|
@ -395,6 +395,7 @@ fn start_compositor2(
|
||||||
bo_drop_queue: Rc::new(ObjectDropQueue::new(&ring)),
|
bo_drop_queue: Rc::new(ObjectDropQueue::new(&ring)),
|
||||||
egg_state: Default::default(),
|
egg_state: Default::default(),
|
||||||
control_centers: Default::default(),
|
control_centers: Default::default(),
|
||||||
|
virtual_outputs: Default::default(),
|
||||||
});
|
});
|
||||||
state.tracker.register(ClientId::from_raw(0));
|
state.tracker.register(ClientId::from_raw(0));
|
||||||
create_dummy_output(&state);
|
create_dummy_output(&state);
|
||||||
|
|
|
||||||
|
|
@ -1605,6 +1605,14 @@ impl ConfigProxyHandler {
|
||||||
self.respond(Response::GetConnector { connector });
|
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> {
|
fn handle_get_connector_active_workspace(&self, connector: Connector) -> Result<(), CphError> {
|
||||||
let output = self.get_output_node(connector)?;
|
let output = self.get_output_node(connector)?;
|
||||||
let workspace = output
|
let workspace = output
|
||||||
|
|
@ -3357,6 +3365,8 @@ impl ConfigProxyHandler {
|
||||||
.handle_connector_supports_arbitrary_modes(connector)
|
.handle_connector_supports_arbitrary_modes(connector)
|
||||||
.wrn("connector_supports_arbitrary_modes")?,
|
.wrn("connector_supports_arbitrary_modes")?,
|
||||||
ClientMessage::GetConnectorByName { name } => self.handle_get_connector_by_name(name),
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ use {
|
||||||
cc_input::InputPane,
|
cc_input::InputPane,
|
||||||
cc_look_and_feel::LookAndFeelPane,
|
cc_look_and_feel::LookAndFeelPane,
|
||||||
cc_outputs::OutputsPane,
|
cc_outputs::OutputsPane,
|
||||||
|
cc_virtual_outputs::VirtualOutputsPane,
|
||||||
cc_window::{WindowPane, WindowSearchPane},
|
cc_window::{WindowPane, WindowSearchPane},
|
||||||
cc_xwayland::XwaylandPane,
|
cc_xwayland::XwaylandPane,
|
||||||
},
|
},
|
||||||
|
|
@ -51,6 +52,7 @@ mod cc_input;
|
||||||
mod cc_look_and_feel;
|
mod cc_look_and_feel;
|
||||||
mod cc_outputs;
|
mod cc_outputs;
|
||||||
mod cc_sidebar;
|
mod cc_sidebar;
|
||||||
|
mod cc_virtual_outputs;
|
||||||
mod cc_window;
|
mod cc_window;
|
||||||
mod cc_xwayland;
|
mod cc_xwayland;
|
||||||
|
|
||||||
|
|
@ -93,6 +95,7 @@ bitflags! {
|
||||||
CCI_GPUS,
|
CCI_GPUS,
|
||||||
CCI_INPUT,
|
CCI_INPUT,
|
||||||
CCI_LOOK_AND_FEEL,
|
CCI_LOOK_AND_FEEL,
|
||||||
|
CCI_VIRTUAL_OUTPUTS,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ControlCenter {
|
pub struct ControlCenter {
|
||||||
|
|
@ -145,6 +148,7 @@ enum PaneType {
|
||||||
Client(ClientPane),
|
Client(ClientPane),
|
||||||
WindowSearch(WindowSearchPane),
|
WindowSearch(WindowSearchPane),
|
||||||
Window(WindowPane),
|
Window(WindowPane),
|
||||||
|
VirtualOutputs(VirtualOutputsPane),
|
||||||
}
|
}
|
||||||
|
|
||||||
struct CcBehavior<'a> {
|
struct CcBehavior<'a> {
|
||||||
|
|
@ -174,6 +178,7 @@ impl Pane {
|
||||||
PaneType::Client(v) => v.title(res),
|
PaneType::Client(v) => v.title(res),
|
||||||
PaneType::WindowSearch(v) => v.title(res),
|
PaneType::WindowSearch(v) => v.title(res),
|
||||||
PaneType::Window(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::Client(p) => p.show(behavior, ui),
|
||||||
PaneType::WindowSearch(p) => p.show(behavior, ui),
|
PaneType::WindowSearch(p) => p.show(behavior, ui),
|
||||||
PaneType::Window(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::Client(_) => ControlCenterInterest::none(),
|
||||||
PaneType::WindowSearch(_) => ControlCenterInterest::none(),
|
PaneType::WindowSearch(_) => ControlCenterInterest::none(),
|
||||||
PaneType::Window(_) => ControlCenterInterest::none(),
|
PaneType::Window(_) => ControlCenterInterest::none(),
|
||||||
|
PaneType::VirtualOutputs(_) => CCI_VIRTUAL_OUTPUTS,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ enum PaneName {
|
||||||
LookAndFeel,
|
LookAndFeel,
|
||||||
Clients,
|
Clients,
|
||||||
WindowSearch,
|
WindowSearch,
|
||||||
|
VirtualOutputs,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PaneName {
|
impl PaneName {
|
||||||
|
|
@ -33,6 +34,7 @@ impl PaneName {
|
||||||
PaneName::LookAndFeel => "Look and Feel",
|
PaneName::LookAndFeel => "Look and Feel",
|
||||||
PaneName::Clients => "Clients",
|
PaneName::Clients => "Clients",
|
||||||
PaneName::WindowSearch => "Window Search",
|
PaneName::WindowSearch => "Window Search",
|
||||||
|
PaneName::VirtualOutputs => "Virtual Outputs",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -79,6 +81,9 @@ impl ControlCenterInner {
|
||||||
PaneName::WindowSearch => {
|
PaneName::WindowSearch => {
|
||||||
PaneType::WindowSearch(self.create_window_search_pane())
|
PaneType::WindowSearch(self.create_window_search_pane())
|
||||||
}
|
}
|
||||||
|
PaneName::VirtualOutputs => {
|
||||||
|
PaneType::VirtualOutputs(self.create_virtual_outputs_pane())
|
||||||
|
}
|
||||||
};
|
};
|
||||||
self.open(tree, ty);
|
self.open(tree, ty);
|
||||||
ui.ctx().request_repaint();
|
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 {
|
impl Global for JayCompositorGlobal {
|
||||||
fn version(&self) -> u32 {
|
fn version(&self) -> u32 {
|
||||||
29
|
30
|
||||||
}
|
}
|
||||||
|
|
||||||
fn required_caps(&self) -> ClientCaps {
|
fn required_caps(&self) -> ClientCaps {
|
||||||
|
|
|
||||||
|
|
@ -270,6 +270,11 @@ impl JayRandr {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_connector(&self, name: &str) -> Option<Rc<ConnectorData>> {
|
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();
|
let namelc = name.to_ascii_lowercase();
|
||||||
for c in self.client.state.connectors.lock().values() {
|
for c in self.client.state.connectors.lock().values() {
|
||||||
if c.name.to_ascii_lowercase() == namelc {
|
if c.name.to_ascii_lowercase() == namelc {
|
||||||
|
|
@ -281,6 +286,11 @@ impl JayRandr {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_output(&self, name: &str) -> Option<Rc<OutputData>> {
|
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();
|
let namelc = name.to_ascii_lowercase();
|
||||||
for c in self.client.state.outputs.lock().values() {
|
for c in self.client.state.outputs.lock().values() {
|
||||||
if c.connector.name.to_ascii_lowercase() == namelc {
|
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);
|
c.set_use_native_gamut(req.use_native_gamut != 0);
|
||||||
Ok(())
|
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! {
|
object_base! {
|
||||||
|
|
|
||||||
|
|
@ -62,7 +62,6 @@ pub struct WpPresentationFeedback {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const KIND_VSYNC: u32 = 0x1;
|
pub const KIND_VSYNC: u32 = 0x1;
|
||||||
#[expect(dead_code)]
|
|
||||||
pub const KIND_HW_CLOCK: u32 = 0x2;
|
pub const KIND_HW_CLOCK: u32 = 0x2;
|
||||||
pub const KIND_HW_COMPLETION: u32 = 0x4;
|
pub const KIND_HW_COMPLETION: u32 = 0x4;
|
||||||
pub const KIND_ZERO_COPY: u32 = 0x8;
|
pub const KIND_ZERO_COPY: u32 = 0x8;
|
||||||
|
|
|
||||||
|
|
@ -114,6 +114,7 @@ mod user_session;
|
||||||
mod utils;
|
mod utils;
|
||||||
mod version;
|
mod version;
|
||||||
mod video;
|
mod video;
|
||||||
|
mod virtual_output;
|
||||||
mod vulkan_core;
|
mod vulkan_core;
|
||||||
mod wheel;
|
mod wheel;
|
||||||
mod wire;
|
mod wire;
|
||||||
|
|
|
||||||
|
|
@ -131,6 +131,7 @@ use {
|
||||||
dmabuf::DmaBufIds,
|
dmabuf::DmaBufIds,
|
||||||
drm::{Drm, wait_for_syncobj::WaitForSyncobj},
|
drm::{Drm, wait_for_syncobj::WaitForSyncobj},
|
||||||
},
|
},
|
||||||
|
virtual_output::VirtualOutputs,
|
||||||
wheel::Wheel,
|
wheel::Wheel,
|
||||||
wire::{
|
wire::{
|
||||||
ExtForeignToplevelListV1Id, ExtIdleNotificationV1Id, JayHeadManagerSessionV1Id,
|
ExtForeignToplevelListV1Id, ExtIdleNotificationV1Id, JayHeadManagerSessionV1Id,
|
||||||
|
|
@ -302,6 +303,7 @@ pub struct State {
|
||||||
pub bo_drop_queue: Rc<ObjectDropQueue<Rc<dyn BufferObject>>>,
|
pub bo_drop_queue: Rc<ObjectDropQueue<Rc<dyn BufferObject>>>,
|
||||||
pub egg_state: EggState,
|
pub egg_state: EggState,
|
||||||
pub control_centers: ControlCenters,
|
pub control_centers: ControlCenters,
|
||||||
|
pub virtual_outputs: VirtualOutputs,
|
||||||
}
|
}
|
||||||
|
|
||||||
// impl Drop for State {
|
// impl Drop for State {
|
||||||
|
|
@ -674,6 +676,7 @@ impl State {
|
||||||
self.icons.clear();
|
self.icons.clear();
|
||||||
self.wait_for_syncobj
|
self.wait_for_syncobj
|
||||||
.set_ctx(ctx.as_ref().and_then(|c| c.syncobj_ctx().cloned()));
|
.set_ctx(ctx.as_ref().and_then(|c| c.syncobj_ctx().cloned()));
|
||||||
|
self.virtual_outputs.handle_render_ctx_change(self);
|
||||||
|
|
||||||
'handle_new_feedback: {
|
'handle_new_feedback: {
|
||||||
if let Some(ctx) = &ctx {
|
if let Some(ctx) = &ctx {
|
||||||
|
|
@ -1184,6 +1187,7 @@ impl State {
|
||||||
self.bo_drop_queue.kill();
|
self.bo_drop_queue.kill();
|
||||||
self.egg_state.clear();
|
self.egg_state.clear();
|
||||||
self.control_centers.clear();
|
self.control_centers.clear();
|
||||||
|
self.virtual_outputs.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn remove_toplevel_id(&self, id: ToplevelIdentifier) {
|
pub fn remove_toplevel_id(&self, id: ToplevelIdentifier) {
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,9 @@ use {
|
||||||
},
|
},
|
||||||
std::{rc::Rc, time::Duration},
|
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>) {
|
pub async fn handle_backend_events(state: Rc<State>) {
|
||||||
let mut beh = BackendEventHandler { state };
|
let mut beh = BackendEventHandler { state };
|
||||||
|
|
|
||||||
|
|
@ -306,6 +306,8 @@ impl ConnectorHandler {
|
||||||
.handle_output_connected(&self.state, &output_data);
|
.handle_output_connected(&self.state, &output_data);
|
||||||
self.state.trigger_cci(CCI_OUTPUTS);
|
self.state.trigger_cci(CCI_OUTPUTS);
|
||||||
self.state.wlr_output_managers.announce_head(&output_data);
|
self.state.wlr_output_managers.announce_head(&output_data);
|
||||||
|
global.add_damage_area(&global.pos.get());
|
||||||
|
self.data.damage();
|
||||||
'outer: loop {
|
'outer: loop {
|
||||||
while let Some(event) = self.data.connector.event() {
|
while let Some(event) = self.data.connector.event() {
|
||||||
match event {
|
match event {
|
||||||
|
|
|
||||||
|
|
@ -334,7 +334,7 @@ impl ToolClient {
|
||||||
self_id: s.registry,
|
self_id: s.registry,
|
||||||
name: s.jay_compositor.0,
|
name: s.jay_compositor.0,
|
||||||
interface: JayCompositor.name(),
|
interface: JayCompositor.name(),
|
||||||
version: s.jay_compositor.1.min(29),
|
version: s.jay_compositor.1.min(30),
|
||||||
id: id.into(),
|
id: id.into(),
|
||||||
});
|
});
|
||||||
self.jay_compositor.set(Some(id));
|
self.jay_compositor.set(Some(id));
|
||||||
|
|
|
||||||
|
|
@ -1146,6 +1146,7 @@ pub enum ConnectorType {
|
||||||
SPI,
|
SPI,
|
||||||
USB,
|
USB,
|
||||||
EmbeddedWindow,
|
EmbeddedWindow,
|
||||||
|
VirtualOutput,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ConnectorType {
|
impl ConnectorType {
|
||||||
|
|
@ -1200,6 +1201,7 @@ impl ConnectorType {
|
||||||
Self::SPI => sys::DRM_MODE_CONNECTOR_SPI,
|
Self::SPI => sys::DRM_MODE_CONNECTOR_SPI,
|
||||||
Self::USB => sys::DRM_MODE_CONNECTOR_USB,
|
Self::USB => sys::DRM_MODE_CONNECTOR_USB,
|
||||||
Self::EmbeddedWindow => sys::DRM_MODE_CONNECTOR_Unknown,
|
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::SPI => CON_SPI,
|
||||||
Self::USB => CON_USB,
|
Self::USB => CON_USB,
|
||||||
Self::EmbeddedWindow => CON_EMBEDDED_WINDOW,
|
Self::EmbeddedWindow => CON_EMBEDDED_WINDOW,
|
||||||
|
Self::VirtualOutput => CON_VIRTUAL_OUTPUT,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1257,6 +1260,7 @@ impl Display for ConnectorType {
|
||||||
Self::SPI => "SPI",
|
Self::SPI => "SPI",
|
||||||
Self::USB => "USB",
|
Self::USB => "USB",
|
||||||
Self::EmbeddedWindow => "EmbeddedWindow",
|
Self::EmbeddedWindow => "EmbeddedWindow",
|
||||||
|
Self::VirtualOutput => "VO",
|
||||||
};
|
};
|
||||||
f.write_str(s)
|
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
|
|
@ -183,6 +183,12 @@ pub enum Action {
|
||||||
name: String,
|
name: String,
|
||||||
latch: bool,
|
latch: bool,
|
||||||
},
|
},
|
||||||
|
CreateVirtualOutput {
|
||||||
|
name: String,
|
||||||
|
},
|
||||||
|
RemoveVirtualOutput {
|
||||||
|
name: String,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default)]
|
#[derive(Debug, Clone, Default)]
|
||||||
|
|
|
||||||
|
|
@ -480,6 +480,20 @@ impl ActionParser<'_> {
|
||||||
latch: true,
|
latch: true,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn parse_create_virtual_output(&mut self, ext: &mut Extractor<'_>) -> ParseResult<Self> {
|
||||||
|
let (name,) = ext.extract((str("name"),))?;
|
||||||
|
Ok(Action::CreateVirtualOutput {
|
||||||
|
name: name.value.to_string(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_remove_virtual_output(&mut self, ext: &mut Extractor<'_>) -> ParseResult<Self> {
|
||||||
|
let (name,) = ext.extract((str("name"),))?;
|
||||||
|
Ok(Action::RemoveVirtualOutput {
|
||||||
|
name: name.value.to_string(),
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Parser for ActionParser<'_> {
|
impl Parser for ActionParser<'_> {
|
||||||
|
|
@ -539,6 +553,8 @@ impl Parser for ActionParser<'_> {
|
||||||
"copy-mark" => self.parse_copy_mark(&mut ext),
|
"copy-mark" => self.parse_copy_mark(&mut ext),
|
||||||
"push-mode" => self.parse_push_mode(&mut ext),
|
"push-mode" => self.parse_push_mode(&mut ext),
|
||||||
"latch-mode" => self.parse_latch_mode(&mut ext),
|
"latch-mode" => self.parse_latch_mode(&mut ext),
|
||||||
|
"create-virtual-output" => self.parse_create_virtual_output(&mut ext),
|
||||||
|
"remove-virtual-output" => self.parse_remove_virtual_output(&mut ext),
|
||||||
v => {
|
v => {
|
||||||
ext.ignore_unused();
|
ext.ignore_unused();
|
||||||
return Err(ActionParserError::UnknownType(v.to_string()).spanned(ty.span));
|
return Err(ActionParserError::UnknownType(v.to_string()).spanned(ty.span));
|
||||||
|
|
|
||||||
|
|
@ -50,10 +50,10 @@ use {
|
||||||
},
|
},
|
||||||
toggle_float_above_fullscreen, toggle_show_bar, toggle_show_titles,
|
toggle_float_above_fullscreen, toggle_show_bar, toggle_show_titles,
|
||||||
video::{
|
video::{
|
||||||
ColorSpace, Connector, DrmDevice, Eotf, connectors, drm_devices,
|
ColorSpace, Connector, DrmDevice, Eotf, connectors, create_virtual_output, drm_devices,
|
||||||
on_connector_connected, on_connector_disconnected, on_graphics_initialized,
|
on_connector_connected, on_connector_disconnected, on_graphics_initialized,
|
||||||
on_new_connector, on_new_drm_device, set_direct_scanout_enabled, set_gfx_api,
|
on_new_connector, on_new_drm_device, remove_virtual_output, set_direct_scanout_enabled,
|
||||||
set_tearing_mode, set_vrr_cursor_hz, set_vrr_mode,
|
set_gfx_api, set_tearing_mode, set_vrr_cursor_hz, set_vrr_mode,
|
||||||
},
|
},
|
||||||
window::Window,
|
window::Window,
|
||||||
workspace::set_workspace_display_order,
|
workspace::set_workspace_display_order,
|
||||||
|
|
@ -476,6 +476,8 @@ impl Action {
|
||||||
state.set_mode(new, latch);
|
state.set_mode(new, latch);
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
Action::CreateVirtualOutput { name } => b.new(move || create_virtual_output(&name)),
|
||||||
|
Action::RemoveVirtualOutput { name } => b.new(move || remove_virtual_output(&name)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -574,6 +574,40 @@
|
||||||
"type",
|
"type",
|
||||||
"name"
|
"name"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Creates a virtual output.\n\nThis is a no-op if a virtual output with that name already exists.\n\nThe virtual output has the connector name `VO-{name}` and the serial number\n`{name}`.\n\nA newly created connector is initially disabled. When a connector is destroyed\nand later recreated, its previous state is restored.\n\n- Example:\n\n ```toml\n [shortcuts]\n alt-x = { type = \"create-virtual-output\", name = \"abcd\" }\n\n [[connectors]]\n match.name = \"VO-abcd\"\n enabled = true\n\n [[outputs]]\n match.connector = \"VO-abcd\"\n mode = { width = 1920, height = 1080, refresh-rate = 120.0 }\n ```\n",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"type": {
|
||||||
|
"const": "create-virtual-output"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The name of the output."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"type",
|
||||||
|
"name"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Removes a virtual output.\n\nThis is a no-op if no virtual output with that name exists.\n",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"type": {
|
||||||
|
"const": "remove-virtual-output"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The name of the output."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"type",
|
||||||
|
"name"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -840,6 +840,55 @@ This table is a tagged union. The variant is determined by the `type` field. It
|
||||||
|
|
||||||
The value of this field should be a string.
|
The value of this field should be a string.
|
||||||
|
|
||||||
|
- `create-virtual-output`:
|
||||||
|
|
||||||
|
Creates a virtual output.
|
||||||
|
|
||||||
|
This is a no-op if a virtual output with that name already exists.
|
||||||
|
|
||||||
|
The virtual output has the connector name `VO-{name}` and the serial number
|
||||||
|
`{name}`.
|
||||||
|
|
||||||
|
A newly created connector is initially disabled. When a connector is destroyed
|
||||||
|
and later recreated, its previous state is restored.
|
||||||
|
|
||||||
|
- Example:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[shortcuts]
|
||||||
|
alt-x = { type = "create-virtual-output", name = "abcd" }
|
||||||
|
|
||||||
|
[[connectors]]
|
||||||
|
match.name = "VO-abcd"
|
||||||
|
enabled = true
|
||||||
|
|
||||||
|
[[outputs]]
|
||||||
|
match.connector = "VO-abcd"
|
||||||
|
mode = { width = 1920, height = 1080, refresh-rate = 120.0 }
|
||||||
|
```
|
||||||
|
|
||||||
|
The table has the following fields:
|
||||||
|
|
||||||
|
- `name` (required):
|
||||||
|
|
||||||
|
The name of the output.
|
||||||
|
|
||||||
|
The value of this field should be a string.
|
||||||
|
|
||||||
|
- `remove-virtual-output`:
|
||||||
|
|
||||||
|
Removes a virtual output.
|
||||||
|
|
||||||
|
This is a no-op if no virtual output with that name exists.
|
||||||
|
|
||||||
|
The table has the following fields:
|
||||||
|
|
||||||
|
- `name` (required):
|
||||||
|
|
||||||
|
The name of the output.
|
||||||
|
|
||||||
|
The value of this field should be a string.
|
||||||
|
|
||||||
|
|
||||||
<a name="types-BarPosition"></a>
|
<a name="types-BarPosition"></a>
|
||||||
### `BarPosition`
|
### `BarPosition`
|
||||||
|
|
|
||||||
|
|
@ -810,6 +810,47 @@ Action:
|
||||||
description: The name of the mode.
|
description: The name of the mode.
|
||||||
required: true
|
required: true
|
||||||
kind: string
|
kind: string
|
||||||
|
create-virtual-output:
|
||||||
|
description: |
|
||||||
|
Creates a virtual output.
|
||||||
|
|
||||||
|
This is a no-op if a virtual output with that name already exists.
|
||||||
|
|
||||||
|
The virtual output has the connector name `VO-{name}` and the serial number
|
||||||
|
`{name}`.
|
||||||
|
|
||||||
|
A newly created connector is initially disabled. When a connector is destroyed
|
||||||
|
and later recreated, its previous state is restored.
|
||||||
|
|
||||||
|
- Example:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[shortcuts]
|
||||||
|
alt-x = { type = "create-virtual-output", name = "abcd" }
|
||||||
|
|
||||||
|
[[connectors]]
|
||||||
|
match.name = "VO-abcd"
|
||||||
|
enabled = true
|
||||||
|
|
||||||
|
[[outputs]]
|
||||||
|
match.connector = "VO-abcd"
|
||||||
|
mode = { width = 1920, height = 1080, refresh-rate = 120.0 }
|
||||||
|
```
|
||||||
|
fields:
|
||||||
|
name:
|
||||||
|
description: The name of the output.
|
||||||
|
required: true
|
||||||
|
kind: string
|
||||||
|
remove-virtual-output:
|
||||||
|
description: |
|
||||||
|
Removes a virtual output.
|
||||||
|
|
||||||
|
This is a no-op if no virtual output with that name exists.
|
||||||
|
fields:
|
||||||
|
name:
|
||||||
|
description: The name of the output.
|
||||||
|
required: true
|
||||||
|
kind: string
|
||||||
|
|
||||||
|
|
||||||
Exec:
|
Exec:
|
||||||
|
|
|
||||||
|
|
@ -105,6 +105,14 @@ request set_use_native_gamut (since = 23) {
|
||||||
use_native_gamut: u32,
|
use_native_gamut: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
request create_virtual_output (since = 30) {
|
||||||
|
name: str,
|
||||||
|
}
|
||||||
|
|
||||||
|
request remove_virtual_output (since = 30) {
|
||||||
|
name: str,
|
||||||
|
}
|
||||||
|
|
||||||
# events
|
# events
|
||||||
|
|
||||||
event global {
|
event global {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue