From 40f50a147be086751b6c4258806c9c29f88e4bd6 Mon Sep 17 00:00:00 2001
From: galister <22305755+galister@users.noreply.github.com>
Date: Thu, 3 Jul 2025 02:42:49 +0900
Subject: [PATCH] bar + overlaybackend refactor
---
uidev/assets/bar/add.svg | 2 +-
uidev/assets/bar/background-off.svg | 2 +-
uidev/assets/bar/background.svg | 2 +-
uidev/assets/bar/cancel.svg | 2 +-
uidev/assets/bar/checkbox-checked.svg | 2 +-
uidev/assets/bar/checkbox.svg | 2 +-
uidev/assets/bar/delete.svg | 2 +-
uidev/assets/bar/fade.svg | 2 +-
uidev/assets/bar/inout.svg | 2 +-
uidev/assets/bar/lock.svg | 2 +-
uidev/assets/bar/lock_open.svg | 2 +-
uidev/assets/bar/mouse.svg | 2 +-
uidev/assets/bar/mouse_lock.svg | 2 +-
uidev/assets/bar/move-all.svg | 2 +-
uidev/assets/bar/move-horizontal.svg | 2 +-
uidev/assets/bar/resize.svg | 2 +-
uidev/assets/bar/screen-add.svg | 2 +-
uidev/assets/bar/screen-options.svg | 2 +-
uidev/assets/bar/screen-remove.svg | 2 +-
uidev/assets/gui/bar.xml | 35 +-
wlx-overlay-s/src/assets/bar/add.svg | 1 +
wlx-overlay-s/src/assets/bar/anchor.svg | 1 +
.../src/assets/bar/background-off.svg | 1 +
wlx-overlay-s/src/assets/bar/background.svg | 1 +
wlx-overlay-s/src/assets/bar/cancel.svg | 1 +
.../src/assets/bar/checkbox-checked.svg | 1 +
wlx-overlay-s/src/assets/bar/checkbox.svg | 1 +
wlx-overlay-s/src/assets/bar/delete.svg | 1 +
wlx-overlay-s/src/assets/bar/fade.svg | 1 +
wlx-overlay-s/src/assets/bar/inout.svg | 1 +
wlx-overlay-s/src/assets/bar/lock.svg | 1 +
wlx-overlay-s/src/assets/bar/lock_open.svg | 1 +
wlx-overlay-s/src/assets/bar/mouse.svg | 1 +
wlx-overlay-s/src/assets/bar/mouse_lock.svg | 1 +
wlx-overlay-s/src/assets/bar/move-all.svg | 1 +
.../src/assets/bar/move-horizontal.svg | 1 +
wlx-overlay-s/src/assets/bar/resize.svg | 1 +
wlx-overlay-s/src/assets/bar/screen-add.svg | 1 +
.../src/assets/bar/screen-options.svg | 1 +
.../src/assets/bar/screen-remove.svg | 1 +
wlx-overlay-s/src/assets/gui/bar.xml | 35 +
wlx-overlay-s/src/backend/common.rs | 217 +--
wlx-overlay-s/src/backend/input.rs | 32 +-
wlx-overlay-s/src/backend/openvr/lines.rs | 50 +-
wlx-overlay-s/src/backend/openvr/mod.rs | 8 +-
wlx-overlay-s/src/backend/openxr/mod.rs | 8 +-
wlx-overlay-s/src/backend/overlay.rs | 129 +-
wlx-overlay-s/src/gui/panel.rs | 165 +--
wlx-overlay-s/src/overlays/anchor.rs | 3 +-
wlx-overlay-s/src/overlays/bar.rs | 45 +
.../src/overlays/keyboard/builder.rs | 12 +-
wlx-overlay-s/src/overlays/keyboard/mod.rs | 52 +-
wlx-overlay-s/src/overlays/mirror.rs | 52 +-
wlx-overlay-s/src/overlays/mod.rs | 1 +
wlx-overlay-s/src/overlays/screen.rs | 1262 -----------------
wlx-overlay-s/src/overlays/screen/backend.rs | 256 ++++
wlx-overlay-s/src/overlays/screen/capture.rs | 442 ++++++
wlx-overlay-s/src/overlays/screen/mod.rs | 115 ++
wlx-overlay-s/src/overlays/screen/pw.rs | 134 ++
wlx-overlay-s/src/overlays/screen/wl.rs | 164 +++
wlx-overlay-s/src/overlays/screen/x11.rs | 209 +++
wlx-overlay-s/src/overlays/tooltip.rs | 0
wlx-overlay-s/src/overlays/watch.rs | 6 +-
wlx-overlay-s/src/overlays/wayvr.rs | 183 ++-
wlx-overlay-s/src/state.rs | 5 +-
65 files changed, 1743 insertions(+), 1935 deletions(-)
mode change 100644 => 120000 uidev/assets/bar/add.svg
mode change 100644 => 120000 uidev/assets/bar/background-off.svg
mode change 100644 => 120000 uidev/assets/bar/background.svg
mode change 100644 => 120000 uidev/assets/bar/cancel.svg
mode change 100644 => 120000 uidev/assets/bar/checkbox-checked.svg
mode change 100644 => 120000 uidev/assets/bar/checkbox.svg
mode change 100644 => 120000 uidev/assets/bar/delete.svg
mode change 100644 => 120000 uidev/assets/bar/fade.svg
mode change 100644 => 120000 uidev/assets/bar/inout.svg
mode change 100644 => 120000 uidev/assets/bar/lock.svg
mode change 100644 => 120000 uidev/assets/bar/lock_open.svg
mode change 100644 => 120000 uidev/assets/bar/mouse.svg
mode change 100644 => 120000 uidev/assets/bar/mouse_lock.svg
mode change 100644 => 120000 uidev/assets/bar/move-all.svg
mode change 100644 => 120000 uidev/assets/bar/move-horizontal.svg
mode change 100644 => 120000 uidev/assets/bar/resize.svg
mode change 100644 => 120000 uidev/assets/bar/screen-add.svg
mode change 100644 => 120000 uidev/assets/bar/screen-options.svg
mode change 100644 => 120000 uidev/assets/bar/screen-remove.svg
mode change 100644 => 120000 uidev/assets/gui/bar.xml
create mode 100644 wlx-overlay-s/src/assets/bar/add.svg
create mode 100644 wlx-overlay-s/src/assets/bar/anchor.svg
create mode 100644 wlx-overlay-s/src/assets/bar/background-off.svg
create mode 100644 wlx-overlay-s/src/assets/bar/background.svg
create mode 100644 wlx-overlay-s/src/assets/bar/cancel.svg
create mode 100644 wlx-overlay-s/src/assets/bar/checkbox-checked.svg
create mode 100644 wlx-overlay-s/src/assets/bar/checkbox.svg
create mode 100644 wlx-overlay-s/src/assets/bar/delete.svg
create mode 100644 wlx-overlay-s/src/assets/bar/fade.svg
create mode 100644 wlx-overlay-s/src/assets/bar/inout.svg
create mode 100644 wlx-overlay-s/src/assets/bar/lock.svg
create mode 100644 wlx-overlay-s/src/assets/bar/lock_open.svg
create mode 100644 wlx-overlay-s/src/assets/bar/mouse.svg
create mode 100644 wlx-overlay-s/src/assets/bar/mouse_lock.svg
create mode 100644 wlx-overlay-s/src/assets/bar/move-all.svg
create mode 100644 wlx-overlay-s/src/assets/bar/move-horizontal.svg
create mode 100644 wlx-overlay-s/src/assets/bar/resize.svg
create mode 100644 wlx-overlay-s/src/assets/bar/screen-add.svg
create mode 100644 wlx-overlay-s/src/assets/bar/screen-options.svg
create mode 100644 wlx-overlay-s/src/assets/bar/screen-remove.svg
create mode 100644 wlx-overlay-s/src/assets/gui/bar.xml
create mode 100644 wlx-overlay-s/src/overlays/bar.rs
delete mode 100644 wlx-overlay-s/src/overlays/screen.rs
create mode 100644 wlx-overlay-s/src/overlays/screen/backend.rs
create mode 100644 wlx-overlay-s/src/overlays/screen/capture.rs
create mode 100644 wlx-overlay-s/src/overlays/screen/mod.rs
create mode 100644 wlx-overlay-s/src/overlays/screen/pw.rs
create mode 100644 wlx-overlay-s/src/overlays/screen/wl.rs
create mode 100644 wlx-overlay-s/src/overlays/screen/x11.rs
create mode 100644 wlx-overlay-s/src/overlays/tooltip.rs
diff --git a/uidev/assets/bar/add.svg b/uidev/assets/bar/add.svg
deleted file mode 100644
index ea95303..0000000
--- a/uidev/assets/bar/add.svg
+++ /dev/null
@@ -1 +0,0 @@
-
diff --git a/uidev/assets/bar/add.svg b/uidev/assets/bar/add.svg
new file mode 120000
index 0000000..d26bb19
--- /dev/null
+++ b/uidev/assets/bar/add.svg
@@ -0,0 +1 @@
+../../../wlx-overlay-s/src/assets/bar/add.svg
\ No newline at end of file
diff --git a/uidev/assets/bar/background-off.svg b/uidev/assets/bar/background-off.svg
deleted file mode 100644
index b757f50..0000000
--- a/uidev/assets/bar/background-off.svg
+++ /dev/null
@@ -1 +0,0 @@
-
diff --git a/uidev/assets/bar/background-off.svg b/uidev/assets/bar/background-off.svg
new file mode 120000
index 0000000..6c79183
--- /dev/null
+++ b/uidev/assets/bar/background-off.svg
@@ -0,0 +1 @@
+../../../wlx-overlay-s/src/assets/bar/background-off.svg
\ No newline at end of file
diff --git a/uidev/assets/bar/background.svg b/uidev/assets/bar/background.svg
deleted file mode 100644
index 0b2f756..0000000
--- a/uidev/assets/bar/background.svg
+++ /dev/null
@@ -1 +0,0 @@
-
diff --git a/uidev/assets/bar/background.svg b/uidev/assets/bar/background.svg
new file mode 120000
index 0000000..47931e4
--- /dev/null
+++ b/uidev/assets/bar/background.svg
@@ -0,0 +1 @@
+../../../wlx-overlay-s/src/assets/bar/background.svg
\ No newline at end of file
diff --git a/uidev/assets/bar/cancel.svg b/uidev/assets/bar/cancel.svg
deleted file mode 100644
index 1bcb38e..0000000
--- a/uidev/assets/bar/cancel.svg
+++ /dev/null
@@ -1 +0,0 @@
-
diff --git a/uidev/assets/bar/cancel.svg b/uidev/assets/bar/cancel.svg
new file mode 120000
index 0000000..cf4283c
--- /dev/null
+++ b/uidev/assets/bar/cancel.svg
@@ -0,0 +1 @@
+../../../wlx-overlay-s/src/assets/bar/cancel.svg
\ No newline at end of file
diff --git a/uidev/assets/bar/checkbox-checked.svg b/uidev/assets/bar/checkbox-checked.svg
deleted file mode 100644
index c29bcad..0000000
--- a/uidev/assets/bar/checkbox-checked.svg
+++ /dev/null
@@ -1 +0,0 @@
-
diff --git a/uidev/assets/bar/checkbox-checked.svg b/uidev/assets/bar/checkbox-checked.svg
new file mode 120000
index 0000000..8db144a
--- /dev/null
+++ b/uidev/assets/bar/checkbox-checked.svg
@@ -0,0 +1 @@
+../../../wlx-overlay-s/src/assets/bar/checkbox-checked.svg
\ No newline at end of file
diff --git a/uidev/assets/bar/checkbox.svg b/uidev/assets/bar/checkbox.svg
deleted file mode 100644
index 39b464f..0000000
--- a/uidev/assets/bar/checkbox.svg
+++ /dev/null
@@ -1 +0,0 @@
-
diff --git a/uidev/assets/bar/checkbox.svg b/uidev/assets/bar/checkbox.svg
new file mode 120000
index 0000000..db92a46
--- /dev/null
+++ b/uidev/assets/bar/checkbox.svg
@@ -0,0 +1 @@
+../../../wlx-overlay-s/src/assets/bar/checkbox.svg
\ No newline at end of file
diff --git a/uidev/assets/bar/delete.svg b/uidev/assets/bar/delete.svg
deleted file mode 100644
index a24e916..0000000
--- a/uidev/assets/bar/delete.svg
+++ /dev/null
@@ -1 +0,0 @@
-
diff --git a/uidev/assets/bar/delete.svg b/uidev/assets/bar/delete.svg
new file mode 120000
index 0000000..cfd57cf
--- /dev/null
+++ b/uidev/assets/bar/delete.svg
@@ -0,0 +1 @@
+../../../wlx-overlay-s/src/assets/bar/delete.svg
\ No newline at end of file
diff --git a/uidev/assets/bar/fade.svg b/uidev/assets/bar/fade.svg
deleted file mode 100644
index 492cc33..0000000
--- a/uidev/assets/bar/fade.svg
+++ /dev/null
@@ -1 +0,0 @@
-
diff --git a/uidev/assets/bar/fade.svg b/uidev/assets/bar/fade.svg
new file mode 120000
index 0000000..26f5a0c
--- /dev/null
+++ b/uidev/assets/bar/fade.svg
@@ -0,0 +1 @@
+../../../wlx-overlay-s/src/assets/bar/fade.svg
\ No newline at end of file
diff --git a/uidev/assets/bar/inout.svg b/uidev/assets/bar/inout.svg
deleted file mode 100644
index 0490d95..0000000
--- a/uidev/assets/bar/inout.svg
+++ /dev/null
@@ -1 +0,0 @@
-
diff --git a/uidev/assets/bar/inout.svg b/uidev/assets/bar/inout.svg
new file mode 120000
index 0000000..6bcdc08
--- /dev/null
+++ b/uidev/assets/bar/inout.svg
@@ -0,0 +1 @@
+../../../wlx-overlay-s/src/assets/bar/inout.svg
\ No newline at end of file
diff --git a/uidev/assets/bar/lock.svg b/uidev/assets/bar/lock.svg
deleted file mode 100644
index e83811b..0000000
--- a/uidev/assets/bar/lock.svg
+++ /dev/null
@@ -1 +0,0 @@
-
diff --git a/uidev/assets/bar/lock.svg b/uidev/assets/bar/lock.svg
new file mode 120000
index 0000000..b904120
--- /dev/null
+++ b/uidev/assets/bar/lock.svg
@@ -0,0 +1 @@
+../../../wlx-overlay-s/src/assets/bar/lock.svg
\ No newline at end of file
diff --git a/uidev/assets/bar/lock_open.svg b/uidev/assets/bar/lock_open.svg
deleted file mode 100644
index 977433f..0000000
--- a/uidev/assets/bar/lock_open.svg
+++ /dev/null
@@ -1 +0,0 @@
-
diff --git a/uidev/assets/bar/lock_open.svg b/uidev/assets/bar/lock_open.svg
new file mode 120000
index 0000000..36aa8df
--- /dev/null
+++ b/uidev/assets/bar/lock_open.svg
@@ -0,0 +1 @@
+../../../wlx-overlay-s/src/assets/bar/lock_open.svg
\ No newline at end of file
diff --git a/uidev/assets/bar/mouse.svg b/uidev/assets/bar/mouse.svg
deleted file mode 100644
index 978985d..0000000
--- a/uidev/assets/bar/mouse.svg
+++ /dev/null
@@ -1 +0,0 @@
-
diff --git a/uidev/assets/bar/mouse.svg b/uidev/assets/bar/mouse.svg
new file mode 120000
index 0000000..181f61d
--- /dev/null
+++ b/uidev/assets/bar/mouse.svg
@@ -0,0 +1 @@
+../../../wlx-overlay-s/src/assets/bar/mouse.svg
\ No newline at end of file
diff --git a/uidev/assets/bar/mouse_lock.svg b/uidev/assets/bar/mouse_lock.svg
deleted file mode 100644
index 5f86575..0000000
--- a/uidev/assets/bar/mouse_lock.svg
+++ /dev/null
@@ -1 +0,0 @@
-
diff --git a/uidev/assets/bar/mouse_lock.svg b/uidev/assets/bar/mouse_lock.svg
new file mode 120000
index 0000000..658174f
--- /dev/null
+++ b/uidev/assets/bar/mouse_lock.svg
@@ -0,0 +1 @@
+../../../wlx-overlay-s/src/assets/bar/mouse_lock.svg
\ No newline at end of file
diff --git a/uidev/assets/bar/move-all.svg b/uidev/assets/bar/move-all.svg
deleted file mode 100644
index 1c1ad5c..0000000
--- a/uidev/assets/bar/move-all.svg
+++ /dev/null
@@ -1 +0,0 @@
-
diff --git a/uidev/assets/bar/move-all.svg b/uidev/assets/bar/move-all.svg
new file mode 120000
index 0000000..dabd956
--- /dev/null
+++ b/uidev/assets/bar/move-all.svg
@@ -0,0 +1 @@
+../../../wlx-overlay-s/src/assets/bar/move-all.svg
\ No newline at end of file
diff --git a/uidev/assets/bar/move-horizontal.svg b/uidev/assets/bar/move-horizontal.svg
deleted file mode 100644
index 3ccc67a..0000000
--- a/uidev/assets/bar/move-horizontal.svg
+++ /dev/null
@@ -1 +0,0 @@
-
diff --git a/uidev/assets/bar/move-horizontal.svg b/uidev/assets/bar/move-horizontal.svg
new file mode 120000
index 0000000..df1c928
--- /dev/null
+++ b/uidev/assets/bar/move-horizontal.svg
@@ -0,0 +1 @@
+../../../wlx-overlay-s/src/assets/bar/move-horizontal.svg
\ No newline at end of file
diff --git a/uidev/assets/bar/resize.svg b/uidev/assets/bar/resize.svg
deleted file mode 100644
index ed581c2..0000000
--- a/uidev/assets/bar/resize.svg
+++ /dev/null
@@ -1 +0,0 @@
-
diff --git a/uidev/assets/bar/resize.svg b/uidev/assets/bar/resize.svg
new file mode 120000
index 0000000..7fe64c3
--- /dev/null
+++ b/uidev/assets/bar/resize.svg
@@ -0,0 +1 @@
+../../../wlx-overlay-s/src/assets/bar/resize.svg
\ No newline at end of file
diff --git a/uidev/assets/bar/screen-add.svg b/uidev/assets/bar/screen-add.svg
deleted file mode 100644
index 96d15de..0000000
--- a/uidev/assets/bar/screen-add.svg
+++ /dev/null
@@ -1 +0,0 @@
-
diff --git a/uidev/assets/bar/screen-add.svg b/uidev/assets/bar/screen-add.svg
new file mode 120000
index 0000000..6a16d93
--- /dev/null
+++ b/uidev/assets/bar/screen-add.svg
@@ -0,0 +1 @@
+../../../wlx-overlay-s/src/assets/bar/screen-add.svg
\ No newline at end of file
diff --git a/uidev/assets/bar/screen-options.svg b/uidev/assets/bar/screen-options.svg
deleted file mode 100644
index 99b6fd3..0000000
--- a/uidev/assets/bar/screen-options.svg
+++ /dev/null
@@ -1 +0,0 @@
-
diff --git a/uidev/assets/bar/screen-options.svg b/uidev/assets/bar/screen-options.svg
new file mode 120000
index 0000000..dfd9078
--- /dev/null
+++ b/uidev/assets/bar/screen-options.svg
@@ -0,0 +1 @@
+../../../wlx-overlay-s/src/assets/bar/screen-options.svg
\ No newline at end of file
diff --git a/uidev/assets/bar/screen-remove.svg b/uidev/assets/bar/screen-remove.svg
deleted file mode 100644
index 6fab54c..0000000
--- a/uidev/assets/bar/screen-remove.svg
+++ /dev/null
@@ -1 +0,0 @@
-
diff --git a/uidev/assets/bar/screen-remove.svg b/uidev/assets/bar/screen-remove.svg
new file mode 120000
index 0000000..5efeec9
--- /dev/null
+++ b/uidev/assets/bar/screen-remove.svg
@@ -0,0 +1 @@
+../../../wlx-overlay-s/src/assets/bar/screen-remove.svg
\ No newline at end of file
diff --git a/uidev/assets/gui/bar.xml b/uidev/assets/gui/bar.xml
deleted file mode 100644
index 669bbd2..0000000
--- a/uidev/assets/gui/bar.xml
+++ /dev/null
@@ -1,34 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/uidev/assets/gui/bar.xml b/uidev/assets/gui/bar.xml
new file mode 120000
index 0000000..b4fb52e
--- /dev/null
+++ b/uidev/assets/gui/bar.xml
@@ -0,0 +1 @@
+../../../wlx-overlay-s/src/assets/gui/bar.xml
\ No newline at end of file
diff --git a/wlx-overlay-s/src/assets/bar/add.svg b/wlx-overlay-s/src/assets/bar/add.svg
new file mode 100644
index 0000000..ea95303
--- /dev/null
+++ b/wlx-overlay-s/src/assets/bar/add.svg
@@ -0,0 +1 @@
+
diff --git a/wlx-overlay-s/src/assets/bar/anchor.svg b/wlx-overlay-s/src/assets/bar/anchor.svg
new file mode 100644
index 0000000..9a501ce
--- /dev/null
+++ b/wlx-overlay-s/src/assets/bar/anchor.svg
@@ -0,0 +1 @@
+
diff --git a/wlx-overlay-s/src/assets/bar/background-off.svg b/wlx-overlay-s/src/assets/bar/background-off.svg
new file mode 100644
index 0000000..b757f50
--- /dev/null
+++ b/wlx-overlay-s/src/assets/bar/background-off.svg
@@ -0,0 +1 @@
+
diff --git a/wlx-overlay-s/src/assets/bar/background.svg b/wlx-overlay-s/src/assets/bar/background.svg
new file mode 100644
index 0000000..0b2f756
--- /dev/null
+++ b/wlx-overlay-s/src/assets/bar/background.svg
@@ -0,0 +1 @@
+
diff --git a/wlx-overlay-s/src/assets/bar/cancel.svg b/wlx-overlay-s/src/assets/bar/cancel.svg
new file mode 100644
index 0000000..1bcb38e
--- /dev/null
+++ b/wlx-overlay-s/src/assets/bar/cancel.svg
@@ -0,0 +1 @@
+
diff --git a/wlx-overlay-s/src/assets/bar/checkbox-checked.svg b/wlx-overlay-s/src/assets/bar/checkbox-checked.svg
new file mode 100644
index 0000000..c29bcad
--- /dev/null
+++ b/wlx-overlay-s/src/assets/bar/checkbox-checked.svg
@@ -0,0 +1 @@
+
diff --git a/wlx-overlay-s/src/assets/bar/checkbox.svg b/wlx-overlay-s/src/assets/bar/checkbox.svg
new file mode 100644
index 0000000..39b464f
--- /dev/null
+++ b/wlx-overlay-s/src/assets/bar/checkbox.svg
@@ -0,0 +1 @@
+
diff --git a/wlx-overlay-s/src/assets/bar/delete.svg b/wlx-overlay-s/src/assets/bar/delete.svg
new file mode 100644
index 0000000..a24e916
--- /dev/null
+++ b/wlx-overlay-s/src/assets/bar/delete.svg
@@ -0,0 +1 @@
+
diff --git a/wlx-overlay-s/src/assets/bar/fade.svg b/wlx-overlay-s/src/assets/bar/fade.svg
new file mode 100644
index 0000000..492cc33
--- /dev/null
+++ b/wlx-overlay-s/src/assets/bar/fade.svg
@@ -0,0 +1 @@
+
diff --git a/wlx-overlay-s/src/assets/bar/inout.svg b/wlx-overlay-s/src/assets/bar/inout.svg
new file mode 100644
index 0000000..0490d95
--- /dev/null
+++ b/wlx-overlay-s/src/assets/bar/inout.svg
@@ -0,0 +1 @@
+
diff --git a/wlx-overlay-s/src/assets/bar/lock.svg b/wlx-overlay-s/src/assets/bar/lock.svg
new file mode 100644
index 0000000..e83811b
--- /dev/null
+++ b/wlx-overlay-s/src/assets/bar/lock.svg
@@ -0,0 +1 @@
+
diff --git a/wlx-overlay-s/src/assets/bar/lock_open.svg b/wlx-overlay-s/src/assets/bar/lock_open.svg
new file mode 100644
index 0000000..977433f
--- /dev/null
+++ b/wlx-overlay-s/src/assets/bar/lock_open.svg
@@ -0,0 +1 @@
+
diff --git a/wlx-overlay-s/src/assets/bar/mouse.svg b/wlx-overlay-s/src/assets/bar/mouse.svg
new file mode 100644
index 0000000..978985d
--- /dev/null
+++ b/wlx-overlay-s/src/assets/bar/mouse.svg
@@ -0,0 +1 @@
+
diff --git a/wlx-overlay-s/src/assets/bar/mouse_lock.svg b/wlx-overlay-s/src/assets/bar/mouse_lock.svg
new file mode 100644
index 0000000..5f86575
--- /dev/null
+++ b/wlx-overlay-s/src/assets/bar/mouse_lock.svg
@@ -0,0 +1 @@
+
diff --git a/wlx-overlay-s/src/assets/bar/move-all.svg b/wlx-overlay-s/src/assets/bar/move-all.svg
new file mode 100644
index 0000000..1c1ad5c
--- /dev/null
+++ b/wlx-overlay-s/src/assets/bar/move-all.svg
@@ -0,0 +1 @@
+
diff --git a/wlx-overlay-s/src/assets/bar/move-horizontal.svg b/wlx-overlay-s/src/assets/bar/move-horizontal.svg
new file mode 100644
index 0000000..3ccc67a
--- /dev/null
+++ b/wlx-overlay-s/src/assets/bar/move-horizontal.svg
@@ -0,0 +1 @@
+
diff --git a/wlx-overlay-s/src/assets/bar/resize.svg b/wlx-overlay-s/src/assets/bar/resize.svg
new file mode 100644
index 0000000..ed581c2
--- /dev/null
+++ b/wlx-overlay-s/src/assets/bar/resize.svg
@@ -0,0 +1 @@
+
diff --git a/wlx-overlay-s/src/assets/bar/screen-add.svg b/wlx-overlay-s/src/assets/bar/screen-add.svg
new file mode 100644
index 0000000..96d15de
--- /dev/null
+++ b/wlx-overlay-s/src/assets/bar/screen-add.svg
@@ -0,0 +1 @@
+
diff --git a/wlx-overlay-s/src/assets/bar/screen-options.svg b/wlx-overlay-s/src/assets/bar/screen-options.svg
new file mode 100644
index 0000000..99b6fd3
--- /dev/null
+++ b/wlx-overlay-s/src/assets/bar/screen-options.svg
@@ -0,0 +1 @@
+
diff --git a/wlx-overlay-s/src/assets/bar/screen-remove.svg b/wlx-overlay-s/src/assets/bar/screen-remove.svg
new file mode 100644
index 0000000..6fab54c
--- /dev/null
+++ b/wlx-overlay-s/src/assets/bar/screen-remove.svg
@@ -0,0 +1 @@
+
diff --git a/wlx-overlay-s/src/assets/gui/bar.xml b/wlx-overlay-s/src/assets/gui/bar.xml
new file mode 100644
index 0000000..eaa5a30
--- /dev/null
+++ b/wlx-overlay-s/src/assets/gui/bar.xml
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/wlx-overlay-s/src/backend/common.rs b/wlx-overlay-s/src/backend/common.rs
index 20a6b08..d48dd56 100644
--- a/wlx-overlay-s/src/backend/common.rs
+++ b/wlx-overlay-s/src/backend/common.rs
@@ -13,11 +13,10 @@ use crate::{
overlays::{
anchor::create_anchor,
keyboard::{KEYBOARD_NAME, builder::create_keyboard},
- screen::WlxClientAlias,
+ screen::create_screens,
watch::{WATCH_NAME, create_watch},
},
state::AppState,
- subsystem::hid::{get_keymap_wl, get_keymap_x11},
};
use super::overlay::{OverlayData, OverlayID};
@@ -37,22 +36,11 @@ pub enum BackendError {
Fatal(#[from] anyhow::Error),
}
-#[cfg(feature = "wayland")]
-fn create_wl_client() -> Option {
- wlx_capture::wayland::WlxClient::new()
-}
-
-#[cfg(not(feature = "wayland"))]
-fn create_wl_client() -> Option {
- None
-}
-
pub struct OverlayContainer
where
T: Default,
{
overlays: IdMap>,
- wl: Option,
}
impl OverlayContainer
@@ -62,55 +50,35 @@ where
pub fn new(app: &mut AppState, headless: bool) -> anyhow::Result {
let mut overlays = IdMap::new();
let mut show_screens = app.session.config.show_screens.clone();
- let mut wl = None;
- let mut keymap = None;
-
- app.screens.clear();
+ let mut maybe_keymap = None;
if headless {
log::info!("Running in headless mode; keyboard will be en-US");
} else {
- wl = create_wl_client();
-
- let data = if let Some(wl) = wl.as_mut() {
- log::info!("Wayland detected.");
- keymap = get_keymap_wl()
- .map_err(|f| log::warn!("Could not load keyboard layout: {f}"))
- .ok();
- crate::overlays::screen::create_screens_wayland(wl, app)
- } else {
- log::info!("Wayland not detected, assuming X11.");
- keymap = get_keymap_x11()
- .map_err(|f| log::warn!("Could not load keyboard layout: {f}"))
- .ok();
- match crate::overlays::screen::create_screens_x11pw(app) {
- Ok(data) => data,
- Err(e) => {
- log::info!("Will not use X11 PipeWire capture: {e:?}");
- crate::overlays::screen::create_screens_xshm(app)?
+ match create_screens(app) {
+ Ok((data, keymap)) => {
+ if show_screens.is_empty() {
+ if let Some((_, s, _)) = data.screens.first() {
+ show_screens.arc_set(s.name.clone());
+ }
+ for (meta, mut state, backend) in data.screens {
+ if show_screens.arc_get(state.name.as_ref()) {
+ state.show_hide = true;
+ }
+ overlays.insert(
+ state.id.0,
+ OverlayData:: {
+ state,
+ ..OverlayData::from_backend(backend)
+ },
+ );
+ app.screens.push(meta);
+ }
}
- }
- };
- if show_screens.is_empty() {
- if let Some((_, s, _)) = data.screens.first() {
- show_screens.arc_set(s.name.clone());
+ maybe_keymap = keymap;
}
- }
-
- for (meta, mut state, backend) in data.screens {
- if show_screens.arc_get(state.name.as_ref()) {
- state.show_hide = true;
- }
- overlays.insert(
- state.id.0,
- OverlayData:: {
- state,
- backend,
- ..Default::default()
- },
- );
- app.screens.push(meta);
+ Err(e) => log::error!("Unable to initialize screens: {e:?}"),
}
}
@@ -121,147 +89,12 @@ where
watch.state.want_visible = true;
overlays.insert(watch.state.id.0, watch);
- let mut keyboard = create_keyboard(app, keymap)?;
+ let mut keyboard = create_keyboard(app, maybe_keymap)?;
keyboard.state.show_hide = show_screens.arc_get(KEYBOARD_NAME);
keyboard.state.want_visible = false;
overlays.insert(keyboard.state.id.0, keyboard);
- Ok(Self { overlays, wl })
- }
-
- #[cfg(not(feature = "wayland"))]
- pub fn update(&mut self, _app: &mut AppState) -> anyhow::Result>> {
- Ok(vec![])
- }
- #[cfg(feature = "wayland")]
- #[allow(clippy::too_many_lines, clippy::cognitive_complexity)]
- #[allow(clippy::all, clippy::pedantic, clippy::nursery, clippy::cargo)]
- pub fn update(&mut self, app: &mut AppState) -> anyhow::Result>> {
- use crate::overlays::screen::{
- create_screen_interaction, create_screen_renderer_wl, load_pw_token_config,
- };
- use glam::vec2;
- use wlx_capture::wayland::OutputChangeEvent;
-
- let mut removed_overlays = vec![];
- let Some(wl) = self.wl.as_mut() else {
- return Ok(removed_overlays);
- };
-
- wl.dispatch_pending();
-
- let mut create_ran = false;
- let mut extent_dirty = false;
- let mut watch_dirty = false;
-
- let mut maybe_token_store = None;
-
- for ev in wl.iter_events().collect::>() {
- match ev {
- OutputChangeEvent::Create(_) => {
- if create_ran {
- continue;
- }
- let data = crate::overlays::screen::create_screens_wayland(wl, app);
- create_ran = true;
- for (meta, state, backend) in data.screens {
- self.overlays.insert(
- state.id.0,
- OverlayData:: {
- state,
- backend,
- ..Default::default()
- },
- );
- app.screens.push(meta);
- watch_dirty = true;
- }
- }
- OutputChangeEvent::Destroy(id) => {
- let Some(idx) = app.screens.iter().position(|s| s.native_handle == id) else {
- continue;
- };
-
- let meta = &app.screens[idx];
- let removed = self.overlays.remove(meta.id.0).unwrap();
- removed_overlays.push(removed);
- log::info!("{}: Destroyed", meta.name);
- app.screens.remove(idx);
- watch_dirty = true;
- extent_dirty = true;
- }
- OutputChangeEvent::Logical(id) => {
- let Some(meta) = app.screens.iter().find(|s| s.native_handle == id) else {
- continue;
- };
- let output = wl.outputs.get(id).unwrap();
- let Some(overlay) = self.overlays.get_mut(meta.id.0) else {
- continue;
- };
- let logical_pos =
- vec2(output.logical_pos.0 as f32, output.logical_pos.1 as f32);
- let logical_size =
- vec2(output.logical_size.0 as f32, output.logical_size.1 as f32);
- let transform = output.transform.into();
- overlay
- .backend
- .set_interaction(Box::new(create_screen_interaction(
- logical_pos,
- logical_size,
- transform,
- )));
- extent_dirty = true;
- }
- OutputChangeEvent::Physical(id) => {
- let Some(meta) = app.screens.iter().find(|s| s.native_handle == id) else {
- continue;
- };
- let output = wl.outputs.get(id).unwrap();
- let Some(overlay) = self.overlays.get_mut(meta.id.0) else {
- continue;
- };
-
- let has_wlr_dmabuf = wl.maybe_wlr_dmabuf_mgr.is_some();
- let has_wlr_screencopy = wl.maybe_wlr_screencopy_mgr.is_some();
-
- let pw_token_store = maybe_token_store.get_or_insert_with(|| {
- load_pw_token_config().unwrap_or_else(|e| {
- log::warn!("Failed to load PipeWire token config: {:?}", e);
- Default::default()
- })
- });
-
- if let Some(renderer) = create_screen_renderer_wl(
- output,
- has_wlr_dmabuf,
- has_wlr_screencopy,
- pw_token_store,
- &app,
- ) {
- overlay.backend.set_renderer(Box::new(renderer));
- }
- extent_dirty = true;
- }
- }
- }
-
- if extent_dirty && !create_ran {
- let extent = wl.get_desktop_extent();
- let origin = wl.get_desktop_origin();
- app.hid_provider
- .inner
- .set_desktop_extent(vec2(extent.0 as f32, extent.1 as f32));
- app.hid_provider
- .inner
- .set_desktop_origin(vec2(origin.0 as f32, origin.1 as f32));
- }
-
- if watch_dirty {
- let _watch = self.mut_by_name(WATCH_NAME).unwrap(); // want panic
- todo!();
- }
-
- Ok(removed_overlays)
+ Ok(Self { overlays })
}
pub fn mut_by_selector(&mut self, selector: &OverlaySelector) -> Option<&mut OverlayData> {
diff --git a/wlx-overlay-s/src/backend/input.rs b/wlx-overlay-s/src/backend/input.rs
index ad6be23..25fef65 100644
--- a/wlx-overlay-s/src/backend/input.rs
+++ b/wlx-overlay-s/src/backend/input.rs
@@ -256,24 +256,6 @@ pub struct Haptics {
pub frequency: f32,
}
-pub trait InteractionHandler {
- fn on_hover(&mut self, app: &mut AppState, hit: &PointerHit) -> Option;
- fn on_left(&mut self, app: &mut AppState, pointer: usize);
- fn on_pointer(&mut self, app: &mut AppState, hit: &PointerHit, pressed: bool);
- fn on_scroll(&mut self, app: &mut AppState, hit: &PointerHit, delta_y: f32, delta_x: f32);
-}
-
-pub struct DummyInteractionHandler;
-
-impl InteractionHandler for DummyInteractionHandler {
- fn on_left(&mut self, _app: &mut AppState, _pointer: usize) {}
- fn on_hover(&mut self, _app: &mut AppState, _hit: &PointerHit) -> Option {
- None
- }
- fn on_pointer(&mut self, _app: &mut AppState, _hit: &PointerHit, _pressed: bool) {}
- fn on_scroll(&mut self, _app: &mut AppState, _hit: &PointerHit, _delta_y: f32, _delta_x: f32) {}
-}
-
#[derive(Debug, Clone, Copy, Default)]
struct RayHit {
overlay: OverlayID,
@@ -493,12 +475,16 @@ impl Pointer {
hits.sort_by(|a, b| a.dist.total_cmp(&b.dist));
for hit in &hits {
- let overlay = overlays.get_by_id(hit.overlay).unwrap(); // safe because we just got the id from the overlay
+ let overlay = overlays.mut_by_id(hit.overlay).unwrap(); // safe because we just got the id from the overlay
- let uv = overlay
- .state
- .interaction_transform
- .transform_point2(hit.local_pos);
+ let Some(uv) = overlay
+ .backend
+ .as_mut()
+ .get_interaction_transform()
+ .map(|a| a.transform_point2(hit.local_pos))
+ else {
+ continue;
+ };
if uv.x < 0.0 || uv.x > 1.0 || uv.y < 0.0 || uv.y > 1.0 {
continue;
diff --git a/wlx-overlay-s/src/backend/openvr/lines.rs b/wlx-overlay-s/src/backend/openvr/lines.rs
index 9c6f450..28a237f 100644
--- a/wlx-overlay-s/src/backend/openvr/lines.rs
+++ b/wlx-overlay-s/src/backend/openvr/lines.rs
@@ -1,6 +1,6 @@
use std::f32::consts::PI;
-use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
+use std::sync::atomic::{AtomicUsize, Ordering};
use ash::vk::SubmitInfo;
use glam::{Affine3A, Vec3, Vec3A, Vec4};
@@ -8,6 +8,7 @@ use idmap::IdMap;
use ovr_overlay::overlay::OverlayManager;
use ovr_overlay::sys::ETrackingUniverseOrigin;
use vulkano::{
+ VulkanObject,
command_buffer::{
CommandBufferBeginInfo, CommandBufferLevel, CommandBufferUsage, RecordingCommandBuffer,
},
@@ -15,16 +16,15 @@ use vulkano::{
image::view::ImageView,
image::{Image, ImageLayout},
sync::{
- fence::{Fence, FenceCreateInfo},
AccessFlags, DependencyInfo, ImageMemoryBarrier, PipelineStages,
+ fence::{Fence, FenceCreateInfo},
},
- VulkanObject,
};
use wgui::gfx::WGfx;
+use crate::backend::input::{Haptics, PointerHit};
use crate::backend::overlay::{
- FrameMeta, OverlayData, OverlayRenderer, OverlayState, ShouldRender, SplitOverlayBackend,
- Z_ORDER_LINES,
+ FrameMeta, OverlayBackend, OverlayData, OverlayState, ShouldRender, Z_ORDER_LINES,
};
use crate::graphics::CommandBuffers;
use crate::state::AppState;
@@ -81,12 +81,6 @@ impl LinePool {
show_hide: true,
..Default::default()
},
- backend: Box::new(SplitOverlayBackend {
- renderer: Box::new(StaticRenderer {
- view: self.view.clone(),
- }),
- ..Default::default()
- }),
data: OpenVrOverlayData {
width: 0.002,
override_width: true,
@@ -94,7 +88,9 @@ impl LinePool {
image_dirty: true,
..Default::default()
},
- ..Default::default()
+ ..OverlayData::from_backend(Box::new(LineBackend {
+ view: self.view.clone(),
+ }))
};
data.state.z_order = Z_ORDER_LINES;
data.state.dirty = true;
@@ -177,29 +173,29 @@ impl LinePool {
}
}
-struct StaticRenderer {
+struct LineBackend {
view: Arc,
}
-impl OverlayRenderer for StaticRenderer {
- fn init(&mut self, _app: &mut AppState) -> anyhow::Result<()> {
+impl OverlayBackend for LineBackend {
+ fn init(&mut self, _: &mut AppState) -> anyhow::Result<()> {
Ok(())
}
- fn pause(&mut self, _app: &mut AppState) -> anyhow::Result<()> {
+ fn pause(&mut self, _: &mut AppState) -> anyhow::Result<()> {
Ok(())
}
- fn resume(&mut self, _app: &mut AppState) -> anyhow::Result<()> {
+ fn resume(&mut self, _: &mut AppState) -> anyhow::Result<()> {
Ok(())
}
- fn should_render(&mut self, _app: &mut AppState) -> anyhow::Result {
+ fn should_render(&mut self, _: &mut AppState) -> anyhow::Result {
Ok(ShouldRender::Unable)
}
fn render(
&mut self,
- _app: &mut AppState,
- _tgt: Arc,
- _buf: &mut CommandBuffers,
- _alpha: f32,
+ _: &mut AppState,
+ _: Arc,
+ _: &mut CommandBuffers,
+ _: f32,
) -> anyhow::Result {
Ok(false)
}
@@ -209,6 +205,16 @@ impl OverlayRenderer for StaticRenderer {
..Default::default()
})
}
+
+ fn on_hover(&mut self, _: &mut AppState, _: &PointerHit) -> Option {
+ None
+ }
+ fn on_left(&mut self, _: &mut AppState, _: usize) {}
+ fn on_pointer(&mut self, _: &mut AppState, _: &PointerHit, _: bool) {}
+ fn on_scroll(&mut self, _: &mut AppState, _: &PointerHit, _: f32, _: f32) {}
+ fn get_interaction_transform(&mut self) -> Option {
+ None
+ }
}
pub fn transition_layout(
diff --git a/wlx-overlay-s/src/backend/openvr/mod.rs b/wlx-overlay-s/src/backend/openvr/mod.rs
index 806cbd3..f0fb700 100644
--- a/wlx-overlay-s/src/backend/openvr/mod.rs
+++ b/wlx-overlay-s/src/backend/openvr/mod.rs
@@ -204,11 +204,6 @@ pub fn openvr_run(
state.tasks.retrieve_due(&mut due_tasks);
- let mut removed_overlays = overlays.update(&mut state)?;
- for o in &mut removed_overlays {
- o.destroy(&mut overlay_mgr);
- }
-
while let Some(task) = due_tasks.pop_front() {
match task {
TaskType::Overlay(sel, f) => {
@@ -230,8 +225,7 @@ pub fn openvr_run(
overlays.add(OverlayData {
state,
- backend,
- ..Default::default()
+ ..OverlayData::from_backend(backend)
});
}
TaskType::DropOverlay(sel) => {
diff --git a/wlx-overlay-s/src/backend/openxr/mod.rs b/wlx-overlay-s/src/backend/openxr/mod.rs
index 5018986..e5bdaab 100644
--- a/wlx-overlay-s/src/backend/openxr/mod.rs
+++ b/wlx-overlay-s/src/backend/openxr/mod.rs
@@ -486,11 +486,6 @@ pub fn openxr_run(
)?;
// End layer submit
- let removed_overlays = overlays.update(&mut app)?;
- for o in removed_overlays {
- delete_queue.push((o, cur_frame + 5));
- }
-
notifications.submit_pending(&mut app);
app.tasks.retrieve_due(&mut due_tasks);
@@ -515,8 +510,7 @@ pub fn openxr_run(
overlays.add(OverlayData {
state: overlay_state,
- backend: overlay_backend,
- ..Default::default()
+ ..OverlayData::from_backend(overlay_backend)
});
}
TaskType::DropOverlay(sel) => {
diff --git a/wlx-overlay-s/src/backend/overlay.rs b/wlx-overlay-s/src/backend/overlay.rs
index a863ae1..75386ea 100644
--- a/wlx-overlay-s/src/backend/overlay.rs
+++ b/wlx-overlay-s/src/backend/overlay.rs
@@ -6,7 +6,6 @@ use std::{
},
};
-use anyhow::Ok;
use glam::{Affine2, Affine3A, Mat3A, Quat, Vec2, Vec3, Vec3A};
use serde::Deserialize;
use vulkano::{format::Format, image::view::ImageView};
@@ -17,16 +16,11 @@ use crate::{
use super::{
common::snap_upright,
- input::{DummyInteractionHandler, Haptics, InteractionHandler, PointerHit},
+ input::{Haptics, PointerHit},
};
static OVERLAY_AUTO_INCREMENT: AtomicUsize = AtomicUsize::new(0);
-pub trait OverlayBackend: OverlayRenderer + InteractionHandler {
- fn set_renderer(&mut self, renderer: Box);
- fn set_interaction(&mut self, interaction: Box);
-}
-
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Default)]
pub struct OverlayID(pub usize);
@@ -56,7 +50,6 @@ pub struct OverlayState {
pub saved_transform: Option,
pub positioning: Positioning,
pub curvature: Option,
- pub interaction_transform: Affine2,
pub birthframe: usize,
}
@@ -81,32 +74,28 @@ impl Default for OverlayState {
spawn_rotation: Quat::IDENTITY,
saved_transform: None,
transform: Affine3A::IDENTITY,
- interaction_transform: Affine2::IDENTITY,
birthframe: 0,
}
}
}
-pub struct OverlayData
-where
- T: Default,
-{
+pub struct OverlayData {
pub state: OverlayState,
pub backend: Box,
pub primary_pointer: Option,
pub data: T,
}
-impl Default for OverlayData
+impl OverlayData
where
T: Default,
{
- fn default() -> Self {
+ pub fn from_backend(backend: Box) -> Self {
Self {
state: OverlayState::default(),
- backend: Box::::default(),
+ backend,
primary_pointer: None,
- data: Default::default(),
+ data: T::default(),
}
}
}
@@ -166,7 +155,7 @@ impl OverlayState {
app.input_state.pointers[hand].pose
}
Positioning::Anchored => app.anchor,
- Positioning::Static => return,
+ Positioning::FollowOverlay { .. } | Positioning::Static => return,
};
if hard_reset {
@@ -191,7 +180,7 @@ impl OverlayState {
app.input_state.pointers[hand].pose
}
Positioning::Anchored => snap_upright(app.anchor, Vec3A::Y),
- Positioning::Static => return false,
+ Positioning::FollowOverlay { .. } | Positioning::Static => return false,
};
self.saved_transform = Some(parent_transform.inverse() * self.transform);
@@ -307,7 +296,7 @@ pub enum ShouldRender {
Unable,
}
-pub trait OverlayRenderer {
+pub trait OverlayBackend {
/// Called once, before the first frame is rendered
fn init(&mut self, app: &mut AppState) -> anyhow::Result<()>;
fn pause(&mut self, app: &mut AppState) -> anyhow::Result<()>;
@@ -330,37 +319,13 @@ pub trait OverlayRenderer {
///
/// Must be true if should_render was also true on the same frame.
fn frame_meta(&mut self) -> Option;
-}
-pub struct FallbackRenderer;
-
-impl OverlayRenderer for FallbackRenderer {
- fn init(&mut self, _app: &mut AppState) -> anyhow::Result<()> {
- Ok(())
- }
- fn pause(&mut self, _app: &mut AppState) -> anyhow::Result<()> {
- Ok(())
- }
- fn resume(&mut self, _app: &mut AppState) -> anyhow::Result<()> {
- Ok(())
- }
- fn should_render(&mut self, _app: &mut AppState) -> anyhow::Result {
- Ok(ShouldRender::Unable)
- }
- fn render(
- &mut self,
- _app: &mut AppState,
- _tgt: Arc,
- _buf: &mut CommandBuffers,
- _alpha: f32,
- ) -> anyhow::Result {
- Ok(false)
- }
- fn frame_meta(&mut self) -> Option {
- None
- }
+ fn on_hover(&mut self, app: &mut AppState, hit: &PointerHit) -> Option;
+ fn on_left(&mut self, app: &mut AppState, pointer: usize);
+ fn on_pointer(&mut self, app: &mut AppState, hit: &PointerHit, pressed: bool);
+ fn on_scroll(&mut self, app: &mut AppState, hit: &PointerHit, delta_y: f32, delta_x: f32);
+ fn get_interaction_transform(&mut self) -> Option;
}
-// Boilerplate and dummies
#[derive(Clone, Copy, Debug, Default)]
pub enum Positioning {
@@ -377,74 +342,12 @@ pub enum Positioning {
FollowHand { hand: usize, lerp: f32 },
/// Normally follows hand, but paused due to interaction
FollowHandPaused { hand: usize, lerp: f32 },
+ /// Follow another overlay
+ FollowOverlay { id: usize },
/// Stays in place, no recentering
Static,
}
-pub struct SplitOverlayBackend {
- pub renderer: Box,
- pub interaction: Box,
-}
-
-impl Default for SplitOverlayBackend {
- fn default() -> Self {
- Self {
- renderer: Box::new(FallbackRenderer),
- interaction: Box::new(DummyInteractionHandler),
- }
- }
-}
-
-impl OverlayBackend for SplitOverlayBackend {
- fn set_renderer(&mut self, renderer: Box) {
- self.renderer = renderer;
- }
- fn set_interaction(&mut self, interaction: Box) {
- self.interaction = interaction;
- }
-}
-impl OverlayRenderer for SplitOverlayBackend {
- fn init(&mut self, app: &mut AppState) -> anyhow::Result<()> {
- self.renderer.init(app)
- }
- fn pause(&mut self, app: &mut AppState) -> anyhow::Result<()> {
- self.renderer.pause(app)
- }
- fn resume(&mut self, app: &mut AppState) -> anyhow::Result<()> {
- self.renderer.resume(app)
- }
- fn should_render(&mut self, app: &mut AppState) -> anyhow::Result {
- self.renderer.should_render(app)
- }
- fn render(
- &mut self,
- app: &mut AppState,
- tgt: Arc,
- buf: &mut CommandBuffers,
- alpha: f32,
- ) -> anyhow::Result {
- self.renderer.render(app, tgt, buf, alpha)
- }
- fn frame_meta(&mut self) -> Option {
- self.renderer.frame_meta()
- }
-}
-
-impl InteractionHandler for SplitOverlayBackend {
- fn on_left(&mut self, app: &mut AppState, pointer: usize) {
- self.interaction.on_left(app, pointer);
- }
- fn on_hover(&mut self, app: &mut AppState, hit: &PointerHit) -> Option {
- self.interaction.on_hover(app, hit)
- }
- fn on_scroll(&mut self, app: &mut AppState, hit: &PointerHit, delta_y: f32, delta_x: f32) {
- self.interaction.on_scroll(app, hit, delta_y, delta_x);
- }
- fn on_pointer(&mut self, app: &mut AppState, hit: &PointerHit, pressed: bool) {
- self.interaction.on_pointer(app, hit, pressed);
- }
-}
-
pub fn ui_transform(extent: [u32; 2]) -> Affine2 {
let aspect = extent[0] as f32 / extent[1] as f32;
let scale = if aspect < 1.0 {
diff --git a/wlx-overlay-s/src/gui/panel.rs b/wlx-overlay-s/src/gui/panel.rs
index d912398..98825a9 100644
--- a/wlx-overlay-s/src/gui/panel.rs
+++ b/wlx-overlay-s/src/gui/panel.rs
@@ -1,6 +1,6 @@
use std::sync::Arc;
-use glam::{Vec2, vec2};
+use glam::{Affine2, Vec2, vec2};
use vulkano::{command_buffer::CommandBufferUsage, image::view::ImageView};
use wgui::{
event::{
@@ -14,8 +14,8 @@ use wgui::{
use crate::{
backend::{
- input::{Haptics, InteractionHandler, PointerHit, PointerMode},
- overlay::{FrameMeta, OverlayBackend, OverlayRenderer, ShouldRender},
+ input::{Haptics, PointerHit, PointerMode},
+ overlay::{FrameMeta, OverlayBackend, ShouldRender, ui_transform},
},
graphics::{CommandBuffers, ExtentExt},
gui,
@@ -32,6 +32,7 @@ pub struct GuiPanel {
pub state: S,
pub timers: Vec,
pub listeners: EventListenerCollection,
+ interaction_transform: Option,
context: WguiContext,
timestep: Timestep,
}
@@ -62,6 +63,7 @@ impl GuiPanel {
state,
timers: vec![],
listeners,
+ interaction_transform: None,
},
parser_result,
))
@@ -80,6 +82,7 @@ impl GuiPanel {
state,
timers: vec![],
listeners: EventListenerCollection::default(),
+ interaction_transform: None,
})
}
@@ -98,88 +101,14 @@ impl GuiPanel {
}
impl OverlayBackend for GuiPanel {
- fn set_renderer(&mut self, _: Box) {
- log::debug!("Attempted to replace renderer on GuiPanel!");
- }
- fn set_interaction(&mut self, _: Box) {
- log::debug!("Attempted to replace interaction layer on GuiPanel!");
- }
-}
-
-impl InteractionHandler for GuiPanel {
- fn on_scroll(&mut self, app: &mut AppState, hit: &PointerHit, delta_y: f32, delta_x: f32) {
- self.layout
- .push_event(
- &self.listeners,
- &WguiEvent::MouseWheel(MouseWheelEvent {
- shift: vec2(delta_x, delta_y),
- pos: hit.uv * self.layout.content_size,
- device: hit.pointer,
- }),
- (app, &mut self.state),
- )
- .unwrap(); // want panic
- }
-
- fn on_hover(&mut self, app: &mut AppState, hit: &PointerHit) -> Option {
- self.push_event(
- app,
- &WguiEvent::MouseMotion(MouseMotionEvent {
- pos: hit.uv * self.layout.content_size,
- device: hit.pointer,
- }),
- );
-
- self.layout
- .check_toggle_haptics_triggered()
- .then_some(Haptics {
- intensity: 0.1,
- duration: 0.01,
- frequency: 5.0,
- })
- }
-
- fn on_left(&mut self, app: &mut AppState, pointer: usize) {
- self.push_event(
- app,
- &WguiEvent::MouseLeave(MouseLeaveEvent { device: pointer }),
- );
- }
-
- fn on_pointer(&mut self, app: &mut AppState, hit: &PointerHit, pressed: bool) {
- let button = match hit.mode {
- PointerMode::Left => MouseButton::Left,
- PointerMode::Right => MouseButton::Right,
- PointerMode::Middle => MouseButton::Middle,
- _ => return,
- };
-
- if pressed {
- self.push_event(
- app,
- &WguiEvent::MouseDown(MouseDownEvent {
- pos: hit.uv * self.layout.content_size,
- button,
- device: hit.pointer,
- }),
- );
- } else {
- self.push_event(
- app,
- &WguiEvent::MouseUp(MouseUpEvent {
- pos: hit.uv * self.layout.content_size,
- button,
- device: hit.pointer,
- }),
- );
- }
- }
-}
-
-impl OverlayRenderer for GuiPanel {
fn init(&mut self, _app: &mut AppState) -> anyhow::Result<()> {
if self.layout.content_size.x * self.layout.content_size.y == 0.0 {
self.update_layout()?;
+ self.interaction_transform = Some(ui_transform([
+ //TODO: dynamic
+ self.layout.content_size.x as _,
+ self.layout.content_size.y as _,
+ ]));
}
Ok(())
}
@@ -253,4 +182,76 @@ impl OverlayRenderer for GuiPanel {
..Default::default()
})
}
+
+ fn on_scroll(&mut self, app: &mut AppState, hit: &PointerHit, delta_y: f32, delta_x: f32) {
+ self.layout
+ .push_event(
+ &self.listeners,
+ &WguiEvent::MouseWheel(MouseWheelEvent {
+ shift: vec2(delta_x, delta_y),
+ pos: hit.uv * self.layout.content_size,
+ device: hit.pointer,
+ }),
+ (app, &mut self.state),
+ )
+ .unwrap(); // want panic
+ }
+
+ fn on_hover(&mut self, app: &mut AppState, hit: &PointerHit) -> Option {
+ self.push_event(
+ app,
+ &WguiEvent::MouseMotion(MouseMotionEvent {
+ pos: hit.uv * self.layout.content_size,
+ device: hit.pointer,
+ }),
+ );
+
+ self.layout
+ .check_toggle_haptics_triggered()
+ .then_some(Haptics {
+ intensity: 0.1,
+ duration: 0.01,
+ frequency: 5.0,
+ })
+ }
+
+ fn on_left(&mut self, app: &mut AppState, pointer: usize) {
+ self.push_event(
+ app,
+ &WguiEvent::MouseLeave(MouseLeaveEvent { device: pointer }),
+ );
+ }
+
+ fn on_pointer(&mut self, app: &mut AppState, hit: &PointerHit, pressed: bool) {
+ let button = match hit.mode {
+ PointerMode::Left => MouseButton::Left,
+ PointerMode::Right => MouseButton::Right,
+ PointerMode::Middle => MouseButton::Middle,
+ _ => return,
+ };
+
+ if pressed {
+ self.push_event(
+ app,
+ &WguiEvent::MouseDown(MouseDownEvent {
+ pos: hit.uv * self.layout.content_size,
+ button,
+ device: hit.pointer,
+ }),
+ );
+ } else {
+ self.push_event(
+ app,
+ &WguiEvent::MouseUp(MouseUpEvent {
+ pos: hit.uv * self.layout.content_size,
+ button,
+ device: hit.pointer,
+ }),
+ );
+ }
+ }
+
+ fn get_interaction_transform(&mut self) -> Option {
+ self.interaction_transform
+ }
}
diff --git a/wlx-overlay-s/src/overlays/anchor.rs b/wlx-overlay-s/src/overlays/anchor.rs
index b79d515..407ee38 100644
--- a/wlx-overlay-s/src/overlays/anchor.rs
+++ b/wlx-overlay-s/src/overlays/anchor.rs
@@ -25,7 +25,6 @@ where
positioning: Positioning::Static,
..Default::default()
},
- backend: Box::new(panel),
- ..Default::default()
+ ..OverlayData::from_backend(Box::new(panel))
})
}
diff --git a/wlx-overlay-s/src/overlays/bar.rs b/wlx-overlay-s/src/overlays/bar.rs
new file mode 100644
index 0000000..9dcfca5
--- /dev/null
+++ b/wlx-overlay-s/src/overlays/bar.rs
@@ -0,0 +1,45 @@
+use crate::{
+ backend::overlay::{OverlayData, OverlayState},
+ gui::panel::GuiPanel,
+ state::AppState,
+};
+
+pub const BAR_NAME: &str = "bar";
+
+struct BarState {}
+
+#[allow(clippy::significant_drop_tightening)]
+pub fn create_bar(app: &mut AppState) -> anyhow::Result>
+where
+ O: Default,
+{
+ let state = BarState {};
+ let (mut panel, parser) = GuiPanel::new_from_template(app, "gui/bar.xml", state)?;
+
+ for (id, widget_id) in parser.ids {
+ match id.as_ref() {
+ "lock" => {}
+ "anchor" => {}
+ "mouse" => {}
+ "fade" => {}
+ "move" => {}
+ "resize" => {}
+ "inout" => {}
+ "delete" => {}
+ _ => {}
+ }
+ }
+
+ panel.update_layout()?;
+
+ Ok(OverlayData {
+ state: OverlayState {
+ name: BAR_NAME.into(),
+ want_visible: true,
+ interactable: true,
+ spawn_scale: 0.15,
+ ..Default::default()
+ },
+ ..OverlayData::from_backend(Box::new(panel))
+ })
+}
diff --git a/wlx-overlay-s/src/overlays/keyboard/builder.rs b/wlx-overlay-s/src/overlays/keyboard/builder.rs
index a7415a6..4ff8585 100644
--- a/wlx-overlay-s/src/overlays/keyboard/builder.rs
+++ b/wlx-overlay-s/src/overlays/keyboard/builder.rs
@@ -1,6 +1,6 @@
use std::{collections::HashMap, rc::Rc};
-use glam::{Affine2, Mat4, Vec2, Vec3, vec2, vec3a};
+use glam::{Mat4, Vec2, Vec3, vec2, vec3a};
use wgui::{
animation::{Animation, AnimationEasing},
drawing::Color,
@@ -260,12 +260,6 @@ where
panel.layout.update(vec2(2048., 2048.), 0.0)?;
- let interaction_transform = Affine2::from_translation(vec2(0.5, 0.5))
- * Affine2::from_scale(vec2(
- 1.,
- -panel.layout.content_size.x / panel.layout.content_size.y,
- ));
-
let width = layout.row_size * 0.05 * app.session.config.keyboard_scale;
Ok(OverlayData {
@@ -277,11 +271,9 @@ where
interactable: true,
spawn_scale: width,
spawn_point: vec3a(0., -0.5, 0.),
- interaction_transform,
..Default::default()
},
- backend: Box::new(KeyboardBackend { panel }),
- ..Default::default()
+ ..OverlayData::from_backend(Box::new(KeyboardBackend { panel }))
})
}
diff --git a/wlx-overlay-s/src/overlays/keyboard/mod.rs b/wlx-overlay-s/src/overlays/keyboard/mod.rs
index e367b47..0a54c18 100644
--- a/wlx-overlay-s/src/overlays/keyboard/mod.rs
+++ b/wlx-overlay-s/src/overlays/keyboard/mod.rs
@@ -12,8 +12,8 @@ use wgui::{
use crate::{
backend::{
- input::{Haptics, InteractionHandler, PointerHit},
- overlay::{FrameMeta, OverlayBackend, OverlayRenderer, ShouldRender},
+ input::{Haptics, PointerHit},
+ overlay::{FrameMeta, OverlayBackend, ShouldRender},
},
graphics::CommandBuffers,
gui::panel::GuiPanel,
@@ -32,34 +32,6 @@ struct KeyboardBackend {
}
impl OverlayBackend for KeyboardBackend {
- fn set_interaction(&mut self, interaction: Box) {
- self.panel.set_interaction(interaction);
- }
- fn set_renderer(&mut self, renderer: Box) {
- self.panel.set_renderer(renderer);
- }
-}
-
-impl InteractionHandler for KeyboardBackend {
- fn on_pointer(&mut self, app: &mut AppState, hit: &PointerHit, pressed: bool) {
- self.panel.on_pointer(app, hit, pressed);
- self.panel.push_event(
- app,
- &wgui::event::Event::InternalStateChange(InternalStateChangeEvent { metadata: 0 }),
- );
- }
- fn on_scroll(&mut self, app: &mut AppState, hit: &PointerHit, delta_y: f32, delta_x: f32) {
- self.panel.on_scroll(app, hit, delta_y, delta_x);
- }
- fn on_left(&mut self, app: &mut AppState, pointer: usize) {
- self.panel.on_left(app, pointer);
- }
- fn on_hover(&mut self, app: &mut AppState, hit: &PointerHit) -> Option {
- self.panel.on_hover(app, hit)
- }
-}
-
-impl OverlayRenderer for KeyboardBackend {
fn init(&mut self, app: &mut AppState) -> anyhow::Result<()> {
self.panel.init(app)
}
@@ -91,6 +63,26 @@ impl OverlayRenderer for KeyboardBackend {
);
Ok(())
}
+
+ fn on_pointer(&mut self, app: &mut AppState, hit: &PointerHit, pressed: bool) {
+ self.panel.on_pointer(app, hit, pressed);
+ self.panel.push_event(
+ app,
+ &wgui::event::Event::InternalStateChange(InternalStateChangeEvent { metadata: 0 }),
+ );
+ }
+ fn on_scroll(&mut self, app: &mut AppState, hit: &PointerHit, delta_y: f32, delta_x: f32) {
+ self.panel.on_scroll(app, hit, delta_y, delta_x);
+ }
+ fn on_left(&mut self, app: &mut AppState, pointer: usize) {
+ self.panel.on_left(app, pointer);
+ }
+ fn on_hover(&mut self, app: &mut AppState, hit: &PointerHit) -> Option {
+ self.panel.on_hover(app, hit)
+ }
+ fn get_interaction_transform(&mut self) -> Option {
+ self.panel.get_interaction_transform()
+ }
}
struct KeyboardState {
diff --git a/wlx-overlay-s/src/overlays/mirror.rs b/wlx-overlay-s/src/overlays/mirror.rs
index 734fe89..02b0661 100644
--- a/wlx-overlay-s/src/overlays/mirror.rs
+++ b/wlx-overlay-s/src/overlays/mirror.rs
@@ -4,34 +4,34 @@ use std::{
};
use futures::{Future, FutureExt};
+use glam::Affine2;
use vulkano::image::view::ImageView;
-use wlx_capture::pipewire::{pipewire_select_screen, PipewireCapture, PipewireSelectScreenResult};
+use wlx_capture::pipewire::{PipewireCapture, PipewireSelectScreenResult, pipewire_select_screen};
use crate::{
backend::{
common::OverlaySelector,
- overlay::{
- ui_transform, FrameMeta, OverlayBackend, OverlayRenderer, OverlayState, ShouldRender,
- SplitOverlayBackend,
- },
+ input::{Haptics, PointerHit},
+ overlay::{FrameMeta, OverlayBackend, OverlayState, ShouldRender, ui_transform},
task::TaskType,
},
graphics::CommandBuffers,
state::{AppSession, AppState},
};
-use super::screen::ScreenRenderer;
+use super::screen::backend::ScreenBackend;
type PinnedSelectorFuture = core::pin::Pin<
Box>>,
>;
-pub struct MirrorRenderer {
+pub struct MirrorBackend {
name: Arc,
- renderer: Option,
+ renderer: Option,
selector: Option,
last_extent: [u32; 3],
+ interaction_transform: Option,
}
-impl MirrorRenderer {
+impl MirrorBackend {
pub fn new(name: Arc) -> Self {
let selector = Box::pin(pipewire_select_screen(None, false, false, false, false));
Self {
@@ -39,11 +39,12 @@ impl MirrorRenderer {
renderer: None,
selector: Some(selector),
last_extent: [0; 3],
+ interaction_transform: None,
}
}
}
-impl OverlayRenderer for MirrorRenderer {
+impl OverlayBackend for MirrorBackend {
fn init(&mut self, _app: &mut AppState) -> anyhow::Result<()> {
Ok(())
}
@@ -64,10 +65,8 @@ impl OverlayRenderer for MirrorRenderer {
let node_id = pw_result.streams.first().unwrap().node_id; // streams guaranteed to have at least one element
log::info!("{}: PipeWire node selected: {}", self.name.clone(), node_id);
let capture = PipewireCapture::new(self.name.clone(), node_id);
- self.renderer = Some(ScreenRenderer::new_raw(
- self.name.clone(),
- Box::new(capture),
- ));
+ self.renderer =
+ Some(ScreenBackend::new_raw(self.name.clone(), Box::new(capture)));
app.tasks.enqueue(TaskType::Overlay(
OverlaySelector::Name(self.name.clone()),
Box::new(|app, o| {
@@ -106,13 +105,7 @@ impl OverlayRenderer for MirrorRenderer {
let extent = meta.extent;
if self.last_extent != extent {
self.last_extent = extent;
- // resized
- app.tasks.enqueue(TaskType::Overlay(
- OverlaySelector::Name(self.name.clone()),
- Box::new(move |_app, o| {
- o.interaction_transform = ui_transform([extent[0], extent[1]]);
- }),
- ));
+ self.interaction_transform = Some(ui_transform([extent[0], extent[1]]));
}
}
}
@@ -133,7 +126,17 @@ impl OverlayRenderer for MirrorRenderer {
}
fn frame_meta(&mut self) -> Option {
- self.renderer.as_mut().and_then(ScreenRenderer::frame_meta)
+ self.renderer.as_mut().and_then(ScreenBackend::frame_meta)
+ }
+
+ fn on_hover(&mut self, _: &mut AppState, _: &PointerHit) -> Option {
+ None
+ }
+ fn on_left(&mut self, _: &mut AppState, _: usize) {}
+ fn on_pointer(&mut self, _: &mut AppState, _: &PointerHit, _: bool) {}
+ fn on_scroll(&mut self, _: &mut AppState, _: &PointerHit, _: f32, _: f32) {}
+ fn get_interaction_transform(&mut self) -> Option {
+ self.interaction_transform
}
}
@@ -149,10 +152,7 @@ pub fn new_mirror(
spawn_scale: 0.5 * session.config.desktop_view_scale,
..Default::default()
};
- let backend = Box::new(SplitOverlayBackend {
- renderer: Box::new(MirrorRenderer::new(name)),
- ..Default::default()
- });
+ let backend = Box::new(MirrorBackend::new(name));
(state, backend)
}
diff --git a/wlx-overlay-s/src/overlays/mod.rs b/wlx-overlay-s/src/overlays/mod.rs
index 498cc19..68536e0 100644
--- a/wlx-overlay-s/src/overlays/mod.rs
+++ b/wlx-overlay-s/src/overlays/mod.rs
@@ -1,4 +1,5 @@
pub mod anchor;
+pub mod bar;
pub mod custom;
pub mod keyboard;
#[cfg(feature = "wayland")]
diff --git a/wlx-overlay-s/src/overlays/screen.rs b/wlx-overlay-s/src/overlays/screen.rs
deleted file mode 100644
index 7ff3b76..0000000
--- a/wlx-overlay-s/src/overlays/screen.rs
+++ /dev/null
@@ -1,1262 +0,0 @@
-use core::slice;
-use serde::{Deserialize, Serialize};
-use std::{
- f32::consts::PI,
- ptr,
- sync::{Arc, LazyLock, atomic::AtomicU64},
- time::Instant,
-};
-use vulkano::{
- buffer::{BufferUsage, Subbuffer},
- command_buffer::CommandBufferUsage,
- device::Queue,
- format::Format,
- image::{Image, sampler::Filter, view::ImageView},
- pipeline::graphics::{color_blend::AttachmentBlend, input_assembly::PrimitiveTopology},
-};
-use wgui::gfx::{WGfx, cmd::XferCommandBuffer, pass::WGfxPass, pipeline::WGfxPipeline};
-use wlx_capture::frame as wlx_frame;
-
-use wlx_capture::{
- WlxCapture,
- frame::{FrameFormat, MouseMeta, WlxFrame},
-};
-
-#[cfg(feature = "pipewire")]
-use {
- crate::config_io,
- std::error::Error,
- std::{path::PathBuf, task},
- wlx_capture::pipewire::PipewireCapture,
- wlx_capture::pipewire::PipewireSelectScreenResult,
-};
-
-#[cfg(all(feature = "x11", feature = "pipewire"))]
-use wlx_capture::pipewire::PipewireStream;
-
-#[cfg(feature = "wayland")]
-use {
- crate::config::AStrMapExt,
- wlx_capture::{
- wayland::{WlxClient, WlxOutput, wayland_client::protocol::wl_output},
- wlr_dmabuf::WlrDmabufCapture,
- wlr_screencopy::WlrScreencopyCapture,
- },
-};
-
-#[cfg(feature = "x11")]
-use wlx_capture::xshm::{XshmCapture, XshmScreen};
-
-use glam::{Affine2, Affine3A, Quat, Vec2, Vec3, vec2, vec3a};
-
-use crate::{
- backend::{
- input::{Haptics, InteractionHandler, PointerHit, PointerMode},
- overlay::{
- FrameMeta, OverlayRenderer, OverlayState, Positioning, ShouldRender,
- SplitOverlayBackend,
- },
- },
- config::{GeneralConfig, PwTokenMap, def_pw_tokens},
- graphics::{
- CommandBuffers, Vert2Uv,
- dmabuf::{WGfxDmabuf, fourcc_to_vk},
- upload_quad_vertices,
- },
- state::{AppSession, AppState, ScreenMeta},
- subsystem::{
- hid::{MOUSE_LEFT, MOUSE_MIDDLE, MOUSE_RIGHT},
- input::KeyboardFocus,
- },
-};
-
-#[cfg(feature = "wayland")]
-pub type WlxClientAlias = wlx_capture::wayland::WlxClient;
-
-#[cfg(not(feature = "wayland"))]
-pub(crate) type WlxClientAlias = ();
-
-const CURSOR_SIZE: f32 = 16. / 1440.;
-
-static START: LazyLock = LazyLock::new(Instant::now);
-static NEXT_MOVE: AtomicU64 = AtomicU64::new(0);
-
-fn can_move() -> bool {
- START.elapsed().as_millis() as u64 > NEXT_MOVE.load(std::sync::atomic::Ordering::Relaxed)
-}
-
-fn set_next_move(millis_from_now: u64) {
- NEXT_MOVE.store(
- START.elapsed().as_millis() as u64 + millis_from_now,
- std::sync::atomic::Ordering::Relaxed,
- );
-}
-
-pub struct ScreenInteractionHandler {
- mouse_transform: Affine2,
-}
-impl ScreenInteractionHandler {
- fn new(pos: Vec2, size: Vec2, transform: Transform) -> Self {
- let transform = match transform {
- Transform::_90 | Transform::Flipped90 => Affine2::from_cols(
- vec2(0., size.y),
- vec2(-size.x, 0.),
- vec2(pos.x + size.x, pos.y),
- ),
- Transform::_180 | Transform::Flipped180 => Affine2::from_cols(
- vec2(-size.x, 0.),
- vec2(0., -size.y),
- vec2(pos.x + size.x, pos.y + size.y),
- ),
- Transform::_270 | Transform::Flipped270 => Affine2::from_cols(
- vec2(0., -size.y),
- vec2(size.x, 0.),
- vec2(pos.x, pos.y + size.y),
- ),
- _ => Affine2::from_cols(vec2(size.x, 0.), vec2(0., size.y), pos),
- };
-
- Self {
- mouse_transform: transform,
- }
- }
-}
-
-impl InteractionHandler for ScreenInteractionHandler {
- fn on_hover(&mut self, app: &mut AppState, hit: &PointerHit) -> Option {
- #[cfg(debug_assertions)]
- log::trace!("Hover: {:?}", hit.uv);
- if can_move()
- && (!app.session.config.focus_follows_mouse_mode
- || app.input_state.pointers[hit.pointer].now.move_mouse)
- {
- let pos = self.mouse_transform.transform_point2(hit.uv);
- app.hid_provider.inner.mouse_move(pos);
- set_next_move(u64::from(app.session.config.mouse_move_interval_ms));
- }
- None
- }
- fn on_pointer(&mut self, app: &mut AppState, hit: &PointerHit, pressed: bool) {
- let btn = match hit.mode {
- PointerMode::Right => MOUSE_RIGHT,
- PointerMode::Middle => MOUSE_MIDDLE,
- _ => MOUSE_LEFT,
- };
-
- if pressed {
- set_next_move(u64::from(app.session.config.click_freeze_time_ms));
- }
-
- app.hid_provider.inner.send_button(btn, pressed);
-
- if !pressed {
- return;
- }
- let pos = self.mouse_transform.transform_point2(hit.uv);
- app.hid_provider.inner.mouse_move(pos);
- }
- fn on_scroll(&mut self, app: &mut AppState, _hit: &PointerHit, delta_y: f32, delta_x: f32) {
- app.hid_provider
- .inner
- .wheel((delta_y * 64.) as i32, (delta_x * 64.) as i32);
- }
- fn on_left(&mut self, _app: &mut AppState, _hand: usize) {}
-}
-
-struct MousePass {
- pass: WGfxPass,
- buf_vert: Subbuffer<[Vert2Uv]>,
-}
-
-struct ScreenPipeline {
- mouse: Option,
- pipeline: Arc>,
- pass: WGfxPass,
- buf_alpha: Subbuffer<[f32]>,
- extentf: [f32; 2],
-}
-
-impl ScreenPipeline {
- fn new(extent: &[u32; 3], app: &mut AppState) -> anyhow::Result {
- let extentf = [extent[0] as f32, extent[1] as f32];
-
- let pipeline = app.gfx.create_pipeline(
- app.gfx_extras.shaders.get("vert_quad").unwrap().clone(), // want panic
- app.gfx_extras.shaders.get("frag_screen").unwrap().clone(), // want panic
- app.gfx.surface_format,
- Some(AttachmentBlend::default()),
- PrimitiveTopology::TriangleStrip,
- false,
- )?;
-
- let buf_alpha = app
- .gfx
- .empty_buffer(BufferUsage::TRANSFER_DST | BufferUsage::UNIFORM_BUFFER, 1)?;
-
- let set0 = pipeline.uniform_sampler(
- 0,
- app.gfx_extras.fallback_image.clone(),
- app.gfx.texture_filter,
- )?;
- let set1 = pipeline.buffer(1, buf_alpha.clone())?;
-
- let pass = pipeline.create_pass(
- extentf,
- app.gfx_extras.quad_verts.clone(),
- 0..4,
- 0..1,
- vec![set0, set1],
- )?;
-
- Ok(Self {
- mouse: None,
- pipeline,
- pass,
- buf_alpha,
- extentf,
- })
- }
-
- fn ensure_mouse_initialized(&mut self, cmd_xfer: &mut XferCommandBuffer) -> anyhow::Result<()> {
- if self.mouse.is_some() {
- return Ok(());
- }
-
- #[rustfmt::skip]
- let mouse_bytes = [
- 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff,
- 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0xff,
- 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0xff,
- 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff,
- ];
-
- let image =
- cmd_xfer.upload_image(4, 4, vulkano::format::Format::R8G8B8A8_UNORM, &mouse_bytes)?;
-
- let view = ImageView::new_default(image)?;
-
- let buf_vert = cmd_xfer
- .graphics
- .empty_buffer(BufferUsage::TRANSFER_DST | BufferUsage::VERTEX_BUFFER, 4)?;
-
- let set0 = self.pipeline.uniform_sampler(0, view, Filter::Nearest)?;
-
- let set1 = self.pipeline.buffer(1, self.buf_alpha.clone())?;
-
- let pass = self.pipeline.create_pass(
- self.extentf,
- buf_vert.clone(),
- 0..4,
- 0..1,
- vec![set0, set1],
- )?;
-
- self.mouse = Some(MousePass { pass, buf_vert });
- Ok(())
- }
-
- fn render(
- &mut self,
- image: Arc,
- mouse: Option,
- app: &mut AppState,
- tgt: Arc,
- buf: &mut CommandBuffers,
- alpha: f32,
- ) -> anyhow::Result<()> {
- let view = ImageView::new_default(image)?;
-
- self.pass.update_sampler(0, view, app.gfx.texture_filter)?;
- self.buf_alpha.write()?[0] = alpha;
-
- let mut cmd = app
- .gfx
- .create_gfx_command_buffer(CommandBufferUsage::OneTimeSubmit)?;
- cmd.begin_rendering(tgt)?;
- cmd.run_ref(&self.pass)?;
-
- if let (Some(mouse), Some(pass)) = (mouse, self.mouse.as_mut()) {
- let size = CURSOR_SIZE * self.extentf[1];
- let half_size = size * 0.5;
-
- upload_quad_vertices(
- &mut pass.buf_vert,
- self.extentf[0],
- self.extentf[1],
- mouse.x.mul_add(self.extentf[0], -half_size),
- mouse.y.mul_add(self.extentf[1], -half_size),
- size,
- size,
- )?;
-
- cmd.run_ref(&pass.pass)?;
- }
-
- cmd.end_rendering()?;
- buf.push(cmd.build()?);
- Ok(())
- }
-}
-
-macro_rules! new_wlx_capture {
- ($capture_queue:expr, $capture:expr) => {
- if $capture_queue.is_none() {
- Box::new(MainThreadWlxCapture::new($capture)) as Box>
- } else {
- Box::new($capture) as Box>
- }
- };
-}
-
-pub struct ScreenRenderer {
- name: Arc,
- capture: Box>,
- pipeline: Option,
- cur_frame: Option,
- meta: Option,
-}
-
-impl ScreenRenderer {
- pub fn new_raw(
- name: Arc,
- capture: Box>,
- ) -> Self {
- Self {
- name,
- capture,
- pipeline: None,
- cur_frame: None,
- meta: None,
- }
- }
-
- #[cfg(feature = "wayland")]
- pub fn new_wlr_dmabuf(output: &WlxOutput, app: &AppState) -> Option {
- let client = WlxClient::new()?;
- let capture = new_wlx_capture!(
- app.gfx_extras.queue_capture,
- WlrDmabufCapture::new(client, output.id)
- );
- Some(Self::new_raw(output.name.clone(), capture))
- }
-
- #[cfg(feature = "wayland")]
- pub fn new_wlr_screencopy(output: &WlxOutput, app: &AppState) -> Option {
- let client = WlxClient::new()?;
- let capture = new_wlx_capture!(
- app.gfx_extras.queue_capture,
- WlrScreencopyCapture::new(client, output.id)
- );
- Some(Self::new_raw(output.name.clone(), capture))
- }
-
- #[cfg(feature = "wayland")]
- pub fn new_pw(
- output: &WlxOutput,
- token: Option<&str>,
- app: &AppState,
- ) -> anyhow::Result<(Self, Option /* pipewire restore token */)> {
- let name = output.name.clone();
- let embed_mouse = !app.session.config.double_cursor_fix;
-
- let select_screen_result = select_pw_screen(
- &format!(
- "Now select: {} {} {} @ {},{}",
- &output.name,
- &output.make,
- &output.model,
- &output.logical_pos.0,
- &output.logical_pos.1
- ),
- token,
- embed_mouse,
- true,
- true,
- false,
- )?;
-
- let node_id = select_screen_result.streams.first().unwrap().node_id; // streams guaranteed to have at least one element
-
- let capture = new_wlx_capture!(
- app.gfx_extras.queue_capture,
- PipewireCapture::new(name, node_id)
- );
- Ok((
- Self::new_raw(output.name.clone(), capture),
- select_screen_result.restore_token,
- ))
- }
-
- #[cfg(feature = "x11")]
- pub fn new_xshm(screen: Arc, app: &AppState) -> Self {
- let capture = new_wlx_capture!(
- app.gfx_extras.queue_capture,
- XshmCapture::new(screen.clone())
- );
- Self::new_raw(screen.name.clone(), capture)
- }
-}
-
-#[derive(Clone)]
-pub struct WlxCaptureIn {
- name: Arc,
- gfx: Arc,
- queue: Arc,
-}
-
-#[derive(Clone)]
-pub struct WlxCaptureOut {
- image: Arc,
- format: FrameFormat,
- mouse: Option,
-}
-
-fn upload_image(
- me: &WlxCaptureIn,
- width: u32,
- height: u32,
- format: Format,
- data: &[u8],
-) -> Option> {
- let mut cmd_xfer = match me
- .gfx
- .create_xfer_command_buffer_with_queue(me.queue.clone(), CommandBufferUsage::OneTimeSubmit)
- {
- Ok(x) => x,
- Err(e) => {
- log::error!("{}: Could not create vkCommandBuffer: {:?}", me.name, e);
- return None;
- }
- };
- let image = match cmd_xfer.upload_image(width, height, format, data) {
- Ok(x) => x,
- Err(e) => {
- log::error!("{}: Could not create vkImage: {:?}", me.name, e);
- return None;
- }
- };
-
- if let Err(e) = cmd_xfer.build_and_execute_now() {
- log::error!("{}: Could not execute upload: {:?}", me.name, e);
- return None;
- }
-
- Some(image)
-}
-
-fn receive_callback(me: &WlxCaptureIn, frame: wlx_frame::WlxFrame) -> Option {
- match frame {
- WlxFrame::Dmabuf(frame) => {
- if !frame.is_valid() {
- log::error!("{}: Invalid frame", me.name);
- return None;
- }
- log::trace!("{}: New DMA-buf frame", me.name);
- let format = frame.format;
- match me.gfx.dmabuf_texture(frame) {
- Ok(image) => Some(WlxCaptureOut {
- image,
- format,
- mouse: None,
- }),
- Err(e) => {
- log::error!("{}: Failed to create DMA-buf vkImage: {}", me.name, e);
- None
- }
- }
- }
- WlxFrame::MemFd(frame) => {
- let Some(fd) = frame.plane.fd else {
- log::error!("{}: No fd in MemFd frame", me.name);
- return None;
- };
-
- let format = match fourcc_to_vk(frame.format.fourcc) {
- Ok(x) => x,
- Err(e) => {
- log::error!("{}: {}", me.name, e);
- return None;
- }
- };
-
- let len = frame.plane.stride as usize * frame.format.height as usize;
- let offset = i64::from(frame.plane.offset);
-
- let map = unsafe {
- libc::mmap(
- ptr::null_mut(),
- len,
- libc::PROT_READ,
- libc::MAP_SHARED,
- fd,
- offset,
- )
- } as *const u8;
-
- let data = unsafe { slice::from_raw_parts(map, len) };
-
- let image = {
- let maybe_image =
- upload_image(me, frame.format.width, frame.format.height, format, data);
-
- unsafe { libc::munmap(map as *mut _, len) };
- maybe_image
- }?;
-
- Some(WlxCaptureOut {
- image,
- format: frame.format,
- mouse: None,
- })
- }
- WlxFrame::MemPtr(frame) => {
- log::trace!("{}: New MemPtr frame", me.name);
-
- let format = match fourcc_to_vk(frame.format.fourcc) {
- Ok(x) => x,
- Err(e) => {
- log::error!("{}: {}", me.name, e);
- return None;
- }
- };
-
- let data = unsafe { slice::from_raw_parts(frame.ptr as *const u8, frame.size) };
- let image = upload_image(me, frame.format.width, frame.format.height, format, data)?;
-
- Some(WlxCaptureOut {
- image,
- format: frame.format,
- mouse: frame.mouse,
- })
- }
- }
-}
-
-impl OverlayRenderer for ScreenRenderer {
- fn init(&mut self, _app: &mut AppState) -> anyhow::Result<()> {
- Ok(())
- }
- fn should_render(&mut self, app: &mut AppState) -> anyhow::Result {
- if !self.capture.is_ready() {
- let supports_dmabuf = app
- .gfx
- .device
- .enabled_extensions()
- .ext_external_memory_dma_buf
- && self.capture.supports_dmbuf();
-
- let allow_dmabuf = &*app.session.config.capture_method != "pw_fallback"
- && &*app.session.config.capture_method != "screencopy";
-
- let capture_method = app.session.config.capture_method.clone();
-
- let dmabuf_formats = if !supports_dmabuf {
- log::info!("Capture method does not support DMA-buf");
- if app.gfx_extras.queue_capture.is_none() {
- log::warn!(
- "Current GPU does not support multiple queues. Software capture will take place on the main thread. Expect degraded performance."
- );
- }
- &Vec::new()
- } else if !allow_dmabuf {
- log::info!("Not using DMA-buf capture due to {capture_method}");
- if app.gfx_extras.queue_capture.is_none() {
- log::warn!(
- "Current GPU does not support multiple queues. Software capture will take place on the main thread. Expect degraded performance."
- );
- }
- &Vec::new()
- } else {
- log::warn!(
- "Using DMA-buf capture. If screens are blank for you, switch to SHM using:"
- );
- log::warn!(
- "echo 'capture_method: pw_fallback' > ~/.config/wlxoverlay/conf.d/pw_fallback.yaml"
- );
-
- &app.gfx_extras.drm_formats
- };
-
- let user_data = WlxCaptureIn {
- name: self.name.clone(),
- gfx: app.gfx.clone(),
- queue: app
- .gfx_extras
- .queue_capture
- .as_ref()
- .unwrap_or_else(|| &app.gfx.queue_xfer)
- .clone(),
- };
-
- self.capture
- .init(dmabuf_formats, user_data, receive_callback);
- self.capture.request_new_frame();
- return Ok(ShouldRender::Unable);
- }
-
- if let Some(frame) = self.capture.receive() {
- self.meta = Some(FrameMeta {
- extent: extent_from_format(frame.format, &app.session.config),
- transform: affine_from_format(&frame.format),
- format: frame.image.format(),
- });
- self.cur_frame = Some(frame);
- }
-
- if let (Some(capture), None) = (self.cur_frame.as_ref(), self.pipeline.as_ref()) {
- self.pipeline = Some({
- let mut pipeline = ScreenPipeline::new(&capture.image.extent(), app)?;
- let mut upload = app
- .gfx
- .create_xfer_command_buffer(CommandBufferUsage::OneTimeSubmit)?;
- pipeline.ensure_mouse_initialized(&mut upload)?;
- upload.build_and_execute_now()?;
- pipeline
- });
- }
-
- if self.cur_frame.is_some() {
- Ok(ShouldRender::Should)
- } else {
- Ok(ShouldRender::Unable)
- }
- }
- fn render(
- &mut self,
- app: &mut AppState,
- tgt: Arc,
- buf: &mut CommandBuffers,
- alpha: f32,
- ) -> anyhow::Result {
- let Some(capture) = self.cur_frame.take() else {
- return Ok(false);
- };
-
- // want panic; must be Some if cur_frame is also Some
- self.pipeline.as_mut().unwrap().render(
- capture.image,
- capture.mouse,
- app,
- tgt,
- buf,
- alpha,
- )?;
-
- self.capture.request_new_frame();
- Ok(true)
- }
- fn pause(&mut self, _app: &mut AppState) -> anyhow::Result<()> {
- self.capture.pause();
- Ok(())
- }
- fn resume(&mut self, _app: &mut AppState) -> anyhow::Result<()> {
- self.capture.resume();
- Ok(())
- }
- fn frame_meta(&mut self) -> Option {
- self.meta
- }
-}
-
-#[cfg(feature = "wayland")]
-#[allow(clippy::useless_let_if_seq)]
-pub fn create_screen_renderer_wl(
- output: &WlxOutput,
- has_wlr_dmabuf: bool,
- has_wlr_screencopy: bool,
- pw_token_store: &mut PwTokenMap,
- app: &AppState,
-) -> Option {
- let mut capture: Option = None;
- if (&*app.session.config.capture_method == "wlr-dmabuf") && has_wlr_dmabuf {
- log::info!("{}: Using Wlr DMA-Buf", &output.name);
- capture = ScreenRenderer::new_wlr_dmabuf(output, app);
- }
-
- if &*app.session.config.capture_method == "screencopy" && has_wlr_screencopy {
- log::info!("{}: Using Wlr Screencopy Wl-SHM", &output.name);
- capture = ScreenRenderer::new_wlr_screencopy(output, app);
- }
-
- if capture.is_none() {
- log::info!("{}: Using Pipewire capture", &output.name);
-
- let display_name = &*output.name;
-
- // Find existing token by display
- let token = pw_token_store
- .arc_get(display_name)
- .map(std::string::String::as_str);
-
- if let Some(t) = token {
- log::info!("Found existing Pipewire token for display {display_name}: {t}");
- }
-
- match ScreenRenderer::new_pw(output, token, app) {
- Ok((renderer, restore_token)) => {
- capture = Some(renderer);
-
- if let Some(token) = restore_token {
- if pw_token_store.arc_set(display_name.into(), token.clone()) {
- log::info!("Adding Pipewire token {token}");
- }
- }
- }
- Err(e) => {
- log::warn!(
- "{}: Failed to create Pipewire capture: {:?}",
- &output.name,
- e
- );
- }
- }
- }
- capture
-}
-
-pub fn create_screen_interaction(
- logical_pos: Vec2,
- logical_size: Vec2,
- transform: Transform,
-) -> ScreenInteractionHandler {
- ScreenInteractionHandler::new(logical_pos, logical_size, transform)
-}
-
-fn create_screen_state(
- name: Arc,
- res: (i32, i32),
- transform: Transform,
- session: &AppSession,
-) -> OverlayState {
- let angle = if session.config.upright_screen_fix {
- match transform {
- Transform::_90 | Transform::Flipped90 => PI / 2.,
- Transform::_180 | Transform::Flipped180 => PI,
- Transform::_270 | Transform::Flipped270 => -PI / 2.,
- _ => 0.,
- }
- } else {
- 0.
- };
-
- let center = Vec2 { x: 0.5, y: 0.5 };
- let interaction_transform = match transform {
- Transform::_90 | Transform::Flipped90 => Affine2::from_cols(
- Vec2::NEG_Y * (res.0 as f32 / res.1 as f32),
- Vec2::NEG_X,
- center,
- ),
- Transform::_180 | Transform::Flipped180 => Affine2::from_cols(
- Vec2::NEG_X,
- Vec2::NEG_Y * (-res.0 as f32 / res.1 as f32),
- center,
- ),
- Transform::_270 | Transform::Flipped270 => {
- Affine2::from_cols(Vec2::Y * (res.0 as f32 / res.1 as f32), Vec2::X, center)
- }
- _ if res.1 > res.0 => {
- // Xorg upright screens
- Affine2::from_cols(Vec2::X * (res.1 as f32 / res.0 as f32), Vec2::NEG_Y, center)
- }
- _ => Affine2::from_cols(Vec2::X, Vec2::NEG_Y * (res.0 as f32 / res.1 as f32), center),
- };
-
- OverlayState {
- name,
- keyboard_focus: Some(KeyboardFocus::PhysicalScreen),
- grabbable: true,
- recenter: true,
- positioning: Positioning::Anchored,
- interactable: true,
- spawn_scale: 1.5 * session.config.desktop_view_scale,
- spawn_point: vec3a(0., 0.5, 0.),
- spawn_rotation: Quat::from_axis_angle(Vec3::Z, angle),
- interaction_transform,
- ..Default::default()
- }
-}
-
-#[derive(Deserialize, Serialize, Default)]
-pub struct TokenConf {
- #[serde(default = "def_pw_tokens")]
- pub pw_tokens: PwTokenMap,
-}
-
-#[cfg(feature = "pipewire")]
-fn get_pw_token_path() -> PathBuf {
- let mut path = config_io::ConfigRoot::Generic.get_conf_d_path();
- path.push("pw_tokens.yaml");
- path
-}
-
-#[cfg(feature = "pipewire")]
-pub fn save_pw_token_config(tokens: PwTokenMap) -> Result<(), Box> {
- let conf = TokenConf { pw_tokens: tokens };
- let yaml = serde_yaml::to_string(&conf)?;
- std::fs::write(get_pw_token_path(), yaml)?;
-
- Ok(())
-}
-
-#[cfg(feature = "pipewire")]
-pub fn load_pw_token_config() -> Result> {
- let yaml = std::fs::read_to_string(get_pw_token_path())?;
- let conf: TokenConf = serde_yaml::from_str(yaml.as_str())?;
- Ok(conf.pw_tokens)
-}
-
-pub struct ScreenCreateData {
- pub screens: Vec<(ScreenMeta, OverlayState, Box)>,
-}
-
-#[cfg(not(feature = "wayland"))]
-pub fn create_screens_wayland(_wl: &mut WlxClientAlias, _app: &AppState) -> ScreenCreateData {
- ScreenCreateData {
- screens: Vec::default(),
- }
-}
-
-#[cfg(feature = "wayland")]
-pub fn create_screens_wayland(wl: &mut WlxClientAlias, app: &mut AppState) -> ScreenCreateData {
- let mut screens = vec![];
-
- // Load existing Pipewire tokens from file
- let mut pw_tokens: PwTokenMap = load_pw_token_config().unwrap_or_default();
-
- let pw_tokens_copy = pw_tokens.clone();
- let has_wlr_dmabuf = wl.maybe_wlr_dmabuf_mgr.is_some();
- let has_wlr_screencopy = wl.maybe_wlr_screencopy_mgr.is_some();
-
- for (id, output) in &wl.outputs {
- if app.screens.iter().any(|s| s.name == output.name) {
- continue;
- }
-
- log::info!(
- "{}: Init screen of res {:?}, logical {:?} at {:?}",
- output.name,
- output.size,
- output.logical_size,
- output.logical_pos,
- );
-
- if let Some(renderer) = create_screen_renderer_wl(
- output,
- has_wlr_dmabuf,
- has_wlr_screencopy,
- &mut pw_tokens,
- app,
- ) {
- let logical_pos = vec2(output.logical_pos.0 as f32, output.logical_pos.1 as f32);
- let logical_size = vec2(output.logical_size.0 as f32, output.logical_size.1 as f32);
- let transform = output.transform.into();
- let interaction = create_screen_interaction(logical_pos, logical_size, transform);
-
- let logical_size_landscape = if output.size.0 > output.size.1 {
- output.logical_size
- } else {
- (output.logical_size.1, output.logical_size.0)
- };
-
- let state = create_screen_state(
- output.name.clone(),
- logical_size_landscape,
- transform,
- &app.session,
- );
-
- let meta = ScreenMeta {
- name: wl.outputs[id].name.clone(),
- id: state.id,
- native_handle: *id,
- };
-
- let backend = Box::new(SplitOverlayBackend {
- renderer: Box::new(renderer),
- interaction: Box::new(interaction),
- });
- screens.push((meta, state, backend));
- }
- }
-
- if pw_tokens_copy != pw_tokens {
- // Token list changed, re-create token config file
- if let Err(err) = save_pw_token_config(pw_tokens) {
- log::error!("Failed to save Pipewire token config: {err}");
- }
- }
-
- let extent = wl.get_desktop_extent();
- let origin = wl.get_desktop_origin();
-
- app.hid_provider
- .inner
- .set_desktop_extent(vec2(extent.0 as f32, extent.1 as f32));
- app.hid_provider
- .inner
- .set_desktop_origin(vec2(origin.0 as f32, origin.1 as f32));
-
- ScreenCreateData { screens }
-}
-
-#[cfg(not(feature = "x11"))]
-pub fn create_screens_xshm(_app: &mut AppState) -> anyhow::Result {
- anyhow::bail!("X11 support not enabled")
-}
-
-#[cfg(not(all(feature = "x11", feature = "pipewire")))]
-pub fn create_screens_x11pw(_app: &mut AppState) -> anyhow::Result {
- anyhow::bail!("Pipewire support not enabled")
-}
-
-#[cfg(all(feature = "x11", feature = "pipewire"))]
-pub fn create_screens_x11pw(app: &mut AppState) -> anyhow::Result {
- use wlx_capture::xshm::xshm_get_monitors;
-
- // Load existing Pipewire tokens from file
- let mut pw_tokens: PwTokenMap = load_pw_token_config().unwrap_or_default();
- let pw_tokens_copy = pw_tokens.clone();
- let token = pw_tokens.arc_get("x11").map(std::string::String::as_str);
- let embed_mouse = !app.session.config.double_cursor_fix;
-
- let select_screen_result = select_pw_screen(
- "Select ALL screens on the screencast pop-up!",
- token,
- embed_mouse,
- true,
- true,
- true,
- )?;
-
- if let Some(restore_token) = select_screen_result.restore_token {
- if pw_tokens.arc_set("x11".into(), restore_token.clone()) {
- log::info!("Adding Pipewire token {restore_token}");
- }
- }
- if pw_tokens_copy != pw_tokens {
- // Token list changed, re-create token config file
- if let Err(err) = save_pw_token_config(pw_tokens) {
- log::error!("Failed to save Pipewire token config: {err}");
- }
- }
-
- let monitors = match xshm_get_monitors() {
- Ok(m) => m,
- Err(e) => {
- anyhow::bail!(e.to_string());
- }
- };
- log::info!("Got {} monitors", monitors.len());
- log::info!("Got {} streams", select_screen_result.streams.len());
-
- let mut extent = vec2(0., 0.);
- let screens = select_screen_result
- .streams
- .into_iter()
- .enumerate()
- .map(|(i, s)| {
- let m = best_match(&s, monitors.iter().map(AsRef::as_ref)).unwrap();
- log::info!("Stream {i} is {}", m.name);
- extent.x = extent.x.max((m.monitor.x() + m.monitor.width()) as f32);
- extent.y = extent.y.max((m.monitor.y() + m.monitor.height()) as f32);
-
- let size = (m.monitor.width(), m.monitor.height());
- let interaction = create_screen_interaction(
- vec2(m.monitor.x() as f32, m.monitor.y() as f32),
- vec2(m.monitor.width() as f32, m.monitor.height() as f32),
- Transform::Normal,
- );
-
- let state = create_screen_state(m.name.clone(), size, Transform::Normal, &app.session);
-
- let meta = ScreenMeta {
- name: m.name.clone(),
- id: state.id,
- native_handle: 0,
- };
-
- let renderer = ScreenRenderer::new_raw(
- m.name.clone(),
- new_wlx_capture!(
- app.gfx_extras.queue_capture,
- PipewireCapture::new(m.name.clone(), s.node_id)
- ),
- );
-
- let backend = Box::new(SplitOverlayBackend {
- renderer: Box::new(renderer),
- interaction: Box::new(interaction),
- });
- (meta, state, backend)
- })
- .collect();
-
- app.hid_provider.inner.set_desktop_extent(extent);
- app.hid_provider.inner.set_desktop_origin(vec2(0.0, 0.0));
-
- Ok(ScreenCreateData { screens })
-}
-
-#[cfg(feature = "x11")]
-pub fn create_screens_xshm(app: &mut AppState) -> anyhow::Result {
- use wlx_capture::xshm::xshm_get_monitors;
-
- let mut extent = vec2(0., 0.);
-
- let monitors = match xshm_get_monitors() {
- Ok(m) => m,
- Err(e) => {
- anyhow::bail!(e.to_string());
- }
- };
-
- let screens = monitors
- .into_iter()
- .map(|s| {
- extent.x = extent.x.max((s.monitor.x() + s.monitor.width()) as f32);
- extent.y = extent.y.max((s.monitor.y() + s.monitor.height()) as f32);
-
- let size = (s.monitor.width(), s.monitor.height());
- let pos = (s.monitor.x(), s.monitor.y());
- let renderer = ScreenRenderer::new_xshm(s.clone(), app);
-
- log::info!(
- "{}: Init X11 screen of res {:?} at {:?}",
- s.name.clone(),
- size,
- pos,
- );
-
- let interaction = create_screen_interaction(
- vec2(s.monitor.x() as f32, s.monitor.y() as f32),
- vec2(size.0 as f32, size.1 as f32),
- Transform::Normal,
- );
-
- let state = create_screen_state(s.name.clone(), size, Transform::Normal, &app.session);
-
- let meta = ScreenMeta {
- name: s.name.clone(),
- id: state.id,
- native_handle: 0,
- };
-
- let backend = Box::new(SplitOverlayBackend {
- renderer: Box::new(renderer),
- interaction: Box::new(interaction),
- });
- (meta, state, backend)
- })
- .collect();
-
- app.hid_provider.inner.set_desktop_extent(extent);
- app.hid_provider.inner.set_desktop_origin(vec2(0.0, 0.0));
-
- Ok(ScreenCreateData { screens })
-}
-
-#[allow(unused)]
-#[derive(Clone, Copy)]
-pub enum Transform {
- Normal,
- _90,
- _180,
- _270,
- Flipped,
- Flipped90,
- Flipped180,
- Flipped270,
-}
-
-#[cfg(feature = "wayland")]
-impl From for Transform {
- fn from(t: wl_output::Transform) -> Self {
- match t {
- wl_output::Transform::_90 => Self::_90,
- wl_output::Transform::_180 => Self::_180,
- wl_output::Transform::_270 => Self::_270,
- wl_output::Transform::Flipped => Self::Flipped,
- wl_output::Transform::Flipped90 => Self::Flipped90,
- wl_output::Transform::Flipped180 => Self::Flipped180,
- wl_output::Transform::Flipped270 => Self::Flipped270,
- _ => Self::Normal,
- }
- }
-}
-
-fn extent_from_format(fmt: FrameFormat, config: &GeneralConfig) -> [u32; 3] {
- // screens above a certain resolution will have severe aliasing
- let height_limit = if config.screen_render_down {
- u32::from(config.screen_max_height.min(2560))
- } else {
- 2560
- };
-
- let h = fmt.height.min(height_limit);
- let w = (fmt.width as f32 / fmt.height as f32 * h as f32) as u32;
- [w, h, 1]
-}
-
-fn affine_from_format(format: &FrameFormat) -> Affine3A {
- const FLIP_X: Vec3 = Vec3 {
- x: -1.0,
- y: 1.0,
- z: 1.0,
- };
-
- match format.transform {
- wlx_frame::Transform::Rotated90 => Affine3A::from_rotation_z(-PI / 2.0),
- wlx_frame::Transform::Rotated180 => Affine3A::from_rotation_z(PI),
- wlx_frame::Transform::Rotated270 => Affine3A::from_rotation_z(PI / 2.0),
- wlx_frame::Transform::Flipped => Affine3A::from_scale(FLIP_X),
- wlx_frame::Transform::Flipped90 => {
- Affine3A::from_scale(FLIP_X) * Affine3A::from_rotation_z(-PI / 2.0)
- }
- wlx_frame::Transform::Flipped180 => {
- Affine3A::from_scale(FLIP_X) * Affine3A::from_rotation_z(PI)
- }
- wlx_frame::Transform::Flipped270 => {
- Affine3A::from_scale(FLIP_X) * Affine3A::from_rotation_z(PI / 2.0)
- }
- _ => Affine3A::IDENTITY,
- }
-}
-
-#[cfg(all(feature = "pipewire", feature = "x11"))]
-fn best_match<'a>(
- stream: &PipewireStream,
- mut streams: impl Iterator- ,
-) -> Option<&'a XshmScreen> {
- let mut best = streams.next();
- log::debug!("stream: {:?}", stream.position);
- log::debug!("first: {:?}", best.map(|b| &b.monitor));
- let Some(position) = stream.position else {
- return best;
- };
-
- let mut best_dist = best.map_or(i32::MAX, |b| {
- (b.monitor.x() - position.0).abs() + (b.monitor.y() - position.1).abs()
- });
- for stream in streams {
- log::debug!("checking: {:?}", stream.monitor);
- let dist =
- (stream.monitor.x() - position.0).abs() + (stream.monitor.y() - position.1).abs();
- if dist < best_dist {
- best = Some(stream);
- best_dist = dist;
- }
- }
- log::debug!("best: {:?}", best.map(|b| &b.monitor));
- best
-}
-
-#[cfg(feature = "pipewire")]
-#[allow(clippy::fn_params_excessive_bools)]
-fn select_pw_screen(
- instructions: &str,
- token: Option<&str>,
- embed_mouse: bool,
- screens_only: bool,
- persist: bool,
- multiple: bool,
-) -> Result {
- use crate::subsystem::notifications::DbusNotificationSender;
- use std::time::Duration;
- use wlx_capture::pipewire::pipewire_select_screen;
-
- let future = async move {
- let print_at = Instant::now() + Duration::from_millis(250);
- let mut notify = None;
-
- let f = pipewire_select_screen(token, embed_mouse, screens_only, persist, multiple);
- futures::pin_mut!(f);
-
- loop {
- match futures::poll!(&mut f) {
- task::Poll::Ready(result) => return result,
- task::Poll::Pending => {
- if Instant::now() >= print_at {
- log::info!("{instructions}");
- if let Ok(sender) = DbusNotificationSender::new() {
- if let Ok(id) = sender.notify_send(instructions, "", 2, 0, 0, true) {
- notify = Some((sender, id));
- }
- }
- break;
- }
- futures::future::lazy(|_| {
- std::thread::sleep(Duration::from_millis(10));
- })
- .await;
- }
- }
- }
-
- let result = f.await;
- if let Some((sender, id)) = notify {
- let _ = sender.notify_close(id);
- }
- result
- };
-
- futures::executor::block_on(future)
-}
-
-// Used when a separate GPU queue is not available
-// In this case, receive_callback needs to run on the main thread
-struct MainThreadWlxCapture
-where
- T: WlxCapture<(), WlxFrame>,
-{
- inner: T,
- data: Option,
-}
-
-impl MainThreadWlxCapture
-where
- T: WlxCapture<(), WlxFrame>,
-{
- pub const fn new(inner: T) -> Self {
- Self { inner, data: None }
- }
-}
-
-impl WlxCapture for MainThreadWlxCapture
-where
- T: WlxCapture<(), WlxFrame>,
-{
- fn init(
- &mut self,
- dmabuf_formats: &[wlx_frame::DrmFormat],
- user_data: WlxCaptureIn,
- _: fn(&WlxCaptureIn, WlxFrame) -> Option,
- ) {
- self.data = Some(user_data);
- self.inner.init(dmabuf_formats, (), receive_callback_dummy);
- }
- fn is_ready(&self) -> bool {
- self.inner.is_ready()
- }
- fn request_new_frame(&mut self) {
- self.inner.request_new_frame();
- }
- fn pause(&mut self) {
- self.inner.pause();
- }
- fn resume(&mut self) {
- self.inner.resume();
- }
- fn receive(&mut self) -> Option {
- self.inner
- .receive()
- .and_then(|frame| receive_callback(self.data.as_ref().unwrap(), frame))
- }
- fn supports_dmbuf(&self) -> bool {
- self.inner.supports_dmbuf()
- }
-}
-
-#[allow(clippy::trivially_copy_pass_by_ref, clippy::unnecessary_wraps)]
-const fn receive_callback_dummy(_: &(), frame: wlx_frame::WlxFrame) -> Option {
- Some(frame)
-}
diff --git a/wlx-overlay-s/src/overlays/screen/backend.rs b/wlx-overlay-s/src/overlays/screen/backend.rs
new file mode 100644
index 0000000..cece571
--- /dev/null
+++ b/wlx-overlay-s/src/overlays/screen/backend.rs
@@ -0,0 +1,256 @@
+use std::{
+ sync::{Arc, LazyLock, atomic::AtomicU64},
+ time::Instant,
+};
+
+use glam::{Affine2, Vec2, vec2};
+use vulkano::image::view::ImageView;
+use wlx_capture::WlxCapture;
+
+use crate::{
+ backend::{
+ input::{Haptics, PointerHit, PointerMode},
+ overlay::{FrameMeta, OverlayBackend, ShouldRender},
+ },
+ graphics::CommandBuffers,
+ state::AppState,
+ subsystem::hid::{MOUSE_LEFT, MOUSE_MIDDLE, MOUSE_RIGHT},
+};
+
+use super::{
+ Transform,
+ capture::{ScreenPipeline, WlxCaptureIn, WlxCaptureOut, receive_callback},
+};
+
+const CURSOR_SIZE: f32 = 16. / 1440.;
+
+static START: LazyLock = LazyLock::new(Instant::now);
+static NEXT_MOVE: AtomicU64 = AtomicU64::new(0);
+
+fn can_move() -> bool {
+ START.elapsed().as_millis() as u64 > NEXT_MOVE.load(std::sync::atomic::Ordering::Relaxed)
+}
+
+fn set_next_move(millis_from_now: u64) {
+ NEXT_MOVE.store(
+ START.elapsed().as_millis() as u64 + millis_from_now,
+ std::sync::atomic::Ordering::Relaxed,
+ );
+}
+
+pub struct ScreenBackend {
+ name: Arc,
+ capture: Box>,
+ pipeline: Option,
+ cur_frame: Option,
+ meta: Option,
+ mouse_transform: Affine2,
+ interaction_transform: Option,
+}
+
+impl ScreenBackend {
+ pub fn new_raw(
+ name: Arc,
+ capture: Box>,
+ ) -> Self {
+ Self {
+ name,
+ capture,
+ pipeline: None,
+ cur_frame: None,
+ meta: None,
+ mouse_transform: Affine2::ZERO,
+ interaction_transform: None,
+ }
+ }
+
+ pub(super) fn set_mouse_transform(&mut self, pos: Vec2, size: Vec2, transform: Transform) {
+ self.mouse_transform = match transform {
+ Transform::_90 | Transform::Flipped90 => Affine2::from_cols(
+ vec2(0., size.y),
+ vec2(-size.x, 0.),
+ vec2(pos.x + size.x, pos.y),
+ ),
+ Transform::_180 | Transform::Flipped180 => Affine2::from_cols(
+ vec2(-size.x, 0.),
+ vec2(0., -size.y),
+ vec2(pos.x + size.x, pos.y + size.y),
+ ),
+ Transform::_270 | Transform::Flipped270 => Affine2::from_cols(
+ vec2(0., -size.y),
+ vec2(size.x, 0.),
+ vec2(pos.x, pos.y + size.y),
+ ),
+ _ => Affine2::from_cols(vec2(size.x, 0.), vec2(0., size.y), pos),
+ };
+ }
+
+ pub(super) fn get_interaction_transform(&mut self, res: Vec2, transform: Transform) {
+ let center = Vec2 { x: 0.5, y: 0.5 };
+ self.interaction_transform = Some(match transform {
+ Transform::_90 | Transform::Flipped90 => {
+ Affine2::from_cols(Vec2::NEG_Y * (res.x / res.y), Vec2::NEG_X, center)
+ }
+ Transform::_180 | Transform::Flipped180 => {
+ Affine2::from_cols(Vec2::NEG_X, Vec2::NEG_Y * (-res.x / res.y), center)
+ }
+ Transform::_270 | Transform::Flipped270 => {
+ Affine2::from_cols(Vec2::Y * (res.x / res.y), Vec2::X, center)
+ }
+ _ if res.y > res.x => {
+ // Xorg upright screens
+ Affine2::from_cols(Vec2::X * (res.y / res.x), Vec2::NEG_Y, center)
+ }
+ _ => Affine2::from_cols(Vec2::X, Vec2::NEG_Y * (res.x / res.y), center),
+ });
+ }
+}
+
+impl OverlayBackend for ScreenBackend {
+ fn init(&mut self, _app: &mut AppState) -> anyhow::Result<()> {
+ Ok(())
+ }
+ fn should_render(&mut self, app: &mut AppState) -> anyhow::Result {
+ if !self.capture.is_ready() {
+ let supports_dmabuf = app
+ .gfx
+ .device
+ .enabled_extensions()
+ .ext_external_memory_dma_buf
+ && self.capture.supports_dmbuf();
+
+ let allow_dmabuf = &*app.session.config.capture_method != "pw_fallback"
+ && &*app.session.config.capture_method != "screencopy";
+
+ let capture_method = app.session.config.capture_method.clone();
+
+ let dmabuf_formats = if !supports_dmabuf {
+ log::info!("Capture method does not support DMA-buf");
+ if app.gfx_extras.queue_capture.is_none() {
+ log::warn!(
+ "Current GPU does not support multiple queues. Software capture will take place on the main thread. Expect degraded performance."
+ );
+ }
+ &Vec::new()
+ } else if !allow_dmabuf {
+ log::info!("Not using DMA-buf capture due to {capture_method}");
+ if app.gfx_extras.queue_capture.is_none() {
+ log::warn!(
+ "Current GPU does not support multiple queues. Software capture will take place on the main thread. Expect degraded performance."
+ );
+ }
+ &Vec::new()
+ } else {
+ log::warn!(
+ "Using DMA-buf capture. If screens are blank for you, switch to SHM using:"
+ );
+ log::warn!(
+ "echo 'capture_method: pw_fallback' > ~/.config/wlxoverlay/conf.d/pw_fallback.yaml"
+ );
+
+ &app.gfx_extras.drm_formats
+ };
+
+ let user_data = WlxCaptureIn::new(self.name.clone(), app);
+ self.capture
+ .init(dmabuf_formats, user_data, receive_callback);
+ self.capture.request_new_frame();
+ return Ok(ShouldRender::Unable);
+ }
+
+ if let Some(frame) = self.capture.receive() {
+ let meta = frame.get_frame_meta(&app.session.config);
+
+ if let Some(pipeline) = self.pipeline.as_mut() {
+ if self.meta.is_some_and(|old| old.extent != meta.extent) {
+ pipeline.set_extent(app, [meta.extent[0] as _, meta.extent[1] as _])?;
+ }
+ } else {
+ self.pipeline = Some(ScreenPipeline::new(&meta, app)?);
+ }
+
+ self.meta = Some(meta);
+ self.cur_frame = Some(frame);
+ }
+
+ if self.cur_frame.is_some() {
+ Ok(ShouldRender::Should)
+ } else {
+ Ok(ShouldRender::Unable)
+ }
+ }
+ fn render(
+ &mut self,
+ app: &mut AppState,
+ tgt: Arc,
+ buf: &mut CommandBuffers,
+ alpha: f32,
+ ) -> anyhow::Result {
+ let Some(capture) = self.cur_frame.take() else {
+ return Ok(false);
+ };
+
+ // want panic; must be Some if cur_frame is also Some
+ self.pipeline
+ .as_mut()
+ .unwrap()
+ .render(&capture, app, tgt, buf, alpha)?;
+
+ self.capture.request_new_frame();
+ Ok(true)
+ }
+ fn pause(&mut self, _app: &mut AppState) -> anyhow::Result<()> {
+ self.capture.pause();
+ Ok(())
+ }
+ fn resume(&mut self, _app: &mut AppState) -> anyhow::Result<()> {
+ self.capture.resume();
+ Ok(())
+ }
+ fn frame_meta(&mut self) -> Option {
+ self.meta
+ }
+
+ fn on_hover(&mut self, app: &mut AppState, hit: &PointerHit) -> Option {
+ #[cfg(debug_assertions)]
+ log::trace!("Hover: {:?}", hit.uv);
+ if can_move()
+ && (!app.session.config.focus_follows_mouse_mode
+ || app.input_state.pointers[hit.pointer].now.move_mouse)
+ {
+ let pos = self.mouse_transform.transform_point2(hit.uv);
+ app.hid_provider.inner.mouse_move(pos);
+ set_next_move(u64::from(app.session.config.mouse_move_interval_ms));
+ }
+ None
+ }
+ fn on_pointer(&mut self, app: &mut AppState, hit: &PointerHit, pressed: bool) {
+ let btn = match hit.mode {
+ PointerMode::Right => MOUSE_RIGHT,
+ PointerMode::Middle => MOUSE_MIDDLE,
+ _ => MOUSE_LEFT,
+ };
+
+ if pressed {
+ set_next_move(u64::from(app.session.config.click_freeze_time_ms));
+ }
+
+ app.hid_provider.inner.send_button(btn, pressed);
+
+ if !pressed {
+ return;
+ }
+ let pos = self.mouse_transform.transform_point2(hit.uv);
+ app.hid_provider.inner.mouse_move(pos);
+ }
+ fn on_scroll(&mut self, app: &mut AppState, _hit: &PointerHit, delta_y: f32, delta_x: f32) {
+ app.hid_provider
+ .inner
+ .wheel((delta_y * 64.) as i32, (delta_x * 64.) as i32);
+ }
+ fn on_left(&mut self, _app: &mut AppState, _hand: usize) {}
+
+ fn get_interaction_transform(&mut self) -> Option {
+ self.interaction_transform
+ }
+}
diff --git a/wlx-overlay-s/src/overlays/screen/capture.rs b/wlx-overlay-s/src/overlays/screen/capture.rs
new file mode 100644
index 0000000..e33dcbf
--- /dev/null
+++ b/wlx-overlay-s/src/overlays/screen/capture.rs
@@ -0,0 +1,442 @@
+use std::{f32::consts::PI, sync::Arc};
+
+use glam::{Affine3A, Vec3};
+use vulkano::{
+ buffer::{BufferUsage, Subbuffer},
+ command_buffer::CommandBufferUsage,
+ device::Queue,
+ format::Format,
+ image::{Image, sampler::Filter, view::ImageView},
+ pipeline::graphics::{color_blend::AttachmentBlend, input_assembly::PrimitiveTopology},
+};
+use wgui::gfx::{WGfx, pass::WGfxPass, pipeline::WGfxPipeline};
+use wlx_capture::{
+ WlxCapture,
+ frame::{self as wlx_frame, DrmFormat, FrameFormat, MouseMeta, WlxFrame},
+};
+
+use crate::{
+ backend::overlay::FrameMeta,
+ config::GeneralConfig,
+ graphics::{
+ CommandBuffers, Vert2Uv,
+ dmabuf::{WGfxDmabuf, fourcc_to_vk},
+ upload_quad_vertices,
+ },
+ state::AppState,
+};
+
+const CURSOR_SIZE: f32 = 16. / 1440.;
+
+struct MousePass {
+ pass: WGfxPass,
+ buf_vert: Subbuffer<[Vert2Uv]>,
+}
+
+pub(super) struct ScreenPipeline {
+ mouse: MousePass,
+ pipeline: Arc>,
+ pass: WGfxPass,
+ buf_alpha: Subbuffer<[f32]>,
+ extentf: [f32; 2],
+}
+
+impl ScreenPipeline {
+ pub(super) fn new(meta: &FrameMeta, app: &mut AppState) -> anyhow::Result {
+ let extentf = [meta.extent[0] as f32, meta.extent[1] as f32];
+
+ let pipeline = app.gfx.create_pipeline(
+ app.gfx_extras.shaders.get("vert_quad").unwrap().clone(), // want panic
+ app.gfx_extras.shaders.get("frag_screen").unwrap().clone(), // want panic
+ app.gfx.surface_format,
+ Some(AttachmentBlend::default()),
+ PrimitiveTopology::TriangleStrip,
+ false,
+ )?;
+
+ let buf_alpha = app
+ .gfx
+ .empty_buffer(BufferUsage::TRANSFER_DST | BufferUsage::UNIFORM_BUFFER, 1)?;
+
+ Ok(Self {
+ pass: Self::create_pass(app, pipeline.clone(), extentf, buf_alpha.clone())?,
+ mouse: Self::create_mouse_pass(app, pipeline.clone(), extentf, buf_alpha.clone())?,
+ pipeline,
+ extentf,
+ buf_alpha,
+ })
+ }
+
+ pub fn set_extent(&mut self, app: &mut AppState, extentf: [f32; 2]) -> anyhow::Result<()> {
+ self.pass = Self::create_pass(app, self.pipeline.clone(), extentf, self.buf_alpha.clone())?;
+ self.mouse =
+ Self::create_mouse_pass(app, self.pipeline.clone(), extentf, self.buf_alpha.clone())?;
+ Ok(())
+ }
+
+ fn create_pass(
+ app: &mut AppState,
+ pipeline: Arc>,
+ extentf: [f32; 2],
+ buf_alpha: Subbuffer<[f32]>,
+ ) -> anyhow::Result> {
+ let set0 = pipeline.uniform_sampler(
+ 0,
+ app.gfx_extras.fallback_image.clone(),
+ app.gfx.texture_filter,
+ )?;
+ let set1 = pipeline.buffer(1, buf_alpha)?;
+ pipeline.create_pass(
+ extentf,
+ app.gfx_extras.quad_verts.clone(),
+ 0..4,
+ 0..1,
+ vec![set0, set1],
+ )
+ }
+
+ fn create_mouse_pass(
+ app: &mut AppState,
+ pipeline: Arc>,
+ extentf: [f32; 2],
+ buf_alpha: Subbuffer<[f32]>,
+ ) -> anyhow::Result {
+ #[rustfmt::skip]
+ let mouse_bytes = [
+ 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff,
+ 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0xff,
+ 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0xff,
+ 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff,
+ ];
+
+ let mut cmd_xfer = app
+ .gfx
+ .create_xfer_command_buffer(CommandBufferUsage::OneTimeSubmit)?;
+
+ let image =
+ cmd_xfer.upload_image(4, 4, vulkano::format::Format::R8G8B8A8_UNORM, &mouse_bytes)?;
+
+ let view = ImageView::new_default(image)?;
+
+ let buf_vert = cmd_xfer
+ .graphics
+ .empty_buffer(BufferUsage::TRANSFER_DST | BufferUsage::VERTEX_BUFFER, 4)?;
+
+ let set0 = pipeline.uniform_sampler(0, view, Filter::Nearest)?;
+ let set1 = pipeline.buffer(1, buf_alpha)?;
+ let pass = pipeline.create_pass(extentf, buf_vert.clone(), 0..4, 0..1, vec![set0, set1])?;
+
+ cmd_xfer.build_and_execute_now()?;
+ Ok(MousePass { pass, buf_vert })
+ }
+
+ pub(super) fn render(
+ &mut self,
+ capture: &WlxCaptureOut,
+ app: &mut AppState,
+ tgt: Arc,
+ buf: &mut CommandBuffers,
+ alpha: f32,
+ ) -> anyhow::Result<()> {
+ let view = ImageView::new_default(capture.image.clone())?;
+
+ self.pass.update_sampler(0, view, app.gfx.texture_filter)?;
+ self.buf_alpha.write()?[0] = alpha;
+
+ let mut cmd = app
+ .gfx
+ .create_gfx_command_buffer(CommandBufferUsage::OneTimeSubmit)?;
+ cmd.begin_rendering(tgt)?;
+ cmd.run_ref(&self.pass)?;
+
+ if let Some(mouse) = capture.mouse.as_ref() {
+ let size = CURSOR_SIZE * self.extentf[1];
+ let half_size = size * 0.5;
+
+ upload_quad_vertices(
+ &mut self.mouse.buf_vert,
+ self.extentf[0],
+ self.extentf[1],
+ mouse.x.mul_add(self.extentf[0], -half_size),
+ mouse.y.mul_add(self.extentf[1], -half_size),
+ size,
+ size,
+ )?;
+
+ cmd.run_ref(&self.mouse.pass)?;
+ }
+
+ cmd.end_rendering()?;
+ buf.push(cmd.build()?);
+ Ok(())
+ }
+}
+
+#[derive(Clone)]
+pub struct WlxCaptureIn {
+ name: Arc,
+ gfx: Arc,
+ queue: Arc,
+}
+
+impl WlxCaptureIn {
+ pub(super) fn new(name: Arc, app: &AppState) -> Self {
+ Self {
+ name,
+ gfx: app.gfx.clone(),
+ queue: app
+ .gfx_extras
+ .queue_capture
+ .as_ref()
+ .unwrap_or_else(|| &app.gfx.queue_xfer)
+ .clone(),
+ }
+ }
+}
+
+#[derive(Clone)]
+pub struct WlxCaptureOut {
+ image: Arc,
+ format: FrameFormat,
+ mouse: Option,
+}
+
+impl WlxCaptureOut {
+ pub(super) fn get_frame_meta(&self, config: &GeneralConfig) -> FrameMeta {
+ FrameMeta {
+ extent: extent_from_format(self.format, config),
+ transform: affine_from_format(&self.format),
+ format: self.image.format(),
+ }
+ }
+}
+
+fn upload_image(
+ me: &WlxCaptureIn,
+ width: u32,
+ height: u32,
+ format: Format,
+ data: &[u8],
+) -> Option> {
+ let mut cmd_xfer = match me
+ .gfx
+ .create_xfer_command_buffer_with_queue(me.queue.clone(), CommandBufferUsage::OneTimeSubmit)
+ {
+ Ok(x) => x,
+ Err(e) => {
+ log::error!("{}: Could not create vkCommandBuffer: {:?}", me.name, e);
+ return None;
+ }
+ };
+ let image = match cmd_xfer.upload_image(width, height, format, data) {
+ Ok(x) => x,
+ Err(e) => {
+ log::error!("{}: Could not create vkImage: {:?}", me.name, e);
+ return None;
+ }
+ };
+
+ if let Err(e) = cmd_xfer.build_and_execute_now() {
+ log::error!("{}: Could not execute upload: {:?}", me.name, e);
+ return None;
+ }
+
+ Some(image)
+}
+
+pub(super) fn receive_callback(me: &WlxCaptureIn, frame: WlxFrame) -> Option {
+ match frame {
+ WlxFrame::Dmabuf(frame) => {
+ if !frame.is_valid() {
+ log::error!("{}: Invalid frame", me.name);
+ return None;
+ }
+ log::trace!("{}: New DMA-buf frame", me.name);
+ let format = frame.format;
+ match me.gfx.dmabuf_texture(frame) {
+ Ok(image) => Some(WlxCaptureOut {
+ image,
+ format,
+ mouse: None,
+ }),
+ Err(e) => {
+ log::error!("{}: Failed to create DMA-buf vkImage: {}", me.name, e);
+ None
+ }
+ }
+ }
+ WlxFrame::MemFd(frame) => {
+ let Some(fd) = frame.plane.fd else {
+ log::error!("{}: No fd in MemFd frame", me.name);
+ return None;
+ };
+
+ let format = match fourcc_to_vk(frame.format.fourcc) {
+ Ok(x) => x,
+ Err(e) => {
+ log::error!("{}: {}", me.name, e);
+ return None;
+ }
+ };
+
+ let len = frame.plane.stride as usize * frame.format.height as usize;
+ let offset = i64::from(frame.plane.offset);
+
+ let map = unsafe {
+ libc::mmap(
+ std::ptr::null_mut(),
+ len,
+ libc::PROT_READ,
+ libc::MAP_SHARED,
+ fd,
+ offset,
+ )
+ } as *const u8;
+
+ let data = unsafe { std::slice::from_raw_parts(map, len) };
+
+ let image = {
+ let maybe_image =
+ upload_image(me, frame.format.width, frame.format.height, format, data);
+
+ unsafe { libc::munmap(map as *mut _, len) };
+ maybe_image
+ }?;
+
+ Some(WlxCaptureOut {
+ image,
+ format: frame.format,
+ mouse: None,
+ })
+ }
+ WlxFrame::MemPtr(frame) => {
+ log::trace!("{}: New MemPtr frame", me.name);
+
+ let format = match fourcc_to_vk(frame.format.fourcc) {
+ Ok(x) => x,
+ Err(e) => {
+ log::error!("{}: {}", me.name, e);
+ return None;
+ }
+ };
+
+ let data = unsafe { std::slice::from_raw_parts(frame.ptr as *const u8, frame.size) };
+ let image = upload_image(me, frame.format.width, frame.format.height, format, data)?;
+
+ Some(WlxCaptureOut {
+ image,
+ format: frame.format,
+ mouse: frame.mouse,
+ })
+ }
+ }
+}
+
+// Used when a separate GPU queue is not available
+// In this case, receive_callback needs to run on the main thread
+pub(super) struct MainThreadWlxCapture
+where
+ T: WlxCapture<(), WlxFrame>,
+{
+ inner: T,
+ data: Option,
+}
+
+impl MainThreadWlxCapture
+where
+ T: WlxCapture<(), WlxFrame>,
+{
+ pub const fn new(inner: T) -> Self {
+ Self { inner, data: None }
+ }
+}
+
+impl WlxCapture for MainThreadWlxCapture
+where
+ T: WlxCapture<(), WlxFrame>,
+{
+ fn init(
+ &mut self,
+ dmabuf_formats: &[DrmFormat],
+ user_data: WlxCaptureIn,
+ _: fn(&WlxCaptureIn, WlxFrame) -> Option,
+ ) {
+ self.data = Some(user_data);
+ self.inner.init(dmabuf_formats, (), receive_callback_dummy);
+ }
+ fn is_ready(&self) -> bool {
+ self.inner.is_ready()
+ }
+ fn request_new_frame(&mut self) {
+ self.inner.request_new_frame();
+ }
+ fn pause(&mut self) {
+ self.inner.pause();
+ }
+ fn resume(&mut self) {
+ self.inner.resume();
+ }
+ fn receive(&mut self) -> Option {
+ self.inner
+ .receive()
+ .and_then(|frame| receive_callback(self.data.as_ref().unwrap(), frame))
+ }
+ fn supports_dmbuf(&self) -> bool {
+ self.inner.supports_dmbuf()
+ }
+}
+
+#[allow(clippy::trivially_copy_pass_by_ref, clippy::unnecessary_wraps)]
+const fn receive_callback_dummy(_: &(), frame: WlxFrame) -> Option {
+ Some(frame)
+}
+
+fn extent_from_format(fmt: FrameFormat, config: &GeneralConfig) -> [u32; 3] {
+ // screens above a certain resolution will have severe aliasing
+ let height_limit = if config.screen_render_down {
+ u32::from(config.screen_max_height.min(2560))
+ } else {
+ 2560
+ };
+
+ let h = fmt.height.min(height_limit);
+ let w = (fmt.width as f32 / fmt.height as f32 * h as f32) as u32;
+ [w, h, 1]
+}
+
+fn affine_from_format(format: &FrameFormat) -> Affine3A {
+ const FLIP_X: Vec3 = Vec3 {
+ x: -1.0,
+ y: 1.0,
+ z: 1.0,
+ };
+
+ match format.transform {
+ wlx_frame::Transform::Rotated90 => Affine3A::from_rotation_z(-PI / 2.0),
+ wlx_frame::Transform::Rotated180 => Affine3A::from_rotation_z(PI),
+ wlx_frame::Transform::Rotated270 => Affine3A::from_rotation_z(PI / 2.0),
+ wlx_frame::Transform::Flipped => Affine3A::from_scale(FLIP_X),
+ wlx_frame::Transform::Flipped90 => {
+ Affine3A::from_scale(FLIP_X) * Affine3A::from_rotation_z(-PI / 2.0)
+ }
+ wlx_frame::Transform::Flipped180 => {
+ Affine3A::from_scale(FLIP_X) * Affine3A::from_rotation_z(PI)
+ }
+ wlx_frame::Transform::Flipped270 => {
+ Affine3A::from_scale(FLIP_X) * Affine3A::from_rotation_z(PI / 2.0)
+ }
+ _ => Affine3A::IDENTITY,
+ }
+}
+
+macro_rules! new_wlx_capture {
+ ($capture_queue:expr, $capture:expr) => {
+ if $capture_queue.is_none() {
+ Box::new(MainThreadWlxCapture::new($capture)) as Box>
+ } else {
+ Box::new($capture) as Box>
+ }
+ };
+}
+
+pub(super) use new_wlx_capture;
diff --git a/wlx-overlay-s/src/overlays/screen/mod.rs b/wlx-overlay-s/src/overlays/screen/mod.rs
new file mode 100644
index 0000000..c37e6e2
--- /dev/null
+++ b/wlx-overlay-s/src/overlays/screen/mod.rs
@@ -0,0 +1,115 @@
+use std::{f32::consts::PI, sync::Arc};
+
+use backend::ScreenBackend;
+use glam::{Quat, Vec3, vec3a};
+use wayland_client::protocol::wl_output;
+use wl::create_screens_wayland;
+use x11::{create_screens_x11pw, create_screens_xshm};
+
+use crate::{
+ backend::overlay::{OverlayState, Positioning},
+ state::{AppSession, AppState, ScreenMeta},
+ subsystem::{hid::XkbKeymap, input::KeyboardFocus},
+};
+
+pub mod backend;
+mod capture;
+#[cfg(feature = "pipewire")]
+pub mod pw;
+#[cfg(feature = "wayland")]
+pub mod wl;
+#[cfg(feature = "x11")]
+pub mod x11;
+
+#[allow(unused)]
+#[derive(Clone, Copy)]
+pub enum Transform {
+ Normal,
+ _90,
+ _180,
+ _270,
+ Flipped,
+ Flipped90,
+ Flipped180,
+ Flipped270,
+}
+
+#[cfg(feature = "wayland")]
+impl From for Transform {
+ fn from(t: wl_output::Transform) -> Self {
+ match t {
+ wl_output::Transform::_90 => Self::_90,
+ wl_output::Transform::_180 => Self::_180,
+ wl_output::Transform::_270 => Self::_270,
+ wl_output::Transform::Flipped => Self::Flipped,
+ wl_output::Transform::Flipped90 => Self::Flipped90,
+ wl_output::Transform::Flipped180 => Self::Flipped180,
+ wl_output::Transform::Flipped270 => Self::Flipped270,
+ _ => Self::Normal,
+ }
+ }
+}
+
+fn create_screen_state(name: Arc, transform: Transform, session: &AppSession) -> OverlayState {
+ let angle = if session.config.upright_screen_fix {
+ match transform {
+ Transform::_90 | Transform::Flipped90 => PI / 2.,
+ Transform::_180 | Transform::Flipped180 => PI,
+ Transform::_270 | Transform::Flipped270 => -PI / 2.,
+ _ => 0.,
+ }
+ } else {
+ 0.
+ };
+
+ OverlayState {
+ name,
+ keyboard_focus: Some(KeyboardFocus::PhysicalScreen),
+ grabbable: true,
+ recenter: true,
+ positioning: Positioning::Anchored,
+ interactable: true,
+ spawn_scale: 1.5 * session.config.desktop_view_scale,
+ spawn_point: vec3a(0., 0.5, 0.),
+ spawn_rotation: Quat::from_axis_angle(Vec3::Z, angle),
+ ..Default::default()
+ }
+}
+
+pub struct ScreenCreateData {
+ pub screens: Vec<(ScreenMeta, OverlayState, Box)>,
+}
+
+pub fn create_screens(app: &mut AppState) -> anyhow::Result<(ScreenCreateData, Option)> {
+ app.screens.clear();
+
+ #[cfg(feature = "wayland")]
+ {
+ if let Some(mut wl) = wlx_capture::wayland::WlxClient::new() {
+ log::info!("Wayland detected.");
+ let keymap = crate::subsystem::hid::get_keymap_wl()
+ .map_err(|f| log::warn!("Could not load keyboard layout: {f}"))
+ .ok();
+
+ return Ok((create_screens_wayland(&mut wl, app), keymap));
+ }
+ log::info!("Wayland not detected, assuming X11.");
+ }
+
+ #[cfg(feature = "x11")]
+ {
+ let keymap = crate::subsystem::hid::get_keymap_x11()
+ .map_err(|f| log::warn!("Could not load keyboard layout: {f}"))
+ .ok();
+
+ #[cfg(feature = "pipewire")]
+ match create_screens_x11pw(app) {
+ Ok(data) => return Ok((data, keymap)),
+ Err(e) => log::info!("Will not use X11 PipeWire capture: {e:?}"),
+ }
+
+ Ok((create_screens_xshm(app)?, keymap))
+ }
+ #[cfg(not(feature = "x11"))]
+ anyhow::bail!("No backends left to try.")
+}
diff --git a/wlx-overlay-s/src/overlays/screen/pw.rs b/wlx-overlay-s/src/overlays/screen/pw.rs
new file mode 100644
index 0000000..acca283
--- /dev/null
+++ b/wlx-overlay-s/src/overlays/screen/pw.rs
@@ -0,0 +1,134 @@
+use std::{error::Error, path::PathBuf, task, time::Instant};
+
+use serde::{Deserialize, Serialize};
+use wlx_capture::{
+ WlxCapture,
+ pipewire::{PipewireCapture, PipewireSelectScreenResult},
+ wayland::WlxOutput,
+};
+
+use crate::{
+ config::{PwTokenMap, def_pw_tokens},
+ config_io,
+ state::AppState,
+};
+
+use super::{
+ backend::ScreenBackend,
+ capture::{MainThreadWlxCapture, new_wlx_capture},
+};
+
+#[cfg(feature = "wayland")]
+impl ScreenBackend {
+ pub fn new_pw(
+ output: &WlxOutput,
+ token: Option<&str>,
+ app: &AppState,
+ ) -> anyhow::Result<(Self, Option /* pipewire restore token */)> {
+ let name = output.name.clone();
+ let embed_mouse = !app.session.config.double_cursor_fix;
+
+ let select_screen_result = select_pw_screen(
+ &format!(
+ "Now select: {} {} {} @ {},{}",
+ &output.name,
+ &output.make,
+ &output.model,
+ &output.logical_pos.0,
+ &output.logical_pos.1
+ ),
+ token,
+ embed_mouse,
+ true,
+ true,
+ false,
+ )?;
+
+ let node_id = select_screen_result.streams.first().unwrap().node_id; // streams guaranteed to have at least one element
+
+ let capture = new_wlx_capture!(
+ app.gfx_extras.queue_capture,
+ PipewireCapture::new(name, node_id)
+ );
+ Ok((
+ Self::new_raw(output.name.clone(), capture),
+ select_screen_result.restore_token,
+ ))
+ }
+}
+
+#[allow(clippy::fn_params_excessive_bools)]
+pub(super) fn select_pw_screen(
+ instructions: &str,
+ token: Option<&str>,
+ embed_mouse: bool,
+ screens_only: bool,
+ persist: bool,
+ multiple: bool,
+) -> Result {
+ use crate::subsystem::notifications::DbusNotificationSender;
+ use std::time::Duration;
+ use wlx_capture::pipewire::pipewire_select_screen;
+
+ let future = async move {
+ let print_at = Instant::now() + Duration::from_millis(250);
+ let mut notify = None;
+
+ let f = pipewire_select_screen(token, embed_mouse, screens_only, persist, multiple);
+ futures::pin_mut!(f);
+
+ loop {
+ match futures::poll!(&mut f) {
+ task::Poll::Ready(result) => return result,
+ task::Poll::Pending => {
+ if Instant::now() >= print_at {
+ log::info!("{instructions}");
+ if let Ok(sender) = DbusNotificationSender::new() {
+ if let Ok(id) = sender.notify_send(instructions, "", 2, 0, 0, true) {
+ notify = Some((sender, id));
+ }
+ }
+ break;
+ }
+ futures::future::lazy(|_| {
+ std::thread::sleep(Duration::from_millis(10));
+ })
+ .await;
+ }
+ }
+ }
+
+ let result = f.await;
+ if let Some((sender, id)) = notify {
+ let _ = sender.notify_close(id);
+ }
+ result
+ };
+
+ futures::executor::block_on(future)
+}
+
+#[derive(Deserialize, Serialize, Default)]
+pub struct TokenConf {
+ #[serde(default = "def_pw_tokens")]
+ pub pw_tokens: PwTokenMap,
+}
+
+fn get_pw_token_path() -> PathBuf {
+ let mut path = config_io::ConfigRoot::Generic.get_conf_d_path();
+ path.push("pw_tokens.yaml");
+ path
+}
+
+pub fn save_pw_token_config(tokens: PwTokenMap) -> Result<(), Box> {
+ let conf = TokenConf { pw_tokens: tokens };
+ let yaml = serde_yaml::to_string(&conf)?;
+ std::fs::write(get_pw_token_path(), yaml)?;
+ Ok(())
+}
+
+pub fn load_pw_token_config() -> Result> {
+ let yaml = std::fs::read_to_string(get_pw_token_path())?;
+ let conf: TokenConf = serde_yaml::from_str(yaml.as_str())?;
+ Ok(conf.pw_tokens)
+}
diff --git a/wlx-overlay-s/src/overlays/screen/wl.rs b/wlx-overlay-s/src/overlays/screen/wl.rs
new file mode 100644
index 0000000..def11d4
--- /dev/null
+++ b/wlx-overlay-s/src/overlays/screen/wl.rs
@@ -0,0 +1,164 @@
+use glam::vec2;
+use wlx_capture::{
+ WlxCapture,
+ wayland::{WlxClient, WlxOutput},
+ wlr_dmabuf::WlrDmabufCapture,
+ wlr_screencopy::WlrScreencopyCapture,
+};
+
+use crate::{
+ config::{AStrMapExt, PwTokenMap},
+ overlays::screen::create_screen_state,
+ state::{AppState, ScreenMeta},
+};
+
+use super::{
+ ScreenCreateData,
+ backend::ScreenBackend,
+ capture::{MainThreadWlxCapture, new_wlx_capture},
+ pw::{load_pw_token_config, save_pw_token_config},
+};
+
+impl ScreenBackend {
+ pub fn new_wlr_dmabuf(output: &WlxOutput, app: &AppState) -> Option {
+ let client = WlxClient::new()?;
+ let capture = new_wlx_capture!(
+ app.gfx_extras.queue_capture,
+ WlrDmabufCapture::new(client, output.id)
+ );
+ Some(Self::new_raw(output.name.clone(), capture))
+ }
+
+ pub fn new_wlr_screencopy(output: &WlxOutput, app: &AppState) -> Option {
+ let client = WlxClient::new()?;
+ let capture = new_wlx_capture!(
+ app.gfx_extras.queue_capture,
+ WlrScreencopyCapture::new(client, output.id)
+ );
+ Some(Self::new_raw(output.name.clone(), capture))
+ }
+}
+
+#[allow(clippy::useless_let_if_seq)]
+pub fn create_screen_renderer_wl(
+ output: &WlxOutput,
+ has_wlr_dmabuf: bool,
+ has_wlr_screencopy: bool,
+ pw_token_store: &mut PwTokenMap,
+ app: &AppState,
+) -> Option {
+ let mut capture: Option = None;
+ if (&*app.session.config.capture_method == "wlr-dmabuf") && has_wlr_dmabuf {
+ log::info!("{}: Using Wlr DMA-Buf", &output.name);
+ capture = ScreenBackend::new_wlr_dmabuf(output, app);
+ }
+
+ if &*app.session.config.capture_method == "screencopy" && has_wlr_screencopy {
+ log::info!("{}: Using Wlr Screencopy Wl-SHM", &output.name);
+ capture = ScreenBackend::new_wlr_screencopy(output, app);
+ }
+
+ if capture.is_none() {
+ log::info!("{}: Using Pipewire capture", &output.name);
+
+ let display_name = &*output.name;
+
+ // Find existing token by display
+ let token = pw_token_store
+ .arc_get(display_name)
+ .map(std::string::String::as_str);
+
+ if let Some(t) = token {
+ log::info!("Found existing Pipewire token for display {display_name}: {t}");
+ }
+
+ match ScreenBackend::new_pw(output, token, app) {
+ Ok((renderer, restore_token)) => {
+ capture = Some(renderer);
+
+ if let Some(token) = restore_token {
+ if pw_token_store.arc_set(display_name.into(), token.clone()) {
+ log::info!("Adding Pipewire token {token}");
+ }
+ }
+ }
+ Err(e) => {
+ log::warn!(
+ "{}: Failed to create Pipewire capture: {:?}",
+ &output.name,
+ e
+ );
+ }
+ }
+ }
+ capture
+}
+
+pub fn create_screens_wayland(wl: &mut WlxClient, app: &mut AppState) -> ScreenCreateData {
+ let mut screens = vec![];
+
+ // Load existing Pipewire tokens from file
+ let mut pw_tokens: PwTokenMap = load_pw_token_config().unwrap_or_default();
+
+ let pw_tokens_copy = pw_tokens.clone();
+ let has_wlr_dmabuf = wl.maybe_wlr_dmabuf_mgr.is_some();
+ let has_wlr_screencopy = wl.maybe_wlr_screencopy_mgr.is_some();
+
+ for (id, output) in &wl.outputs {
+ if app.screens.iter().any(|s| s.name == output.name) {
+ continue;
+ }
+
+ log::info!(
+ "{}: Init screen of res {:?}, logical {:?} at {:?}",
+ output.name,
+ output.size,
+ output.logical_size,
+ output.logical_pos,
+ );
+
+ if let Some(mut backend) = create_screen_renderer_wl(
+ output,
+ has_wlr_dmabuf,
+ has_wlr_screencopy,
+ &mut pw_tokens,
+ app,
+ ) {
+ let logical_pos = vec2(output.logical_pos.0 as f32, output.logical_pos.1 as f32);
+ let logical_size = vec2(output.logical_size.0 as f32, output.logical_size.1 as f32);
+ let transform = output.transform.into();
+
+ backend.set_mouse_transform(logical_pos, logical_size, transform);
+
+ let state = create_screen_state(output.name.clone(), transform, &app.session);
+
+ let meta = ScreenMeta {
+ name: wl.outputs[id].name.clone(),
+ id: state.id,
+ native_handle: *id,
+ };
+
+ let backend = Box::new(backend);
+ screens.push((meta, state, backend));
+ }
+ }
+
+ if pw_tokens_copy != pw_tokens {
+ // Token list changed, re-create token config file
+ if let Err(err) = save_pw_token_config(pw_tokens) {
+ log::error!("Failed to save Pipewire token config: {err}");
+ }
+ }
+
+ let extent = wl.get_desktop_extent();
+ let origin = wl.get_desktop_origin();
+
+ app.hid_provider
+ .inner
+ .set_desktop_extent(vec2(extent.0 as f32, extent.1 as f32));
+ app.hid_provider
+ .inner
+ .set_desktop_origin(vec2(origin.0 as f32, origin.1 as f32));
+
+ ScreenCreateData { screens }
+}
diff --git a/wlx-overlay-s/src/overlays/screen/x11.rs b/wlx-overlay-s/src/overlays/screen/x11.rs
new file mode 100644
index 0000000..db8e5cf
--- /dev/null
+++ b/wlx-overlay-s/src/overlays/screen/x11.rs
@@ -0,0 +1,209 @@
+use std::sync::Arc;
+
+use glam::vec2;
+use wlx_capture::{
+ WlxCapture,
+ xshm::{XshmCapture, XshmScreen},
+};
+
+use crate::{
+ overlays::screen::create_screen_state,
+ state::{AppState, ScreenMeta},
+};
+
+use super::{
+ ScreenCreateData, Transform,
+ backend::ScreenBackend,
+ capture::{MainThreadWlxCapture, new_wlx_capture},
+};
+
+#[cfg(feature = "pipewire")]
+use wlx_capture::pipewire::PipewireStream;
+
+impl ScreenBackend {
+ pub fn new_xshm(screen: Arc, app: &AppState) -> Self {
+ let capture = new_wlx_capture!(
+ app.gfx_extras.queue_capture,
+ XshmCapture::new(screen.clone())
+ );
+ Self::new_raw(screen.name.clone(), capture)
+ }
+}
+
+#[cfg(feature = "pipewire")]
+pub fn create_screens_x11pw(app: &mut AppState) -> anyhow::Result {
+ use glam::vec2;
+ use wlx_capture::{pipewire::PipewireCapture, xshm::xshm_get_monitors};
+
+ use crate::{
+ config::{AStrMapExt, PwTokenMap},
+ overlays::screen::{
+ create_screen_state,
+ pw::{load_pw_token_config, save_pw_token_config, select_pw_screen},
+ },
+ state::ScreenMeta,
+ };
+
+ use super::ScreenCreateData;
+
+ // Load existing Pipewire tokens from file
+ let mut pw_tokens: PwTokenMap = load_pw_token_config().unwrap_or_default();
+ let pw_tokens_copy = pw_tokens.clone();
+ let token = pw_tokens.arc_get("x11").map(std::string::String::as_str);
+ let embed_mouse = !app.session.config.double_cursor_fix;
+
+ let select_screen_result = select_pw_screen(
+ "Select ALL screens on the screencast pop-up!",
+ token,
+ embed_mouse,
+ true,
+ true,
+ true,
+ )?;
+
+ if let Some(restore_token) = select_screen_result.restore_token {
+ if pw_tokens.arc_set("x11".into(), restore_token.clone()) {
+ log::info!("Adding Pipewire token {restore_token}");
+ }
+ }
+ if pw_tokens_copy != pw_tokens {
+ // Token list changed, re-create token config file
+ if let Err(err) = save_pw_token_config(pw_tokens) {
+ log::error!("Failed to save Pipewire token config: {err}");
+ }
+ }
+
+ let monitors = match xshm_get_monitors() {
+ Ok(m) => m,
+ Err(e) => {
+ anyhow::bail!(e.to_string());
+ }
+ };
+ log::info!("Got {} monitors", monitors.len());
+ log::info!("Got {} streams", select_screen_result.streams.len());
+
+ let mut extent = vec2(0., 0.);
+ let screens = select_screen_result
+ .streams
+ .into_iter()
+ .enumerate()
+ .map(|(i, s)| {
+ let m = best_match(&s, monitors.iter().map(AsRef::as_ref)).unwrap();
+ log::info!("Stream {i} is {}", m.name);
+ extent.x = extent.x.max((m.monitor.x() + m.monitor.width()) as f32);
+ extent.y = extent.y.max((m.monitor.y() + m.monitor.height()) as f32);
+
+ let mut backend = ScreenBackend::new_raw(
+ m.name.clone(),
+ new_wlx_capture!(
+ app.gfx_extras.queue_capture,
+ PipewireCapture::new(m.name.clone(), s.node_id)
+ ),
+ );
+
+ backend.set_mouse_transform(
+ vec2(m.monitor.x() as f32, m.monitor.y() as f32),
+ vec2(m.monitor.width() as f32, m.monitor.height() as f32),
+ Transform::Normal,
+ );
+
+ let state = create_screen_state(m.name.clone(), Transform::Normal, &app.session);
+
+ let meta = ScreenMeta {
+ name: m.name.clone(),
+ id: state.id,
+ native_handle: 0,
+ };
+
+ let backend = Box::new(backend);
+ (meta, state, backend)
+ })
+ .collect();
+
+ app.hid_provider.inner.set_desktop_extent(extent);
+ app.hid_provider.inner.set_desktop_origin(vec2(0.0, 0.0));
+
+ Ok(ScreenCreateData { screens })
+}
+
+#[cfg(feature = "pipewire")]
+fn best_match<'a>(
+ stream: &PipewireStream,
+ mut streams: impl Iterator
- ,
+) -> Option<&'a XshmScreen> {
+ let mut best = streams.next();
+ log::debug!("stream: {:?}", stream.position);
+ log::debug!("first: {:?}", best.map(|b| &b.monitor));
+ let Some(position) = stream.position else {
+ return best;
+ };
+
+ let mut best_dist = best.map_or(i32::MAX, |b| {
+ (b.monitor.x() - position.0).abs() + (b.monitor.y() - position.1).abs()
+ });
+ for stream in streams {
+ log::debug!("checking: {:?}", stream.monitor);
+ let dist =
+ (stream.monitor.x() - position.0).abs() + (stream.monitor.y() - position.1).abs();
+ if dist < best_dist {
+ best = Some(stream);
+ best_dist = dist;
+ }
+ }
+ log::debug!("best: {:?}", best.map(|b| &b.monitor));
+ best
+}
+
+pub fn create_screens_xshm(app: &mut AppState) -> anyhow::Result {
+ use wlx_capture::xshm::xshm_get_monitors;
+
+ let mut extent = vec2(0., 0.);
+
+ let monitors = match xshm_get_monitors() {
+ Ok(m) => m,
+ Err(e) => {
+ anyhow::bail!(e.to_string());
+ }
+ };
+
+ let screens = monitors
+ .into_iter()
+ .map(|s| {
+ extent.x = extent.x.max((s.monitor.x() + s.monitor.width()) as f32);
+ extent.y = extent.y.max((s.monitor.y() + s.monitor.height()) as f32);
+
+ let size = (s.monitor.width(), s.monitor.height());
+ let pos = (s.monitor.x(), s.monitor.y());
+ let mut backend = ScreenBackend::new_xshm(s.clone(), app);
+
+ log::info!(
+ "{}: Init X11 screen of res {:?} at {:?}",
+ s.name.clone(),
+ size,
+ pos,
+ );
+
+ backend.set_mouse_transform(
+ vec2(s.monitor.x() as f32, s.monitor.y() as f32),
+ vec2(size.0 as f32, size.1 as f32),
+ Transform::Normal,
+ );
+
+ let state = create_screen_state(s.name.clone(), Transform::Normal, &app.session);
+
+ let meta = ScreenMeta {
+ name: s.name.clone(),
+ id: state.id,
+ native_handle: 0,
+ };
+
+ let backend = Box::new(backend);
+ (meta, state, backend)
+ })
+ .collect();
+
+ app.hid_provider.inner.set_desktop_extent(extent);
+ app.hid_provider.inner.set_desktop_origin(vec2(0.0, 0.0));
+
+ Ok(ScreenCreateData { screens })
+}
diff --git a/wlx-overlay-s/src/overlays/tooltip.rs b/wlx-overlay-s/src/overlays/tooltip.rs
new file mode 100644
index 0000000..e69de29
diff --git a/wlx-overlay-s/src/overlays/watch.rs b/wlx-overlay-s/src/overlays/watch.rs
index e9f2395..3db1944 100644
--- a/wlx-overlay-s/src/overlays/watch.rs
+++ b/wlx-overlay-s/src/overlays/watch.rs
@@ -10,7 +10,7 @@ use wgui::{
};
use crate::{
- backend::overlay::{OverlayData, OverlayState, Positioning, Z_ORDER_WATCH, ui_transform},
+ backend::overlay::{OverlayData, OverlayState, Positioning, Z_ORDER_WATCH},
gui::{panel::GuiPanel, timer::GuiTimer},
state::AppState,
};
@@ -113,12 +113,10 @@ where
spawn_scale: 0.115, //TODO:configurable
spawn_point: app.session.config.watch_pos,
spawn_rotation: app.session.config.watch_rot,
- interaction_transform: ui_transform([400, 200]),
positioning,
..Default::default()
},
- backend: Box::new(panel),
- ..Default::default()
+ ..OverlayData::from_backend(Box::new(panel))
})
}
diff --git a/wlx-overlay-s/src/overlays/wayvr.rs b/wlx-overlay-s/src/overlays/wayvr.rs
index 79b3ff6..02fd54a 100644
--- a/wlx-overlay-s/src/overlays/wayvr.rs
+++ b/wlx-overlay-s/src/overlays/wayvr.rs
@@ -14,10 +14,10 @@ use wlx_capture::frame::{DmabufFrame, FourCC, FrameFormat, FramePlane};
use crate::{
backend::{
common::{OverlayContainer, OverlaySelector},
- input::{self, InteractionHandler},
+ input::{self},
overlay::{
- FrameMeta, OverlayData, OverlayID, OverlayRenderer, OverlayState, ShouldRender,
- SplitOverlayBackend, Z_ORDER_DASHBOARD, ui_transform,
+ FrameMeta, OverlayBackend, OverlayData, OverlayID, OverlayState, ShouldRender,
+ Z_ORDER_DASHBOARD, ui_transform,
},
task::TaskType,
wayvr::{
@@ -98,91 +98,12 @@ impl WayVRData {
}
}
-pub struct WayVRInteractionHandler {
- context: Rc>,
- mouse_transform: Affine2,
-}
-
-impl WayVRInteractionHandler {
- pub const fn new(context: Rc>, mouse_transform: Affine2) -> Self {
- Self {
- context,
- mouse_transform,
- }
- }
-}
-
-impl InteractionHandler for WayVRInteractionHandler {
- fn on_hover(
- &mut self,
- _app: &mut state::AppState,
- hit: &input::PointerHit,
- ) -> Option {
- let ctx = self.context.borrow();
-
- let wayvr = &mut ctx.wayvr.borrow_mut();
-
- if let Some(disp) = wayvr.data.state.displays.get(&ctx.display) {
- let pos = self.mouse_transform.transform_point2(hit.uv);
- let x = ((pos.x * f32::from(disp.width)) as i32).max(0);
- let y = ((pos.y * f32::from(disp.height)) as i32).max(0);
-
- let ctx = self.context.borrow();
- wayvr
- .data
- .state
- .send_mouse_move(ctx.display, x as u32, y as u32);
- }
-
- wayvr.pending_haptics.take()
- }
-
- fn on_left(&mut self, _app: &mut state::AppState, _pointer: usize) {
- // Ignore event
- }
-
- fn on_pointer(&mut self, _app: &mut state::AppState, hit: &input::PointerHit, pressed: bool) {
- if let Some(index) = match hit.mode {
- input::PointerMode::Left => Some(wayvr::MouseIndex::Left),
- input::PointerMode::Middle => Some(wayvr::MouseIndex::Center),
- input::PointerMode::Right => Some(wayvr::MouseIndex::Right),
- _ => {
- // Unknown pointer event, ignore
- None
- }
- } {
- let ctx = self.context.borrow();
- let wayvr = &mut ctx.wayvr.borrow_mut().data;
- if pressed {
- wayvr.state.send_mouse_down(ctx.display, index);
- } else {
- wayvr.state.send_mouse_up(index);
- }
- }
- }
-
- fn on_scroll(
- &mut self,
- _app: &mut state::AppState,
- _hit: &input::PointerHit,
- delta_y: f32,
- delta_x: f32,
- ) {
- let ctx = self.context.borrow();
- ctx.wayvr
- .borrow_mut()
- .data
- .state
- .send_mouse_scroll(delta_y, delta_x);
- }
-}
-
struct ImageData {
vk_image: Arc,
vk_image_view: Arc,
}
-pub struct WayVRRenderer {
+pub struct WayVRBackend {
pipeline: Arc>,
pass: WGfxPass,
buf_alpha: Subbuffer<[f32]>,
@@ -190,9 +111,11 @@ pub struct WayVRRenderer {
context: Rc>,
graphics: Arc,
resolution: [u16; 2],
+ mouse_transform: Affine2,
+ interaction_transform: Option,
}
-impl WayVRRenderer {
+impl WayVRBackend {
pub fn new(
app: &state::AppState,
wvr: Rc>,
@@ -234,6 +157,8 @@ impl WayVRRenderer {
graphics: app.gfx.clone(),
image: None,
resolution,
+ mouse_transform: Affine2::IDENTITY,
+ interaction_transform: Some(ui_transform([resolution[0] as _, resolution[1] as _])), //TODO:dynamic
})
}
}
@@ -592,7 +517,7 @@ where
Ok(())
}
-impl WayVRRenderer {
+impl WayVRBackend {
fn ensure_software_data(
&mut self,
data: &wayvr::egl_data::RenderSoftwarePixelsData,
@@ -679,7 +604,7 @@ impl WayVRRenderer {
}
}
-impl OverlayRenderer for WayVRRenderer {
+impl OverlayBackend for WayVRBackend {
fn init(&mut self, _app: &mut state::AppState) -> anyhow::Result<()> {
Ok(())
}
@@ -772,6 +697,73 @@ impl OverlayRenderer for WayVRRenderer {
..Default::default()
})
}
+
+ fn on_hover(
+ &mut self,
+ _app: &mut state::AppState,
+ hit: &input::PointerHit,
+ ) -> Option {
+ let ctx = self.context.borrow();
+
+ let wayvr = &mut ctx.wayvr.borrow_mut();
+
+ if let Some(disp) = wayvr.data.state.displays.get(&ctx.display) {
+ let pos = self.mouse_transform.transform_point2(hit.uv);
+ let x = ((pos.x * f32::from(disp.width)) as i32).max(0);
+ let y = ((pos.y * f32::from(disp.height)) as i32).max(0);
+
+ let ctx = self.context.borrow();
+ wayvr
+ .data
+ .state
+ .send_mouse_move(ctx.display, x as u32, y as u32);
+ }
+
+ wayvr.pending_haptics.take()
+ }
+
+ fn on_left(&mut self, _app: &mut state::AppState, _pointer: usize) {
+ // Ignore event
+ }
+
+ fn on_pointer(&mut self, _app: &mut state::AppState, hit: &input::PointerHit, pressed: bool) {
+ if let Some(index) = match hit.mode {
+ input::PointerMode::Left => Some(wayvr::MouseIndex::Left),
+ input::PointerMode::Middle => Some(wayvr::MouseIndex::Center),
+ input::PointerMode::Right => Some(wayvr::MouseIndex::Right),
+ _ => {
+ // Unknown pointer event, ignore
+ None
+ }
+ } {
+ let ctx = self.context.borrow();
+ let wayvr = &mut ctx.wayvr.borrow_mut().data;
+ if pressed {
+ wayvr.state.send_mouse_down(ctx.display, index);
+ } else {
+ wayvr.state.send_mouse_up(index);
+ }
+ }
+ }
+
+ fn on_scroll(
+ &mut self,
+ _app: &mut state::AppState,
+ _hit: &input::PointerHit,
+ delta_y: f32,
+ delta_x: f32,
+ ) {
+ let ctx = self.context.borrow();
+ ctx.wayvr
+ .borrow_mut()
+ .data
+ .state
+ .send_mouse_scroll(delta_y, delta_x);
+ }
+
+ fn get_interaction_transform(&mut self) -> Option {
+ self.interaction_transform
+ }
}
#[allow(dead_code)]
@@ -786,8 +778,6 @@ pub fn create_wayvr_display_overlay(
where
O: Default,
{
- let transform = ui_transform([u32::from(display_width), u32::from(display_height)]);
-
let state = OverlayState {
name: format!("WayVR - {name}").into(),
keyboard_focus: Some(KeyboardFocus::WayVR),
@@ -796,24 +786,21 @@ where
grabbable: true,
spawn_scale: display_scale,
spawn_point: vec3a(0.0, -0.1, -1.0),
- interaction_transform: transform,
..Default::default()
};
let wayvr = app.get_wayvr()?;
- let renderer = WayVRRenderer::new(app, wayvr, display_handle, [display_width, display_height])?;
- let context = renderer.context.clone();
-
- let backend = Box::new(SplitOverlayBackend {
- renderer: Box::new(renderer),
- interaction: Box::new(WayVRInteractionHandler::new(context, Affine2::IDENTITY)),
- });
+ let backend = Box::new(WayVRBackend::new(
+ app,
+ wayvr,
+ display_handle,
+ [display_width, display_height],
+ )?);
Ok(OverlayData {
state,
- backend,
- ..Default::default()
+ ..OverlayData::from_backend(backend)
})
}
diff --git a/wlx-overlay-s/src/state.rs b/wlx-overlay-s/src/state.rs
index f3e7e73..6a92563 100644
--- a/wlx-overlay-s/src/state.rs
+++ b/wlx-overlay-s/src/state.rs
@@ -3,7 +3,6 @@ use idmap::IdMap;
use serde::{Deserialize, Serialize};
use smallvec::{SmallVec, smallvec};
use std::sync::Arc;
-use vulkano::image::view::ImageView;
use wgui::{gfx::WGfx, renderer_vk::context::SharedContext as WSharedContext};
#[cfg(feature = "wayvr")]
@@ -18,7 +17,7 @@ use crate::subsystem::osc::OscSender;
use crate::{
backend::{input::InputState, overlay::OverlayID, task::TaskContainer},
- config::{AStrMap, GeneralConfig},
+ config::GeneralConfig,
config_io,
graphics::WGfxExtras,
overlays::toast::{DisplayMethod, ToastTopic},
@@ -39,7 +38,6 @@ pub struct AppState {
pub input_state: InputState,
pub screens: SmallVec<[ScreenMeta; 8]>,
pub anchor: Affine3A,
- pub sprites: AStrMap>,
pub toast_sound: &'static [u8],
#[cfg(feature = "osc")]
@@ -86,7 +84,6 @@ impl AppState {
input_state: InputState::new(),
screens: smallvec![],
anchor: Affine3A::IDENTITY,
- sprites: AStrMap::new(),
toast_sound: toast_sound_wav,
#[cfg(feature = "osc")]