diff --git a/contrib/wayvr/README.md b/contrib/wayvr/README.md
new file mode 100644
index 0000000..673445d
--- /dev/null
+++ b/contrib/wayvr/README.md
@@ -0,0 +1,90 @@
+
+
+
+
+**WayVR acts as a bridge between Wayland applications and wlx-overlay-s panels, allowing you to display your applications within a VR environment. Internally, WayVR utilizes Smithay to run a Wayland compositor.**
+
+# >> Quick setup <<
+
+#### Configure your applications list
+
+Go to `src/res/wayvr.yaml` to configure your desired application list. This configuration file represents all currently available WayVR options. Feel free to adjust it to your liking.
+
+#### Add WayVR Launcher to your watch
+
+Copy `watch_wayvr_example.yaml` to `~/.config/wlxoverlay/watch.yaml`. This file contains pre-configured **WayVRLauncher** and **WayVRDisplayList** widget types. By default, the _default_catalog_ is used.
+
+That's it; you're all set!
+
+###### _Make sure you have `wayvr` feature enabled in Cargo.toml (enabled by default)_
+
+![alt text](https://raw.githubusercontent.com/galister/wlx-overlay-s/refs/heads/guide/wayvr/watch.jpg)
+
+# Overview
+
+### Features
+
+- Display Wayland applications without GPU overhead (zero-copy via dma-buf)
+- Mouse and keyboard input, with precision scrolling support
+- Tested on AMD and Nvidia
+
+### Supported software
+
+- Basically all Qt applications (they work out of the box)
+- Most XWayland applications via `cage`
+
+### XWayland
+
+WayVR does not have native XWayland support. You can run X11 applications (or these who require DISPLAY set) by wrapping them in a `cage` program, like so:
+
+```yaml
+- name: "Xeyes"
+ target_display: "Disp1"
+ exec: "cage"
+ args: "xeyes -- -fg blue"
+```
+
+instead of:
+
+```yaml
+- name: "Xeyes"
+ target_display: "Disp1"
+ exec: "xeyes"
+ args: "-fg blue"
+```
+
+in `wayvr.yaml` configuration file, in your desired catalog.
+
+### Launching external apps inside WayVR
+
+To launch your app externally:
+
+```sh
+DISPLAY= WAYLAND_DISPLAY=wayland-$(cat $XDG_RUNTIME_DIR/wayvr.disp) yourapp
+```
+
+or (in the most cases):
+
+```
+DISPLAY= WAYLAND_DISPLAY=wayland-20 yourapp
+```
+
+Setting `DISPLAY` to an empty string forces various apps to use Wayland instead of X11.
+
+# Troubleshooting
+
+### My application doesn't launch but others do!
+
+Even though some applications support Wayland, some still check for the `DISPLAY` environment variable and an available X11 server, throwing an error. This can also be fixed by running `cage` on top of them.
+
+### Image corruption
+
+dma-buf textures may display various graphical glitches due to unsupported dma-buf tiling modifiers between GLES<->Vulkan on Radeon RDNA3 graphics cards. Current situation: https://gitlab.freedesktop.org/mesa/mesa/-/issues/11629). Nvidia should work out of the box, without any isues. Alternatively, you can run wlx-overlay-s with `LIBGL_ALWAYS_SOFTWARE=1` to mitigate that (only the Smithay compositor will run in software renderer mode, wlx will still be accelerated).
+
+### Floating windows
+
+Context menus are not functional in most cases yet, including drag & drop support.
+
+### Forced window shadows in GTK
+
+GNOME still insists on rendering client-side decorations instead of server-side ones. This results in all GTK applications looking odd due to additional window shadows. [Fix here, "Client-side decorations"](https://wiki.archlinux.org/title/GTK)
diff --git a/contrib/wayvr/watch_wayvr_example.yaml b/contrib/wayvr/watch_wayvr_example.yaml
new file mode 100644
index 0000000..10f56ef
--- /dev/null
+++ b/contrib/wayvr/watch_wayvr_example.yaml
@@ -0,0 +1,195 @@
+# looking to make changes?
+# drop me in ~/.config/wlxoverlay/watch.yaml
+#
+
+width: 0.115
+
+size: [400, 272]
+
+elements:
+ # background panel
+ - type: Panel
+ rect: [0, 30, 400, 130]
+ corner_radius: 20
+ bg_color: "#24273a"
+
+ - type: Button
+ rect: [2, 162, 26, 36]
+ corner_radius: 4
+ font_size: 15
+ bg_color: "#c6a0f6"
+ fg_color: "#24273a"
+ text: "C"
+ click_up: # destroy if exists, otherwise create
+ - type: Window
+ target: settings
+ action: ShowUi # only triggers if not exists
+ - type: Window
+ target: settings
+ action: Destroy # only triggers if exists since before current frame
+
+ # Keyboard button
+ - type: Button
+ rect: [32, 162, 60, 36]
+ corner_radius: 4
+ font_size: 15
+ fg_color: "#24273a"
+ bg_color: "#a6da95"
+ text: Kbd
+ click_up:
+ - type: Overlay
+ target: "kbd"
+ action: ToggleVisible
+ long_click_up:
+ - type: Overlay
+ target: "kbd"
+ action: Reset
+ right_up:
+ - type: Overlay
+ target: "kbd"
+ action: ToggleImmovable
+ middle_up:
+ - type: Overlay
+ target: "kbd"
+ action: ToggleInteraction
+ scroll_up:
+ - type: Overlay
+ target: "kbd"
+ action:
+ Opacity: { delta: 0.025 }
+ scroll_down:
+ - type: Overlay
+ target: "kbd"
+ action:
+ Opacity: { delta: -0.025 }
+
+ # bottom row, of keyboard + overlays
+ - type: OverlayList
+ rect: [94, 160, 306, 40]
+ corner_radius: 4
+ font_size: 15
+ fg_color: "#cad3f5"
+ bg_color: "#1e2030"
+ layout: Horizontal
+ click_up: ToggleVisible
+ long_click_up: Reset
+ right_up: ToggleImmovable
+ middle_up: ToggleInteraction
+ scroll_up:
+ Opacity: { delta: 0.025 }
+ scroll_down:
+ Opacity: { delta: -0.025 }
+
+ - type: WayVRLauncher
+ rect: [0, 200, 400, 36]
+ corner_radius: 4
+ font_size: 15
+ fg_color: "#24273a"
+ bg_color: "#e590c4"
+ catalog_name: "default_catalog"
+
+ - type: WayVRDisplayList
+ rect: [0, 236, 400, 36]
+ corner_radius: 4
+ font_size: 15
+ fg_color: "#24273a"
+ bg_color: "#ca68a4"
+
+ # local clock
+ - type: Label
+ rect: [19, 90, 200, 50]
+ corner_radius: 4
+ font_size: 46 # Use 32 for 12-hour time
+ fg_color: "#cad3f5"
+ source: Clock
+ format: "%H:%M" # 23:59
+ #format: "%I:%M %p" # 11:59 PM
+
+ # local date
+ - type: Label
+ rect: [20, 117, 200, 20]
+ corner_radius: 4
+ font_size: 14
+ fg_color: "#cad3f5"
+ source: Clock
+ format: "%x" # local date representation
+
+ # local day-of-week
+ - type: Label
+ rect: [20, 137, 200, 50]
+ corner_radius: 4
+ font_size: 14
+ fg_color: "#cad3f5"
+ source: Clock
+ format: "%A" # Tuesday
+ #format: "%a" # Tue
+
+ # alt clock 1
+ - type: Label
+ rect: [210, 90, 200, 50]
+ corner_radius: 4
+ font_size: 24 # Use 18 for 12-hour time
+ fg_color: "#8bd5ca"
+ source: Clock
+ timezone: "Asia/Tokyo" # change TZ1 here
+ format: "%H:%M" # 23:59
+ #format: "%I:%M %p" # 11:59 PM
+ - type: Label
+ rect: [210, 60, 200, 50]
+ corner_radius: 4
+ font_size: 14
+ fg_color: "#8bd5ca"
+ source: Static
+ text: "Tokyo" # change TZ1 label here
+
+ # alt clock 2
+ - type: Label
+ rect: [210, 150, 200, 50]
+ corner_radius: 4
+ font_size: 24 # Use 18 for 12-hour time
+ fg_color: "#b7bdf8"
+ source: Clock
+ timezone: "America/Chicago" # change TZ2 here
+ format: "%H:%M" # 23:59
+ #format: "%I:%M %p" # 11:59 PM
+ - type: Label
+ rect: [210, 120, 200, 50]
+ corner_radius: 4
+ font_size: 14
+ fg_color: "#b7bdf8"
+ source: Static
+ text: "Chicago" # change TZ2 label here
+
+ # batteries
+ - type: BatteryList
+ rect: [0, 5, 400, 30]
+ corner_radius: 4
+ font_size: 16
+ fg_color: "#8bd5ca"
+ fg_color_low: "#B06060"
+ fg_color_charging: "#6080A0"
+ num_devices: 9
+ layout: Horizontal
+ low_threshold: 33
+
+ # volume buttons
+ - type: Button
+ rect: [315, 52, 70, 32]
+ corner_radius: 4
+ font_size: 13
+ fg_color: "#cad3f5"
+ bg_color: "#5b6078"
+ text: "Vol +"
+ click_down:
+ - type: Exec
+ command: ["pactl", "set-sink-volume", "@DEFAULT_SINK@", "+5%"]
+ - type: Button
+ rect: [315, 116, 70, 32]
+ corner_radius: 4
+ font_size: 13
+ fg_color: "#cad3f5"
+ bg_color: "#5b6078"
+ text: "Vol -"
+ click_down:
+ - type: Exec
+ command: ["pactl", "set-sink-volume", "@DEFAULT_SINK@", "-5%"]
diff --git a/src/backend/openvr/mod.rs b/src/backend/openvr/mod.rs
index 2c1b868..95e01a5 100644
--- a/src/backend/openvr/mod.rs
+++ b/src/backend/openvr/mod.rs
@@ -346,7 +346,7 @@ pub fn openvr_run(running: Arc, show_by_default: bool) -> Result<(),
#[cfg(feature = "wayvr")]
if let Some(wayvr) = &state.wayvr {
- wayvr.borrow_mut().tick_finish()?;
+ wayvr.borrow_mut().state.tick_finish()?;
}
// chaperone
diff --git a/src/backend/openxr/mod.rs b/src/backend/openxr/mod.rs
index f2cd591..d6e04cd 100644
--- a/src/backend/openxr/mod.rs
+++ b/src/backend/openxr/mod.rs
@@ -402,7 +402,7 @@ pub fn openxr_run(running: Arc, show_by_default: bool) -> Result<(),
#[cfg(feature = "wayvr")]
if let Some(wayvr) = &app_state.wayvr {
- wayvr.borrow_mut().tick_finish()?;
+ wayvr.borrow_mut().state.tick_finish()?;
}
command_buffer.build_and_execute_now()?;
diff --git a/src/backend/wayvr/README.md b/src/backend/wayvr/README.md
deleted file mode 100644
index fd72057..0000000
--- a/src/backend/wayvr/README.md
+++ /dev/null
@@ -1,37 +0,0 @@
-**WayVR acts as a bridge between Wayland applications and wlx-overlay-s panels, allowing you to display your applications within a VR environment. Internally, WayVR utilizes Smithay to run a Wayland compositor.**
-
-# Features
-
-- Display Wayland applications without GPU overhead (zero-copy via dma-buf)
-- Mouse input
-- Precision scrolling support
-- XWayland "support" via `cage`
-
-# Supported hardware
-
-### Confirmed working GPUs
-
-- Navi 32 family: AMD Radeon RX 7800 XT **\***
-- Navi 23 family: AMD Radeon RX 6600 XT
-- Navi 21 family: AMD Radeon Pro W6800, AMD Radeon RX 6800 XT
-- Nvidia GTX 16 Series
-- _Your GPU here? (Let us know!)_
-
-**\*** - With dmabuf modifier mitigation (probably Mesa bug)
-
-# Supported software
-
-- Basically all Qt applications (they work out of the box)
-- Most XWayland applications via `cage`
-
-# Known issues
-
-- Context menus are not functional in most cases yet
-
-- Due to unknown circumstances, dma-buf textures may display various graphical glitches due to invalid dma-buf tiling modifier. Please report your GPU model when filing an issue. Alternatively, you can run wlx-overlay-s with `LIBGL_ALWAYS_SOFTWARE=1` to mitigate that (only the Smithay compositor will run in software renderer mode, wlx will still be accelerated).
-
-- ~~Potential data race in the rendering pipeline - A texture could be displayed during the clear-and-blit process in the compositor, causing minor artifacts (no fence sync support yet).~~ happens on all overlays on a simulated Monado driver
-
-- Even though some applications support Wayland, some still check for the `DISPLAY` environment variable and an available X11 server, throwing an error. This can be fixed by running `cage`.
-
-- GNOME still insists on rendering client-side decorations instead of server-side ones. This results in all GTK applications looking odd due to additional window shadows. [Fix here, "Client-side decorations"](https://wiki.archlinux.org/title/GTK)
diff --git a/src/backend/wayvr/client.rs b/src/backend/wayvr/client.rs
index 6bb5888..3493e99 100644
--- a/src/backend/wayvr/client.rs
+++ b/src/backend/wayvr/client.rs
@@ -201,7 +201,7 @@ fn export_display_number(display_num: u32) -> anyhow::Result<()> {
.map(PathBuf::from)
.unwrap_or_else(|_| PathBuf::from("/tmp"));
path.push("wayvr.disp");
- std::fs::write(path, format!("{}\n", display_num)).unwrap();
+ std::fs::write(path, format!("{}\n", display_num))?;
Ok(())
}
diff --git a/src/backend/wayvr/display.rs b/src/backend/wayvr/display.rs
index 986cd0d..912f97e 100644
--- a/src/backend/wayvr/display.rs
+++ b/src/backend/wayvr/display.rs
@@ -19,7 +19,7 @@ use crate::{backend::overlay::OverlayID, gen_id};
use super::{
client::WayVRManager, comp::send_frames_surface_tree, egl_data, event_queue::SyncEventQueue,
- process, smithay_wrapper, window,
+ process, smithay_wrapper, time, window,
};
fn generate_auth_key() -> String {
@@ -54,6 +54,7 @@ pub struct Display {
wm: Rc>,
pub displayed_windows: Vec,
wayland_env: super::WaylandEnv,
+ last_pressed_time_ms: u64,
// Render data stuff
gles_texture: GlesTexture, // TODO: drop texture
@@ -121,6 +122,7 @@ impl Display {
primary,
overlay_id: None,
tasks: SyncEventQueue::new(),
+ last_pressed_time_ms: 0,
})
}
@@ -250,7 +252,18 @@ impl Display {
}
}
- pub fn send_mouse_move(&self, manager: &mut WayVRManager, x: u32, y: u32) {
+ pub fn send_mouse_move(
+ &self,
+ config: &super::Config,
+ manager: &mut WayVRManager,
+ x: u32,
+ y: u32,
+ ) {
+ let current_ms = time::get_millis();
+ if self.last_pressed_time_ms + config.click_freeze_time_ms as u64 > 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) {
@@ -283,10 +296,12 @@ impl Display {
}
}
- pub fn send_mouse_down(&self, manager: &mut WayVRManager, index: super::MouseIndex) {
+ pub fn send_mouse_down(&mut self, manager: &mut WayVRManager, 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)
{
diff --git a/src/backend/wayvr/event_queue.rs b/src/backend/wayvr/event_queue.rs
index 40a940b..6f6df55 100644
--- a/src/backend/wayvr/event_queue.rs
+++ b/src/backend/wayvr/event_queue.rs
@@ -3,30 +3,30 @@
use std::{cell::RefCell, collections::VecDeque, rc::Rc};
struct Data {
- queue: VecDeque,
+ queue: VecDeque,
}
#[derive(Clone)]
pub struct SyncEventQueue {
- data: Rc>>,
+ data: Rc>>,
}
impl SyncEventQueue {
- pub fn new() -> Self {
- Self {
- data: Rc::new(RefCell::new(Data {
- queue: Default::default(),
- })),
- }
- }
+ pub fn new() -> Self {
+ Self {
+ data: Rc::new(RefCell::new(Data {
+ queue: Default::default(),
+ })),
+ }
+ }
- pub fn send(&self, message: DataType) {
- let mut data = self.data.borrow_mut();
- data.queue.push_back(message);
- }
+ pub fn send(&self, message: DataType) {
+ let mut data = self.data.borrow_mut();
+ data.queue.push_back(message);
+ }
- pub fn read(&self) -> Option {
- let mut data = self.data.borrow_mut();
- data.queue.pop_front()
- }
+ pub fn read(&self) -> Option {
+ let mut data = self.data.borrow_mut();
+ data.queue.pop_front()
+ }
}
diff --git a/src/backend/wayvr/handle.rs b/src/backend/wayvr/handle.rs
index 2ca7989..2947e3e 100644
--- a/src/backend/wayvr/handle.rs
+++ b/src/backend/wayvr/handle.rs
@@ -20,7 +20,7 @@ macro_rules! gen_id {
}
//ThingHandle
- #[derive(Default, Clone, Copy, PartialEq)]
+ #[derive(Default, Clone, Copy, PartialEq, Hash, Eq)]
pub struct $handle_name {
idx: u32,
generation: u64,
diff --git a/src/backend/wayvr/mod.rs b/src/backend/wayvr/mod.rs
index 485343a..c2dcf90 100644
--- a/src/backend/wayvr/mod.rs
+++ b/src/backend/wayvr/mod.rs
@@ -65,6 +65,12 @@ pub enum WayVRTask {
ProcessTerminationRequest(process::ProcessHandle),
}
+pub struct Config {
+ pub click_freeze_time_ms: u32,
+ pub keyboard_repeat_delay_ms: u32,
+ pub keyboard_repeat_rate: u32,
+}
+
#[allow(dead_code)]
pub struct WayVR {
time_start: u64,
@@ -74,6 +80,7 @@ pub struct WayVR {
wm: Rc>,
egl_data: Rc,
pub processes: process::ProcessVec,
+ config: Config,
tasks: SyncEventQueue,
}
@@ -89,7 +96,7 @@ pub enum TickResult {
}
impl WayVR {
- pub fn new() -> anyhow::Result {
+ pub fn new(config: Config) -> anyhow::Result {
log::info!("Initializing WayVR");
let display: wayland_server::Display = wayland_server::Display::new()?;
let dh = display.handle();
@@ -100,8 +107,11 @@ impl WayVR {
let data_device = DataDeviceState::new::(&dh);
let mut seat = seat_state.new_wl_seat(&dh, "wayvr");
- // TODO: Keyboard repeat delay and rate?
- let seat_keyboard = seat.add_keyboard(Default::default(), 100, 100)?;
+ let seat_keyboard = seat.add_keyboard(
+ Default::default(),
+ config.keyboard_repeat_delay_ms as i32,
+ config.keyboard_repeat_rate as i32,
+ )?;
let seat_pointer = seat.add_pointer();
let tasks = SyncEventQueue::new();
@@ -131,6 +141,7 @@ impl WayVR {
egl_data: Rc::new(egl_data),
wm: Rc::new(RefCell::new(window::WindowManager::new())),
tasks,
+ config,
})
}
@@ -222,7 +233,7 @@ impl WayVR {
}
} else {
log::error!(
- "WayVR window creation failed: Unexpected process PID {}. It wasn't registered before.",
+ "WayVR window creation failed: Unexpected process ID {}. It wasn't registered before.",
client.pid
);
}
@@ -255,12 +266,12 @@ impl WayVR {
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(&mut self.manager, x, y);
+ display.send_mouse_move(&self.config, &mut self.manager, x, y);
}
}
pub fn send_mouse_down(&mut self, display: display::DisplayHandle, index: MouseIndex) {
- if let Some(display) = self.displays.get(&display) {
+ if let Some(display) = self.displays.get_mut(&display) {
display.send_mouse_down(&mut self.manager, index);
}
}
diff --git a/src/config_wayvr.rs b/src/config_wayvr.rs
index 9847619..ef11843 100644
--- a/src/config_wayvr.rs
+++ b/src/config_wayvr.rs
@@ -17,7 +17,7 @@ use crate::{
wayvr,
},
config::{load_known_yaml, ConfigType},
- overlays::wayvr::WayVRAction,
+ overlays::wayvr::{WayVRAction, WayVRState},
};
// Flat version of RelativeTo
@@ -106,10 +106,19 @@ impl WayVRConfig {
None
}
+ pub fn get_wayvr_config(config: &crate::config::GeneralConfig) -> wayvr::Config {
+ wayvr::Config {
+ click_freeze_time_ms: config.click_freeze_time_ms,
+ keyboard_repeat_delay_ms: 200,
+ keyboard_repeat_rate: 50,
+ }
+ }
+
pub fn post_load(
&self,
+ config: &crate::config::GeneralConfig,
tasks: &mut TaskContainer,
- ) -> anyhow::Result