Merge remote-tracking branch 'origin/wlvk' into next-dash-interface

[skip ci]
This commit is contained in:
Aleksander
2025-12-25 13:58:30 +01:00
42 changed files with 1213 additions and 3271 deletions

162
Cargo.lock generated
View File

@@ -376,16 +376,16 @@ dependencies = [
"futures-lite",
"parking",
"polling",
"rustix 1.1.2",
"rustix 1.1.3",
"slab",
"windows-sys 0.61.2",
]
[[package]]
name = "async-lock"
version = "3.4.1"
version = "3.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5fd03604047cee9b6ce9de9f70c6cd540a0520c813cbd49bae61f33ab80ed1dc"
checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311"
dependencies = [
"event-listener",
"event-listener-strategy",
@@ -418,7 +418,7 @@ dependencies = [
"cfg-if",
"event-listener",
"futures-lite",
"rustix 1.1.2",
"rustix 1.1.3",
]
[[package]]
@@ -444,7 +444,7 @@ dependencies = [
"cfg-if",
"futures-core",
"futures-io",
"rustix 1.1.2",
"rustix 1.1.3",
"signal-hook-registry",
"slab",
"windows-sys 0.61.2",
@@ -874,7 +874,7 @@ checksum = "cb9f6e1368bd4621d2c86baa7e37de77a938adf5221e5dd3d6133340101b309e"
dependencies = [
"bitflags 2.10.0",
"polling",
"rustix 1.1.2",
"rustix 1.1.3",
"slab",
"tracing",
]
@@ -898,16 +898,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "138efcf0940a02ebf0cc8d1eff41a1682a46b431630f4c52450d6265876021fa"
dependencies = [
"calloop 0.14.3",
"rustix 1.1.2",
"rustix 1.1.3",
"wayland-backend",
"wayland-client",
]
[[package]]
name = "cc"
version = "1.2.49"
version = "1.2.50"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90583009037521a116abf44494efecd645ba48b6622457080f080b85544e2215"
checksum = "9f50d563227a1c37cc0a263f64eca3334388c01c5e4c4861a9def205c614383c"
dependencies = [
"find-msvc-tools",
"jobserver",
@@ -1637,46 +1637,12 @@ version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76"
[[package]]
name = "drm"
version = "0.14.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "80bc8c5c6c2941f70a55c15f8d9f00f9710ebda3ffda98075f996a0e6c92756f"
dependencies = [
"bitflags 2.10.0",
"bytemuck",
"drm-ffi",
"drm-fourcc",
"libc",
"rustix 0.38.44",
]
[[package]]
name = "drm-ffi"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8e41459d99a9b529845f6d2c909eb9adf3b6d2f82635ae40be8de0601726e8b"
dependencies = [
"drm-sys",
"rustix 0.38.44",
]
[[package]]
name = "drm-fourcc"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0aafbcdb8afc29c1a7ee5fbe53b5d62f4565b35a042a662ca9fecd0b54dae6f4"
[[package]]
name = "drm-sys"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bafb66c8dbc944d69e15cfcc661df7e703beffbaec8bd63151368b06c5f9858c"
dependencies = [
"libc",
"linux-raw-sys 0.6.5",
]
[[package]]
name = "either"
version = "1.15.0"
@@ -2216,7 +2182,7 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bd49230192a3797a9a4d6abe9b3eed6f7fa4c8a8a4947977c6f80025f92cbd8"
dependencies = [
"rustix 1.1.2",
"rustix 1.1.3",
"windows-link 0.2.1",
]
@@ -2321,17 +2287,6 @@ dependencies = [
"windows-sys 0.61.2",
]
[[package]]
name = "gl_generator"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a95dfc23a2b4a9a2f5ab41d194f8bfda3cabec42af4e39f08c339eb2a0c124d"
dependencies = [
"khronos_api",
"log",
"xml-rs",
]
[[package]]
name = "glam"
version = "0.30.9"
@@ -2974,9 +2929,9 @@ dependencies = [
[[package]]
name = "itoa"
version = "1.0.15"
version = "1.0.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
checksum = "7ee5b5339afb4c41626dde77b7a611bd4f2c202b897852b4bcf5d03eddc61010"
[[package]]
name = "jiff"
@@ -3071,22 +3026,6 @@ dependencies = [
"ucd-trie",
]
[[package]]
name = "khronos-egl"
version = "6.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6aae1df220ece3c0ada96b8153459b67eebe9ae9212258bb0134ae60416fdf76"
dependencies = [
"libc",
"pkg-config",
]
[[package]]
name = "khronos_api"
version = "3.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc"
[[package]]
name = "kurbo"
version = "0.11.3"
@@ -3234,12 +3173,6 @@ version = "0.4.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab"
[[package]]
name = "linux-raw-sys"
version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a385b1be4e5c3e362ad2ffa73c392e53f031eaa5b7d648e64cd87f27f6063d7"
[[package]]
name = "linux-raw-sys"
version = "0.11.0"
@@ -3530,9 +3463,9 @@ dependencies = [
[[package]]
name = "ntapi"
version = "0.4.1"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8a3895c6391c39d7fe7ebc444a87eb2991b2a0bc718fdabd071eec617fc68e4"
checksum = "c70f219e21142367c70c0b30c6a9e3a14d55b4d12a204d897fbec83a0363f081"
dependencies = [
"winapi",
]
@@ -4315,7 +4248,7 @@ dependencies = [
"concurrent-queue",
"hermit-abi 0.5.2",
"pin-project-lite",
"rustix 1.1.2",
"rustix 1.1.3",
"windows-sys 0.61.2",
]
@@ -4541,9 +4474,9 @@ dependencies = [
[[package]]
name = "rangemap"
version = "1.7.0"
version = "1.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "acbbbbea733ec66275512d0b9694f34102e7d5406fdbe2ad8d21b28dce92887c"
checksum = "973443cf09a9c8656b574a866ab68dfa19f0867d0340648c7d2f6a71b8a8ea68"
[[package]]
name = "rav1e"
@@ -4877,9 +4810,9 @@ dependencies = [
[[package]]
name = "rustix"
version = "1.1.2"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e"
checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34"
dependencies = [
"bitflags 2.10.0",
"errno",
@@ -4905,9 +4838,9 @@ dependencies = [
[[package]]
name = "ryu"
version = "1.0.20"
version = "1.0.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
checksum = "62049b2877bf12821e8f9ad256ee38fdc31db7387ec2d3b3f403024de2034aea"
[[package]]
name = "same-file"
@@ -5005,15 +4938,15 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.145"
version = "1.0.147"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c"
checksum = "6af14725505314343e673e9ecb7cd7e8a36aa9791eb936235a3567cc31447ae4"
dependencies = [
"itoa",
"memchr",
"ryu",
"serde",
"serde_core",
"zmij",
]
[[package]]
@@ -5221,24 +5154,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "740cea6927892bc182d5bf70c8f79806c8bc9f68f2fb96e55a30be171b63af98"
dependencies = [
"appendlist",
"ash",
"atomic_float",
"bitflags 2.10.0",
"calloop 0.14.3",
"cgmath",
"cursor-icon",
"downcast-rs",
"drm",
"drm-ffi",
"drm-fourcc",
"encoding_rs",
"errno",
"gl_generator",
"indexmap 2.12.1",
"libc",
"libloading",
"profiling",
"rand",
"rustix 1.1.2",
"rustix 1.1.3",
"scopeguard",
"sha2",
"smallvec",
@@ -5293,7 +5223,7 @@ dependencies = [
"log",
"memmap2",
"pkg-config",
"rustix 1.1.2",
"rustix 1.1.3",
"thiserror 2.0.17",
"wayland-backend",
"wayland-client",
@@ -5570,14 +5500,14 @@ checksum = "df7f62577c25e07834649fc3b39fafdc597c0a3527dc1c60129201ccfcbaa50c"
[[package]]
name = "tempfile"
version = "3.23.0"
version = "3.24.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16"
checksum = "655da9c7eb6305c55742045d5a8d2037996d61d8de95806335c7c86ce0f82e9c"
dependencies = [
"fastrand",
"getrandom 0.3.4",
"once_cell",
"rustix 1.1.2",
"rustix 1.1.3",
"windows-sys 0.61.2",
]
@@ -6275,7 +6205,7 @@ checksum = "673a33c33048a5ade91a6b139580fa174e19fb0d23f396dca9fa15f2e1e49b35"
dependencies = [
"cc",
"downcast-rs",
"rustix 1.1.2",
"rustix 1.1.3",
"scoped-tls",
"smallvec",
"wayland-sys",
@@ -6288,7 +6218,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c66a47e840dc20793f2264eb4b3e4ecb4b75d91c0dd4af04b456128e0bdd449d"
dependencies = [
"bitflags 2.10.0",
"rustix 1.1.2",
"rustix 1.1.3",
"wayland-backend",
"wayland-scanner",
]
@@ -6310,21 +6240,11 @@ version = "0.31.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "447ccc440a881271b19e9989f75726d60faa09b95b0200a9b7eb5cc47c3eeb29"
dependencies = [
"rustix 1.1.2",
"rustix 1.1.3",
"wayland-client",
"xcursor",
]
[[package]]
name = "wayland-egl"
version = "0.32.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d36232ee23ba3ea34a6835d68ca1af91d3ca3d6eddcf9c7147c4e0e66901b9fd"
dependencies = [
"wayland-backend",
"wayland-sys",
]
[[package]]
name = "wayland-protocols"
version = "0.32.9"
@@ -6411,7 +6331,7 @@ checksum = "fcbd4f3aba6c9fba70445ad2a484c0ef0356c1a9459b1e8e435bedc1971a6222"
dependencies = [
"bitflags 2.10.0",
"downcast-rs",
"rustix 1.1.2",
"rustix 1.1.3",
"wayland-backend",
"wayland-scanner",
]
@@ -7094,7 +7014,6 @@ dependencies = [
"interprocess",
"json",
"json5 1.3.0",
"khronos-egl",
"libc",
"libmonado",
"log",
@@ -7123,7 +7042,6 @@ dependencies = [
"vulkano",
"vulkano-shaders",
"wayland-client",
"wayland-egl",
"wayvr-ipc",
"wgui",
"winit",
@@ -7162,7 +7080,7 @@ dependencies = [
"libc",
"libloading",
"once_cell",
"rustix 1.1.2",
"rustix 1.1.3",
"x11rb-protocol",
]
@@ -7467,6 +7385,12 @@ dependencies = [
"syn 2.0.111",
]
[[package]]
name = "zmij"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e404bcd8afdaf006e529269d3e85a743f9480c3cef60034d77860d02964f3ba"
[[package]]
name = "zune-core"
version = "0.5.0"
@@ -7484,9 +7408,9 @@ dependencies = [
[[package]]
name = "zune-jpeg"
version = "0.5.7"
version = "0.5.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51d915729b0e7d5fe35c2f294c5dc10b30207cc637920e5b59077bfa3da63f28"
checksum = "e35aee689668bf9bd6f6f3a6c60bb29ba1244b3b43adfd50edd554a371da37d5"
dependencies = [
"zune-core",
]

View File

@@ -315,93 +315,16 @@ impl WayVRClient {
}
}
pub async fn fn_wvr_display_list(
pub async fn fn_wvr_window_list(
client: WayVRClientMutex,
serial: Serial,
) -> anyhow::Result<Vec<packet_server::WvrDisplay>> {
Ok(
send_and_wait!(
client,
serial,
&PacketClient::WvrDisplayList(serial),
WvrDisplayListResponse
)
.list,
)
}
pub async fn fn_wvr_display_get(
client: WayVRClientMutex,
serial: Serial,
handle: packet_server::WvrDisplayHandle,
) -> anyhow::Result<Option<packet_server::WvrDisplay>> {
Ok(send_and_wait!(
client,
serial,
&PacketClient::WvrDisplayGet(serial, handle),
WvrDisplayGetResponse
))
}
pub async fn fn_wvr_display_remove(
client: WayVRClientMutex,
serial: Serial,
handle: packet_server::WvrDisplayHandle,
) -> anyhow::Result<()> {
send_and_wait!(
client,
serial,
&PacketClient::WvrDisplayRemove(serial, handle),
WvrDisplayRemoveResponse
)
.map_err(|e| anyhow::anyhow!("{}", e))
}
pub async fn fn_wvr_display_create(
client: WayVRClientMutex,
serial: Serial,
params: packet_client::WvrDisplayCreateParams,
) -> anyhow::Result<packet_server::WvrDisplayHandle> {
Ok(send_and_wait!(
client,
serial,
&PacketClient::WvrDisplayCreate(serial, params),
WvrDisplayCreateResponse
))
}
pub async fn fn_wvr_display_set_visible(
client: WayVRClientMutex,
handle: packet_server::WvrDisplayHandle,
visible: bool,
) -> anyhow::Result<()> {
send_only!(client, &PacketClient::WvrDisplaySetVisible(handle, visible));
Ok(())
}
pub async fn fn_wvr_display_set_layout(
client: WayVRClientMutex,
handle: packet_server::WvrDisplayHandle,
layout: packet_server::WvrDisplayWindowLayout,
) -> anyhow::Result<()> {
send_only!(
client,
&PacketClient::WvrDisplaySetWindowLayout(handle, layout)
);
Ok(())
}
pub async fn fn_wvr_display_window_list(
client: WayVRClientMutex,
serial: Serial,
handle: packet_server::WvrDisplayHandle,
) -> anyhow::Result<Option<Vec<packet_server::WvrWindow>>> {
Ok(
send_and_wait!(
client,
serial,
&PacketClient::WvrDisplayWindowList(serial, handle),
WvrDisplayWindowListResponse
&PacketClient::WvrWindowList(serial),
WvrWindowListResponse
)
.map(|res| res.list),
)
@@ -478,11 +401,12 @@ impl WayVRClient {
))
}
pub async fn fn_wlx_haptics(
pub async fn fn_wlx_device_haptics(
client: WayVRClientMutex,
device: usize,
params: packet_client::WlxHapticsParams,
) -> anyhow::Result<()> {
send_only!(client, &PacketClient::WlxHaptics(params));
send_only!(client, &PacketClient::WlxDeviceHaptics(device, params));
Ok(())
}

View File

@@ -67,22 +67,13 @@ pub struct WlxModifyPanelParams {
#[derive(Debug, Serialize, Deserialize)]
pub enum PacketClient {
Handshake(Handshake),
WvrDisplayCreate(Serial, WvrDisplayCreateParams),
WvrDisplayGet(Serial, packet_server::WvrDisplayHandle),
WvrDisplayList(Serial),
WvrDisplayRemove(Serial, packet_server::WvrDisplayHandle),
WvrDisplaySetVisible(packet_server::WvrDisplayHandle, bool),
WvrDisplayWindowList(Serial, packet_server::WvrDisplayHandle),
WvrDisplaySetWindowLayout(
packet_server::WvrDisplayHandle,
packet_server::WvrDisplayWindowLayout,
),
WvrWindowList(Serial),
WvrWindowSetVisible(packet_server::WvrWindowHandle, bool),
WvrProcessGet(Serial, packet_server::WvrProcessHandle),
WvrProcessLaunch(Serial, WvrProcessLaunchParams),
WvrProcessList(Serial),
WvrProcessTerminate(packet_server::WvrProcessHandle),
WlxHaptics(WlxHapticsParams),
WlxInputState(Serial),
WlxModifyPanel(WlxModifyPanelParams),
WlxDeviceHaptics(usize, WlxHapticsParams),
}

View File

@@ -48,14 +48,11 @@ pub struct WvrDisplay {
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WvrWindow {
pub pos_x: i32,
pub pos_y: i32,
pub size_x: u32,
pub size_y: u32,
pub visible: bool,
pub handle: WvrWindowHandle,
pub process_handle: WvrProcessHandle,
pub display_handle: WvrDisplayHandle,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
@@ -71,7 +68,6 @@ pub struct WvrWindowList {
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WvrProcess {
pub name: String,
pub display_handle: WvrDisplayHandle,
pub handle: WvrProcessHandle,
pub userdata: HashMap<String, String>,
}
@@ -103,8 +99,6 @@ pub enum WvrDisplayWindowLayout {
#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
pub enum WvrStateChanged {
DisplayCreated,
DisplayRemoved,
ProcessCreated,
ProcessRemoved,
WindowCreated,
@@ -132,11 +126,7 @@ pub enum PacketServer {
Disconnect(Disconnect),
HandshakeSuccess(HandshakeSuccess),
WlxInputStateResponse(Serial, WlxInputState),
WvrDisplayCreateResponse(Serial, WvrDisplayHandle),
WvrDisplayGetResponse(Serial, Option<WvrDisplay>),
WvrDisplayListResponse(Serial, WvrDisplayList),
WvrDisplayRemoveResponse(Serial, Result<(), String>),
WvrDisplayWindowListResponse(Serial, Option<WvrWindowList>),
WvrWindowListResponse(Serial, Option<WvrWindowList>),
WvrProcessGetResponse(Serial, Option<WvrProcess>),
WvrProcessLaunchResponse(Serial, Result<WvrProcessHandle, String>),
WvrProcessListResponse(Serial, WvrProcessList),
@@ -149,11 +139,7 @@ impl PacketServer {
PacketServer::Disconnect(_) => None,
PacketServer::HandshakeSuccess(_) => None,
PacketServer::WlxInputStateResponse(serial, _) => Some(serial),
PacketServer::WvrDisplayCreateResponse(serial, _) => Some(serial),
PacketServer::WvrDisplayGetResponse(serial, _) => Some(serial),
PacketServer::WvrDisplayListResponse(serial, _) => Some(serial),
PacketServer::WvrDisplayRemoveResponse(serial, _) => Some(serial),
PacketServer::WvrDisplayWindowListResponse(serial, _) => Some(serial),
PacketServer::WvrWindowListResponse(serial, _) => Some(serial),
PacketServer::WvrProcessGetResponse(serial, _) => Some(serial),
PacketServer::WvrProcessLaunchResponse(serial, _) => Some(serial),
PacketServer::WvrProcessListResponse(serial, _) => Some(serial),

View File

@@ -37,104 +37,18 @@ fn handle_result<T: Serialize>(pretty_print: bool, result: anyhow::Result<T>) {
}
}
pub async fn wvr_display_create(
state: &mut WayVRClientState,
width: u16,
height: u16,
name: String,
scale: Option<f32>,
attach_to: packet_client::AttachTo,
) {
pub async fn wvr_window_list(state: &mut WayVRClientState) {
handle_result(
state.pretty_print,
WayVRClient::fn_wvr_display_create(
WayVRClient::fn_wvr_window_list(
state.wayvr_client.clone(),
state.serial_generator.increment_get(),
packet_client::WvrDisplayCreateParams {
width,
height,
name,
scale,
attach_to,
},
)
.await
.context("failed to create display"),
);
}
pub async fn wvr_display_list(state: &mut WayVRClientState) {
handle_result(
state.pretty_print,
WayVRClient::fn_wvr_display_list(
state.wayvr_client.clone(),
state.serial_generator.increment_get(),
)
.await
.context("failed to fetch displays"),
);
}
pub async fn wvr_display_get(
state: &mut WayVRClientState,
handle: packet_server::WvrDisplayHandle,
) {
handle_result(
state.pretty_print,
WayVRClient::fn_wvr_display_get(
state.wayvr_client.clone(),
state.serial_generator.increment_get(),
handle,
)
.await
.context("failed to fetch display"),
);
}
pub async fn wvr_display_window_list(
state: &mut WayVRClientState,
handle: packet_server::WvrDisplayHandle,
) {
handle_result(
state.pretty_print,
WayVRClient::fn_wvr_display_window_list(
state.wayvr_client.clone(),
state.serial_generator.increment_get(),
handle,
)
.await
.context("failed to list window displays"),
);
}
pub async fn wvr_display_remove(
state: &mut WayVRClientState,
handle: packet_server::WvrDisplayHandle,
) {
handle_result(
state.pretty_print,
WayVRClient::fn_wvr_display_remove(
state.wayvr_client.clone(),
state.serial_generator.increment_get(),
handle,
)
.await
.context("failed to remove display"),
);
}
pub async fn wvr_display_set_visible(
state: &mut WayVRClientState,
handle: packet_server::WvrDisplayHandle,
visible: bool,
) {
handle_empty_result(
WayVRClient::fn_wvr_display_set_visible(state.wayvr_client.clone(), handle, visible)
.await
.context("failed to set display visibility"),
)
}
pub async fn wvr_window_set_visible(
state: &mut WayVRClientState,
handle: packet_server::WvrWindowHandle,
@@ -214,15 +128,17 @@ pub async fn wvr_process_launch(
)
}
pub async fn wlx_haptics(
pub async fn wlx_device_haptics(
state: &mut WayVRClientState,
device: usize,
intensity: f32,
duration: f32,
frequency: f32,
) {
handle_empty_result(
WayVRClient::fn_wlx_haptics(
WayVRClient::fn_wlx_device_haptics(
state.wayvr_client.clone(),
device,
packet_client::WlxHapticsParams {
intensity,
duration,

View File

@@ -10,10 +10,9 @@ use env_logger::Env;
use wayvr_ipc::{client::WayVRClient, ipc, packet_client};
use crate::helper::{
WayVRClientState, wlx_haptics, wlx_input_state, wlx_panel_modify, wvr_display_create,
wvr_display_get, wvr_display_list, wvr_display_remove, wvr_display_set_visible,
wvr_display_window_list, wvr_process_get, wvr_process_launch, wvr_process_list,
wvr_process_terminate, wvr_window_set_visible,
WayVRClientState, wlx_device_haptics, wlx_input_state, wlx_panel_modify, wvr_process_get,
wvr_process_launch, wvr_process_list, wvr_process_terminate, wvr_window_list,
wvr_window_set_visible,
};
mod helper;
@@ -76,12 +75,12 @@ async fn run_batch(state: &mut WayVRClientState, fail_fast: bool) -> anyhow::Res
}
async fn parse_run_line(state: &mut WayVRClientState, line: &str) -> anyhow::Result<()> {
let mut argv = shell_words::split(&line).with_context(|| format!("parse error"))?;
let mut argv = shell_words::split(line).context("parse error")?;
// clap expects argv[0] to be the binary name
argv.insert(0, env!("CARGO_PKG_NAME").to_string());
let args = Args::try_parse_from(argv).with_context(|| format!("invalid arguments"))?;
let args = Args::try_parse_from(argv).context("invalid arguments")?;
run_once(state, args).await?;
Ok(())
@@ -95,43 +94,8 @@ async fn run_once(state: &mut WayVRClientState, args: Args) -> anyhow::Result<()
Subcommands::InputState => {
wlx_input_state(state).await;
}
Subcommands::DisplayCreate {
width,
height,
name,
scale,
} => {
wvr_display_create(
state,
width,
height,
name,
scale,
packet_client::AttachTo::None,
)
.await;
}
Subcommands::DisplayList => {
wvr_display_list(state).await;
}
Subcommands::DisplayGet { handle } => {
let handle = serde_json::from_str(&handle).context("Invalid handle")?;
wvr_display_get(state, handle).await;
}
Subcommands::DisplayWindowList { handle } => {
let handle = serde_json::from_str(&handle).context("Invalid handle")?;
wvr_display_window_list(state, handle).await;
}
Subcommands::DisplayRemove { handle } => {
let handle = serde_json::from_str(&handle).context("Invalid handle")?;
wvr_display_remove(state, handle).await;
}
Subcommands::DisplaySetVisible {
handle,
visible_0_or_1,
} => {
let handle = serde_json::from_str(&handle).context("Invalid handle")?;
wvr_display_set_visible(state, handle, visible_0_or_1 != 0).await;
Subcommands::WindowList => {
wvr_window_list(state).await;
}
Subcommands::WindowSetVisible {
handle,
@@ -162,11 +126,12 @@ async fn run_once(state: &mut WayVRClientState, args: Args) -> anyhow::Result<()
wvr_process_launch(state, exec, name, env, handle, args, HashMap::new()).await;
}
Subcommands::Haptics {
device,
intensity,
duration,
frequency,
} => {
wlx_haptics(state, intensity, duration, frequency).await;
wlx_device_haptics(state, device, intensity, duration, frequency).await;
}
Subcommands::PanelModify {
overlay,
@@ -220,39 +185,9 @@ enum Subcommands {
},
/// Get the positions of HMD & controllers
InputState,
/// Create a new WayVR display
DisplayCreate {
width: u16,
height: u16,
name: String,
#[arg(short, long)]
scale: Option<f32>,
//attach_to: packet_client::AttachTo,
},
/// List WayVR displays
DisplayList,
/// Retrieve information about a single WayVR display
DisplayGet {
/// A display handle JSON returned by DisplayList or DisplayCreate
handle: String,
},
/// List windows attached to a WayVR display
DisplayWindowList {
/// A display handle JSON returned by DisplayList or DisplayCreate
handle: String,
},
/// List WayVR windows
WindowList,
/// Delete a WayVR display
DisplayRemove {
/// A display handle JSON returned by DisplayList or DisplayCreate
handle: String,
},
/// Change the visibility of a WayVR display
DisplaySetVisible {
/// A display handle JSON returned by DisplayList or DisplayCreate
handle: String,
visible_0_or_1: u8,
},
// DisplaySetLayout skipped
/// Change the visibility of a window on a WayVR display
WindowSetVisible {
@@ -283,6 +218,8 @@ enum Subcommands {
},
/// Trigger haptics on the user's controller
Haptics {
/// 0 for left, 1 for right controller
device: usize,
#[arg(short, long, default_value = "0.25")]
intensity: f32,
#[arg(short, long, default_value = "0.1")]

View File

@@ -1,47 +1,6 @@
use std::{fmt::Display, os::fd::RawFd};
use std::os::fd::RawFd;
#[derive(Debug, Clone, Copy, Default)]
pub struct FourCC {
pub value: u32,
}
impl PartialEq for FourCC {
fn eq(&self, other: &Self) -> bool {
self.value == other.value
}
}
impl From<u32> for FourCC {
fn from(value: u32) -> Self {
Self { value }
}
}
impl From<FourCC> for u32 {
fn from(fourcc: FourCC) -> Self {
fourcc.value
}
}
impl Display for FourCC {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
for i in 0..4 {
if let Some(c) = char::from_u32((self.value >> (i * 8)) & 0xFF) {
write!(f, "{c}")?
} else {
write!(f, "?")?
}
}
Ok(())
}
}
pub const DRM_FORMAT_ARGB8888: u32 = 0x34325241; // AR24
pub const DRM_FORMAT_ABGR8888: u32 = 0x34324241; // AB24
pub const DRM_FORMAT_XRGB8888: u32 = 0x34325258; // XR24
pub const DRM_FORMAT_XBGR8888: u32 = 0x34324258; // XB24
pub const DRM_FORMAT_ABGR2101010: u32 = 0x30334241; // AB30
pub const DRM_FORMAT_XBGR2101010: u32 = 0x30334258; // XB30
use drm_fourcc::{DrmFormat, DrmModifier};
#[cfg(feature = "egl")]
#[rustfmt::skip]
@@ -73,24 +32,27 @@ pub enum Transform {
Flipped270,
}
#[derive(Debug, Clone, Copy, Default)]
#[derive(Debug, Clone, Copy)]
pub struct FrameFormat {
pub width: u32,
pub height: u32,
pub fourcc: FourCC,
pub modifier: u64,
pub drm_format: DrmFormat,
pub transform: Transform,
}
impl FrameFormat {
#[must_use]
pub fn get_mod_hi(&self) -> u32 {
(self.modifier >> 32) as _
let m = u64::from(self.drm_format.modifier);
(m >> 32) as _
}
#[must_use]
pub fn get_mod_lo(&self) -> u32 {
(self.modifier & 0xFFFFFFFF) as _
let m = u64::from(self.drm_format.modifier);
(m & 0xFFFFFFFF) as _
}
pub fn set_mod(&mut self, mod_hi: u32, mod_low: u32) {
self.modifier = ((mod_hi as u64) << 32) + mod_low as u64;
self.drm_format.modifier = DrmModifier::from(((mod_hi as u64) << 32) + mod_low as u64);
}
}
@@ -101,13 +63,6 @@ pub struct FramePlane {
pub stride: i32,
}
#[derive(Default, Clone)]
pub struct DrmFormat {
pub fourcc: FourCC,
pub modifiers: Vec<u64>,
}
#[derive(Default)]
pub struct DmabufFrame {
pub format: FrameFormat,
pub num_planes: usize,
@@ -126,7 +81,7 @@ impl DmabufFrame {
0x3056, // HEIGHT
self.format.height as _,
0x3271, // LINUX_DRM_FOURCC_EXT,
self.format.fourcc.value as _,
self.format.drm_format.code as _,
];
for i in 0..self.num_planes {
@@ -162,14 +117,12 @@ impl DmabufFrame {
}
}
#[derive(Default)]
pub struct MemFdFrame {
pub format: FrameFormat,
pub plane: FramePlane,
pub mouse: Option<MouseMeta>,
}
#[derive(Default)]
pub struct MemPtrFrame {
pub format: FrameFormat,
pub ptr: usize,

View File

@@ -1,7 +1,8 @@
#![allow(dead_code)]
#![allow(clippy::expect_fun_call)]
use frame::{DrmFormat, WlxFrame};
pub use drm_fourcc::{DrmFormat, DrmFourcc, DrmModifier};
use frame::WlxFrame;
pub mod frame;

View File

@@ -1,4 +1,5 @@
use std::any::Any;
use std::collections::HashMap;
use std::sync::Arc;
use std::sync::atomic::AtomicU32;
use std::sync::atomic::Ordering;
@@ -12,6 +13,9 @@ use ashpd::desktop::{
pub use ashpd::Error as AshpdError;
use drm_fourcc::DrmFormat;
use drm_fourcc::DrmFourcc;
use drm_fourcc::DrmModifier;
use pipewire as pw;
use pw::spa;
@@ -33,14 +37,6 @@ use spa::utils::ChoiceEnum;
use spa::utils::ChoiceFlags;
use crate::WlxCapture;
use crate::frame::DRM_FORMAT_ABGR8888;
use crate::frame::DRM_FORMAT_ABGR2101010;
use crate::frame::DRM_FORMAT_ARGB8888;
use crate::frame::DRM_FORMAT_XBGR8888;
use crate::frame::DRM_FORMAT_XBGR2101010;
use crate::frame::DRM_FORMAT_XRGB8888;
use crate::frame::DrmFormat;
use crate::frame::FourCC;
use crate::frame::FrameFormat;
use crate::frame::MouseMeta;
use crate::frame::Transform;
@@ -297,7 +293,15 @@ where
)?;
let _listener = stream
.add_local_listener_with_user_data(FrameFormat::default())
.add_local_listener_with_user_data(FrameFormat {
width: 0,
height: 0,
drm_format: DrmFormat {
code: DrmFourcc::Argb8888,
modifier: DrmModifier::Invalid,
},
transform: Transform::Undefined,
})
.state_changed({
let name = name.clone();
move |_, _, old, new| {
@@ -320,10 +324,10 @@ where
format.width = info.size().width;
format.height = info.size().height;
format.fourcc = spa_to_fourcc(info.format());
format.modifier = info.modifier();
format.drm_format.code = spa_to_fourcc(info.format());
format.drm_format.modifier = DrmModifier::from(info.modifier());
let kind = if format.modifier != 0 {
let kind = if info.modifier() != 0 {
"DMA-buf"
} else {
"SHM"
@@ -425,7 +429,7 @@ where
format: *format,
num_planes: planes.len(),
mouse: mouse_meta,
..Default::default()
planes: Default::default(),
};
dmabuf.planes[..planes.len()].copy_from_slice(&planes[..planes.len()]);
@@ -494,7 +498,17 @@ where
})
.register()?;
let mut format_params: Vec<Vec<u8>> = dmabuf_formats
let mut fourcc_mods: HashMap<DrmFourcc, Vec<DrmModifier>> = HashMap::new();
for f in dmabuf_formats.iter() {
if let Some(v) = fourcc_mods.get_mut(&f.code) {
v.push(f.modifier);
} else {
fourcc_mods.insert(f.code, vec![f.modifier]);
}
}
let mut format_params: Vec<Vec<u8>> = fourcc_mods
.iter()
.filter_map(|f| obj_to_bytes(get_format_params(Some(f))).ok())
.collect();
@@ -591,7 +605,7 @@ fn get_meta_object(key: u32, size: usize) -> Object {
)
}
fn get_format_params(fmt: Option<&DrmFormat>) -> Object {
fn get_format_params(fmt: Option<(&DrmFourcc, &Vec<DrmModifier>)>) -> Object {
let mut obj = spa::pod::object!(
spa::utils::SpaTypes::ObjectParamFormat,
spa::param::ParamType::EnumFormat,
@@ -638,7 +652,7 @@ fn get_format_params(fmt: Option<&DrmFormat>) -> Object {
);
if let Some(fmt) = fmt {
let spa_fmt = fourcc_to_spa(fmt.fourcc);
let spa_fmt = fourcc_to_spa(*fmt.0);
let prop = spa::pod::property!(
spa::param::format::FormatProperties::VideoFormat,
@@ -657,8 +671,8 @@ fn get_format_params(fmt: Option<&DrmFormat>) -> Object {
value: Value::Choice(ChoiceValue::Long(Choice(
ChoiceFlags::empty(),
ChoiceEnum::Enum {
default: fmt.modifiers[0] as _,
alternatives: fmt.modifiers.iter().map(|m| *m as _).collect(),
default: u64::from(fmt.1[0]) as _,
alternatives: fmt.1.iter().map(|m| u64::from(*m) as _).collect(),
},
))),
};
@@ -683,27 +697,27 @@ fn get_format_params(fmt: Option<&DrmFormat>) -> Object {
obj
}
fn fourcc_to_spa(fourcc: FourCC) -> VideoFormat {
match fourcc.value {
DRM_FORMAT_ARGB8888 => VideoFormat::BGRA,
DRM_FORMAT_ABGR8888 => VideoFormat::RGBA,
DRM_FORMAT_XRGB8888 => VideoFormat::BGRx,
DRM_FORMAT_XBGR8888 => VideoFormat::RGBx,
DRM_FORMAT_ABGR2101010 => VideoFormat::ABGR_210LE,
DRM_FORMAT_XBGR2101010 => VideoFormat::xBGR_210LE,
fn fourcc_to_spa(fourcc: DrmFourcc) -> VideoFormat {
match fourcc{
DrmFourcc::Argb8888 => VideoFormat::BGRA,
DrmFourcc::Abgr8888 => VideoFormat::RGBA,
DrmFourcc::Xrgb8888 => VideoFormat::BGRx,
DrmFourcc::Xbgr8888 => VideoFormat::RGBx,
DrmFourcc::Abgr2101010 => VideoFormat::ABGR_210LE,
DrmFourcc::Xbgr2101010 => VideoFormat::xBGR_210LE,
_ => panic!("Unsupported format"),
}
}
#[allow(non_upper_case_globals)]
fn spa_to_fourcc(spa: VideoFormat) -> FourCC {
fn spa_to_fourcc(spa: VideoFormat) -> DrmFourcc {
match spa {
VideoFormat::BGRA => DRM_FORMAT_ARGB8888.into(),
VideoFormat::RGBA => DRM_FORMAT_ABGR8888.into(),
VideoFormat::BGRx => DRM_FORMAT_XRGB8888.into(),
VideoFormat::RGBx => DRM_FORMAT_XBGR8888.into(),
VideoFormat::ABGR_210LE => DRM_FORMAT_ABGR2101010.into(),
VideoFormat::xBGR_210LE => DRM_FORMAT_XBGR2101010.into(),
VideoFormat::BGRA => DrmFourcc::Argb8888,
VideoFormat::RGBA => DrmFourcc::Abgr8888,
VideoFormat::BGRx => DrmFourcc::Xrgb8888,
VideoFormat::RGBx => DrmFourcc::Xbgr8888,
VideoFormat::ABGR_210LE => DrmFourcc::Abgr2101010,
VideoFormat::xBGR_210LE => DrmFourcc::Xbgr2101010,
_ => panic!("Unsupported format"),
}
}

View File

@@ -6,12 +6,13 @@ use std::{
thread::JoinHandle,
};
use drm_fourcc::{DrmFormat, DrmFourcc, DrmModifier};
use smithay_client_toolkit::reexports::protocols_wlr::export_dmabuf::v1::client::zwlr_export_dmabuf_frame_v1::{self, ZwlrExportDmabufFrameV1};
use wayland_client::{Connection, QueueHandle, Dispatch, Proxy};
use crate::{
WlxCapture,
frame::{DmabufFrame, DrmFormat, FramePlane, WlxFrame},
frame::{DmabufFrame, FrameFormat, FramePlane, WlxFrame},
wayland::WlxClient,
};
@@ -168,13 +169,27 @@ fn request_dmabuf_frame(
num_objects,
..
} => {
let mut new_frame = DmabufFrame::default();
new_frame.format.width = width;
new_frame.format.height = height;
new_frame.format.fourcc.value = format;
let mut new_frame = DmabufFrame {
format: FrameFormat {
width,
height,
drm_format: DrmFormat {
code: match DrmFourcc::try_from(format) {
Ok(code) => code,
Err(e) => {
log::error!("Unrecognized fourcc: {e:?}");
return;
}
},
modifier: DrmModifier::Invalid,
},
transform,
},
mouse: None,
num_planes: num_objects as _,
planes: Default::default(),
};
new_frame.format.set_mod(mod_high, mod_low);
new_frame.format.transform = transform;
new_frame.num_planes = num_objects as _;
frame = Some(new_frame);
}
zwlr_export_dmabuf_frame_v1::Event::Object {

View File

@@ -1,3 +1,4 @@
use drm_fourcc::{DrmFormat, DrmFourcc, DrmModifier};
use libc::{O_CREAT, O_RDWR, S_IRUSR, S_IWUSR};
use std::{
any::Any,
@@ -18,10 +19,7 @@ use smithay_client_toolkit::reexports::protocols_wlr::screencopy::v1::client::zw
use crate::{
WlxCapture,
frame::{
DRM_FORMAT_ARGB8888, DRM_FORMAT_XRGB8888, DrmFormat, FourCC, FrameFormat, FramePlane,
MemFdFrame, WlxFrame,
},
frame::{FrameFormat, FramePlane, MemFdFrame, WlxFrame},
wayland::WlxClient,
};
@@ -44,7 +42,7 @@ impl Drop for BufData {
enum ScreenCopyEvent {
Buffer {
data: BufData,
fourcc: FourCC,
drm_format: DrmFormat,
width: u32,
height: u32,
stride: u32,
@@ -213,7 +211,7 @@ where
match event {
ScreenCopyEvent::Buffer {
data,
fourcc,
drm_format,
width,
height,
stride,
@@ -222,16 +220,15 @@ where
format: FrameFormat {
width,
height,
fourcc,
drm_format,
transform,
..Default::default()
},
plane: FramePlane {
fd: Some(data.fd),
offset: 0,
stride: stride as _,
},
..Default::default()
mouse: None,
};
log::trace!("{}: Received screencopy buffer, copying", name.as_ref());
if wait_for_damage {
@@ -331,7 +328,10 @@ impl Dispatch<ZwlrScreencopyFrameV1, SyncSender<ScreenCopyEvent>> for WlxClient
wl_pool,
fd,
},
fourcc,
drm_format: DrmFormat {
code: fourcc,
modifier: DrmModifier::Invalid,
},
width,
height,
stride,
@@ -346,12 +346,12 @@ impl Dispatch<ZwlrScreencopyFrameV1, SyncSender<ScreenCopyEvent>> for WlxClient
}
}
fn fourcc_from_wlshm(shm_format: Format) -> Option<FourCC> {
fn fourcc_from_wlshm(shm_format: Format) -> Option<DrmFourcc> {
match shm_format {
Format::Argb8888 => Some(FourCC::from(DRM_FORMAT_ARGB8888)),
Format::Xrgb8888 => Some(FourCC::from(DRM_FORMAT_XRGB8888)),
Format::Abgr8888 => Some(FourCC::from(DRM_FORMAT_ARGB8888)),
Format::Xbgr8888 => Some(FourCC::from(DRM_FORMAT_XRGB8888)),
Format::Argb8888 => Some(DrmFourcc::Argb8888),
Format::Xrgb8888 => Some(DrmFourcc::Xrgb8888),
Format::Abgr8888 => Some(DrmFourcc::Abgr8888),
Format::Xbgr8888 => Some(DrmFourcc::Xbgr8888),
_ => None,
}
}

View File

@@ -8,11 +8,12 @@ use std::{
},
};
use drm_fourcc::{DrmFormat, DrmFourcc, DrmModifier};
use rxscreen::monitor::Monitor;
use crate::{
WlxCapture,
frame::{DRM_FORMAT_XRGB8888, DrmFormat, FrameFormat, MemPtrFrame, MouseMeta, WlxFrame},
frame::{FrameFormat, MemPtrFrame, MouseMeta, Transform, WlxFrame},
};
pub struct XshmScreen {
@@ -101,8 +102,11 @@ where
format: FrameFormat {
width: image.width() as _,
height: image.height() as _,
fourcc: DRM_FORMAT_XRGB8888.into(),
..Default::default()
drm_format: DrmFormat {
code: DrmFourcc::Xrgb8888,
modifier: DrmModifier::Invalid,
},
transform: Transform::Normal,
},
ptr: unsafe { image.as_ptr() as _ },
size,

View File

@@ -82,23 +82,18 @@ vulkano = { workspace = true }
vulkano-shaders = { workspace = true }
wgui = { path = "../wgui" }
wayvr_ipc = { workspace = true }
bytes = { version = "1.11.0" }
################################
#WayVR-only deps
# Wayland Server deps
################################
khronos-egl = { version = "6.0.0", features = ["static"], optional = true }
smithay = { version = "0.7.0", default-features = false, features = [
"renderer_gl",
"backend_egl",
"backend_drm",
"backend_vulkan",
"xwayland",
"wayland_frontend",
], optional = true }
uuid = { version = "1.19.0", features = ["v4", "fast-rng"], optional = true }
wayland-client = { workspace = true, optional = true }
wayland-egl = { version = "0.32.8", optional = true }
bytes = { version = "1.11.0", optional = true }
wayvr-ipc = { path = "../wayvr-ipc", default-features = false, optional = true }
rust-embed = { workspace = true }
signal-hook = "0.3.18"
################################
@@ -106,6 +101,7 @@ signal-hook = "0.3.18"
[build-dependencies]
regex = { version = "1.12.2" }
# TODO: rename "wayvr" feature to "wayland-server"
[features]
default = ["openvr", "openxr", "osc", "x11", "wayland", "wayvr"]
openvr = ["dep:ovr_overlay", "dep:json"]
@@ -116,13 +112,5 @@ wayland = ["pipewire", "wlx-capture/wlr", "xkbcommon/wayland"]
pipewire = ["wlx-capture/pipewire"]
uidev = ["dep:winit"]
xcb = ["dep:xcb"]
wayvr = [
"dep:khronos-egl",
"dep:smithay",
"dep:uuid",
"dep:wayland-client",
"dep:wayland-egl",
"dep:bytes",
"dep:wayvr-ipc",
]
wayvr = ["dep:smithay", "dep:uuid", "dep:wayland-client"]
as-raw-xcb-connection = []

View File

@@ -10,7 +10,7 @@ use smallvec::{SmallVec, smallvec};
use wlx_common::common::LeftRight;
use wlx_common::windowing::{OverlayWindowState, Positioning};
use crate::backend::task::OverlayTask;
use crate::backend::task::{InputTask, OverlayTask};
use crate::overlays::anchor::{ANCHOR_NAME, GRAB_HELP_NAME};
use crate::overlays::watch::WATCH_NAME;
use crate::state::{AppSession, AppState};
@@ -65,6 +65,18 @@ impl InputState {
}
}
pub fn handle_task(&mut self, task: InputTask) {
match task {
InputTask::Haptics { device, haptics } => {
if let Some(pointer) = self.pointers.get_mut(device) {
pointer.pending_haptics = Some(haptics);
} else {
log::warn!("Can't trigger haptics on non-existing device: {device}");
}
}
}
}
pub const fn pre_update(&mut self) {
self.pointers[0].before = self.pointers[0].now;
self.pointers[1].before = self.pointers[1].now;
@@ -218,6 +230,7 @@ pub struct Pointer {
pub now: PointerState,
pub before: PointerState,
pub last_click: Instant,
pub pending_haptics: Option<Haptics>,
pub(super) interaction: InteractionState,
}
@@ -231,6 +244,7 @@ impl Pointer {
now: PointerState::default(),
before: PointerState::default(),
last_click: Instant::now(),
pending_haptics: None,
interaction: InteractionState::default(),
}
}
@@ -340,6 +354,8 @@ where
{
// already grabbing, ignore everything else
let mut pointer = &mut app.input_state.pointers[idx];
let pending_haptics = pointer.pending_haptics.take();
if let Some(grab_data) = pointer.interaction.grabbed {
if let Some(grabbed) = overlays.mut_by_id(grab_data.grabbed_id) {
handle_grabbed(idx, grabbed, app);
@@ -347,13 +363,13 @@ where
log::warn!("Grabbed overlay {:?} does not exist", grab_data.grabbed_id);
pointer.interaction.grabbed = None;
}
return (0.1, None);
return (0.1, pending_haptics);
}
let hovered_id = pointer.interaction.hovered_id.take();
let (Some(mut hit), haptics) = get_nearest_hit(idx, overlays, app) else {
handle_no_hit(idx, hovered_id, overlays, app);
return (0.0, None); // no hit
return (0.0, pending_haptics); // no hit
};
// focus change
@@ -378,7 +394,7 @@ where
let Some(hovered) = overlays.mut_by_id(hit.overlay) else {
log::warn!("Hit overlay {:?} does not exist", hit.overlay);
return (0.0, None); // no hit
return (0.0, pending_haptics); // no hit
};
pointer = &mut app.input_state.pointers[idx];
pointer.interaction.hovered_id = Some(hit.overlay);
@@ -447,7 +463,7 @@ where
}
}
(hit.dist, haptics)
(hit.dist, haptics.or(pending_haptics))
}
fn handle_no_hit<O>(

View File

@@ -42,9 +42,6 @@ use crate::{
},
};
#[cfg(feature = "wayvr")]
use crate::{backend::wayvr::WayVRAction, overlays::wayvr::wayvr_action};
pub mod helpers;
pub mod input;
pub mod lines;
@@ -157,51 +154,50 @@ pub fn openvr_run(show_by_default: bool, headless: bool) -> Result<(), BackendEr
}
FRAME_COUNTER.fetch_add(1, Ordering::Relaxed);
// extremely cursed
const VREVENT_QUIT: u32 = EVREventType::VREvent_Quit as u32;
const VREVENT_TRACKED_ACTIVATED: u32 = EVREventType::VREvent_TrackedDeviceActivated as u32;
const VREVENT_TRACKED_DEACTIVATED: u32 =
EVREventType::VREvent_TrackedDeviceDeactivated as u32;
const VREVENT_TRACKED_UPDATED: u32 = EVREventType::VREvent_TrackedDeviceUpdated as u32;
const VREVENT_SEATED_ZERO: u32 = EVREventType::VREvent_SeatedZeroPoseReset as u32;
const VREVENT_STANDING_ZERO: u32 = EVREventType::VREvent_StandingZeroPoseReset as u32;
const VREVENT_CHAPERONE_CHANGED: u32 =
EVREventType::VREvent_ChaperoneUniverseHasChanged as u32;
const VREVENT_SCENE_APP_CHANGED: u32 = EVREventType::VREvent_SceneApplicationChanged as u32;
const VREVENT_IPD_CHANGED: u32 = EVREventType::VREvent_IpdChanged as u32;
{
// extremely cursed
const EV_QUIT: u32 = EVREventType::VREvent_Quit as u32;
const EV_DEV_ACTIVATED: u32 = EVREventType::VREvent_TrackedDeviceActivated as u32;
const EV_DEV_DEACTIVATED: u32 = EVREventType::VREvent_TrackedDeviceDeactivated as u32;
const EV_DEV_UPDATED: u32 = EVREventType::VREvent_TrackedDeviceUpdated as u32;
const EV_SEAT_ZERO: u32 = EVREventType::VREvent_SeatedZeroPoseReset as u32;
const EV_STAND_ZERO: u32 = EVREventType::VREvent_StandingZeroPoseReset as u32;
const EV_CHAP_CHANGED: u32 = EVREventType::VREvent_ChaperoneUniverseHasChanged as u32;
const EV_SCENE_CHANGED: u32 = EVREventType::VREvent_SceneApplicationChanged as u32;
const EV_IPD_CHANGED: u32 = EVREventType::VREvent_IpdChanged as u32;
while let Some(event) = system_mgr.poll_next_event() {
match event.event_type {
VREVENT_QUIT => {
log::warn!("Received quit event, shutting down.");
break 'main_loop;
}
VREVENT_TRACKED_ACTIVATED
| VREVENT_TRACKED_DEACTIVATED
| VREVENT_TRACKED_UPDATED => {
next_device_update = Instant::now();
}
VREVENT_SEATED_ZERO
| VREVENT_STANDING_ZERO
| VREVENT_CHAPERONE_CHANGED
| VREVENT_SCENE_APP_CHANGED => {
playspace.playspace_changed(&mut compositor_mgr, &mut chaperone_mgr);
}
VREVENT_IPD_CHANGED => {
if let Ok(ipd) = system_mgr.get_tracked_device_property::<f32>(
TrackedDeviceIndex::HMD,
ETrackedDeviceProperty::Prop_UserIpdMeters_Float,
) {
let ipd = (ipd * 1000.0).round();
if (ipd - app.input_state.ipd).abs() > 0.05 {
log::info!("IPD: {:.1} mm -> {:.1} mm", app.input_state.ipd, ipd);
Toast::new(ToastTopic::IpdChange, "IPD".into(), format!("{ipd:.1} mm"))
.submit(&mut app);
}
app.input_state.ipd = ipd;
while let Some(event) = system_mgr.poll_next_event() {
match event.event_type {
EV_QUIT => {
log::warn!("Received quit event, shutting down.");
break 'main_loop;
}
EV_DEV_ACTIVATED | EV_DEV_DEACTIVATED | EV_DEV_UPDATED => {
next_device_update = Instant::now();
}
EV_SEAT_ZERO | EV_STAND_ZERO | EV_CHAP_CHANGED | EV_SCENE_CHANGED => {
playspace.playspace_changed(&mut compositor_mgr, &mut chaperone_mgr);
}
EV_IPD_CHANGED => {
if let Ok(ipd) = system_mgr.get_tracked_device_property::<f32>(
TrackedDeviceIndex::HMD,
ETrackedDeviceProperty::Prop_UserIpdMeters_Float,
) {
let ipd = (ipd * 1000.0).round();
if (ipd - app.input_state.ipd).abs() > 0.05 {
log::info!("IPD: {:.1} mm -> {:.1} mm", app.input_state.ipd, ipd);
Toast::new(
ToastTopic::IpdChange,
"IPD".into(),
format!("{ipd:.1} mm"),
)
.submit(&mut app);
}
app.input_state.ipd = ipd;
}
}
_ => {}
}
_ => {}
}
}
@@ -220,6 +216,9 @@ pub fn openvr_run(show_by_default: bool, headless: bool) -> Result<(), BackendEr
while let Some(task) = due_tasks.pop_front() {
match task {
TaskType::Input(task) => {
app.input_state.handle_task(task);
}
TaskType::Overlay(task) => {
overlays.handle_task(&mut app, task)?;
}
@@ -232,9 +231,7 @@ pub fn openvr_run(show_by_default: bool, headless: bool) -> Result<(), BackendEr
}
},
#[cfg(feature = "wayvr")]
TaskType::WayVR(action) => {
wayvr_action(&mut app, &mut overlays, &action);
}
TaskType::WayVR(_action) => { /* TODO */ }
}
}
@@ -264,9 +261,7 @@ pub fn openvr_run(show_by_default: bool, headless: bool) -> Result<(), BackendEr
.pointers
.iter()
.any(|p| p.now.toggle_dashboard && !p.before.toggle_dashboard)
{
wayvr_action(&mut app, &mut overlays, &WayVRAction::ToggleDashboard);
}
{ /* TODO */ }
overlays
.values_mut()
@@ -303,11 +298,10 @@ pub fn openvr_run(show_by_default: bool, headless: bool) -> Result<(), BackendEr
let _ = sender.send_params(&overlays, &app.input_state.devices);
}
#[cfg(feature = "wayvr")]
if let Err(e) =
crate::overlays::wayvr::tick_events::<OpenVrOverlayData>(&mut app, &mut overlays)
crate::ipc::events::tick_events::<OpenVrOverlayData>(&mut app, &mut overlays)
{
log::error!("WayVR tick_events failed: {e:?}");
log::error!("WayVR IPC tick_events failed: {e:?}");
}
log::trace!("Rendering frame");
@@ -335,11 +329,6 @@ pub fn openvr_run(show_by_default: bool, headless: bool) -> Result<(), BackendEr
.values_mut()
.for_each(|o| o.after_render(universe.clone(), &mut overlay_mgr, &app.gfx));
#[cfg(feature = "wayvr")]
if let Some(wayvr) = &app.wayvr {
wayvr.borrow_mut().data.tick_finish()?;
}
// chaperone
} // main_loop

View File

@@ -35,9 +35,6 @@ use crate::{
},
};
#[cfg(feature = "wayvr")]
use crate::{backend::wayvr::WayVRAction, overlays::wayvr::wayvr_action};
mod blocker;
mod helpers;
mod input;
@@ -149,8 +146,8 @@ pub fn openxr_run(show_by_default: bool, headless: bool) -> Result<(), BackendEr
};
let pointer_lines = [
lines.allocate(&xr_state, &mut app)?,
lines.allocate(&xr_state, &mut app)?,
lines.allocate(&xr_state, &app)?,
lines.allocate(&xr_state, &app)?,
];
let watch_id = overlays.lookup(WATCH_NAME).unwrap(); // want panic
@@ -303,9 +300,7 @@ pub fn openxr_run(show_by_default: bool, headless: bool) -> Result<(), BackendEr
.pointers
.iter()
.any(|p| p.now.toggle_dashboard && !p.before.toggle_dashboard)
{
wayvr_action(&mut app, &mut overlays, &WayVRAction::ToggleDashboard);
}
{ /* TODO */ }
watch_fade(&mut app, overlays.mut_by_id(watch_id).unwrap()); // want panic
if let Some(ref mut space_mover) = playspace {
@@ -373,9 +368,9 @@ pub fn openxr_run(show_by_default: bool, headless: bool) -> Result<(), BackendEr
#[cfg(feature = "wayvr")]
if let Err(e) =
crate::overlays::wayvr::tick_events::<OpenXrOverlayData>(&mut app, &mut overlays)
crate::ipc::events::tick_events::<OpenXrOverlayData>(&mut app, &mut overlays)
{
log::error!("WayVR tick_events failed: {e:?}");
log::error!("WayVR IPC tick_events failed: {e:?}");
}
// Begin rendering
@@ -458,11 +453,6 @@ pub fn openxr_run(show_by_default: bool, headless: bool) -> Result<(), BackendEr
}
// End layer composition
#[cfg(feature = "wayvr")]
if let Some(wayvr) = &app.wayvr {
wayvr.borrow_mut().data.tick_finish()?;
}
// Begin layer submit
layers.sort_by(|a, b| b.0.total_cmp(&a.0));
@@ -489,6 +479,9 @@ pub fn openxr_run(show_by_default: bool, headless: bool) -> Result<(), BackendEr
app.tasks.retrieve_due(&mut due_tasks);
while let Some(task) = due_tasks.pop_front() {
match task {
TaskType::Input(task) => {
app.input_state.handle_task(task);
}
TaskType::Overlay(task) => {
overlays.handle_task(&mut app, task)?;
}
@@ -499,10 +492,7 @@ pub fn openxr_run(show_by_default: bool, headless: bool) -> Result<(), BackendEr
}
#[cfg(feature = "openvr")]
TaskType::OpenVR(_) => {}
#[cfg(feature = "wayvr")]
TaskType::WayVR(action) => {
wayvr_action(&mut app, &mut overlays, &action);
}
TaskType::WayVR(_action) => { /* TODO */ }
}
}

View File

@@ -8,6 +8,7 @@ use std::{
use serde::Deserialize;
use crate::{
backend::input,
state::AppState,
windowing::{OverlaySelector, window::OverlayWindowConfig},
};
@@ -43,6 +44,13 @@ impl Ord for AppTask {
}
}
pub enum InputTask {
Haptics {
device: usize,
haptics: input::Haptics,
},
}
#[cfg(feature = "openvr")]
pub enum OpenVrTask {
ColorGain(ColorChannel, f32),
@@ -87,6 +95,7 @@ pub enum OverlayTask {
}
pub enum TaskType {
Input(InputTask),
Overlay(OverlayTask),
Playspace(PlayspaceTask),
#[cfg(feature = "openvr")]

View File

@@ -14,12 +14,11 @@ use crate::backend::wayvr::{ExternalProcessRequest, WayVRTask};
use super::{
ProcessWayVREnv,
comp::{self, ClientState},
display, process,
process,
};
pub struct WayVRClient {
pub client: wayland_server::Client,
pub display_handle: display::DisplayHandle,
pub pid: u32,
}
@@ -103,10 +102,13 @@ impl WayVRCompositor {
});
}
pub fn cleanup_handles(&mut self) {
self.state.cleanup();
}
fn accept_connection(
&mut self,
stream: UnixStream,
displays: &mut display::DisplayVec,
processes: &mut process::ProcessVec,
) -> anyhow::Result<()> {
let client = self
@@ -126,16 +128,12 @@ impl WayVRCompositor {
{
// Find process with matching auth key
if process.auth_key.as_str() == auth_key {
// Check if display handle is valid
if displays.get(&process.display_handle).is_some() {
// Add client
self.add_client(WayVRClient {
client,
display_handle: process.display_handle,
pid: creds.pid as u32,
});
return Ok(());
}
// Add client
self.add_client(WayVRClient {
client,
pid: creds.pid as u32,
});
return Ok(());
}
}
}
@@ -158,13 +156,9 @@ impl WayVRCompositor {
Ok(())
}
fn accept_connections(
&mut self,
displays: &mut display::DisplayVec,
processes: &mut process::ProcessVec,
) -> anyhow::Result<()> {
fn accept_connections(&mut self, processes: &mut process::ProcessVec) -> anyhow::Result<()> {
if let Some(stream) = self.listener.accept()?
&& let Err(e) = self.accept_connection(stream, displays, processes)
&& let Err(e) = self.accept_connection(stream, processes)
{
log::error!("Failed to accept connection: {e}");
}
@@ -172,12 +166,8 @@ impl WayVRCompositor {
Ok(())
}
pub fn tick_wayland(
&mut self,
displays: &mut display::DisplayVec,
processes: &mut process::ProcessVec,
) -> anyhow::Result<()> {
if let Err(e) = self.accept_connections(displays, processes) {
pub fn tick_wayland(&mut self, processes: &mut process::ProcessVec) -> anyhow::Result<()> {
if let Err(e) = self.accept_connections(processes) {
log::error!("accept_connections failed: {e}");
}

View File

@@ -1,18 +1,17 @@
use smithay::backend::allocator::dmabuf::Dmabuf;
use smithay::backend::renderer::ImportDma;
use smithay::backend::renderer::gles::GlesRenderer;
use smithay::backend::renderer::utils::on_commit_buffer_handler;
use smithay::backend::renderer::{BufferType, buffer_type};
use smithay::input::{Seat, SeatHandler, SeatState};
use smithay::reexports::wayland_protocols::xdg::shell::server::xdg_toplevel;
use smithay::reexports::wayland_server;
use smithay::reexports::wayland_server::Resource;
use smithay::reexports::wayland_server::protocol::{wl_buffer, wl_seat, wl_surface};
use smithay::reexports::wayland_server::protocol::{wl_buffer, wl_output, wl_seat, wl_surface};
use smithay::wayland::buffer::BufferHandler;
use smithay::wayland::dmabuf::{
DmabufFeedback, DmabufGlobal, DmabufHandler, DmabufState, ImportNotifier,
DmabufFeedback, DmabufGlobal, DmabufHandler, DmabufState, ImportNotifier, get_dmabuf,
};
use smithay::wayland::output::OutputHandler;
use smithay::wayland::shm::{ShmHandler, ShmState};
use smithay::wayland::shm::{ShmHandler, ShmState, with_buffer_contents};
use smithay::wayland::single_pixel_buffer::get_single_pixel_buffer;
use smithay::{
delegate_compositor, delegate_data_device, delegate_dmabuf, delegate_output, delegate_seat,
delegate_shm, delegate_xdg_shell,
@@ -23,7 +22,7 @@ use std::sync::{Arc, Mutex};
use smithay::utils::Serial;
use smithay::wayland::compositor::{
self, SurfaceAttributes, TraversalAction, with_surface_tree_downward,
self, BufferAssignment, SurfaceAttributes, TraversalAction, with_surface_tree_downward,
};
use smithay::wayland::selection::SelectionHandler;
@@ -37,11 +36,14 @@ use wayland_server::Client;
use wayland_server::backend::{ClientData, ClientId, DisconnectReason};
use wayland_server::protocol::wl_surface::WlSurface;
use crate::backend::wayvr::SurfaceBufWithImage;
use crate::backend::wayvr::image_importer::ImageImporter;
use crate::ipc::event_queue::SyncEventQueue;
use super::WayVRTask;
use super::event_queue::SyncEventQueue;
pub struct Application {
pub gles_renderer: GlesRenderer,
pub image_importer: ImageImporter,
pub dmabuf_state: (DmabufState, DmabufGlobal, Option<DmabufFeedback>),
pub compositor: compositor::CompositorState,
pub xdg_shell: XdgShellState,
@@ -56,6 +58,10 @@ impl Application {
pub fn check_redraw(&mut self, surface: &WlSurface) -> bool {
self.redraw_requests.remove(&surface.id())
}
pub fn cleanup(&mut self) {
self.image_importer.cleanup();
}
}
impl compositor::CompositorHandler for Application {
@@ -71,7 +77,74 @@ impl compositor::CompositorHandler for Application {
}
fn commit(&mut self, surface: &WlSurface) {
on_commit_buffer_handler::<Self>(surface);
smithay::wayland::compositor::with_states(surface, |states| {
let mut guard = states.cached_state.get::<SurfaceAttributes>();
let attrs = guard.current();
match attrs.buffer.take() {
Some(BufferAssignment::NewBuffer(buffer)) => {
let current = SurfaceBufWithImage::get_from_surface(states);
if current.is_none_or(|c| c.buffer != buffer) {
match buffer_type(&buffer) {
Some(BufferType::Dma) => {
let dmabuf = get_dmabuf(&buffer).unwrap(); // always Ok due to buffer_type
if let Ok(image) =
self.image_importer.get_or_import_dmabuf(dmabuf.clone())
{
let sbwi = SurfaceBufWithImage {
image,
buffer,
transform: wl_transform_to_frame_transform(
attrs.buffer_transform,
),
scale: attrs.buffer_scale,
};
sbwi.apply_to_surface(states);
}
}
Some(BufferType::Shm) => {
with_buffer_contents(&buffer, |data, size, buf| {
if let Ok(image) =
self.image_importer.import_shm(data, size, buf)
{
let sbwi = SurfaceBufWithImage {
image,
buffer: buffer.clone(),
transform: wl_transform_to_frame_transform(
attrs.buffer_transform,
),
scale: attrs.buffer_scale,
};
sbwi.apply_to_surface(states);
}
});
}
Some(BufferType::SinglePixel) => {
let spb = get_single_pixel_buffer(&buffer).unwrap(); // always Ok
if let Ok(image) = self.image_importer.import_spb(spb) {
let sbwi = SurfaceBufWithImage {
image,
buffer,
transform: wl_transform_to_frame_transform(
// does this even matter
attrs.buffer_transform,
),
scale: attrs.buffer_scale,
};
sbwi.apply_to_surface(states);
}
}
Some(other) => log::warn!("Unsupported wl_buffer format: {other:?}"),
None => { /* don't draw anything */ }
}
}
}
Some(BufferAssignment::Removed) => {}
None => {}
}
});
self.redraw_requests.insert(surface.id());
}
}
@@ -197,7 +270,7 @@ impl DmabufHandler for Application {
dmabuf: Dmabuf,
notifier: ImportNotifier,
) {
if self.gles_renderer.import_dmabuf(&dmabuf, None).is_ok() {
if self.image_importer.get_or_import_dmabuf(dmabuf).is_ok() {
let _ = notifier.successful::<Self>();
} else {
notifier.failed();
@@ -234,3 +307,19 @@ pub fn send_frames_surface_tree(surface: &wl_surface::WlSurface, time: u32) {
|_, _, &()| true,
);
}
fn wl_transform_to_frame_transform(
transform: wl_output::Transform,
) -> wlx_capture::frame::Transform {
match transform {
wl_output::Transform::Normal => wlx_capture::frame::Transform::Normal,
wl_output::Transform::_90 => wlx_capture::frame::Transform::Rotated90,
wl_output::Transform::_180 => wlx_capture::frame::Transform::Rotated180,
wl_output::Transform::_270 => wlx_capture::frame::Transform::Rotated270,
wl_output::Transform::Flipped => wlx_capture::frame::Transform::Flipped,
wl_output::Transform::Flipped90 => wlx_capture::frame::Transform::Flipped90,
wl_output::Transform::Flipped180 => wlx_capture::frame::Transform::Flipped180,
wl_output::Transform::Flipped270 => wlx_capture::frame::Transform::Flipped270,
_ => wlx_capture::frame::Transform::Undefined,
}
}

View File

@@ -1,605 +0,0 @@
use std::{cell::RefCell, rc::Rc, sync::Arc};
use smithay::{
backend::renderer::{
Bind, Color32F, Frame, Renderer,
element::{
Kind,
surface::{WaylandSurfaceRenderElement, render_elements_from_surface_tree},
},
gles::{GlesRenderer, GlesTexture, ffi},
utils::draw_render_elements,
},
input,
utils::{Logical, Point, Rectangle, Size, Transform},
wayland::shell::xdg::ToplevelSurface,
};
use wayvr_ipc::packet_server;
use crate::{
backend::wayvr::time::get_millis, gen_id, subsystem::hid::WheelDelta, windowing::OverlayID,
};
use super::{
BlitMethod, WayVRSignal, client::WayVRCompositor, comp::send_frames_surface_tree, egl_data,
event_queue::SyncEventQueue, process, smithay_wrapper, time, window,
};
fn generate_auth_key() -> String {
let uuid = uuid::Uuid::new_v4();
uuid.to_string()
}
#[derive(Debug)]
pub struct DisplayWindow {
pub window_handle: window::WindowHandle,
pub toplevel: ToplevelSurface,
pub process_handle: process::ProcessHandle,
}
pub struct SpawnProcessResult {
pub auth_key: String,
pub child: std::process::Child,
}
#[derive(Debug)]
pub enum DisplayTask {
ProcessCleanup(process::ProcessHandle),
}
const MAX_DISPLAY_SIZE: u16 = 8192;
#[derive(Debug)]
pub struct Display {
// Display info stuff
pub width: u16,
pub height: u16,
pub name: String,
pub visible: bool,
pub layout: packet_server::WvrDisplayWindowLayout,
pub overlay_id: Option<OverlayID>,
pub wants_redraw: bool,
pub rendered_frame_count: u32,
pub primary: bool,
pub wm: Rc<RefCell<window::WindowManager>>,
pub displayed_windows: Vec<DisplayWindow>,
wayland_env: super::WaylandEnv,
last_pressed_time_ms: u64,
pub no_windows_since: Option<u64>,
// Render data stuff
gles_texture: GlesTexture, // TODO: drop texture
egl_image: khronos_egl::Image,
egl_data: Rc<egl_data::EGLData>,
pub render_data: egl_data::RenderData,
pub tasks: SyncEventQueue<DisplayTask>,
}
impl Drop for Display {
fn drop(&mut self) {
let _ = self
.egl_data
.egl
.destroy_image(self.egl_data.display, self.egl_image);
}
}
pub struct DisplayInitParams<'a> {
pub wm: Rc<RefCell<window::WindowManager>>,
pub config: &'a super::Config,
pub renderer: &'a mut GlesRenderer,
pub egl_data: Rc<egl_data::EGLData>,
pub wayland_env: super::WaylandEnv,
pub width: u16,
pub height: u16,
pub name: &'a str,
pub primary: bool,
}
impl Display {
pub fn new(params: DisplayInitParams) -> anyhow::Result<Self> {
if params.width > MAX_DISPLAY_SIZE {
anyhow::bail!(
"display width ({}) is larger than {}",
params.width,
MAX_DISPLAY_SIZE
);
}
if params.height > MAX_DISPLAY_SIZE {
anyhow::bail!(
"display height ({}) is larger than {}",
params.height,
MAX_DISPLAY_SIZE
);
}
let tex_format = ffi::RGBA;
let internal_format = ffi::RGBA8;
let tex_id = params.renderer.with_context(|gl| {
smithay_wrapper::create_framebuffer_texture(
gl,
u32::from(params.width),
u32::from(params.height),
tex_format,
internal_format,
)
})?;
let egl_image = params.egl_data.create_egl_image(tex_id)?;
let render_data = match params.config.blit_method {
BlitMethod::Dmabuf => match params.egl_data.create_dmabuf_data(&egl_image) {
Ok(dmabuf_data) => egl_data::RenderData::Dmabuf(dmabuf_data),
Err(e) => {
log::error!(
"create_dmabuf_data failed: {e:?}. Using software blitting (This will be slow!)"
);
egl_data::RenderData::Software(None)
}
},
BlitMethod::Software => egl_data::RenderData::Software(None),
};
let opaque = false;
let size = (i32::from(params.width), i32::from(params.height)).into();
let gles_texture = unsafe {
GlesTexture::from_raw(params.renderer, Some(tex_format), opaque, tex_id, size)
};
Ok(Self {
egl_data: params.egl_data,
width: params.width,
height: params.height,
name: String::from(params.name),
primary: params.primary,
wayland_env: params.wayland_env,
wm: params.wm,
displayed_windows: Vec::new(),
render_data,
egl_image,
gles_texture,
last_pressed_time_ms: 0,
no_windows_since: None,
overlay_id: None,
tasks: SyncEventQueue::new(),
visible: true,
wants_redraw: true,
rendered_frame_count: 0,
layout: packet_server::WvrDisplayWindowLayout::Tiling,
})
}
pub fn as_packet(&self, handle: DisplayHandle) -> packet_server::WvrDisplay {
packet_server::WvrDisplay {
width: self.width,
height: self.height,
name: self.name.clone(),
visible: self.visible,
handle: handle.as_packet(),
}
}
pub fn add_window(
&mut self,
window_handle: window::WindowHandle,
process_handle: process::ProcessHandle,
toplevel: &ToplevelSurface,
) {
log::debug!("Attaching toplevel surface into display");
self.displayed_windows.push(DisplayWindow {
window_handle,
process_handle,
toplevel: toplevel.clone(),
});
self.reposition_windows();
}
pub fn remove_window(&mut self, window_handle: window::WindowHandle) {
self.displayed_windows
.retain(|disp| disp.window_handle != window_handle);
}
pub fn reposition_windows(&mut self) {
let window_count = self.displayed_windows.len();
match &self.layout {
packet_server::WvrDisplayWindowLayout::Tiling => {
let mut i = 0;
for win in &mut self.displayed_windows {
if let Some(window) = self.wm.borrow_mut().windows.get_mut(&win.window_handle) {
if !window.visible {
continue;
}
let d_cur = i as f32 / window_count as f32;
let d_next = (i + 1) as f32 / window_count as f32;
let left = (d_cur * f32::from(self.width)) as i32;
let right = (d_next * f32::from(self.width)) as i32;
window.set_pos(left, 0);
window.set_size((right - left) as u32, u32::from(self.height));
i += 1;
}
}
}
packet_server::WvrDisplayWindowLayout::Stacking(opts) => {
let do_margins = |margins: &packet_server::Margins, window: &mut window::Window| {
let top = i32::from(margins.top);
let bottom = i32::from(self.height) - i32::from(margins.bottom);
let left = i32::from(margins.left);
let right = i32::from(self.width) - i32::from(margins.right);
let width = right - left;
let height = bottom - top;
if width < 0 || height < 0 {
return; // wrong parameters, do nothing!
}
window.set_pos(left, top);
window.set_size(width as u32, height as u32);
};
let mut i = 0;
for win in &mut self.displayed_windows {
if let Some(window) = self.wm.borrow_mut().windows.get_mut(&win.window_handle) {
if !window.visible {
continue;
}
do_margins(
if i == 0 {
&opts.margins_first
} else {
&opts.margins_rest
},
window,
);
i += 1;
}
}
}
}
}
pub fn tick(
&mut self,
config: &super::Config,
handle: &DisplayHandle,
signals: &mut SyncEventQueue<WayVRSignal>,
) {
if self.visible {
if !self.displayed_windows.is_empty() {
self.no_windows_since = None;
} else if let Some(auto_hide_delay) = config.auto_hide_delay
&& let Some(s) = self.no_windows_since
&& s + u64::from(auto_hide_delay) < get_millis()
{
// Auto-hide after specific time
signals.send(WayVRSignal::DisplayVisibility(*handle, false));
}
}
while let Some(task) = self.tasks.read() {
match task {
DisplayTask::ProcessCleanup(process_handle) => {
let count = self.displayed_windows.len();
self.displayed_windows
.retain(|win| win.process_handle != process_handle);
log::info!(
"Cleanup finished for display \"{}\". Current window count: {}",
self.name,
self.displayed_windows.len()
);
self.no_windows_since = Some(get_millis());
if count != self.displayed_windows.len() {
signals.send(WayVRSignal::BroadcastStateChanged(
packet_server::WvrStateChanged::WindowRemoved,
));
}
self.reposition_windows();
}
}
}
}
#[allow(clippy::significant_drop_tightening)]
pub fn tick_render(&mut self, renderer: &mut GlesRenderer, time_ms: u64) -> anyhow::Result<()> {
let mut gles_texture = self.gles_texture.clone();
let mut target = renderer.bind(&mut gles_texture)?;
let size = Size::from((i32::from(self.width), i32::from(self.height)));
let damage: Rectangle<i32, smithay::utils::Physical> = Rectangle::from_size(size);
let elements: Vec<WaylandSurfaceRenderElement<GlesRenderer>> = self
.displayed_windows
.iter()
.flat_map(|display_window| {
let wm = self.wm.borrow_mut();
if let Some(window) = wm.windows.get(&display_window.window_handle) {
if !window.visible {
return vec![];
}
render_elements_from_surface_tree(
renderer,
display_window.toplevel.wl_surface(),
(window.pos_x, window.pos_y),
1.0,
1.0,
Kind::Unspecified,
)
} else {
// Failed to fetch window
vec![]
}
})
.collect();
let mut frame = renderer.render(&mut target, size, Transform::Normal)?;
let clear_color = if self.displayed_windows.is_empty() {
Color32F::new(0.5, 0.5, 0.5, 0.5)
} else {
Color32F::new(0.0, 0.0, 0.0, 0.0)
};
frame.clear(clear_color, &[damage])?;
draw_render_elements(&mut frame, 1.0, &elements, &[damage])?;
let _sync_point = frame.finish()?;
for window in &self.displayed_windows {
send_frames_surface_tree(window.toplevel.wl_surface(), time_ms as u32);
}
if let egl_data::RenderData::Software(_) = &self.render_data {
// Read OpenGL texture into memory. Slow!
let pixel_data = renderer.with_context(|gl| unsafe {
gl.BindTexture(ffi::TEXTURE_2D, self.gles_texture.tex_id());
let len = self.width as usize * self.height as usize * 4;
let mut data: Box<[u8]> = Box::new_uninit_slice(len).assume_init();
gl.ReadPixels(
0,
0,
i32::from(self.width),
i32::from(self.height),
ffi::RGBA,
ffi::UNSIGNED_BYTE,
data.as_mut_ptr().cast(),
);
let data: Arc<[u8]> = Arc::from(data);
data
})?;
self.render_data =
egl_data::RenderData::Software(Some(egl_data::RenderSoftwarePixelsData {
data: pixel_data,
width: self.width,
height: self.height,
}));
}
self.rendered_frame_count += 1;
Ok(())
}
fn get_hovered_window(&self, cursor_x: u32, cursor_y: u32) -> Option<window::WindowHandle> {
let wm = self.wm.borrow();
for cell in self.displayed_windows.iter().rev() {
if let Some(window) = wm.windows.get(&cell.window_handle) {
if !window.visible {
continue;
}
if (cursor_x as i32) >= window.pos_x
&& (cursor_x as i32) < window.pos_x + window.size_x as i32
&& (cursor_y as i32) >= window.pos_y
&& (cursor_y as i32) < window.pos_y + window.size_y as i32
{
return Some(cell.window_handle);
}
}
}
None
}
pub const fn trigger_rerender(&mut self) {
self.wants_redraw = true;
}
pub fn set_visible(&mut self, visible: bool) {
log::info!("Display \"{}\" visible: {}", self.name.as_str(), visible);
if self.visible == visible {
return;
}
self.visible = visible;
if visible {
self.no_windows_since = None;
self.trigger_rerender();
}
}
pub fn set_layout(&mut self, layout: packet_server::WvrDisplayWindowLayout) {
log::info!("Display \"{}\" layout: {:?}", self.name.as_str(), layout);
if self.layout == layout {
return;
}
self.layout = layout;
self.trigger_rerender();
self.reposition_windows();
}
pub fn send_mouse_move(
&self,
config: &super::Config,
manager: &mut WayVRCompositor,
x: u32,
y: u32,
) {
let current_ms = time::get_millis();
if self.last_pressed_time_ms + u64::from(config.click_freeze_time_ms) > current_ms {
return;
}
if let Some(window_handle) = self.get_hovered_window(x, y) {
let wm = self.wm.borrow();
if let Some(window) = wm.windows.get(&window_handle) {
let surf = window.toplevel.wl_surface().clone();
let point = Point::<f64, Logical>::from((
f64::from(x as i32 - window.pos_x),
f64::from(y as i32 - window.pos_y),
));
manager.seat_pointer.motion(
&mut manager.state,
Some((surf, Point::from((0.0, 0.0)))),
&input::pointer::MotionEvent {
serial: manager.serial_counter.next_serial(),
time: 0,
location: point,
},
);
manager.seat_pointer.frame(&mut manager.state);
}
}
}
const fn get_mouse_index_number(index: super::MouseIndex) -> u32 {
match index {
super::MouseIndex::Left => 0x110, /* BTN_LEFT */
super::MouseIndex::Center => 0x112, /* BTN_MIDDLE */
super::MouseIndex::Right => 0x111, /* BTN_RIGHT */
}
}
pub fn send_mouse_down(&mut self, manager: &mut WayVRCompositor, index: super::MouseIndex) {
// Change keyboard focus to pressed window
let loc = manager.seat_pointer.current_location();
self.last_pressed_time_ms = time::get_millis();
if let Some(window_handle) =
self.get_hovered_window(loc.x.max(0.0) as u32, loc.y.max(0.0) as u32)
{
let wm = self.wm.borrow();
if let Some(window) = wm.windows.get(&window_handle) {
let surf = window.toplevel.wl_surface().clone();
manager.seat_keyboard.set_focus(
&mut manager.state,
Some(surf),
manager.serial_counter.next_serial(),
);
}
}
manager.seat_pointer.button(
&mut manager.state,
&input::pointer::ButtonEvent {
button: Self::get_mouse_index_number(index),
serial: manager.serial_counter.next_serial(),
time: 0,
state: smithay::backend::input::ButtonState::Pressed,
},
);
manager.seat_pointer.frame(&mut manager.state);
}
pub fn send_mouse_up(manager: &mut WayVRCompositor, index: super::MouseIndex) {
manager.seat_pointer.button(
&mut manager.state,
&input::pointer::ButtonEvent {
button: Self::get_mouse_index_number(index),
serial: manager.serial_counter.next_serial(),
time: 0,
state: smithay::backend::input::ButtonState::Released,
},
);
manager.seat_pointer.frame(&mut manager.state);
}
pub fn send_mouse_scroll(manager: &mut WayVRCompositor, delta: WheelDelta) {
manager.seat_pointer.axis(
&mut manager.state,
input::pointer::AxisFrame {
source: None,
relative_direction: (
smithay::backend::input::AxisRelativeDirection::Identical,
smithay::backend::input::AxisRelativeDirection::Identical,
),
time: 0,
axis: (f64::from(delta.x), f64::from(-delta.y)),
v120: Some((0, (delta.y * -64.0) as i32)),
stop: (false, false),
},
);
manager.seat_pointer.frame(&mut manager.state);
}
fn configure_env(&self, cmd: &mut std::process::Command, auth_key: &str) {
cmd.env_remove("DISPLAY"); // Goodbye X11
cmd.env("WAYLAND_DISPLAY", self.wayland_env.display_num_string());
cmd.env("WAYVR_DISPLAY_AUTH", auth_key);
}
pub fn spawn_process(
&mut self,
exec_path: &str,
args: &[&str],
env: &[(&str, &str)],
working_dir: Option<&str>,
) -> anyhow::Result<SpawnProcessResult> {
log::info!("Spawning subprocess with exec path \"{exec_path}\"");
let auth_key = generate_auth_key();
let mut cmd = std::process::Command::new(exec_path);
self.configure_env(&mut cmd, auth_key.as_str());
cmd.args(args);
if let Some(working_dir) = working_dir {
cmd.current_dir(working_dir);
}
for e in env {
cmd.env(e.0, e.1);
}
match cmd.spawn() {
Ok(child) => Ok(SpawnProcessResult { auth_key, child }),
Err(e) => {
anyhow::bail!(
"Failed to launch process with path \"{exec_path}\": {e}. Make sure your exec path exists."
);
}
}
}
}
gen_id!(DisplayVec, Display, DisplayCell, DisplayHandle);
impl DisplayHandle {
pub const fn from_packet(handle: packet_server::WvrDisplayHandle) -> Self {
Self {
generation: handle.generation,
idx: handle.idx,
}
}
pub const fn as_packet(&self) -> packet_server::WvrDisplayHandle {
packet_server::WvrDisplayHandle {
idx: self.idx,
generation: self.generation,
}
}
}

View File

@@ -1,315 +0,0 @@
use std::sync::Arc;
use crate::backend::wayvr::egl_ex::{
PFNEGLGETPLATFORMDISPLAYEXTPROC, PFNEGLQUERYDMABUFFORMATSEXTPROC,
PFNEGLQUERYDMABUFMODIFIERSEXTPROC,
};
use super::egl_ex;
use anyhow::Context;
#[derive(Debug)]
pub struct EGLData {
pub egl: khronos_egl::Instance<khronos_egl::Static>,
pub display: khronos_egl::Display,
pub config: khronos_egl::Config,
pub context: khronos_egl::Context,
}
#[macro_export]
macro_rules! bind_egl_function {
($func_type:ident, $func:expr) => {
std::mem::transmute_copy::<_, $func_type>($func).unwrap()
};
}
#[derive(Debug, Clone)]
pub struct DMAbufModifierInfo {
pub modifiers: Vec<u64>,
pub fourcc: u32,
}
#[derive(Debug, Clone)]
pub struct RenderDMAbufData {
pub fd: i32,
pub stride: i32,
pub offset: i32,
pub mod_info: DMAbufModifierInfo,
}
#[derive(Debug, Clone)]
pub struct RenderSoftwarePixelsData {
pub data: Arc<[u8]>,
pub width: u16,
pub height: u16,
}
#[derive(Debug, Clone)]
pub enum RenderData {
Dmabuf(RenderDMAbufData),
Software(Option<RenderSoftwarePixelsData>), // will be set if the next image data is available
}
fn load_egl_func(
egl: &khronos_egl::Instance<khronos_egl::Static>,
func_name: &str,
) -> anyhow::Result<extern "system" fn()> {
let raw_fn = egl
.get_proc_address(func_name)
.ok_or_else(|| anyhow::anyhow!("Required EGL function {func_name} not found"))?;
Ok(raw_fn)
}
fn get_disp(
egl: &khronos_egl::Instance<khronos_egl::Static>,
) -> anyhow::Result<khronos_egl::Display> {
unsafe {
if let Ok(func) = load_egl_func(egl, "eglGetPlatformDisplayEXT") {
let egl_get_platform_display_ext =
bind_egl_function!(PFNEGLGETPLATFORMDISPLAYEXTPROC, &func);
let display_ext = egl_get_platform_display_ext(
egl_ex::EGL_PLATFORM_WAYLAND_EXT, // platform
std::ptr::null_mut(), // void *native_display
std::ptr::null_mut(), // EGLint *attrib_list
);
if display_ext.is_null() {
log::warn!("eglGetPlatformDisplayEXT failed, using eglGetDisplay instead");
} else {
return Ok(khronos_egl::Display::from_ptr(display_ext));
}
}
egl
.get_display(khronos_egl::DEFAULT_DISPLAY)
.context(
"Both eglGetPlatformDisplayEXT and eglGetDisplay failed. This shouldn't happen unless you don't have any display manager running. Cannot continue, check your EGL installation."
)
}
}
impl EGLData {
pub fn new() -> anyhow::Result<Self> {
let egl = khronos_egl::Instance::new(khronos_egl::Static);
let display = get_disp(&egl)?;
let (major, minor) = egl.initialize(display)?;
log::debug!("EGL version: {major}.{minor}");
let attrib_list = [
khronos_egl::RED_SIZE,
8,
khronos_egl::GREEN_SIZE,
8,
khronos_egl::BLUE_SIZE,
8,
khronos_egl::SURFACE_TYPE,
khronos_egl::WINDOW_BIT,
khronos_egl::RENDERABLE_TYPE,
khronos_egl::OPENGL_BIT,
khronos_egl::NONE,
];
let config = egl
.choose_first_config(display, &attrib_list)?
.context("Failed to get EGL config")?;
egl.bind_api(khronos_egl::OPENGL_ES_API)?;
log::debug!("eglCreateContext");
// Require OpenGL ES 3.0
let context_attrib_list = [
khronos_egl::CONTEXT_MAJOR_VERSION,
3,
khronos_egl::CONTEXT_MINOR_VERSION,
0,
khronos_egl::NONE,
];
let context = egl.create_context(display, config, None, &context_attrib_list)?;
log::debug!("eglMakeCurrent");
egl.make_current(display, None, None, Some(context))?;
Ok(Self {
egl,
display,
config,
context,
})
}
fn query_dmabuf_mod_info(&self) -> anyhow::Result<DMAbufModifierInfo> {
let target_fourcc = 0x3432_4258; //XB24
unsafe {
let egl_query_dmabuf_formats_ext = bind_egl_function!(
PFNEGLQUERYDMABUFFORMATSEXTPROC,
&load_egl_func(&self.egl, "eglQueryDmaBufFormatsEXT")?
);
// Query format count
let mut num_formats: khronos_egl::Int = 0;
egl_query_dmabuf_formats_ext(
self.display.as_ptr(),
0,
std::ptr::null_mut(),
&raw mut num_formats,
);
// Retrieve format list
let mut formats: Vec<i32> = vec![0; num_formats as usize];
egl_query_dmabuf_formats_ext(
self.display.as_ptr(),
num_formats,
formats.as_mut_ptr(),
&raw mut num_formats,
);
/*for (idx, format) in formats.iter().enumerate() {
let bytes = format.to_le_bytes();
log::trace!(
"idx {}, format {}{}{}{} (hex {:#x})",
idx,
bytes[0] as char,
bytes[1] as char,
bytes[2] as char,
bytes[3] as char,
format
);
}*/
let egl_query_dmabuf_modifiers_ext = bind_egl_function!(
PFNEGLQUERYDMABUFMODIFIERSEXTPROC,
&load_egl_func(&self.egl, "eglQueryDmaBufModifiersEXT")?
);
let mut num_mods: khronos_egl::Int = 0;
// Query modifier count
egl_query_dmabuf_modifiers_ext(
self.display.as_ptr(),
target_fourcc,
0,
std::ptr::null_mut(),
std::ptr::null_mut(),
&raw mut num_mods,
);
if num_mods == 0 {
anyhow::bail!("eglQueryDmaBufModifiersEXT modifier count is zero");
}
let mut mods: Vec<u64> = vec![0; num_mods as usize];
egl_query_dmabuf_modifiers_ext(
self.display.as_ptr(),
target_fourcc,
num_mods,
mods.as_mut_ptr(),
std::ptr::null_mut(),
&raw mut num_mods,
);
if mods[0] == 0xFFFF_FFFF_FFFF_FFFF {
anyhow::bail!("modifier is -1")
}
log::trace!("Modifier list:");
for modifier in &mods {
log::trace!("{modifier:#x}");
}
// We should not change these modifier values. Passing all of them to the Vulkan dmabuf
// texture system causes significant graphical corruption due to invalid memory layout and
// tiling on this specific GPU model (very probably others also have the same issue).
// It is not guaranteed that this modifier will be present in other models.
// If not, the full list of modifiers will be passed. Further testing is required.
// For now, it looks like only NAVI32-based gpus have this problem.
let mod_whitelist: [u64; 2] = [
0x200_0000_2086_bf04, /* AMD RX 7800 XT, Navi32 */
0x200_0000_1866_bf04, /* AMD RX 7600 XT, Navi33 */
];
for modifier in &mod_whitelist {
if mods.contains(modifier) {
log::warn!("Using whitelisted dmabuf tiling modifier: {modifier:#x}");
mods = vec![*modifier, 0x0 /* also important (???) */];
break;
}
}
Ok(DMAbufModifierInfo {
modifiers: mods,
fourcc: target_fourcc as u32,
})
}
}
pub fn create_dmabuf_data(
&self,
egl_image: &khronos_egl::Image,
) -> anyhow::Result<RenderDMAbufData> {
use egl_ex::PFNEGLEXPORTDMABUFIMAGEMESAPROC as FUNC;
unsafe {
let egl_export_dmabuf_image_mesa =
bind_egl_function!(FUNC, &load_egl_func(&self.egl, "eglExportDMABUFImageMESA")?);
let mut fds: [i32; 3] = [0; 3];
let mut strides: [i32; 3] = [0; 3];
let mut offsets: [i32; 3] = [0; 3];
let ret = egl_export_dmabuf_image_mesa(
self.display.as_ptr(),
egl_image.as_ptr(),
fds.as_mut_ptr(),
strides.as_mut_ptr(),
offsets.as_mut_ptr(),
);
if ret != khronos_egl::TRUE {
anyhow::bail!("eglExportDMABUFImageMESA failed with return code {ret}");
}
if fds[0] <= 0 {
anyhow::bail!("fd is <=0 (got {})", fds[0]);
}
// many planes in RGB data?
if fds[1] != 0 || strides[1] != 0 || offsets[1] != 0 {
anyhow::bail!("multi-planar data received, packed RGB expected");
}
if strides[0] < 0 {
anyhow::bail!("strides is < 0");
}
if offsets[0] < 0 {
anyhow::bail!("offsets is < 0");
}
let mod_info = self.query_dmabuf_mod_info()?;
Ok(RenderDMAbufData {
fd: fds[0],
stride: strides[0],
offset: offsets[0],
mod_info,
})
}
}
pub fn create_egl_image(&self, gl_tex_id: u32) -> anyhow::Result<khronos_egl::Image> {
unsafe {
Ok(self.egl.create_image(
self.display,
self.context,
khronos_egl::GL_TEXTURE_2D as std::ffi::c_uint,
khronos_egl::ClientBuffer::from_ptr(gl_tex_id as *mut std::ffi::c_void),
&[khronos_egl::ATTRIB_NONE],
)?)
}
}
}

View File

@@ -1,49 +0,0 @@
#![allow(clippy::all)]
pub const EGL_PLATFORM_WAYLAND_EXT: khronos_egl::Enum = 0x31D8;
// eglGetPlatformDisplayEXT
// https://registry.khronos.org/EGL/extensions/EXT/EGL_EXT_platform_base.txt
pub type PFNEGLGETPLATFORMDISPLAYEXTPROC = Option<
unsafe extern "C" fn(
platform: khronos_egl::Enum,
native_display: *mut std::ffi::c_void,
attrib_list: *mut khronos_egl::Enum,
) -> khronos_egl::EGLDisplay,
>;
// eglExportDMABUFImageMESA
// https://registry.khronos.org/EGL/extensions/MESA/EGL_MESA_image_dma_buf_export.txt
pub type PFNEGLEXPORTDMABUFIMAGEMESAPROC = Option<
unsafe extern "C" fn(
dpy: khronos_egl::EGLDisplay,
image: khronos_egl::EGLImage,
fds: *mut i32,
strides: *mut khronos_egl::Int,
offsets: *mut khronos_egl::Int,
) -> khronos_egl::Boolean,
>;
// eglQueryDmaBufModifiersEXT
// https://registry.khronos.org/EGL/extensions/EXT/EGL_EXT_image_dma_buf_import_modifiers.txt
pub type PFNEGLQUERYDMABUFMODIFIERSEXTPROC = Option<
unsafe extern "C" fn(
dpy: khronos_egl::EGLDisplay,
format: khronos_egl::Int,
max_modifiers: khronos_egl::Int,
modifiers: *mut u64,
external_only: *mut khronos_egl::Boolean,
num_modifiers: *mut khronos_egl::Int,
) -> khronos_egl::Boolean,
>;
// eglQueryDmaBufFormatsEXT
// https://registry.khronos.org/EGL/extensions/EXT/EGL_EXT_image_dma_buf_import_modifiers.txt
pub type PFNEGLQUERYDMABUFFORMATSEXTPROC = Option<
unsafe extern "C" fn(
dpy: khronos_egl::EGLDisplay,
max_formats: khronos_egl::Int,
formats: *mut khronos_egl::Int,
num_formats: *mut khronos_egl::Int,
) -> khronos_egl::Boolean,
>;

View File

@@ -0,0 +1,111 @@
use std::{collections::HashMap, os::fd::AsRawFd, sync::Arc};
use anyhow::Context;
use smithay::{
backend::allocator::{
Buffer,
dmabuf::{Dmabuf, WeakDmabuf},
},
wayland::{
shm::{BufferData, shm_format_to_fourcc},
single_pixel_buffer::SinglePixelBufferUserData,
},
};
use vulkano::{format::Format, image::view::ImageView};
use wgui::gfx::WGfx;
use wlx_capture::frame::{DmabufFrame, FrameFormat, Transform};
use crate::graphics::dmabuf::{WGfxDmabuf, fourcc_to_vk};
pub struct ImageImporter {
gfx: Arc<WGfx>,
dmabufs: HashMap<WeakDmabuf, Arc<ImageView>>,
}
impl ImageImporter {
pub fn new(gfx: Arc<WGfx>) -> Self {
Self {
gfx,
dmabufs: HashMap::new(),
}
}
pub fn import_spb(
&mut self,
spb: &SinglePixelBufferUserData,
) -> anyhow::Result<Arc<ImageView>> {
let mut cmd_buf = self.gfx.create_xfer_command_buffer(
vulkano::command_buffer::CommandBufferUsage::OneTimeSubmit,
)?;
let rgba = spb.rgba8888();
let image = cmd_buf.upload_image(1, 1, Format::R8G8B8A8_UNORM, &rgba)?;
cmd_buf.build_and_execute_now()?; //TODO: async
let image_view = ImageView::new_default(image)?;
Ok(image_view)
}
pub fn import_shm(
&mut self,
data: *const u8,
size: usize,
bd: BufferData,
) -> anyhow::Result<Arc<ImageView>> {
let mut cmd_buf = self.gfx.create_xfer_command_buffer(
vulkano::command_buffer::CommandBufferUsage::OneTimeSubmit,
)?;
let fourcc = shm_format_to_fourcc(bd.format)
.with_context(|| format!("Could not conver {:?} to fourcc", bd.format))?;
let format = fourcc_to_vk(fourcc)
.with_context(|| format!("Could not convert {fourcc} to vkFormat"))?;
let data = unsafe { std::slice::from_raw_parts(data, size) };
let image = cmd_buf.upload_image(bd.width as _, bd.height as _, format, data)?;
cmd_buf.build_and_execute_now()?; //TODO: async
let image_view = ImageView::new_default(image)?;
Ok(image_view)
}
pub fn get_or_import_dmabuf(&mut self, dmabuf: Dmabuf) -> anyhow::Result<Arc<ImageView>> {
let mut frame = DmabufFrame {
format: FrameFormat {
width: dmabuf.width(),
height: dmabuf.height(),
drm_format: dmabuf.format(),
transform: Transform::Undefined,
},
num_planes: dmabuf.num_planes(),
planes: Default::default(),
mouse: None,
};
for (i, handle) in dmabuf.handles().enumerate() {
// even if the original OwnedFd is dropped, the vkImage will hold reference on the DMA-buf
frame.planes[i].fd = Some(handle.as_raw_fd());
}
for (i, offset) in dmabuf.offsets().enumerate() {
frame.planes[i].offset = offset;
}
for (i, stride) in dmabuf.strides().enumerate() {
frame.planes[i].stride = stride as _;
}
let image = self.gfx.dmabuf_texture(frame)?;
let image_view = ImageView::new_default(image)?;
self.dmabufs.insert(dmabuf.weak(), image_view.clone());
Ok(image_view)
}
pub fn cleanup(&mut self) {
self.dmabufs.retain(|k, _| !k.is_gone());
}
}

View File

@@ -1,33 +1,22 @@
pub mod client;
mod comp;
pub mod display;
pub mod egl_data;
mod egl_ex;
pub mod event_queue;
mod handle;
mod process;
pub mod server_ipc;
mod smithay_wrapper;
mod image_importer;
pub mod process;
mod time;
mod window;
pub mod window;
use anyhow::Context;
use comp::Application;
use display::{Display, DisplayInitParams, DisplayVec};
use event_queue::SyncEventQueue;
use process::ProcessVec;
use serde::Deserialize;
use server_ipc::WayVRServer;
use slotmap::SecondaryMap;
use smallvec::SmallVec;
use smithay::{
backend::{
egl,
renderer::{ImportDma, gles::GlesRenderer},
},
input::{SeatState, keyboard::XkbConfig},
output::{Mode, Output},
reexports::wayland_server::{self, backend::ClientId},
reexports::wayland_server::{self, backend::ClientId, protocol::wl_buffer},
wayland::{
compositor,
compositor::{self, SurfaceData},
dmabuf::{DmabufFeedbackBuilder, DmabufState},
selection::data_device::DataDeviceState,
shell::xdg::{ToplevelSurface, XdgShellState},
@@ -37,19 +26,28 @@ use smithay::{
use std::{
cell::RefCell,
collections::{HashMap, HashSet},
mem::MaybeUninit,
rc::Rc,
sync::Arc,
time::{Duration, Instant},
};
use time::get_millis;
use wayvr_ipc::{
packet_client::{self},
packet_server,
};
use vulkano::image::view::ImageView;
use wayvr_ipc::packet_server;
use wgui::gfx::WGfx;
use wlx_capture::frame::Transform;
use xkbcommon::xkb;
use crate::{
backend::{
task::{OverlayTask, TaskType},
wayvr::{image_importer::ImageImporter, window::Window},
},
graphics::WGfxExtras,
ipc::{event_queue::SyncEventQueue, ipc_server, signal::WayVRSignal},
state::AppState,
subsystem::hid::{MODS_TO_KEYS, WheelDelta},
windowing::{OverlayID, OverlaySelector},
};
const STR_INVALID_HANDLE_DISP: &str = "Invalid display handle";
@@ -87,19 +85,6 @@ pub enum WayVRTask {
ProcessTerminationRequest(process::ProcessHandle),
}
#[derive(Clone)]
pub enum WayVRSignal {
DisplayVisibility(display::DisplayHandle, bool),
DisplayWindowLayout(
display::DisplayHandle,
packet_server::WvrDisplayWindowLayout,
),
BroadcastStateChanged(packet_server::WvrStateChanged),
DropOverlay(crate::windowing::OverlayID),
Haptics(super::input::Haptics),
CustomTask(crate::backend::task::ModifyPanelTask),
}
pub enum BlitMethod {
Dmabuf,
Software,
@@ -125,22 +110,21 @@ pub struct Config {
pub struct WayVRState {
time_start: u64,
pub displays: display::DisplayVec,
pub manager: client::WayVRCompositor,
wm: Rc<RefCell<window::WindowManager>>,
egl_data: Rc<egl_data::EGLData>,
pub wm: Rc<RefCell<window::WindowManager>>,
pub processes: process::ProcessVec,
pub config: Config,
dashboard_display: Option<display::DisplayHandle>,
pub tasks: SyncEventQueue<WayVRTask>,
pub signals: SyncEventQueue<WayVRSignal>,
ticks: u64,
cur_modifiers: u8,
signals: SyncEventQueue<WayVRSignal>,
mouse_freeze: Instant,
window_to_overlay: HashMap<window::WindowHandle, OverlayID>,
overlay_to_window: SecondaryMap<OverlayID, window::WindowHandle>,
}
pub struct WayVR {
pub state: WayVRState,
pub ipc_server: WayVRServer,
}
pub enum MouseIndex {
@@ -151,15 +135,16 @@ pub enum MouseIndex {
pub enum TickTask {
NewExternalProcess(ExternalProcessRequest), // Call WayVRCompositor::add_client after receiving this message
NewDisplay(
packet_client::WvrDisplayCreateParams,
Option<display::DisplayHandle>, /* existing handle? */
),
}
impl WayVR {
#[allow(clippy::too_many_lines, clippy::cognitive_complexity)]
pub fn new(config: Config) -> anyhow::Result<Self> {
pub fn new(
gfx: Arc<WGfx>,
gfx_extras: &WGfxExtras,
config: Config,
signals: SyncEventQueue<WayVRSignal>,
) -> anyhow::Result<Self> {
log::info!("Initializing WayVR");
let display: wayland_server::Display<Application> = wayland_server::Display::new()?;
let dh = display.handle();
@@ -170,8 +155,8 @@ impl WayVR {
let data_device = DataDeviceState::new::<Application>(&dh);
let mut seat = seat_state.new_wl_seat(&dh, "wayvr");
let dummy_width = 1280;
let dummy_height = 720;
let dummy_width = 1920;
let dummy_height = 1080;
let dummy_milli_hz = 60000; /* refresh rate in millihertz */
let output = Output::new(
@@ -193,53 +178,38 @@ impl WayVR {
output.change_current_state(Some(mode), None, None, None);
output.set_preferred(mode);
let egl_data = egl_data::EGLData::new()?;
let smithay_display = smithay_wrapper::get_egl_display(&egl_data)?;
let smithay_context = smithay_wrapper::get_egl_context(&egl_data, &smithay_display)?;
let render_node = egl::EGLDevice::device_for_display(&smithay_display)
.and_then(|device| device.try_get_render_node());
let gles_renderer = unsafe { GlesRenderer::new(smithay_context)? };
let dmabuf_default_feedback = match render_node {
Ok(Some(node)) => {
let dmabuf_formats = gles_renderer.dmabuf_formats();
let dmabuf_default_feedback =
DmabufFeedbackBuilder::new(node.dev_id(), dmabuf_formats)
.build()
.unwrap();
Some(dmabuf_default_feedback)
}
Ok(None) => {
log::warn!("dmabuf: Failed to query render node");
None
}
Err(err) => {
log::warn!("dmabuf: Failed to get egl device for display: {err}");
None
}
let main_device = {
let (major, minor) = gfx_extras.drm_device.as_ref().context("No DRM device!")?;
libc::makedev(*major as _, *minor as _)
};
let dmabuf_state = dmabuf_default_feedback.map_or_else(
|| {
let dmabuf_formats = gles_renderer.dmabuf_formats();
let mut dmabuf_state = DmabufState::new();
let dmabuf_global =
dmabuf_state.create_global::<Application>(&display.handle(), dmabuf_formats);
(dmabuf_state, dmabuf_global, None)
},
|default_feedback| {
let mut dmabuf_state = DmabufState::new();
let dmabuf_global = dmabuf_state
.create_global_with_default_feedback::<Application>(
&display.handle(),
&default_feedback,
);
(dmabuf_state, dmabuf_global, Some(default_feedback))
},
);
// this will throw a compile-time error if smithay's drm-fourcc is out of sync with wlx-capture's
let mut formats: Vec<smithay::backend::allocator::Format> = vec![];
for f in gfx_extras.drm_formats.iter() {
formats.push(f.clone());
}
let dmabuf_state = DmabufFeedbackBuilder::new(main_device, formats.clone())
.build()
.map_or_else(
|_| {
log::info!("Falling back to zwp_linux_dmabuf_v1 version 3.");
let mut dmabuf_state = DmabufState::new();
let dmabuf_global =
dmabuf_state.create_global::<Application>(&display.handle(), formats);
(dmabuf_state, dmabuf_global, None)
},
|default_feedback| {
let mut dmabuf_state = DmabufState::new();
let dmabuf_global = dmabuf_state
.create_global_with_default_feedback::<Application>(
&display.handle(),
&default_feedback,
);
(dmabuf_state, dmabuf_global, Some(default_feedback))
},
);
let seat_keyboard = seat.add_keyboard(
XkbConfig::default(),
@@ -250,7 +220,10 @@ impl WayVR {
let tasks = SyncEventQueue::new();
let dma_importer = ImageImporter::new(gfx);
let state = Application {
image_importer: dma_importer,
compositor,
xdg_shell,
seat_state,
@@ -259,109 +232,54 @@ impl WayVR {
wayvr_tasks: tasks.clone(),
redraw_requests: HashSet::new(),
dmabuf_state,
gles_renderer,
};
let time_start = get_millis();
let ipc_server = WayVRServer::new()?;
let state = WayVRState {
time_start,
manager: client::WayVRCompositor::new(state, display, seat_keyboard, seat_pointer)?,
displays: DisplayVec::new(),
processes: ProcessVec::new(),
egl_data: Rc::new(egl_data),
wm: Rc::new(RefCell::new(window::WindowManager::new())),
config,
dashboard_display: None,
ticks: 0,
tasks,
signals: SyncEventQueue::new(),
cur_modifiers: 0,
signals,
mouse_freeze: Instant::now(),
window_to_overlay: HashMap::new(),
overlay_to_window: SecondaryMap::new(),
};
Ok(Self { state, ipc_server })
}
pub fn render_display(&mut self, display: display::DisplayHandle) -> anyhow::Result<bool> {
let display = self
.state
.displays
.get_mut(&display)
.context(STR_INVALID_HANDLE_DISP)?;
/* Buffer warm-up is required, always two first calls of this function are always rendered */
if !display.wants_redraw && display.rendered_frame_count >= 2 {
// Nothing changed, do not render
return Ok(false);
}
if !display.visible {
// Display is invisible, do not render
return Ok(false);
}
// millis since the start of wayvr
let time_ms = get_millis() - self.state.time_start;
display.tick_render(&mut self.state.manager.state.gles_renderer, time_ms)?;
display.wants_redraw = false;
Ok(true)
Ok(Self { state })
}
#[allow(clippy::too_many_lines, clippy::cognitive_complexity)]
pub fn tick_events(&mut self, app: &AppState) -> anyhow::Result<Vec<TickTask>> {
pub fn tick_events(&mut self, app: &mut AppState) -> anyhow::Result<Vec<TickTask>> {
let mut tasks: Vec<TickTask> = Vec::new();
self.ipc_server.tick(&mut server_ipc::TickParams {
state: &mut self.state,
app.ipc_server.tick(&mut ipc_server::TickParams {
wayland_state: &mut self.state,
input_state: &app.input_state,
tasks: &mut tasks,
app,
signals: &app.wayvr_signals,
});
// Check for redraw events
for (_, disp) in self.state.displays.iter_mut() {
for disp_window in &disp.displayed_windows {
if self
.state
.manager
.state
.check_redraw(disp_window.toplevel.wl_surface())
{
disp.wants_redraw = true;
}
}
}
// Tick all child processes
let mut to_remove: SmallVec<[(process::ProcessHandle, display::DisplayHandle); 2]> =
SmallVec::new();
let mut to_remove: SmallVec<[process::ProcessHandle; 2]> = SmallVec::new();
for (handle, process) in self.state.processes.iter_mut() {
if !process.is_running() {
to_remove.push((handle, process.display_handle()));
to_remove.push(handle);
}
}
for (p_handle, disp_handle) in &to_remove {
for p_handle in &to_remove {
self.state.processes.remove(p_handle);
if let Some(display) = self.state.displays.get_mut(disp_handle) {
display
.tasks
.send(display::DisplayTask::ProcessCleanup(*p_handle));
display.wants_redraw = true;
}
}
for (handle, display) in self.state.displays.iter_mut() {
display.tick(&self.state.config, &handle, &mut self.state.signals);
}
if !to_remove.is_empty() {
self.state.signals.send(WayVRSignal::BroadcastStateChanged(
app.wayvr_signals.send(WayVRSignal::BroadcastStateChanged(
packet_server::WvrStateChanged::ProcessRemoved,
));
}
@@ -392,17 +310,11 @@ impl WayVR {
.state
.wm
.borrow_mut()
.create_window(client.display_handle, &toplevel);
.create_window(&toplevel, process_handle);
let Some(display) = self.state.displays.get_mut(&client.display_handle)
else {
// This shouldn't happen, scream if it does
log::error!("Could not attach window handle into display");
continue;
};
//TODO: create overlay
display.add_window(window_handle, process_handle, &toplevel);
self.state.signals.send(WayVRSignal::BroadcastStateChanged(
app.wayvr_signals.send(WayVRSignal::BroadcastStateChanged(
packet_server::WvrStateChanged::WindowCreated,
));
}
@@ -419,18 +331,15 @@ impl WayVR {
continue;
};
let Some(display) = self.state.displays.get_mut(&client.display_handle)
else {
log::warn!("DropToplevel: Couldn't find matching display");
continue;
};
if let Some(oid) = self.state.window_to_overlay.get(&window_handle) {
app.tasks.enqueue(TaskType::Overlay(OverlayTask::Drop(
OverlaySelector::Id(*oid),
)));
}
display.remove_window(window_handle);
wm.remove_window(window_handle);
drop(wm);
display.reposition_windows();
}
}
WayVRTask::ProcessTerminationRequest(process_handle) => {
@@ -441,12 +350,11 @@ impl WayVR {
}
}
self.state
.manager
.tick_wayland(&mut self.state.displays, &mut self.state.processes)?;
self.state.manager.tick_wayland(&mut self.state.processes)?;
if self.state.ticks.is_multiple_of(200) {
self.state.manager.cleanup_clients();
self.state.manager.cleanup_handles();
}
self.state.ticks += 1;
@@ -454,44 +362,6 @@ impl WayVR {
Ok(tasks)
}
pub fn tick_finish(&mut self) -> anyhow::Result<()> {
self.state
.manager
.state
.gles_renderer
.with_context(|gl| unsafe {
gl.Flush();
gl.Finish();
})?;
Ok(())
}
#[allow(dead_code)]
pub fn get_primary_display(displays: &DisplayVec) -> Option<display::DisplayHandle> {
for (idx, cell) in displays.vec.iter().enumerate() {
if let Some(cell) = cell
&& cell.obj.primary
{
return Some(DisplayVec::get_handle(cell, idx));
}
}
None
}
pub fn get_display_by_name(
displays: &DisplayVec,
name: &str,
) -> Option<display::DisplayHandle> {
for (idx, cell) in displays.vec.iter().enumerate() {
if let Some(cell) = cell
&& cell.obj.name == name
{
return Some(DisplayVec::get_handle(cell, idx));
}
}
None
}
pub fn terminate_process(&mut self, process_handle: process::ProcessHandle) {
self.state
.tasks
@@ -500,24 +370,30 @@ impl WayVR {
}
impl WayVRState {
pub fn send_mouse_move(&mut self, display: display::DisplayHandle, x: u32, y: u32) {
if let Some(display) = self.displays.get(&display) {
display.send_mouse_move(&self.config, &mut self.manager, x, y);
pub fn send_mouse_move(&mut self, handle: window::WindowHandle, x: u32, y: u32) {
if self.mouse_freeze > Instant::now() {
return;
}
if let Some(window) = self.wm.borrow_mut().windows.get_mut(&handle) {
window.send_mouse_move(&mut self.manager, x, y);
}
}
pub fn send_mouse_down(&mut self, display: display::DisplayHandle, index: MouseIndex) {
if let Some(display) = self.displays.get_mut(&display) {
display.send_mouse_down(&mut self.manager, index);
pub fn send_mouse_down(&mut self, handle: window::WindowHandle, index: MouseIndex) {
self.mouse_freeze =
Instant::now() + Duration::from_millis(self.config.click_freeze_time_ms as _);
if let Some(window) = self.wm.borrow_mut().windows.get_mut(&handle) {
window.send_mouse_down(&mut self.manager, index);
}
}
pub fn send_mouse_up(&mut self, index: MouseIndex) {
Display::send_mouse_up(&mut self.manager, index);
Window::send_mouse_up(&mut self.manager, index);
}
pub fn send_mouse_scroll(&mut self, delta: WheelDelta) {
Display::send_mouse_scroll(&mut self.manager, delta);
Window::send_mouse_scroll(&mut self.manager, delta);
}
pub fn send_key(&mut self, virtual_key: u32, down: bool) {
@@ -541,125 +417,9 @@ impl WayVRState {
self.cur_modifiers = modifiers;
}
pub fn set_display_visible(&mut self, display: display::DisplayHandle, visible: bool) {
if let Some(display) = self.displays.get_mut(&display) {
display.set_visible(visible);
}
}
pub fn set_display_layout(
&mut self,
display: display::DisplayHandle,
layout: packet_server::WvrDisplayWindowLayout,
) {
if let Some(display) = self.displays.get_mut(&display) {
display.set_layout(layout);
}
}
pub fn get_render_data(
&self,
display: display::DisplayHandle,
) -> Option<&egl_data::RenderData> {
self.displays
.get(&display)
.map(|display| &display.render_data)
}
pub fn create_display(
&mut self,
width: u16,
height: u16,
name: &str,
primary: bool,
) -> anyhow::Result<display::DisplayHandle> {
let display = display::Display::new(DisplayInitParams {
wm: self.wm.clone(),
egl_data: self.egl_data.clone(),
renderer: &mut self.manager.state.gles_renderer,
wayland_env: self.manager.wayland_env.clone(),
config: &self.config,
width,
height,
name,
primary,
})?;
let handle = self.displays.add(display);
self.signals.send(WayVRSignal::BroadcastStateChanged(
packet_server::WvrStateChanged::DisplayCreated,
));
Ok(handle)
}
pub fn destroy_display(&mut self, handle: display::DisplayHandle) -> anyhow::Result<()> {
let Some(display) = self.displays.get(&handle) else {
anyhow::bail!("Display not found");
};
if let Some(overlay_id) = display.overlay_id {
self.signals.send(WayVRSignal::DropOverlay(overlay_id));
} else {
log::warn!("Destroying display without OverlayID set"); // This shouldn't happen, but log it anyways.
}
let mut process_names = Vec::<String>::new();
for (_, process) in self.processes.iter_mut() {
if process.display_handle() == handle {
process_names.push(process.get_name());
}
}
if !display.displayed_windows.is_empty() || !process_names.is_empty() {
anyhow::bail!(
"Display is not empty. Attached processes: {}",
process_names.join(", ")
);
}
self.manager.cleanup_clients();
for client in &self.manager.clients {
if client.display_handle == handle {
// This shouldn't happen, but make sure we are all set to destroy this display
anyhow::bail!("Wayland client still exists");
}
}
self.displays.remove(&handle);
self.signals.send(WayVRSignal::BroadcastStateChanged(
packet_server::WvrStateChanged::DisplayRemoved,
));
Ok(())
}
pub fn get_or_create_dashboard_display(
&mut self,
width: u16,
height: u16,
name: &str,
) -> anyhow::Result<(bool /* newly created? */, display::DisplayHandle)> {
if let Some(handle) = &self.dashboard_display {
// ensure it still exists
if self.displays.get(handle).is_some() {
return Ok((false, *handle));
}
}
let new_disp = self.create_display(width, height, name, false)?;
self.dashboard_display = Some(new_disp);
Ok((true, new_disp))
}
// Check if process with given arguments already exists
pub fn process_query(
&self,
display_handle: display::DisplayHandle,
exec_path: &str,
args: &[&str],
_env: &[(&str, &str)],
@@ -668,10 +428,7 @@ impl WayVRState {
if let Some(cell) = &cell
&& let process::Process::Managed(process) = &cell.obj
{
if process.display_handle != display_handle
|| process.exec_path != exec_path
|| process.args != args
{
if process.exec_path != exec_path || process.args != args {
continue;
}
return Some(process::ProcessVec::get_handle(cell, idx));
@@ -681,40 +438,39 @@ impl WayVRState {
None
}
pub fn add_external_process(
&mut self,
display_handle: display::DisplayHandle,
pid: u32,
) -> process::ProcessHandle {
pub fn add_external_process(&mut self, pid: u32) -> process::ProcessHandle {
self.processes
.add(process::Process::External(process::ExternalProcess {
pid,
display_handle,
}))
.add(process::Process::External(process::ExternalProcess { pid }))
}
pub fn spawn_process(
&mut self,
display_handle: display::DisplayHandle,
exec_path: &str,
args: &[&str],
env: &[(&str, &str)],
working_dir: Option<&str>,
userdata: HashMap<String, String>,
) -> anyhow::Result<process::ProcessHandle> {
let display = self
.displays
.get_mut(&display_handle)
.context(STR_INVALID_HANDLE_DISP)?;
let auth_key = generate_auth_key();
let res = display.spawn_process(exec_path, args, env, working_dir)?;
let mut cmd = std::process::Command::new(exec_path);
self.configure_env(&mut cmd, auth_key.as_str());
cmd.args(args);
if let Some(working_dir) = working_dir {
cmd.current_dir(working_dir);
}
for e in env {
cmd.env(e.0, e.1);
}
let child = cmd.spawn().context("Failed to spawn child process")?;
let handle = self
.processes
.add(process::Process::Managed(process::WayVRProcess {
auth_key: res.auth_key,
child: res.child,
display_handle,
auth_key,
child,
exec_path: String::from(exec_path),
userdata,
args: args.iter().map(|x| String::from(*x)).collect(),
@@ -731,6 +487,25 @@ impl WayVRState {
Ok(handle)
}
fn configure_env(&self, cmd: &mut std::process::Command, auth_key: &str) {
cmd.env_remove("DISPLAY"); // Goodbye X11
cmd.env(
"WAYLAND_DISPLAY",
self.manager.wayland_env.display_num_string(),
);
cmd.env("WAYVR_DISPLAY_AUTH", auth_key);
}
}
fn generate_auth_key() -> String {
let uuid = uuid::Uuid::new_v4();
uuid.to_string()
}
pub struct SpawnProcessResult {
pub auth_key: String,
pub child: std::process::Child,
}
#[derive(Deserialize, Clone)]
@@ -751,3 +526,34 @@ pub enum WayVRAction {
},
ToggleDashboard,
}
struct SurfaceBufWithImageContainer {
inner: RefCell<SurfaceBufWithImage>,
}
#[derive(Clone)]
pub struct SurfaceBufWithImage {
buffer: wl_buffer::WlBuffer,
pub image: Arc<ImageView>,
pub transform: Transform,
pub scale: i32,
}
impl SurfaceBufWithImage {
fn apply_to_surface(self, surface_data: &SurfaceData) {
let container = surface_data.data_map.get_or_insert(|| unsafe {
SurfaceBufWithImageContainer {
inner: RefCell::new(MaybeUninit::uninit().assume_init()),
}
});
container.inner.replace(self);
}
pub fn get_from_surface(surface_data: &SurfaceData) -> Option<Self> {
surface_data
.data_map
.get::<SurfaceBufWithImageContainer>()
.map(|x| x.inner.borrow().clone())
}
}

View File

@@ -4,14 +4,11 @@ use wayvr_ipc::packet_server;
use crate::gen_id;
use super::display;
#[derive(Debug)]
#[allow(dead_code)]
pub struct WayVRProcess {
pub auth_key: String,
pub child: std::process::Child,
pub display_handle: display::DisplayHandle,
pub exec_path: String,
pub args: Vec<String>,
@@ -24,7 +21,6 @@ pub struct WayVRProcess {
#[derive(Debug)]
pub struct ExternalProcess {
pub pid: u32,
pub display_handle: display::DisplayHandle,
}
#[derive(Debug)]
@@ -34,13 +30,6 @@ pub enum Process {
}
impl Process {
pub const fn display_handle(&self) -> display::DisplayHandle {
match self {
Self::Managed(p) => p.display_handle,
Self::External(p) => p.display_handle,
}
}
pub fn is_running(&mut self) -> bool {
match self {
Self::Managed(p) => p.is_running(),
@@ -67,13 +56,11 @@ impl Process {
Self::Managed(p) => packet_server::WvrProcess {
name: p.get_name().unwrap_or_else(|| String::from("unknown")),
userdata: p.userdata.clone(),
display_handle: p.display_handle.as_packet(),
handle: handle.as_packet(),
},
Self::External(p) => packet_server::WvrProcess {
name: p.get_name().unwrap_or_else(|| String::from("unknown")),
userdata: HashMap::default(),
display_handle: p.display_handle.as_packet(),
handle: handle.as_packet(),
},
}

View File

@@ -1,54 +0,0 @@
use super::egl_data;
use smithay::backend::{egl as smithay_egl, renderer::gles::ffi};
pub fn get_egl_display(data: &egl_data::EGLData) -> anyhow::Result<smithay_egl::EGLDisplay> {
Ok(unsafe { smithay_egl::EGLDisplay::from_raw(data.display.as_ptr(), data.config.as_ptr())? })
}
pub fn get_egl_context(
data: &egl_data::EGLData,
display: &smithay_egl::EGLDisplay,
) -> anyhow::Result<smithay_egl::EGLContext> {
let display_ptr = display.get_display_handle().handle;
debug_assert!(std::ptr::eq(display_ptr, data.display.as_ptr()));
let config_ptr = data.config.as_ptr();
let context_ptr = data.context.as_ptr();
Ok(unsafe { smithay_egl::EGLContext::from_raw(display_ptr, config_ptr, context_ptr)? })
}
pub fn create_framebuffer_texture(
gl: &ffi::Gles2,
width: u32,
height: u32,
tex_format: u32,
internal_format: u32,
) -> u32 {
unsafe {
let mut tex = 0;
gl.GenTextures(1, &raw mut tex);
gl.BindTexture(ffi::TEXTURE_2D, tex);
gl.TexParameteri(
ffi::TEXTURE_2D,
ffi::TEXTURE_MIN_FILTER,
ffi::NEAREST as i32,
);
gl.TexParameteri(
ffi::TEXTURE_2D,
ffi::TEXTURE_MAG_FILTER,
ffi::NEAREST as i32,
);
gl.TexImage2D(
ffi::TEXTURE_2D,
0,
internal_format as i32,
width as i32,
height as i32,
0,
tex_format,
ffi::UNSIGNED_BYTE,
std::ptr::null(),
);
gl.BindTexture(ffi::TEXTURE_2D, 0);
tex
}
}

View File

@@ -1,39 +1,36 @@
use smithay::wayland::shell::xdg::ToplevelSurface;
use smithay::{
input,
utils::{Logical, Point},
wayland::shell::xdg::ToplevelSurface,
};
use wayvr_ipc::packet_server;
use crate::gen_id;
use super::display;
use crate::{
backend::wayvr::{client::WayVRCompositor, process},
gen_id,
subsystem::hid::WheelDelta,
};
#[derive(Debug)]
pub struct Window {
pub pos_x: i32,
pub pos_y: i32,
pub size_x: u32,
pub size_y: u32,
pub visible: bool,
pub toplevel: ToplevelSurface,
pub display_handle: display::DisplayHandle,
pub process: process::ProcessHandle,
}
impl Window {
pub fn new(display_handle: display::DisplayHandle, toplevel: &ToplevelSurface) -> Self {
fn new(toplevel: &ToplevelSurface, process: process::ProcessHandle) -> Self {
Self {
pos_x: 0,
pos_y: 0,
size_x: 0,
size_y: 0,
visible: true,
toplevel: toplevel.clone(),
display_handle,
process,
}
}
pub const fn set_pos(&mut self, pos_x: i32, pos_y: i32) {
self.pos_x = pos_x;
self.pos_y = pos_y;
}
pub fn set_size(&mut self, size_x: u32, size_y: u32) {
self.toplevel.with_pending_state(|state| {
//state.bounds = Some((size_x as i32, size_y as i32).into());
@@ -44,6 +41,86 @@ impl Window {
self.size_x = size_x;
self.size_y = size_y;
}
pub fn send_mouse_move(&self, manager: &mut WayVRCompositor, x: u32, y: u32) {
let surf = self.toplevel.wl_surface().clone();
let point = Point::<f64, Logical>::from((f64::from(x as i32), f64::from(y as i32)));
manager.seat_pointer.motion(
&mut manager.state,
Some((surf, Point::from((0.0, 0.0)))),
&input::pointer::MotionEvent {
serial: manager.serial_counter.next_serial(),
time: 0,
location: point,
},
);
manager.seat_pointer.frame(&mut manager.state);
}
const fn get_mouse_index_number(index: super::MouseIndex) -> u32 {
match index {
super::MouseIndex::Left => 0x110, /* BTN_LEFT */
super::MouseIndex::Center => 0x112, /* BTN_MIDDLE */
super::MouseIndex::Right => 0x111, /* BTN_RIGHT */
}
}
pub fn send_mouse_down(&mut self, manager: &mut WayVRCompositor, index: super::MouseIndex) {
let surf = self.toplevel.wl_surface().clone();
// Change keyboard focus to pressed window
manager.seat_keyboard.set_focus(
&mut manager.state,
Some(surf),
manager.serial_counter.next_serial(),
);
manager.seat_pointer.button(
&mut manager.state,
&input::pointer::ButtonEvent {
button: Self::get_mouse_index_number(index),
serial: manager.serial_counter.next_serial(),
time: 0,
state: smithay::backend::input::ButtonState::Pressed,
},
);
manager.seat_pointer.frame(&mut manager.state);
}
pub fn send_mouse_up(manager: &mut WayVRCompositor, index: super::MouseIndex) {
manager.seat_pointer.button(
&mut manager.state,
&input::pointer::ButtonEvent {
button: Self::get_mouse_index_number(index),
serial: manager.serial_counter.next_serial(),
time: 0,
state: smithay::backend::input::ButtonState::Released,
},
);
manager.seat_pointer.frame(&mut manager.state);
}
pub fn send_mouse_scroll(manager: &mut WayVRCompositor, delta: WheelDelta) {
manager.seat_pointer.axis(
&mut manager.state,
input::pointer::AxisFrame {
source: None,
relative_direction: (
smithay::backend::input::AxisRelativeDirection::Identical,
smithay::backend::input::AxisRelativeDirection::Identical,
),
time: 0,
axis: (f64::from(delta.x), f64::from(-delta.y)),
v120: Some((0, (delta.y * -64.0) as i32)),
stop: (false, false),
},
);
manager.seat_pointer.frame(&mut manager.state);
}
}
#[derive(Debug)]
@@ -72,10 +149,10 @@ impl WindowManager {
pub fn create_window(
&mut self,
display_handle: display::DisplayHandle,
toplevel: &ToplevelSurface,
process: process::ProcessHandle,
) -> WindowHandle {
self.windows.add(Window::new(display_handle, toplevel))
self.windows.add(Window::new(toplevel, process))
}
pub fn remove_window(&mut self, window_handle: WindowHandle) {

View File

@@ -10,6 +10,7 @@ use std::{
use anyhow::Context;
use serde::{Deserialize, Serialize};
use wgui::gfx::WGfx;
use wlx_common::{common::LeftRight, config::GeneralConfig, windowing::Positioning};
use crate::{
@@ -19,7 +20,9 @@ use crate::{
},
config::load_config_with_conf_d,
config_io,
overlays::wayvr::{WayVRData, executable_exists_in_path},
graphics::WGfxExtras,
ipc::{event_queue::SyncEventQueue, signal::WayVRSignal},
overlays::wayvr::WayVRData,
};
// Flat version of RelativeTo
@@ -135,9 +138,6 @@ pub struct WayVRDashboard {
#[derive(Deserialize, Serialize)]
pub struct WayVRConfig {
#[serde(default = "def_true")]
pub run_compositor_at_start: bool,
#[serde(default = "Default::default")]
pub catalogs: HashMap<String, WayVRCatalog>,
@@ -201,9 +201,12 @@ impl WayVRConfig {
pub fn post_load(
&self,
gfx: Arc<WGfx>,
gfx_extras: &WGfxExtras,
config: &GeneralConfig,
tasks: &mut TaskContainer,
) -> anyhow::Result<Option<Rc<RefCell<WayVRData>>>> {
signals: SyncEventQueue<WayVRSignal>,
) -> anyhow::Result<Rc<RefCell<WayVRData>>> {
let primary_count = self
.displays
.iter()
@@ -212,10 +215,6 @@ impl WayVRConfig {
if primary_count > 1 {
anyhow::bail!("Number of primary displays is more than 1")
} else if primary_count == 0 {
log::warn!(
"No primary display specified. External Wayland applications will not be attached."
);
}
for (catalog_name, catalog) in &self.catalogs {
@@ -231,15 +230,12 @@ impl WayVRConfig {
}
}
if self.run_compositor_at_start {
// Start Wayland server instantly
Ok(Some(Rc::new(RefCell::new(WayVRData::new(
Self::get_wayvr_config(config, self)?,
)?))))
} else {
// Lazy-init WayVR later if the user requested
Ok(None)
}
Ok(Rc::new(RefCell::new(WayVRData::new(
gfx,
gfx_extras,
Self::get_wayvr_config(config, self)?,
signals,
)?)))
}
}
@@ -258,6 +254,19 @@ fn get_default_dashboard_exec() -> (
(String::from("wayvr-dashboard"), None)
}
pub fn executable_exists_in_path(command: &str) -> bool {
let Ok(path) = std::env::var("PATH") else {
return false; // very unlikely to happen
};
for dir in path.split(':') {
let exec_path = std::path::PathBuf::from(dir).join(command);
if exec_path.exists() && exec_path.is_file() {
return true; // executable found
}
}
false
}
pub fn load_wayvr() -> WayVRConfig {
let config_root_path = config_io::ConfigRoot::WayVR.ensure_dir();
log::info!("WayVR Config root path: {}", config_root_path.display());

View File

@@ -5,7 +5,7 @@ use std::{
};
use anyhow::Context;
use smallvec::SmallVec;
use smallvec::{SmallVec, smallvec};
use vulkano::{
VulkanError, VulkanObject,
device::Device,
@@ -19,12 +19,7 @@ use vulkano::{
sync::Sharing,
};
use wgui::gfx::WGfx;
use wlx_capture::frame::{
DRM_FORMAT_ABGR8888, DRM_FORMAT_ABGR2101010, DRM_FORMAT_ARGB8888, DRM_FORMAT_XBGR8888,
DRM_FORMAT_XBGR2101010, DRM_FORMAT_XRGB8888, DmabufFrame, DrmFormat, FourCC,
};
pub const DRM_FORMAT_MOD_INVALID: u64 = 0xff_ffff_ffff_ffff;
use wlx_capture::{DrmFormat, DrmFourcc, DrmModifier, frame::DmabufFrame};
pub trait WGfxDmabuf {
fn dmabuf_texture_ex(
@@ -47,7 +42,7 @@ impl WGfxDmabuf for WGfx {
modifiers: &[u64],
) -> anyhow::Result<Arc<Image>> {
let extent = [frame.format.width, frame.format.height, 1];
let format = fourcc_to_vk(frame.format.fourcc)?;
let format = fourcc_to_vk(frame.format.drm_format.code)?;
let image = unsafe {
create_dmabuf_image(
@@ -116,11 +111,11 @@ impl WGfxDmabuf for WGfx {
}
fn dmabuf_texture(&self, frame: DmabufFrame) -> anyhow::Result<Arc<Image>> {
let mut modifiers: Vec<u64> = vec![];
let mut modifiers: SmallVec<[u64; 4]> = smallvec![];
let mut tiling: ImageTiling = ImageTiling::Optimal;
let mut layouts: Vec<SubresourceLayout> = vec![];
if frame.format.modifier != DRM_FORMAT_MOD_INVALID {
if !matches!(frame.format.drm_format.modifier, DrmModifier::Invalid) {
(0..frame.num_planes).for_each(|i| {
let plane = &frame.planes[i];
layouts.push(SubresourceLayout {
@@ -130,7 +125,7 @@ impl WGfxDmabuf for WGfx {
array_pitch: None,
depth_pitch: None,
});
modifiers.push(frame.format.modifier);
modifiers.push(frame.format.drm_format.modifier.into());
});
tiling = ImageTiling::DrmFormatModifier;
}
@@ -304,50 +299,56 @@ pub(super) unsafe fn create_dmabuf_image(
}
}
pub fn get_drm_formats(device: Arc<Device>) -> Vec<DrmFormat> {
pub(super) fn get_drm_formats(device: Arc<Device>) -> Vec<DrmFormat> {
let possible_formats = [
DRM_FORMAT_ABGR8888.into(),
DRM_FORMAT_XBGR8888.into(),
DRM_FORMAT_ARGB8888.into(),
DRM_FORMAT_XRGB8888.into(),
DRM_FORMAT_ABGR2101010.into(),
DRM_FORMAT_XBGR2101010.into(),
DrmFourcc::Abgr8888,
DrmFourcc::Xbgr8888,
DrmFourcc::Argb8888,
DrmFourcc::Xrgb8888,
DrmFourcc::Abgr2101010,
DrmFourcc::Xbgr2101010,
];
let mut final_formats = vec![];
let mut out_formats = vec![];
for &f in &possible_formats {
let Ok(vk_fmt) = fourcc_to_vk(f) else {
for &code in &possible_formats {
let Ok(vk_fmt) = fourcc_to_vk(code) else {
continue;
};
let Ok(props) = device.physical_device().format_properties(vk_fmt) else {
continue;
};
let mut fmt = DrmFormat {
fourcc: f,
modifiers: props
.drm_format_modifier_properties
.iter()
// important bit: only allow single-plane
.filter(|m| m.drm_format_modifier_plane_count == 1)
.map(|m| m.drm_format_modifier)
.collect(),
};
fmt.modifiers.push(DRM_FORMAT_MOD_INVALID); // implicit modifiers support
final_formats.push(fmt);
for m in props
.drm_format_modifier_properties
.iter()
.filter(|m| m.drm_format_modifier_plane_count == 1)
.map(|m| m.drm_format_modifier)
{
out_formats.push(DrmFormat {
code,
modifier: DrmModifier::from(m),
});
}
// accept implicit modifiers
out_formats.push(DrmFormat {
code,
modifier: DrmModifier::Invalid,
});
}
log::debug!("Supported DRM formats:");
for f in &final_formats {
log::debug!(" {} {:?}", f.fourcc, f.modifiers);
for f in &out_formats {
log::debug!(" {} {:?}", f.code, f.modifier);
}
final_formats
out_formats
}
pub fn fourcc_to_vk(fourcc: FourCC) -> anyhow::Result<Format> {
match fourcc.value {
DRM_FORMAT_ABGR8888 | DRM_FORMAT_XBGR8888 => Ok(Format::R8G8B8A8_UNORM),
DRM_FORMAT_ARGB8888 | DRM_FORMAT_XRGB8888 => Ok(Format::B8G8R8A8_UNORM),
DRM_FORMAT_ABGR2101010 | DRM_FORMAT_XBGR2101010 => Ok(Format::A2B10G10R10_UNORM_PACK32),
pub fn fourcc_to_vk(fourcc: DrmFourcc) -> anyhow::Result<Format> {
match fourcc {
DrmFourcc::Abgr8888 | DrmFourcc::Xbgr8888 => Ok(Format::R8G8B8A8_UNORM),
DrmFourcc::Argb8888 | DrmFourcc::Xrgb8888 => Ok(Format::B8G8R8A8_UNORM),
DrmFourcc::Abgr2101010 | DrmFourcc::Xbgr2101010 => Ok(Format::A2B10G10R10_UNORM_PACK32),
_ => anyhow::bail!("Unsupported format {fourcc}"),
}
}

View File

@@ -19,7 +19,7 @@ use wgui::gfx::WGfx;
#[cfg(feature = "openvr")]
use vulkano::instance::InstanceCreateFlags;
use wlx_capture::frame::DrmFormat;
use wlx_capture::DrmFormat;
use crate::shaders::{frag_color, frag_grid, frag_screen, frag_srgb, vert_quad};
@@ -73,6 +73,7 @@ pub struct WGfxExtras {
pub queue_capture: Option<Arc<Queue>>,
pub quad_verts: Vert2Buf,
pub fallback_image: Arc<ImageView>,
pub drm_device: Option<(i64, i64)>,
}
impl WGfxExtras {
@@ -135,12 +136,23 @@ impl WGfxExtras {
let fallback_image = ImageView::new_default(fallback_image)?;
let p = gfx.device.physical_device().properties();
let drm_device = if let (Some(maj), Some(min)) = (p.render_major, p.render_minor) {
log::info!("DRM render device: {maj} {min}");
Some((maj, min))
} else {
log::warn!("No DRM device.");
None
};
Ok(Self {
shaders,
drm_formats,
queue_capture,
quad_verts,
fallback_image,
drm_device,
})
}
}
@@ -271,6 +283,13 @@ pub fn init_openxr_graphics(
.ext_image_drm_format_modifier;
}
if physical_device
.supported_extensions()
.ext_physical_device_drm
{
device_extensions.ext_physical_device_drm = true;
}
let device_extensions_raw = device_extensions
.into_iter()
.filter_map(|(name, enabled)| {
@@ -432,6 +451,11 @@ pub fn init_openvr_graphics(
my_extensions.ext_filter_cubic = true;
}
if p.supported_extensions().ext_physical_device_drm {
// needed for wayland_server
my_extensions.ext_physical_device_drm = true;
}
log::debug!(
"Device exts for {}: {:?}",
p.properties().device_name,

View File

@@ -0,0 +1,99 @@
use std::{cell::RefCell, rc::Rc};
use wayvr_ipc::packet_server;
#[cfg(feature = "wayvr")]
use crate::{backend::wayvr, overlays::wayvr::WayVRData};
use crate::{
backend::{
self,
task::{InputTask, OverlayTask, TaskType},
},
ipc::signal::WayVRSignal,
state::AppState,
windowing::{OverlaySelector, manager::OverlayWindowManager},
};
#[cfg(feature = "wayvr")]
fn process_tick_tasks(
tick_tasks: Vec<backend::wayvr::TickTask>,
r_wayvr: &Rc<RefCell<WayVRData>>,
) -> anyhow::Result<()> {
for tick_task in tick_tasks {
match tick_task {
backend::wayvr::TickTask::NewExternalProcess(request) => {
let mut wayvr = r_wayvr.borrow_mut();
log::info!("Registering external process with PID {}", request.pid);
wayvr.data.state.add_external_process(request.pid);
wayvr
.data
.state
.manager
.add_client(wayvr::client::WayVRClient {
client: request.client,
pid: request.pid,
});
}
}
}
Ok(())
}
pub fn tick_events<O>(
app: &mut AppState,
_overlays: &mut OverlayWindowManager<O>,
) -> anyhow::Result<()>
where
O: Default,
{
#[cfg(feature = "wayvr")]
let wayland_server = app.wayland_server.clone();
while let Some(signal) = app.wayvr_signals.read() {
match signal {
#[cfg(feature = "wayvr")]
WayVRSignal::BroadcastStateChanged(packet) => {
app.ipc_server
.broadcast(packet_server::PacketServer::WvrStateChanged(packet));
}
WayVRSignal::DeviceHaptics(device, haptics) => {
app.tasks
.enqueue(TaskType::Input(InputTask::Haptics { device, haptics }));
}
WayVRSignal::DropOverlay(overlay_id) => {
app.tasks
.enqueue(TaskType::Overlay(OverlayTask::Drop(OverlaySelector::Id(
overlay_id,
))));
}
WayVRSignal::CustomTask(custom_task) => {
app.tasks
.enqueue(TaskType::Overlay(OverlayTask::ModifyPanel(custom_task)));
}
}
}
#[cfg(feature = "wayvr")]
{
if let Some(wayland_server) = wayland_server {
let tick_tasks = wayland_server.borrow_mut().data.tick_events(app)?;
process_tick_tasks(tick_tasks, &wayland_server)?;
}
}
#[cfg(not(feature = "wayvr"))]
{
use super::ipc_server::TickParams;
app.ipc_server.tick(&mut TickParams {
input_state: &app.input_state,
signals: &app.wayvr_signals,
});
}
Ok(())
}

View File

@@ -1,6 +1,10 @@
use crate::state::AppState;
#[cfg(feature = "wayvr")]
use crate::backend::wayvr::{self, WayVRState};
use super::{TickTask, WayVRSignal, display, process, window};
use crate::{
backend::input::InputState,
ipc::{event_queue::SyncEventQueue, signal::WayVRSignal},
};
use bytes::BufMut;
use glam::Vec3A;
use interprocess::local_socket::{self, ToNsName, traits::Listener};
@@ -71,9 +75,12 @@ fn read_payload(conn: &mut local_socket::Stream, size: u32) -> Option<Payload> {
}
pub struct TickParams<'a> {
pub state: &'a mut super::WayVRState,
pub tasks: &'a mut Vec<TickTask>,
pub app: &'a AppState,
#[cfg(feature = "wayvr")]
pub wayland_state: &'a mut WayVRState,
#[cfg(feature = "wayvr")]
pub tasks: &'a mut Vec<wayvr::TickTask>,
pub signals: &'a SyncEventQueue<WayVRSignal>,
pub input_state: &'a InputState,
}
pub fn gen_args_vec(input: &str) -> Vec<&str> {
@@ -149,44 +156,11 @@ impl Connection {
Ok(())
}
fn handle_wvr_display_list(
&mut self,
params: &TickParams,
serial: ipc::Serial,
) -> anyhow::Result<()> {
let list: Vec<packet_server::WvrDisplay> = params
.state
.displays
.vec
.iter()
.enumerate()
.filter_map(|(idx, opt_cell)| {
let Some(cell) = opt_cell else {
return None;
};
let display = &cell.obj;
Some(display.as_packet(display::DisplayHandle::new(idx as u32, cell.generation)))
})
.collect();
send_packet(
&mut self.conn,
&ipc::data_encode(&PacketServer::WvrDisplayListResponse(
serial,
packet_server::WvrDisplayList { list },
)),
)?;
Ok(())
}
fn handle_wlx_input_state(
&mut self,
params: &TickParams,
serial: ipc::Serial,
) -> anyhow::Result<()> {
let input_state = &params.app.input_state;
let to_arr = |vec: &Vec3A| -> [f32; 3] { [vec.x, vec.y, vec.z] };
send_packet(
@@ -194,12 +168,12 @@ impl Connection {
&ipc::data_encode(&PacketServer::WlxInputStateResponse(
serial,
packet_server::WlxInputState {
hmd_pos: to_arr(&input_state.hmd.translation),
hmd_pos: to_arr(&params.input_state.hmd.translation),
left: WlxInputStatePointer {
pos: to_arr(&input_state.pointers[0].raw_pose.translation),
pos: to_arr(&params.input_state.pointers[0].raw_pose.translation),
},
right: WlxInputStatePointer {
pos: to_arr(&input_state.pointers[0].raw_pose.translation),
pos: to_arr(&params.input_state.pointers[0].raw_pose.translation),
},
},
)),
@@ -208,148 +182,55 @@ impl Connection {
Ok(())
}
fn handle_wvr_display_create(
#[cfg(feature = "wayvr")]
fn handle_wvr_window_list(
&mut self,
params: &mut TickParams,
serial: ipc::Serial,
packet_params: packet_client::WvrDisplayCreateParams,
) -> anyhow::Result<()> {
let display_handle = params.state.create_display(
packet_params.width,
packet_params.height,
&packet_params.name,
false,
)?;
params
.tasks
.push(TickTask::NewDisplay(packet_params, Some(display_handle)));
send_packet(
&mut self.conn,
&ipc::data_encode(&PacketServer::WvrDisplayCreateResponse(
serial,
display_handle.as_packet(),
)),
)?;
Ok(())
}
fn handle_wvr_display_remove(
&mut self,
params: &mut TickParams,
serial: ipc::Serial,
handle: packet_server::WvrDisplayHandle,
) -> anyhow::Result<()> {
let res = params
.state
.destroy_display(display::DisplayHandle::from_packet(handle))
.map_err(|e| format!("{e:?}"));
send_packet(
&mut self.conn,
&ipc::data_encode(&PacketServer::WvrDisplayRemoveResponse(serial, res)),
)?;
Ok(())
}
fn handle_wvr_display_set_visible(
params: &mut TickParams,
handle: packet_server::WvrDisplayHandle,
visible: bool,
) {
params.state.signals.send(WayVRSignal::DisplayVisibility(
display::DisplayHandle::from_packet(handle),
visible,
));
}
fn handle_wvr_display_set_window_layout(
params: &mut TickParams,
handle: packet_server::WvrDisplayHandle,
layout: packet_server::WvrDisplayWindowLayout,
) {
params.state.signals.send(WayVRSignal::DisplayWindowLayout(
display::DisplayHandle::from_packet(handle),
layout,
));
}
fn handle_wvr_display_window_list(
&mut self,
params: &mut TickParams,
serial: ipc::Serial,
display_handle: packet_server::WvrDisplayHandle,
) -> anyhow::Result<()> {
let mut send = |list: Option<packet_server::WvrWindowList>| -> anyhow::Result<()> {
send_packet(
&mut self.conn,
&ipc::data_encode(&PacketServer::WvrDisplayWindowListResponse(serial, list)),
&ipc::data_encode(&PacketServer::WvrWindowListResponse(serial, list)),
)
};
let Some(display) = params
.state
.displays
.get(&display::DisplayHandle::from_packet(display_handle.clone()))
else {
return send(None);
};
send(Some(packet_server::WvrWindowList {
list: display
.displayed_windows
list: params
.wayland_state
.wm
.borrow_mut()
.windows
.iter()
.filter_map(|disp_win| {
params
.state
.wm
.borrow_mut()
.windows
.get(&disp_win.window_handle)
.map(|win| packet_server::WvrWindow {
handle: window::WindowHandle::as_packet(&disp_win.window_handle),
process_handle: process::ProcessHandle::as_packet(
&disp_win.process_handle,
),
pos_x: win.pos_x,
pos_y: win.pos_y,
size_x: win.size_x,
size_y: win.size_y,
visible: win.visible,
display_handle: display_handle.clone(),
})
.map(|(handle, win)| packet_server::WvrWindow {
handle: wayvr::window::WindowHandle::as_packet(&handle),
process_handle: wayvr::process::ProcessHandle::as_packet(&win.process),
size_x: win.size_x,
size_y: win.size_y,
visible: win.visible,
})
.collect::<Vec<_>>(),
}))
}
#[cfg(feature = "wayvr")]
fn handle_wvr_window_set_visible(
params: &mut TickParams,
handle: packet_server::WvrWindowHandle,
visible: bool,
) {
let to_resize = if let Some(window) = params
.state
if let Some(window) = params
.wayland_state
.wm
.borrow_mut()
.windows
.get_mut(&window::WindowHandle::from_packet(handle))
.get_mut(&wayvr::window::WindowHandle::from_packet(handle))
{
window.visible = visible;
Some(window.display_handle)
} else {
None
};
if let Some(to_resize) = to_resize
&& let Some(display) = params.state.displays.get_mut(&to_resize)
{
display.reposition_windows();
display.trigger_rerender();
}
}
#[cfg(feature = "wayvr")]
fn handle_wvr_process_launch(
&mut self,
params: &mut TickParams,
@@ -359,8 +240,7 @@ impl Connection {
let args_vec = gen_args_vec(&packet_params.args);
let env_vec = gen_env_vec(&packet_params.env);
let res = params.state.spawn_process(
super::display::DisplayHandle::from_packet(packet_params.target_display),
let res = params.wayland_state.spawn_process(
&packet_params.exec,
&args_vec,
&env_vec,
@@ -378,34 +258,14 @@ impl Connection {
Ok(())
}
fn handle_wvr_display_get(
&mut self,
params: &TickParams,
serial: ipc::Serial,
display_handle: packet_server::WvrDisplayHandle,
) -> anyhow::Result<()> {
let native_handle = &display::DisplayHandle::from_packet(display_handle);
let disp = params
.state
.displays
.get(native_handle)
.map(|disp| disp.as_packet(*native_handle));
send_packet(
&mut self.conn,
&ipc::data_encode(&PacketServer::WvrDisplayGetResponse(serial, disp)),
)?;
Ok(())
}
#[cfg(feature = "wayvr")]
fn handle_wvr_process_list(
&mut self,
params: &TickParams,
serial: ipc::Serial,
) -> anyhow::Result<()> {
let list: Vec<packet_server::WvrProcess> = params
.state
.wayland_state
.processes
.vec
.iter()
@@ -415,7 +275,10 @@ impl Connection {
return None;
};
let process = &cell.obj;
Some(process.to_packet(process::ProcessHandle::new(idx as u32, cell.generation)))
Some(process.to_packet(wayvr::process::ProcessHandle::new(
idx as u32,
cell.generation,
)))
})
.collect();
@@ -431,12 +294,13 @@ impl Connection {
}
// This request doesn't return anything to the client
#[cfg(feature = "wayvr")]
fn handle_wvr_process_terminate(
params: &mut TickParams,
process_handle: packet_server::WvrProcessHandle,
) {
let native_handle = &process::ProcessHandle::from_packet(process_handle);
let process = params.state.processes.get_mut(native_handle);
let native_handle = &wayvr::process::ProcessHandle::from_packet(process_handle);
let process = params.wayland_state.processes.get_mut(native_handle);
let Some(process) = process else {
return;
@@ -445,15 +309,16 @@ impl Connection {
process.terminate();
}
#[cfg(feature = "wayvr")]
fn handle_wvr_process_get(
&mut self,
params: &TickParams,
serial: ipc::Serial,
process_handle: packet_server::WvrProcessHandle,
) -> anyhow::Result<()> {
let native_handle = &process::ProcessHandle::from_packet(process_handle);
let native_handle = &wayvr::process::ProcessHandle::from_packet(process_handle);
let process = params
.state
.wayland_state
.processes
.get(native_handle)
.map(|process| process.to_packet(*native_handle));
@@ -466,11 +331,13 @@ impl Connection {
Ok(())
}
fn handle_wlx_haptics(
fn handle_wlx_device_haptics(
params: &mut TickParams,
device: usize,
haptics_params: packet_client::WlxHapticsParams,
) {
params.state.signals.send(super::WayVRSignal::Haptics(
params.signals.send(WayVRSignal::DeviceHaptics(
device,
crate::backend::input::Haptics {
duration: haptics_params.duration,
frequency: haptics_params.frequency,
@@ -486,9 +353,8 @@ impl Connection {
use crate::backend::task::{ModifyPanelCommand, ModifyPanelTask};
params
.state
.signals
.send(super::WayVRSignal::CustomTask(ModifyPanelTask {
.send(WayVRSignal::CustomTask(ModifyPanelTask {
overlay: custom_params.overlay,
element: custom_params.element,
command: match custom_params.command {
@@ -511,6 +377,9 @@ impl Connection {
}));
}
// FIXME: we should probably respond an error to the client in case if wayland server feature is disabled
// fix this after we're done with the webkit-based wayvr-dashboard
#[allow(unused_variables)]
fn process_payload(&mut self, params: &mut TickParams, payload: Payload) -> anyhow::Result<()> {
let packet: PacketClient = ipc::data_decode(&payload)?;
@@ -524,44 +393,32 @@ impl Connection {
PacketClient::WlxInputState(serial) => {
self.handle_wlx_input_state(params, serial)?;
}
PacketClient::WvrDisplayList(serial) => {
self.handle_wvr_display_list(params, serial)?;
}
PacketClient::WvrDisplayGet(serial, display_handle) => {
self.handle_wvr_display_get(params, serial, display_handle)?;
}
PacketClient::WvrDisplayRemove(serial, display_handle) => {
self.handle_wvr_display_remove(params, serial, display_handle)?;
}
PacketClient::WvrDisplaySetVisible(display_handle, visible) => {
Self::handle_wvr_display_set_visible(params, display_handle, visible);
}
PacketClient::WvrDisplaySetWindowLayout(display_handle, layout) => {
Self::handle_wvr_display_set_window_layout(params, display_handle, layout);
}
PacketClient::WvrDisplayWindowList(serial, display_handle) => {
self.handle_wvr_display_window_list(params, serial, display_handle)?;
PacketClient::WvrWindowList(serial) => {
#[cfg(feature = "wayvr")]
self.handle_wvr_window_list(params, serial)?;
}
PacketClient::WvrWindowSetVisible(window_handle, visible) => {
#[cfg(feature = "wayvr")]
Self::handle_wvr_window_set_visible(params, window_handle, visible);
}
PacketClient::WvrProcessGet(serial, process_handle) => {
#[cfg(feature = "wayvr")]
self.handle_wvr_process_get(params, serial, process_handle)?;
}
PacketClient::WvrProcessList(serial) => {
#[cfg(feature = "wayvr")]
self.handle_wvr_process_list(params, serial)?;
}
PacketClient::WvrProcessLaunch(serial, packet_params) => {
#[cfg(feature = "wayvr")]
self.handle_wvr_process_launch(params, serial, packet_params)?;
}
PacketClient::WvrDisplayCreate(serial, packet_params) => {
self.handle_wvr_display_create(params, serial, packet_params)?;
}
PacketClient::WvrProcessTerminate(process_handle) => {
#[cfg(feature = "wayvr")]
Self::handle_wvr_process_terminate(params, process_handle);
}
PacketClient::WlxHaptics(haptics_params) => {
Self::handle_wlx_haptics(params, haptics_params);
PacketClient::WlxDeviceHaptics(device, haptics_params) => {
Self::handle_wlx_device_haptics(params, device, haptics_params);
}
PacketClient::WlxModifyPanel(custom_params) => {
Self::handle_wlx_panel(params, custom_params);

View File

@@ -0,0 +1,4 @@
pub mod event_queue;
pub mod events;
pub mod ipc_server;
pub mod signal;

View File

@@ -0,0 +1,8 @@
#[derive(Clone)]
pub enum WayVRSignal {
#[cfg(feature = "wayvr")]
BroadcastStateChanged(wayvr_ipc::packet_server::WvrStateChanged),
DeviceHaptics(usize, crate::backend::input::Haptics),
DropOverlay(crate::windowing::OverlayID),
CustomTask(crate::backend::task::ModifyPanelTask),
}

View File

@@ -22,6 +22,7 @@ mod config;
mod config_io;
mod graphics;
mod gui;
mod ipc;
mod overlays;
mod shaders;
mod state;

View File

@@ -222,9 +222,13 @@ impl OverlayBackend for ScreenBackend {
fn render(&mut self, app: &mut AppState, rdr: &mut RenderResources) -> anyhow::Result<()> {
// want panic; must be some if should_render was not Unable
let capture = self.cur_frame.as_ref().unwrap();
let image = capture.image.clone();
// want panic; must be Some if cur_frame is also Some
self.pipeline.as_mut().unwrap().render(&capture, app, rdr)?;
self.pipeline
.as_mut()
.unwrap()
.render(image, capture.mouse.as_ref(), app, rdr)?;
self.capture.request_new_frame();
Ok(())
}

View File

@@ -17,8 +17,8 @@ use wgui::gfx::{
pipeline::{WGfxPipeline, WPipelineCreateInfo},
};
use wlx_capture::{
WlxCapture,
frame::{self as wlx_frame, DrmFormat, FrameFormat, MouseMeta, Transform, WlxFrame},
DrmFormat, WlxCapture,
frame::{self as wlx_frame, FrameFormat, MouseMeta, WlxFrame},
};
use wlx_common::{config::GeneralConfig, overlays::StereoMode};
@@ -39,7 +39,8 @@ struct BufPass {
buf_vert: Subbuffer<[Vert2Uv]>,
}
pub(super) struct ScreenPipeline {
/// A render pipeline that supports mouse + stereo
pub struct ScreenPipeline {
mouse: BufPass,
pass: SmallVec<[BufPass; 2]>,
pipeline: Arc<WGfxPipeline<Vert2Uv>>,
@@ -49,11 +50,7 @@ pub(super) struct ScreenPipeline {
}
impl ScreenPipeline {
pub(super) fn new(
meta: &FrameMeta,
app: &mut AppState,
stereo: StereoMode,
) -> anyhow::Result<Self> {
pub fn new(meta: &FrameMeta, app: &mut AppState, stereo: StereoMode) -> anyhow::Result<Self> {
let extentf = [meta.extent[0] as f32, meta.extent[1] as f32];
let pipeline = app.gfx.create_pipeline(
@@ -198,13 +195,13 @@ impl ScreenPipeline {
Ok(BufPass { pass, buf_vert })
}
pub(super) fn render(
pub fn render(
&mut self,
capture: &WlxCaptureOut,
image: Arc<ImageView>,
mouse: Option<&MouseMeta>,
app: &mut AppState,
rdr: &mut RenderResources,
) -> anyhow::Result<()> {
let view = ImageView::new_default(capture.image.clone())?;
self.buf_alpha.write()?[0] = rdr.alpha;
for (eye, cmd_buf) in rdr.cmd_bufs.iter_mut().enumerate() {
@@ -212,11 +209,11 @@ impl ScreenPipeline {
current
.pass
.update_sampler(0, view.clone(), app.gfx.texture_filter)?;
.update_sampler(0, image.clone(), app.gfx.texture_filter)?;
cmd_buf.run_ref(&current.pass)?;
if let Some(mouse) = capture.mouse.as_ref() {
if let Some(mouse) = mouse.as_ref() {
let size = CURSOR_SIZE * self.extentf[1];
let half_size = size * 0.5;
@@ -325,10 +322,10 @@ impl WlxCaptureIn {
}
#[derive(Clone)]
pub struct WlxCaptureOut {
image: Arc<Image>,
format: FrameFormat,
mouse: Option<MouseMeta>,
pub(super) struct WlxCaptureOut {
pub(super) image: Arc<ImageView>,
pub(super) format: FrameFormat,
pub(super) mouse: Option<MouseMeta>,
}
impl WlxCaptureOut {
@@ -340,10 +337,6 @@ impl WlxCaptureOut {
format: self.image.format(),
}
}
pub(super) const fn get_transform(&self) -> Transform {
self.format.transform
}
}
fn upload_image(
@@ -390,7 +383,7 @@ pub(super) fn receive_callback(me: &WlxCaptureIn, frame: WlxFrame) -> Option<Wlx
let format = frame.format;
match me.gfx.dmabuf_texture(frame) {
Ok(image) => Some(WlxCaptureOut {
image,
image: ImageView::new_default(image).ok()?,
format,
mouse: None,
}),
@@ -406,7 +399,7 @@ pub(super) fn receive_callback(me: &WlxCaptureIn, frame: WlxFrame) -> Option<Wlx
return None;
};
let format = match fourcc_to_vk(frame.format.fourcc) {
let format = match fourcc_to_vk(frame.format.drm_format.code) {
Ok(x) => x,
Err(e) => {
log::error!("{}: {}", me.name, e);
@@ -439,7 +432,7 @@ pub(super) fn receive_callback(me: &WlxCaptureIn, frame: WlxFrame) -> Option<Wlx
}?;
Some(WlxCaptureOut {
image,
image: ImageView::new_default(image).ok()?,
format: frame.format,
mouse: None,
})
@@ -447,7 +440,7 @@ pub(super) fn receive_callback(me: &WlxCaptureIn, frame: WlxFrame) -> Option<Wlx
WlxFrame::MemPtr(frame) => {
log::trace!("{}: New MemPtr frame", me.name);
let format = match fourcc_to_vk(frame.format.fourcc) {
let format = match fourcc_to_vk(frame.format.drm_format.code) {
Ok(x) => x,
Err(e) => {
log::error!("{}: {}", me.name, e);
@@ -459,7 +452,7 @@ pub(super) fn receive_callback(me: &WlxCaptureIn, frame: WlxFrame) -> Option<Wlx
let image = upload_image(me, frame.format.width, frame.format.height, format, data)?;
Some(WlxCaptureOut {
image,
image: ImageView::new_default(image).ok()?,
format: frame.format,
mouse: frame.mouse,
})

View File

@@ -14,7 +14,7 @@ use crate::{
};
pub mod backend;
mod capture;
pub mod capture;
#[cfg(feature = "wayland")]
pub mod mirror;
#[cfg(feature = "pipewire")]

File diff suppressed because it is too large Load Diff

View File

@@ -12,11 +12,6 @@ version: 1
# "software": Read pixel data to memory via glReadPixels() every time a content has been updated. Minor performance impact on large resolutions
blit_method: "dmabuf"
# Set to true if you want to make Wyland server instantly available.
# By default, WayVR starts only when it's needed.
# (this option is primarily used for remote starting external processes and development purposes)
run_compositor_at_start: false
# Automatically close overlays with zero window count?
auto_hide: true

View File

@@ -27,6 +27,7 @@ use crate::{
config_io::{self, get_config_file_path},
graphics::WGfxExtras,
gui,
ipc::{event_queue::SyncEventQueue, ipc_server, signal::WayVRSignal},
subsystem::{audio::AudioOutput, dbus::DbusConnector, input::HidWrapper},
};
@@ -53,11 +54,14 @@ pub struct AppState {
pub xr_backend: XrBackend,
pub ipc_server: ipc_server::WayVRServer,
pub wayvr_signals: SyncEventQueue<WayVRSignal>,
#[cfg(feature = "osc")]
pub osc_sender: Option<OscSender>,
#[cfg(feature = "wayvr")]
pub wayvr: Option<Rc<RefCell<WayVRData>>>, // Dynamically created if requested
pub wayland_server: Option<Rc<RefCell<WayVRData>>>,
}
#[allow(unused_mut)]
@@ -71,17 +75,26 @@ impl AppState {
let mut tasks = TaskContainer::new();
let session = AppSession::load();
let wayvr_signals = SyncEventQueue::new();
#[cfg(feature = "wayvr")]
let wayvr = session
let wayland_server = session
.wayvr_config
.post_load(&session.config, &mut tasks)?;
.post_load(
gfx.clone(),
&gfx_extras,
&session.config,
&mut tasks,
wayvr_signals.clone(),
)
.inspect_err(|e| log::error!("Could not initialize wayland server: {e:?}"))
.ok();
let mut hid_provider = HidWrapper::new();
#[cfg(feature = "wayvr")]
if let Some(wayvr) = wayvr.as_ref() {
hid_provider.set_wayvr(wayvr.clone());
if let Some(wayland_server) = wayland_server.as_ref() {
hid_provider.set_wayvr(wayland_server.clone());
}
#[cfg(feature = "osc")]
@@ -114,6 +127,8 @@ impl AppState {
let dbus = DbusConnector::default();
let ipc_server = ipc_server::WayVRServer::new()?;
Ok(Self {
session,
tasks,
@@ -135,30 +150,17 @@ impl AppState {
)?,
dbus,
xr_backend,
ipc_server,
wayvr_signals,
#[cfg(feature = "osc")]
osc_sender,
#[cfg(feature = "wayvr")]
wayvr,
wayland_server,
})
}
#[cfg(feature = "wayvr")]
#[allow(dead_code)]
pub fn get_wayvr(&mut self) -> anyhow::Result<Rc<RefCell<WayVRData>>> {
if let Some(wvr) = &self.wayvr {
Ok(wvr.clone())
} else {
let wayvr = Rc::new(RefCell::new(WayVRData::new(
WayVRConfig::get_wayvr_config(&self.session.config, &self.session.wayvr_config)?,
)?));
self.hid_provider.set_wayvr(wayvr.clone());
self.wayvr = Some(wayvr.clone());
Ok(wayvr)
}
}
pub fn try_load_bytes(path: &str, fallback_data: &'static [u8]) -> &'static [u8] {
if path.is_empty() {
return fallback_data;
@@ -187,7 +189,7 @@ pub struct AppSession {
pub config: GeneralConfig,
#[cfg(feature = "wayvr")]
pub wayvr_config: WayVRConfig,
pub wayvr_config: WayVRConfig, // TODO: rename to "wayland_server_config"
pub toast_topics: IdMap<ToastTopic, ToastDisplayMethod>,
}