bar + overlaybackend refactor
@@ -1 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><!-- Icon from Material Symbols by Google - https://github.com/google/material-design-icons/blob/master/LICENSE --><path fill="white" d="M11 17h2v-4h4v-2h-4V7h-2v4H7v2h4zm-6 4q-.825 0-1.412-.587T3 19V5q0-.825.588-1.412T5 3h14q.825 0 1.413.588T21 5v14q0 .825-.587 1.413T19 21z"/></svg>
|
|
||||||
|
Before Width: | Height: | Size: 368 B After Width: | Height: | Size: 45 B |
1
uidev/assets/bar/add.svg
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
../../../wlx-overlay-s/src/assets/bar/add.svg
|
||||||
|
Before Width: | Height: | Size: 368 B After Width: | Height: | Size: 45 B |
@@ -1 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><!-- Icon from Material Symbols by Google - https://github.com/google/material-design-icons/blob/master/LICENSE --><path fill="white" d="M7.5 18q.625 0 1.063-.437T9 16.5t-.437-1.062T7.5 15t-1.062.438T6 16.5t.438 1.063T7.5 18m0-9q.625 0 1.063-.437T9 7.5t-.437-1.062T7.5 6t-1.062.438T6 7.5t.438 1.063T7.5 9m0 4.5q.625 0 1.063-.437T9 12t-.437-1.062T7.5 10.5t-1.062.438T6 12t.438 1.063T7.5 13.5m9 4.5q.625 0 1.063-.437T18 16.5t-.437-1.062T16.5 15t-1.062.438T15 16.5t.438 1.063T16.5 18m0-9q.625 0 1.063-.437T18 7.5t-.437-1.062T16.5 6t-1.062.438T15 7.5t.438 1.063T16.5 9M5 21q-.825 0-1.412-.587T3 19V5q0-.825.588-1.412T5 3h14q.825 0 1.413.588T21 5v14q0 .825-.587 1.413T19 21zm0-2h14V5H5zM5 5v14zm11.5 8.5q.625 0 1.063-.437T18 12t-.437-1.062T16.5 10.5t-1.062.438T15 12t.438 1.063t1.062.437M12 9q.625 0 1.063-.437T13.5 7.5t-.437-1.062T12 6t-1.062.438T10.5 7.5t.438 1.063T12 9m0 9q.625 0 1.063-.437T13.5 16.5t-.437-1.062T12 15t-1.062.438T10.5 16.5t.438 1.063T12 18m0-4.5q.625 0 1.063-.437T13.5 12t-.437-1.062T12 10.5t-1.062.438T10.5 12t.438 1.063T12 13.5"/></svg>
|
|
||||||
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 56 B |
1
uidev/assets/bar/background-off.svg
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
../../../wlx-overlay-s/src/assets/bar/background-off.svg
|
||||||
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 56 B |
@@ -1 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><!-- Icon from Material Symbols by Google - https://github.com/google/material-design-icons/blob/master/LICENSE --><path fill="white" d="M7.5 18q.625 0 1.063-.437T9 16.5t-.437-1.062T7.5 15t-1.062.438T6 16.5t.438 1.063T7.5 18m0-9q.625 0 1.063-.437T9 7.5t-.437-1.062T7.5 6t-1.062.438T6 7.5t.438 1.063T7.5 9m0 4.5q.625 0 1.063-.437T9 12t-.437-1.062T7.5 10.5t-1.062.438T6 12t.438 1.063T7.5 13.5m9 4.5q.625 0 1.063-.437T18 16.5t-.437-1.062T16.5 15t-1.062.438T15 16.5t.438 1.063T16.5 18m0-9q.625 0 1.063-.437T18 7.5t-.437-1.062T16.5 6t-1.062.438T15 7.5t.438 1.063T16.5 9M5 21q-.825 0-1.412-.587T3 19V5q0-.825.588-1.412T5 3h14q.825 0 1.413.588T21 5v14q0 .825-.587 1.413T19 21zm11.5-7.5q.625 0 1.063-.437T18 12t-.437-1.062T16.5 10.5t-1.062.438T15 12t.438 1.063t1.062.437M12 9q.625 0 1.063-.437T13.5 7.5t-.437-1.062T12 6t-1.062.438T10.5 7.5t.438 1.063T12 9m0 9q.625 0 1.063-.437T13.5 16.5t-.437-1.062T12 15t-1.062.438T10.5 16.5t.438 1.063T12 18m0-4.5q.625 0 1.063-.437T13.5 12t-.437-1.062T12 10.5t-1.062.438T10.5 12t.438 1.063T12 13.5"/></svg>
|
|
||||||
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 52 B |
1
uidev/assets/bar/background.svg
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
../../../wlx-overlay-s/src/assets/bar/background.svg
|
||||||
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 52 B |
@@ -1 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><!-- Icon from Material Symbols by Google - https://github.com/google/material-design-icons/blob/master/LICENSE --><path fill="white" d="m8.4 17l3.6-3.6l3.6 3.6l1.4-1.4l-3.6-3.6L17 8.4L15.6 7L12 10.6L8.4 7L7 8.4l3.6 3.6L7 15.6zm3.6 5q-2.075 0-3.9-.788t-3.175-2.137T2.788 15.9T2 12t.788-3.9t2.137-3.175T8.1 2.788T12 2t3.9.788t3.175 2.137T21.213 8.1T22 12t-.788 3.9t-2.137 3.175t-3.175 2.138T12 22"/></svg>
|
|
||||||
|
Before Width: | Height: | Size: 488 B After Width: | Height: | Size: 48 B |
1
uidev/assets/bar/cancel.svg
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
../../../wlx-overlay-s/src/assets/bar/cancel.svg
|
||||||
|
Before Width: | Height: | Size: 488 B After Width: | Height: | Size: 48 B |
@@ -1 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><!-- Icon from Material Symbols by Google - https://github.com/google/material-design-icons/blob/master/LICENSE --><path fill="white" d="m10.6 16.2l7.05-7.05l-1.4-1.4l-5.65 5.65l-2.85-2.85l-1.4 1.4zM5 21q-.825 0-1.412-.587T3 19V5q0-.825.588-1.412T5 3h14q.825 0 1.413.588T21 5v14q0 .825-.587 1.413T19 21z"/></svg>
|
|
||||||
|
Before Width: | Height: | Size: 396 B After Width: | Height: | Size: 58 B |
1
uidev/assets/bar/checkbox-checked.svg
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
../../../wlx-overlay-s/src/assets/bar/checkbox-checked.svg
|
||||||
|
Before Width: | Height: | Size: 396 B After Width: | Height: | Size: 58 B |
@@ -1 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><!-- Icon from Material Symbols by Google - https://github.com/google/material-design-icons/blob/master/LICENSE --><path fill="white" d="M5 21q-.825 0-1.412-.587T3 19V5q0-.825.588-1.412T5 3h14q.825 0 1.413.588T21 5v14q0 .825-.587 1.413T19 21zm0-2h14V5H5z"/></svg>
|
|
||||||
|
Before Width: | Height: | Size: 347 B After Width: | Height: | Size: 50 B |
1
uidev/assets/bar/checkbox.svg
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
../../../wlx-overlay-s/src/assets/bar/checkbox.svg
|
||||||
|
Before Width: | Height: | Size: 347 B After Width: | Height: | Size: 50 B |
@@ -1 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><!-- Icon from Material Symbols by Google - https://github.com/google/material-design-icons/blob/master/LICENSE --><path fill="white" d="M7 21q-.825 0-1.412-.587T5 19V6H4V4h5V3h6v1h5v2h-1v13q0 .825-.587 1.413T17 21zm2-4h2V8H9zm4 0h2V8h-2z"/></svg>
|
|
||||||
|
Before Width: | Height: | Size: 331 B After Width: | Height: | Size: 48 B |
1
uidev/assets/bar/delete.svg
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
../../../wlx-overlay-s/src/assets/bar/delete.svg
|
||||||
|
Before Width: | Height: | Size: 331 B After Width: | Height: | Size: 48 B |
@@ -1 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><!-- Icon from Material Symbols by Google - https://github.com/google/material-design-icons/blob/master/LICENSE --><path fill="white" d="M5 21q-.825 0-1.412-.587T3 19V5q0-.825.588-1.412T5 3h5v18zm7 0q-.425 0-.712-.288T11 20t.288-.712T12 19t.713.288T13 20t-.288.713T12 21m0-4q-.425 0-.712-.288T11 16t.288-.712T12 15t.713.288T13 16t-.288.713T12 17m0-4q-.425 0-.712-.288T11 12t.288-.712T12 11t.713.288T13 12t-.288.713T12 13m0-4q-.425 0-.712-.288T11 8t.288-.712T12 7t.713.288T13 8t-.288.713T12 9m0-4q-.425 0-.712-.288T11 4t.288-.712T12 3t.713.288T13 4t-.288.713T12 5m2 14q-.425 0-.712-.288T13 18t.288-.712T14 17t.713.288T15 18t-.288.713T14 19m0-4q-.425 0-.712-.288T13 14t.288-.712T14 13t.713.288T15 14t-.288.713T14 15m0-4q-.425 0-.712-.288T13 10t.288-.712T14 9t.713.288T15 10t-.288.713T14 11m0-4q-.425 0-.712-.288T13 6t.288-.712T14 5t.713.288T15 6t-.288.713T14 7m2 14q-.425 0-.712-.288T15 20t.288-.712T16 19t.713.288T17 20t-.288.713T16 21m0-4q-.425 0-.712-.288T15 16t.288-.712T16 15t.713.288T17 16t-.288.713T16 17m0-4q-.425 0-.712-.288T15 12t.288-.712T16 11t.713.288T17 12t-.288.713T16 13m0-4q-.425 0-.712-.288T15 8t.288-.712T16 7t.713.288T17 8t-.288.713T16 9m0-4q-.425 0-.712-.288T15 4t.288-.712T16 3t.713.288T17 4t-.288.713T16 5m2 14q-.425 0-.712-.288T17 18t.288-.712T18 17t.713.288T19 18t-.288.713T18 19m0-4q-.425 0-.712-.288T17 14t.288-.712T18 13t.713.288T19 14t-.288.713T18 15m0-4q-.425 0-.712-.288T17 10t.288-.712T18 9t.713.288T19 10t-.288.713T18 11m0-4q-.425 0-.712-.288T17 6t.288-.712T18 5t.713.288T19 6t-.288.713T18 7m2 14q-.425 0-.712-.288T19 20t.288-.712T20 19t.713.288T21 20t-.288.713T20 21m0-4q-.425 0-.712-.288T19 16t.288-.712T20 15t.713.288T21 16t-.288.713T20 17m0-4q-.425 0-.712-.288T19 12t.288-.712T20 11t.713.288T21 12t-.288.713T20 13m0-4q-.425 0-.712-.288T19 8t.288-.712T20 7t.713.288T21 8t-.288.713T20 9m0-4q-.425 0-.712-.288T19 4t.288-.712T20 3t.713.288T21 4t-.288.713T20 5"/></svg>
|
|
||||||
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 46 B |
1
uidev/assets/bar/fade.svg
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
../../../wlx-overlay-s/src/assets/bar/fade.svg
|
||||||
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 46 B |
@@ -1 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><!-- Icon from Material Symbols by Google - https://github.com/google/material-design-icons/blob/master/LICENSE --><path fill="white" d="M8 13V5.825L5.425 8.4L4 7l5-5l5 5l-1.425 1.4L10 5.825V13zm7 9l-5-5l1.425-1.4L14 18.175V11h2v7.175l2.575-2.575L20 17z"/></svg>
|
|
||||||
|
Before Width: | Height: | Size: 346 B After Width: | Height: | Size: 47 B |
1
uidev/assets/bar/inout.svg
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
../../../wlx-overlay-s/src/assets/bar/inout.svg
|
||||||
|
Before Width: | Height: | Size: 346 B After Width: | Height: | Size: 47 B |
@@ -1 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><!-- Icon from Material Symbols by Google - https://github.com/google/material-design-icons/blob/master/LICENSE --><path fill="white" d="M6 22q-.825 0-1.412-.587T4 20V10q0-.825.588-1.412T6 8h1V6q0-2.075 1.463-3.537T12 1t3.538 1.463T17 6v2h1q.825 0 1.413.588T20 10v10q0 .825-.587 1.413T18 22zm6-5q.825 0 1.413-.587T14 15t-.587-1.412T12 13t-1.412.588T10 15t.588 1.413T12 17M9 8h6V6q0-1.25-.875-2.125T12 3t-2.125.875T9 6z"/></svg>
|
|
||||||
|
Before Width: | Height: | Size: 511 B After Width: | Height: | Size: 46 B |
1
uidev/assets/bar/lock.svg
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
../../../wlx-overlay-s/src/assets/bar/lock.svg
|
||||||
|
Before Width: | Height: | Size: 511 B After Width: | Height: | Size: 46 B |
@@ -1 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><!-- Icon from Material Symbols by Google - https://github.com/google/material-design-icons/blob/master/LICENSE --><path fill="white" d="M6 8h9V6q0-1.25-.875-2.125T12 3t-2.125.875T9 6H7q0-2.075 1.463-3.537T12 1t3.538 1.463T17 6v2h1q.825 0 1.413.588T20 10v10q0 .825-.587 1.413T18 22H6q-.825 0-1.412-.587T4 20V10q0-.825.588-1.412T6 8m6 9q.825 0 1.413-.587T14 15t-.587-1.412T12 13t-1.412.588T10 15t.588 1.413T12 17"/></svg>
|
|
||||||
|
Before Width: | Height: | Size: 504 B After Width: | Height: | Size: 51 B |
1
uidev/assets/bar/lock_open.svg
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
../../../wlx-overlay-s/src/assets/bar/lock_open.svg
|
||||||
|
Before Width: | Height: | Size: 504 B After Width: | Height: | Size: 51 B |
@@ -1 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><!-- Icon from Material Symbols by Google - https://github.com/google/material-design-icons/blob/master/LICENSE --><path fill="white" d="M12 22q-2.925 0-4.962-2.037T5 15v-4h14v4q0 2.925-2.037 4.963T12 22M5 9q0-2.625 1.7-4.587T11 2.074V9zm8 0V2.075q2.6.375 4.3 2.338T19 9z"/></svg>
|
|
||||||
|
Before Width: | Height: | Size: 364 B After Width: | Height: | Size: 47 B |
1
uidev/assets/bar/mouse.svg
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
../../../wlx-overlay-s/src/assets/bar/mouse.svg
|
||||||
|
Before Width: | Height: | Size: 364 B After Width: | Height: | Size: 47 B |
@@ -1 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><!-- Icon from Material Symbols by Google - https://github.com/google/material-design-icons/blob/master/LICENSE --><path fill="white" d="M15.85 22q-.35 0-.6-.25t-.25-.6v-3.3q0-.35.25-.6t.6-.25H16v-1q0-.825.588-1.412T18 14t1.413.588T20 16v1h.15q.35 0 .6.25t.25.6v3.3q0 .35-.25.6t-.6.25zM17 17h2v-1q0-.425-.288-.712T18 15t-.712.288T17 16zM5 9q0-2.625 1.7-4.587T11 2.074V9zm8 0V2.075q2.6.375 4.3 2.338T19 9zm-1 13q-2.925 0-4.962-2.037T5 15v-4h14v1.1q-.25-.05-.488-.075T18 12q-2.075 0-3.537 1.463T13 17v4.925q-.25.05-.488.063T12 22"/></svg>
|
|
||||||
|
Before Width: | Height: | Size: 620 B After Width: | Height: | Size: 52 B |
1
uidev/assets/bar/mouse_lock.svg
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
../../../wlx-overlay-s/src/assets/bar/mouse_lock.svg
|
||||||
|
Before Width: | Height: | Size: 620 B After Width: | Height: | Size: 52 B |
@@ -1 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><!-- Icon from Material Symbols by Google - https://github.com/google/material-design-icons/blob/master/LICENSE --><path fill="white" d="M3 21v-6h2v2.6l3.1-3.1l1.4 1.4L6.4 19H9v2zm12 0v-2h2.6l-3.1-3.1l1.4-1.4l3.1 3.1V15h2v6zM8.1 9.5L5 6.4V9H3V3h6v2H6.4l3.1 3.1zm7.8 0l-1.4-1.4L17.6 5H15V3h6v6h-2V6.4zM12 14q-.825 0-1.412-.587T10 12t.588-1.412T12 10t1.413.588T14 12t-.587 1.413T12 14"/></svg>
|
|
||||||
|
Before Width: | Height: | Size: 475 B After Width: | Height: | Size: 50 B |
1
uidev/assets/bar/move-all.svg
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
../../../wlx-overlay-s/src/assets/bar/move-all.svg
|
||||||
|
Before Width: | Height: | Size: 475 B After Width: | Height: | Size: 50 B |
@@ -1 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><!-- Icon from Material Symbols by Google - https://github.com/google/material-design-icons/blob/master/LICENSE --><path fill="white" d="m7 17l-5-5l5-5l1.4 1.4L5.825 11h12.35L15.6 8.4L17 7l5 5l-5 5l-1.4-1.4l2.575-2.6H5.825L8.4 15.6z"/></svg>
|
|
||||||
|
Before Width: | Height: | Size: 325 B After Width: | Height: | Size: 57 B |
1
uidev/assets/bar/move-horizontal.svg
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
../../../wlx-overlay-s/src/assets/bar/move-horizontal.svg
|
||||||
|
Before Width: | Height: | Size: 325 B After Width: | Height: | Size: 57 B |
@@ -1 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><!-- Icon from Material Symbols by Google - https://github.com/google/material-design-icons/blob/master/LICENSE --><path fill="white" d="M4 5q-.425 0-.712-.288T3 4t.288-.712T4 3t.713.288T5 4t-.288.713T4 5m4 0q-.425 0-.712-.288T7 4t.288-.712T8 3t.713.288T9 4t-.288.713T8 5m4 0q-.425 0-.712-.288T11 4t.288-.712T12 3t.713.288T13 4t-.288.713T12 5M4 9q-.425 0-.712-.288T3 8t.288-.712T4 7t.713.288T5 8t-.288.713T4 9m0 4q-.425 0-.712-.288T3 12t.288-.712T4 11t.713.288T5 12t-.288.713T4 13m16 0q-.425 0-.712-.288T19 12t.288-.712T20 11t.713.288T21 12t-.288.713T20 13m0 4q-.425 0-.712-.288T19 16t.288-.712T20 15t.713.288T21 16t-.288.713T20 17m-8 4q-.425 0-.712-.288T11 20t.288-.712T12 19t.713.288T13 20t-.288.713T12 21m4 0q-.425 0-.712-.288T15 20t.288-.712T16 19t.713.288T17 20t-.288.713T16 21m4 0q-.425 0-.712-.288T19 20t.288-.712T20 19t.713.288T21 20t-.288.713T20 21M19 8V5h-3q-.425 0-.712-.288T15 4t.288-.712T16 3h3q.825 0 1.413.588T21 5v3q0 .425-.288.713T20 9t-.712-.288T19 8M3 19v-3q0-.425.288-.712T4 15t.713.288T5 16v3h3q.425 0 .713.288T9 20t-.288.713T8 21H5q-.825 0-1.412-.587T3 19"/></svg>
|
|
||||||
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 48 B |
1
uidev/assets/bar/resize.svg
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
../../../wlx-overlay-s/src/assets/bar/resize.svg
|
||||||
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 48 B |
@@ -1 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><!-- Icon from Material Symbols by Google - https://github.com/google/material-design-icons/blob/master/LICENSE --><path fill="white" d="M11 15h2v-3h3v-2h-3V7h-2v3H8v2h3zm-3 6v-2H4q-.825 0-1.412-.587T2 17V5q0-.825.588-1.412T4 3h16q.825 0 1.413.588T22 5v12q0 .825-.587 1.413T20 19h-4v2z"/></svg>
|
|
||||||
|
Before Width: | Height: | Size: 378 B After Width: | Height: | Size: 52 B |
1
uidev/assets/bar/screen-add.svg
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
../../../wlx-overlay-s/src/assets/bar/screen-add.svg
|
||||||
|
Before Width: | Height: | Size: 378 B After Width: | Height: | Size: 52 B |
@@ -1 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><!-- Icon from Material Symbols by Google - https://github.com/google/material-design-icons/blob/master/LICENSE --><path fill="white" d="M4 20q-.825 0-1.412-.587T2 18V6q0-.825.588-1.412T4 4h16q.825 0 1.413.588T22 6v12q0 .825-.587 1.413T20 20zm7-3h2l.3-1.5q.3-.125.563-.262t.537-.338l1.45.45l1-1.7l-1.15-1q.05-.325.05-.65t-.05-.65l1.15-1l-1-1.7l-1.45.45q-.275-.2-.537-.338T13.3 8.5L13 7h-2l-.3 1.5q-.3.125-.562.263T9.6 9.1l-1.45-.45l-1 1.7l1.15 1q-.05.325-.05.65t.05.65l-1.15 1l1 1.7l1.45-.45q.275.2.538.338t.562.262zm1-3q-.825 0-1.412-.587T10 12t.588-1.412T12 10t1.413.588T14 12t-.587 1.413T12 14"/></svg>
|
|
||||||
|
Before Width: | Height: | Size: 689 B After Width: | Height: | Size: 56 B |
1
uidev/assets/bar/screen-options.svg
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
../../../wlx-overlay-s/src/assets/bar/screen-options.svg
|
||||||
|
Before Width: | Height: | Size: 689 B After Width: | Height: | Size: 56 B |
@@ -1 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><!-- Icon from Material Symbols by Google - https://github.com/google/material-design-icons/blob/master/LICENSE --><path fill="white" d="M8 12h8v-2H8zm0 9v-2H4q-.825 0-1.412-.587T2 17V5q0-.825.588-1.412T4 3h16q.825 0 1.413.588T22 5v12q0 .825-.587 1.413T20 19h-4v2z"/></svg>
|
|
||||||
|
Before Width: | Height: | Size: 357 B After Width: | Height: | Size: 55 B |
1
uidev/assets/bar/screen-remove.svg
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
../../../wlx-overlay-s/src/assets/bar/screen-remove.svg
|
||||||
|
Before Width: | Height: | Size: 357 B After Width: | Height: | Size: 55 B |
@@ -1,34 +0,0 @@
|
|||||||
<layout>
|
|
||||||
<include src="wlx_theme.xml" />
|
|
||||||
|
|
||||||
<template name="TopButton">
|
|
||||||
<rectangle color="~bg_color" padding="8" round="50%">
|
|
||||||
<sprite color="~device_color" width="48" height="48" src="${src}" />
|
|
||||||
</rectangle>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<elements>
|
|
||||||
<div box_sizing="content_box" flex_direction="column" justify_content="center" align_content="center">
|
|
||||||
<rectangle padding="10" gap="8" round="100%" color="~bg_color" justify_content="center" align_content="center">
|
|
||||||
<TopButton src="bar/lock_open.svg" />
|
|
||||||
<TopButton src="bar/mouse.svg" />
|
|
||||||
<TopButton src="bar/fade.svg" />
|
|
||||||
<TopButton src="bar/move-all.svg" />
|
|
||||||
<TopButton src="bar/resize.svg" />
|
|
||||||
<TopButton src="bar/inout.svg" />
|
|
||||||
<TopButton src="bar/delete.svg" />
|
|
||||||
</rectangle>
|
|
||||||
<rectangle padding="8" gap="8" round="100%" color="~bg_color_active" justify_content="center" align_content="center">
|
|
||||||
<label size="18" text="Opacity" color="~text_color" />
|
|
||||||
<label size="18" text="100%" color="~text_color" weight="bold" />
|
|
||||||
<rectangle width="200" height="20" round="100%" color="~slider_bg_color">
|
|
||||||
<div width="150" />
|
|
||||||
<rectangle width="50" round="100%" color="~slider_fg_color" justify_content="center" align_content="center">
|
|
||||||
</rectangle>
|
|
||||||
</rectangle>
|
|
||||||
<label size="18" text="Additive:" color="~text_color" />
|
|
||||||
<sprite color="~device_color" width="20" height="20" src="bar/checkbox-checked.svg" />
|
|
||||||
</rectangle>
|
|
||||||
</div>
|
|
||||||
</elements>
|
|
||||||
</layout>
|
|
||||||
1
uidev/assets/gui/bar.xml
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
../../../wlx-overlay-s/src/assets/gui/bar.xml
|
||||||
1
wlx-overlay-s/src/assets/bar/add.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><!-- Icon from Material Symbols by Google - https://github.com/google/material-design-icons/blob/master/LICENSE --><path fill="white" d="M11 17h2v-4h4v-2h-4V7h-2v4H7v2h4zm-6 4q-.825 0-1.412-.587T3 19V5q0-.825.588-1.412T5 3h14q.825 0 1.413.588T21 5v14q0 .825-.587 1.413T19 21z"/></svg>
|
||||||
|
After Width: | Height: | Size: 368 B |
1
wlx-overlay-s/src/assets/bar/anchor.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><!-- Icon from Material Symbols by Google - https://github.com/google/material-design-icons/blob/master/LICENSE --><path fill="white" d="M12 22q-1.525 0-3.125-.55t-2.9-1.5t-2.137-2.225T3 15v-3l4 3l-1.55 1.55q.725 1.275 2.3 2.2T11 19.925V11H8V9h3V7.825q-.875-.325-1.437-1.088T9 5q0-1.25.875-2.125T12 2t2.125.875T15 5q0 .975-.562 1.738T13 7.825V9h3v2h-3v8.925q1.675-.25 3.25-1.175t2.3-2.2L17 15l4-3v3q0 1.45-.837 2.725t-2.138 2.225t-2.9 1.5T12 22m0-16q.425 0 .713-.287T13 5t-.288-.712T12 4t-.712.288T11 5t.288.713T12 6"/></svg>
|
||||||
|
After Width: | Height: | Size: 609 B |
1
wlx-overlay-s/src/assets/bar/background-off.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><!-- Icon from Material Symbols by Google - https://github.com/google/material-design-icons/blob/master/LICENSE --><path fill="white" d="M7.5 18q.625 0 1.063-.437T9 16.5t-.437-1.062T7.5 15t-1.062.438T6 16.5t.438 1.063T7.5 18m0-9q.625 0 1.063-.437T9 7.5t-.437-1.062T7.5 6t-1.062.438T6 7.5t.438 1.063T7.5 9m0 4.5q.625 0 1.063-.437T9 12t-.437-1.062T7.5 10.5t-1.062.438T6 12t.438 1.063T7.5 13.5m9 4.5q.625 0 1.063-.437T18 16.5t-.437-1.062T16.5 15t-1.062.438T15 16.5t.438 1.063T16.5 18m0-9q.625 0 1.063-.437T18 7.5t-.437-1.062T16.5 6t-1.062.438T15 7.5t.438 1.063T16.5 9M5 21q-.825 0-1.412-.587T3 19V5q0-.825.588-1.412T5 3h14q.825 0 1.413.588T21 5v14q0 .825-.587 1.413T19 21zm0-2h14V5H5zM5 5v14zm11.5 8.5q.625 0 1.063-.437T18 12t-.437-1.062T16.5 10.5t-1.062.438T15 12t.438 1.063t1.062.437M12 9q.625 0 1.063-.437T13.5 7.5t-.437-1.062T12 6t-1.062.438T10.5 7.5t.438 1.063T12 9m0 9q.625 0 1.063-.437T13.5 16.5t-.437-1.062T12 15t-1.062.438T10.5 16.5t.438 1.063T12 18m0-4.5q.625 0 1.063-.437T13.5 12t-.437-1.062T12 10.5t-1.062.438T10.5 12t.438 1.063T12 13.5"/></svg>
|
||||||
|
After Width: | Height: | Size: 1.1 KiB |
1
wlx-overlay-s/src/assets/bar/background.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><!-- Icon from Material Symbols by Google - https://github.com/google/material-design-icons/blob/master/LICENSE --><path fill="white" d="M7.5 18q.625 0 1.063-.437T9 16.5t-.437-1.062T7.5 15t-1.062.438T6 16.5t.438 1.063T7.5 18m0-9q.625 0 1.063-.437T9 7.5t-.437-1.062T7.5 6t-1.062.438T6 7.5t.438 1.063T7.5 9m0 4.5q.625 0 1.063-.437T9 12t-.437-1.062T7.5 10.5t-1.062.438T6 12t.438 1.063T7.5 13.5m9 4.5q.625 0 1.063-.437T18 16.5t-.437-1.062T16.5 15t-1.062.438T15 16.5t.438 1.063T16.5 18m0-9q.625 0 1.063-.437T18 7.5t-.437-1.062T16.5 6t-1.062.438T15 7.5t.438 1.063T16.5 9M5 21q-.825 0-1.412-.587T3 19V5q0-.825.588-1.412T5 3h14q.825 0 1.413.588T21 5v14q0 .825-.587 1.413T19 21zm11.5-7.5q.625 0 1.063-.437T18 12t-.437-1.062T16.5 10.5t-1.062.438T15 12t.438 1.063t1.062.437M12 9q.625 0 1.063-.437T13.5 7.5t-.437-1.062T12 6t-1.062.438T10.5 7.5t.438 1.063T12 9m0 9q.625 0 1.063-.437T13.5 16.5t-.437-1.062T12 15t-1.062.438T10.5 16.5t.438 1.063T12 18m0-4.5q.625 0 1.063-.437T13.5 12t-.437-1.062T12 10.5t-1.062.438T10.5 12t.438 1.063T12 13.5"/></svg>
|
||||||
|
After Width: | Height: | Size: 1.1 KiB |
1
wlx-overlay-s/src/assets/bar/cancel.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><!-- Icon from Material Symbols by Google - https://github.com/google/material-design-icons/blob/master/LICENSE --><path fill="white" d="m8.4 17l3.6-3.6l3.6 3.6l1.4-1.4l-3.6-3.6L17 8.4L15.6 7L12 10.6L8.4 7L7 8.4l3.6 3.6L7 15.6zm3.6 5q-2.075 0-3.9-.788t-3.175-2.137T2.788 15.9T2 12t.788-3.9t2.137-3.175T8.1 2.788T12 2t3.9.788t3.175 2.137T21.213 8.1T22 12t-.788 3.9t-2.137 3.175t-3.175 2.138T12 22"/></svg>
|
||||||
|
After Width: | Height: | Size: 488 B |
1
wlx-overlay-s/src/assets/bar/checkbox-checked.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><!-- Icon from Material Symbols by Google - https://github.com/google/material-design-icons/blob/master/LICENSE --><path fill="white" d="m10.6 16.2l7.05-7.05l-1.4-1.4l-5.65 5.65l-2.85-2.85l-1.4 1.4zM5 21q-.825 0-1.412-.587T3 19V5q0-.825.588-1.412T5 3h14q.825 0 1.413.588T21 5v14q0 .825-.587 1.413T19 21z"/></svg>
|
||||||
|
After Width: | Height: | Size: 396 B |
1
wlx-overlay-s/src/assets/bar/checkbox.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><!-- Icon from Material Symbols by Google - https://github.com/google/material-design-icons/blob/master/LICENSE --><path fill="white" d="M5 21q-.825 0-1.412-.587T3 19V5q0-.825.588-1.412T5 3h14q.825 0 1.413.588T21 5v14q0 .825-.587 1.413T19 21zm0-2h14V5H5z"/></svg>
|
||||||
|
After Width: | Height: | Size: 347 B |
1
wlx-overlay-s/src/assets/bar/delete.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><!-- Icon from Material Symbols by Google - https://github.com/google/material-design-icons/blob/master/LICENSE --><path fill="white" d="M7 21q-.825 0-1.412-.587T5 19V6H4V4h5V3h6v1h5v2h-1v13q0 .825-.587 1.413T17 21zm2-4h2V8H9zm4 0h2V8h-2z"/></svg>
|
||||||
|
After Width: | Height: | Size: 331 B |
1
wlx-overlay-s/src/assets/bar/fade.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><!-- Icon from Material Symbols by Google - https://github.com/google/material-design-icons/blob/master/LICENSE --><path fill="white" d="M5 21q-.825 0-1.412-.587T3 19V5q0-.825.588-1.412T5 3h5v18zm7 0q-.425 0-.712-.288T11 20t.288-.712T12 19t.713.288T13 20t-.288.713T12 21m0-4q-.425 0-.712-.288T11 16t.288-.712T12 15t.713.288T13 16t-.288.713T12 17m0-4q-.425 0-.712-.288T11 12t.288-.712T12 11t.713.288T13 12t-.288.713T12 13m0-4q-.425 0-.712-.288T11 8t.288-.712T12 7t.713.288T13 8t-.288.713T12 9m0-4q-.425 0-.712-.288T11 4t.288-.712T12 3t.713.288T13 4t-.288.713T12 5m2 14q-.425 0-.712-.288T13 18t.288-.712T14 17t.713.288T15 18t-.288.713T14 19m0-4q-.425 0-.712-.288T13 14t.288-.712T14 13t.713.288T15 14t-.288.713T14 15m0-4q-.425 0-.712-.288T13 10t.288-.712T14 9t.713.288T15 10t-.288.713T14 11m0-4q-.425 0-.712-.288T13 6t.288-.712T14 5t.713.288T15 6t-.288.713T14 7m2 14q-.425 0-.712-.288T15 20t.288-.712T16 19t.713.288T17 20t-.288.713T16 21m0-4q-.425 0-.712-.288T15 16t.288-.712T16 15t.713.288T17 16t-.288.713T16 17m0-4q-.425 0-.712-.288T15 12t.288-.712T16 11t.713.288T17 12t-.288.713T16 13m0-4q-.425 0-.712-.288T15 8t.288-.712T16 7t.713.288T17 8t-.288.713T16 9m0-4q-.425 0-.712-.288T15 4t.288-.712T16 3t.713.288T17 4t-.288.713T16 5m2 14q-.425 0-.712-.288T17 18t.288-.712T18 17t.713.288T19 18t-.288.713T18 19m0-4q-.425 0-.712-.288T17 14t.288-.712T18 13t.713.288T19 14t-.288.713T18 15m0-4q-.425 0-.712-.288T17 10t.288-.712T18 9t.713.288T19 10t-.288.713T18 11m0-4q-.425 0-.712-.288T17 6t.288-.712T18 5t.713.288T19 6t-.288.713T18 7m2 14q-.425 0-.712-.288T19 20t.288-.712T20 19t.713.288T21 20t-.288.713T20 21m0-4q-.425 0-.712-.288T19 16t.288-.712T20 15t.713.288T21 16t-.288.713T20 17m0-4q-.425 0-.712-.288T19 12t.288-.712T20 11t.713.288T21 12t-.288.713T20 13m0-4q-.425 0-.712-.288T19 8t.288-.712T20 7t.713.288T21 8t-.288.713T20 9m0-4q-.425 0-.712-.288T19 4t.288-.712T20 3t.713.288T21 4t-.288.713T20 5"/></svg>
|
||||||
|
After Width: | Height: | Size: 1.9 KiB |
1
wlx-overlay-s/src/assets/bar/inout.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><!-- Icon from Material Symbols by Google - https://github.com/google/material-design-icons/blob/master/LICENSE --><path fill="white" d="M8 13V5.825L5.425 8.4L4 7l5-5l5 5l-1.425 1.4L10 5.825V13zm7 9l-5-5l1.425-1.4L14 18.175V11h2v7.175l2.575-2.575L20 17z"/></svg>
|
||||||
|
After Width: | Height: | Size: 346 B |
1
wlx-overlay-s/src/assets/bar/lock.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><!-- Icon from Material Symbols by Google - https://github.com/google/material-design-icons/blob/master/LICENSE --><path fill="white" d="M6 22q-.825 0-1.412-.587T4 20V10q0-.825.588-1.412T6 8h1V6q0-2.075 1.463-3.537T12 1t3.538 1.463T17 6v2h1q.825 0 1.413.588T20 10v10q0 .825-.587 1.413T18 22zm6-5q.825 0 1.413-.587T14 15t-.587-1.412T12 13t-1.412.588T10 15t.588 1.413T12 17M9 8h6V6q0-1.25-.875-2.125T12 3t-2.125.875T9 6z"/></svg>
|
||||||
|
After Width: | Height: | Size: 511 B |
1
wlx-overlay-s/src/assets/bar/lock_open.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><!-- Icon from Material Symbols by Google - https://github.com/google/material-design-icons/blob/master/LICENSE --><path fill="white" d="M6 8h9V6q0-1.25-.875-2.125T12 3t-2.125.875T9 6H7q0-2.075 1.463-3.537T12 1t3.538 1.463T17 6v2h1q.825 0 1.413.588T20 10v10q0 .825-.587 1.413T18 22H6q-.825 0-1.412-.587T4 20V10q0-.825.588-1.412T6 8m6 9q.825 0 1.413-.587T14 15t-.587-1.412T12 13t-1.412.588T10 15t.588 1.413T12 17"/></svg>
|
||||||
|
After Width: | Height: | Size: 504 B |
1
wlx-overlay-s/src/assets/bar/mouse.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><!-- Icon from Material Symbols by Google - https://github.com/google/material-design-icons/blob/master/LICENSE --><path fill="white" d="M12 22q-2.925 0-4.962-2.037T5 15v-4h14v4q0 2.925-2.037 4.963T12 22M5 9q0-2.625 1.7-4.587T11 2.074V9zm8 0V2.075q2.6.375 4.3 2.338T19 9z"/></svg>
|
||||||
|
After Width: | Height: | Size: 364 B |
1
wlx-overlay-s/src/assets/bar/mouse_lock.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><!-- Icon from Material Symbols by Google - https://github.com/google/material-design-icons/blob/master/LICENSE --><path fill="white" d="M15.85 22q-.35 0-.6-.25t-.25-.6v-3.3q0-.35.25-.6t.6-.25H16v-1q0-.825.588-1.412T18 14t1.413.588T20 16v1h.15q.35 0 .6.25t.25.6v3.3q0 .35-.25.6t-.6.25zM17 17h2v-1q0-.425-.288-.712T18 15t-.712.288T17 16zM5 9q0-2.625 1.7-4.587T11 2.074V9zm8 0V2.075q2.6.375 4.3 2.338T19 9zm-1 13q-2.925 0-4.962-2.037T5 15v-4h14v1.1q-.25-.05-.488-.075T18 12q-2.075 0-3.537 1.463T13 17v4.925q-.25.05-.488.063T12 22"/></svg>
|
||||||
|
After Width: | Height: | Size: 620 B |
1
wlx-overlay-s/src/assets/bar/move-all.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><!-- Icon from Material Symbols by Google - https://github.com/google/material-design-icons/blob/master/LICENSE --><path fill="white" d="M3 21v-6h2v2.6l3.1-3.1l1.4 1.4L6.4 19H9v2zm12 0v-2h2.6l-3.1-3.1l1.4-1.4l3.1 3.1V15h2v6zM8.1 9.5L5 6.4V9H3V3h6v2H6.4l3.1 3.1zm7.8 0l-1.4-1.4L17.6 5H15V3h6v6h-2V6.4zM12 14q-.825 0-1.412-.587T10 12t.588-1.412T12 10t1.413.588T14 12t-.587 1.413T12 14"/></svg>
|
||||||
|
After Width: | Height: | Size: 475 B |
1
wlx-overlay-s/src/assets/bar/move-horizontal.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><!-- Icon from Material Symbols by Google - https://github.com/google/material-design-icons/blob/master/LICENSE --><path fill="white" d="m7 17l-5-5l5-5l1.4 1.4L5.825 11h12.35L15.6 8.4L17 7l5 5l-5 5l-1.4-1.4l2.575-2.6H5.825L8.4 15.6z"/></svg>
|
||||||
|
After Width: | Height: | Size: 325 B |
1
wlx-overlay-s/src/assets/bar/resize.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><!-- Icon from Material Symbols by Google - https://github.com/google/material-design-icons/blob/master/LICENSE --><path fill="white" d="M4 5q-.425 0-.712-.288T3 4t.288-.712T4 3t.713.288T5 4t-.288.713T4 5m4 0q-.425 0-.712-.288T7 4t.288-.712T8 3t.713.288T9 4t-.288.713T8 5m4 0q-.425 0-.712-.288T11 4t.288-.712T12 3t.713.288T13 4t-.288.713T12 5M4 9q-.425 0-.712-.288T3 8t.288-.712T4 7t.713.288T5 8t-.288.713T4 9m0 4q-.425 0-.712-.288T3 12t.288-.712T4 11t.713.288T5 12t-.288.713T4 13m16 0q-.425 0-.712-.288T19 12t.288-.712T20 11t.713.288T21 12t-.288.713T20 13m0 4q-.425 0-.712-.288T19 16t.288-.712T20 15t.713.288T21 16t-.288.713T20 17m-8 4q-.425 0-.712-.288T11 20t.288-.712T12 19t.713.288T13 20t-.288.713T12 21m4 0q-.425 0-.712-.288T15 20t.288-.712T16 19t.713.288T17 20t-.288.713T16 21m4 0q-.425 0-.712-.288T19 20t.288-.712T20 19t.713.288T21 20t-.288.713T20 21M19 8V5h-3q-.425 0-.712-.288T15 4t.288-.712T16 3h3q.825 0 1.413.588T21 5v3q0 .425-.288.713T20 9t-.712-.288T19 8M3 19v-3q0-.425.288-.712T4 15t.713.288T5 16v3h3q.425 0 .713.288T9 20t-.288.713T8 21H5q-.825 0-1.412-.587T3 19"/></svg>
|
||||||
|
After Width: | Height: | Size: 1.1 KiB |
1
wlx-overlay-s/src/assets/bar/screen-add.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><!-- Icon from Material Symbols by Google - https://github.com/google/material-design-icons/blob/master/LICENSE --><path fill="white" d="M11 15h2v-3h3v-2h-3V7h-2v3H8v2h3zm-3 6v-2H4q-.825 0-1.412-.587T2 17V5q0-.825.588-1.412T4 3h16q.825 0 1.413.588T22 5v12q0 .825-.587 1.413T20 19h-4v2z"/></svg>
|
||||||
|
After Width: | Height: | Size: 378 B |
1
wlx-overlay-s/src/assets/bar/screen-options.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><!-- Icon from Material Symbols by Google - https://github.com/google/material-design-icons/blob/master/LICENSE --><path fill="white" d="M4 20q-.825 0-1.412-.587T2 18V6q0-.825.588-1.412T4 4h16q.825 0 1.413.588T22 6v12q0 .825-.587 1.413T20 20zm7-3h2l.3-1.5q.3-.125.563-.262t.537-.338l1.45.45l1-1.7l-1.15-1q.05-.325.05-.65t-.05-.65l1.15-1l-1-1.7l-1.45.45q-.275-.2-.537-.338T13.3 8.5L13 7h-2l-.3 1.5q-.3.125-.562.263T9.6 9.1l-1.45-.45l-1 1.7l1.15 1q-.05.325-.05.65t.05.65l-1.15 1l1 1.7l1.45-.45q.275.2.538.338t.562.262zm1-3q-.825 0-1.412-.587T10 12t.588-1.412T12 10t1.413.588T14 12t-.587 1.413T12 14"/></svg>
|
||||||
|
After Width: | Height: | Size: 689 B |
1
wlx-overlay-s/src/assets/bar/screen-remove.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><!-- Icon from Material Symbols by Google - https://github.com/google/material-design-icons/blob/master/LICENSE --><path fill="white" d="M8 12h8v-2H8zm0 9v-2H4q-.825 0-1.412-.587T2 17V5q0-.825.588-1.412T4 3h16q.825 0 1.413.588T22 5v12q0 .825-.587 1.413T20 19h-4v2z"/></svg>
|
||||||
|
After Width: | Height: | Size: 357 B |
35
wlx-overlay-s/src/assets/gui/bar.xml
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
<layout>
|
||||||
|
<include src="wlx_theme.xml" />
|
||||||
|
|
||||||
|
<template name="TopButton">
|
||||||
|
<rectangle id="~id" color="~bg_color" padding="8" round="50%">
|
||||||
|
<sprite color="~device_color" width="48" height="48" src="${src}" />
|
||||||
|
</rectangle>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<elements>
|
||||||
|
<div box_sizing="content_box" flex_direction="column" justify_content="center" align_content="center">
|
||||||
|
<rectangle padding="10" gap="8" round="100%" color="~bg_color" justify_content="center" align_content="center">
|
||||||
|
<TopButton id="lock" src="bar/lock_open.svg" />
|
||||||
|
<TopButton id="anchor" src="bar/anchor.svg" />
|
||||||
|
<TopButton id="mouse" src="bar/mouse.svg" />
|
||||||
|
<TopButton id="fade" src="bar/fade.svg" />
|
||||||
|
<TopButton id="move" src="bar/move-all.svg" />
|
||||||
|
<TopButton id="resize" src="bar/resize.svg" />
|
||||||
|
<TopButton id="inout" src="bar/inout.svg" />
|
||||||
|
<TopButton id="delete" src="bar/delete.svg" />
|
||||||
|
</rectangle>
|
||||||
|
<rectangle padding="8" gap="8" round="100%" color="~bg_color_active" justify_content="center" align_content="center">
|
||||||
|
<label size="18" text="Opacity" color="~text_color" />
|
||||||
|
<label size="18" text="100%" color="~text_color" weight="bold" />
|
||||||
|
<rectangle width="200" height="20" round="100%" color="~slider_bg_color">
|
||||||
|
<div width="150" />
|
||||||
|
<rectangle width="50" round="100%" color="~slider_fg_color" justify_content="center" align_content="center">
|
||||||
|
</rectangle>
|
||||||
|
</rectangle>
|
||||||
|
<label size="18" text="Additive:" color="~text_color" />
|
||||||
|
<sprite color="~device_color" width="20" height="20" src="bar/checkbox-checked.svg" />
|
||||||
|
</rectangle>
|
||||||
|
</div>
|
||||||
|
</elements>
|
||||||
|
</layout>
|
||||||
@@ -13,11 +13,10 @@ use crate::{
|
|||||||
overlays::{
|
overlays::{
|
||||||
anchor::create_anchor,
|
anchor::create_anchor,
|
||||||
keyboard::{KEYBOARD_NAME, builder::create_keyboard},
|
keyboard::{KEYBOARD_NAME, builder::create_keyboard},
|
||||||
screen::WlxClientAlias,
|
screen::create_screens,
|
||||||
watch::{WATCH_NAME, create_watch},
|
watch::{WATCH_NAME, create_watch},
|
||||||
},
|
},
|
||||||
state::AppState,
|
state::AppState,
|
||||||
subsystem::hid::{get_keymap_wl, get_keymap_x11},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::overlay::{OverlayData, OverlayID};
|
use super::overlay::{OverlayData, OverlayID};
|
||||||
@@ -37,22 +36,11 @@ pub enum BackendError {
|
|||||||
Fatal(#[from] anyhow::Error),
|
Fatal(#[from] anyhow::Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "wayland")]
|
|
||||||
fn create_wl_client() -> Option<WlxClientAlias> {
|
|
||||||
wlx_capture::wayland::WlxClient::new()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(feature = "wayland"))]
|
|
||||||
fn create_wl_client() -> Option<WlxClientAlias> {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct OverlayContainer<T>
|
pub struct OverlayContainer<T>
|
||||||
where
|
where
|
||||||
T: Default,
|
T: Default,
|
||||||
{
|
{
|
||||||
overlays: IdMap<usize, OverlayData<T>>,
|
overlays: IdMap<usize, OverlayData<T>>,
|
||||||
wl: Option<WlxClientAlias>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> OverlayContainer<T>
|
impl<T> OverlayContainer<T>
|
||||||
@@ -62,42 +50,17 @@ where
|
|||||||
pub fn new(app: &mut AppState, headless: bool) -> anyhow::Result<Self> {
|
pub fn new(app: &mut AppState, headless: bool) -> anyhow::Result<Self> {
|
||||||
let mut overlays = IdMap::new();
|
let mut overlays = IdMap::new();
|
||||||
let mut show_screens = app.session.config.show_screens.clone();
|
let mut show_screens = app.session.config.show_screens.clone();
|
||||||
let mut wl = None;
|
let mut maybe_keymap = None;
|
||||||
let mut keymap = None;
|
|
||||||
|
|
||||||
app.screens.clear();
|
|
||||||
|
|
||||||
if headless {
|
if headless {
|
||||||
log::info!("Running in headless mode; keyboard will be en-US");
|
log::info!("Running in headless mode; keyboard will be en-US");
|
||||||
} else {
|
} else {
|
||||||
wl = create_wl_client();
|
match create_screens(app) {
|
||||||
|
Ok((data, keymap)) => {
|
||||||
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)?
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if show_screens.is_empty() {
|
if show_screens.is_empty() {
|
||||||
if let Some((_, s, _)) = data.screens.first() {
|
if let Some((_, s, _)) = data.screens.first() {
|
||||||
show_screens.arc_set(s.name.clone());
|
show_screens.arc_set(s.name.clone());
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
for (meta, mut state, backend) in data.screens {
|
for (meta, mut state, backend) in data.screens {
|
||||||
if show_screens.arc_get(state.name.as_ref()) {
|
if show_screens.arc_get(state.name.as_ref()) {
|
||||||
state.show_hide = true;
|
state.show_hide = true;
|
||||||
@@ -106,14 +69,19 @@ where
|
|||||||
state.id.0,
|
state.id.0,
|
||||||
OverlayData::<T> {
|
OverlayData::<T> {
|
||||||
state,
|
state,
|
||||||
backend,
|
..OverlayData::from_backend(backend)
|
||||||
..Default::default()
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
app.screens.push(meta);
|
app.screens.push(meta);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
maybe_keymap = keymap;
|
||||||
|
}
|
||||||
|
Err(e) => log::error!("Unable to initialize screens: {e:?}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let anchor = create_anchor(app)?;
|
let anchor = create_anchor(app)?;
|
||||||
overlays.insert(anchor.state.id.0, anchor);
|
overlays.insert(anchor.state.id.0, anchor);
|
||||||
|
|
||||||
@@ -121,147 +89,12 @@ where
|
|||||||
watch.state.want_visible = true;
|
watch.state.want_visible = true;
|
||||||
overlays.insert(watch.state.id.0, watch);
|
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.show_hide = show_screens.arc_get(KEYBOARD_NAME);
|
||||||
keyboard.state.want_visible = false;
|
keyboard.state.want_visible = false;
|
||||||
overlays.insert(keyboard.state.id.0, keyboard);
|
overlays.insert(keyboard.state.id.0, keyboard);
|
||||||
|
|
||||||
Ok(Self { overlays, wl })
|
Ok(Self { overlays })
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(feature = "wayland"))]
|
|
||||||
pub fn update(&mut self, _app: &mut AppState) -> anyhow::Result<Vec<OverlayData<T>>> {
|
|
||||||
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<Vec<OverlayData<T>>> {
|
|
||||||
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::<Vec<_>>() {
|
|
||||||
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::<T> {
|
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn mut_by_selector(&mut self, selector: &OverlaySelector) -> Option<&mut OverlayData<T>> {
|
pub fn mut_by_selector(&mut self, selector: &OverlaySelector) -> Option<&mut OverlayData<T>> {
|
||||||
|
|||||||
@@ -256,24 +256,6 @@ pub struct Haptics {
|
|||||||
pub frequency: f32,
|
pub frequency: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait InteractionHandler {
|
|
||||||
fn on_hover(&mut self, app: &mut AppState, hit: &PointerHit) -> Option<Haptics>;
|
|
||||||
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<Haptics> {
|
|
||||||
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)]
|
#[derive(Debug, Clone, Copy, Default)]
|
||||||
struct RayHit {
|
struct RayHit {
|
||||||
overlay: OverlayID,
|
overlay: OverlayID,
|
||||||
@@ -493,12 +475,16 @@ impl Pointer {
|
|||||||
hits.sort_by(|a, b| a.dist.total_cmp(&b.dist));
|
hits.sort_by(|a, b| a.dist.total_cmp(&b.dist));
|
||||||
|
|
||||||
for hit in &hits {
|
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
|
let Some(uv) = overlay
|
||||||
.state
|
.backend
|
||||||
.interaction_transform
|
.as_mut()
|
||||||
.transform_point2(hit.local_pos);
|
.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 {
|
if uv.x < 0.0 || uv.x > 1.0 || uv.y < 0.0 || uv.y > 1.0 {
|
||||||
continue;
|
continue;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use std::f32::consts::PI;
|
use std::f32::consts::PI;
|
||||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||||
|
|
||||||
use ash::vk::SubmitInfo;
|
use ash::vk::SubmitInfo;
|
||||||
use glam::{Affine3A, Vec3, Vec3A, Vec4};
|
use glam::{Affine3A, Vec3, Vec3A, Vec4};
|
||||||
@@ -8,6 +8,7 @@ use idmap::IdMap;
|
|||||||
use ovr_overlay::overlay::OverlayManager;
|
use ovr_overlay::overlay::OverlayManager;
|
||||||
use ovr_overlay::sys::ETrackingUniverseOrigin;
|
use ovr_overlay::sys::ETrackingUniverseOrigin;
|
||||||
use vulkano::{
|
use vulkano::{
|
||||||
|
VulkanObject,
|
||||||
command_buffer::{
|
command_buffer::{
|
||||||
CommandBufferBeginInfo, CommandBufferLevel, CommandBufferUsage, RecordingCommandBuffer,
|
CommandBufferBeginInfo, CommandBufferLevel, CommandBufferUsage, RecordingCommandBuffer,
|
||||||
},
|
},
|
||||||
@@ -15,16 +16,15 @@ use vulkano::{
|
|||||||
image::view::ImageView,
|
image::view::ImageView,
|
||||||
image::{Image, ImageLayout},
|
image::{Image, ImageLayout},
|
||||||
sync::{
|
sync::{
|
||||||
fence::{Fence, FenceCreateInfo},
|
|
||||||
AccessFlags, DependencyInfo, ImageMemoryBarrier, PipelineStages,
|
AccessFlags, DependencyInfo, ImageMemoryBarrier, PipelineStages,
|
||||||
|
fence::{Fence, FenceCreateInfo},
|
||||||
},
|
},
|
||||||
VulkanObject,
|
|
||||||
};
|
};
|
||||||
use wgui::gfx::WGfx;
|
use wgui::gfx::WGfx;
|
||||||
|
|
||||||
|
use crate::backend::input::{Haptics, PointerHit};
|
||||||
use crate::backend::overlay::{
|
use crate::backend::overlay::{
|
||||||
FrameMeta, OverlayData, OverlayRenderer, OverlayState, ShouldRender, SplitOverlayBackend,
|
FrameMeta, OverlayBackend, OverlayData, OverlayState, ShouldRender, Z_ORDER_LINES,
|
||||||
Z_ORDER_LINES,
|
|
||||||
};
|
};
|
||||||
use crate::graphics::CommandBuffers;
|
use crate::graphics::CommandBuffers;
|
||||||
use crate::state::AppState;
|
use crate::state::AppState;
|
||||||
@@ -81,12 +81,6 @@ impl LinePool {
|
|||||||
show_hide: true,
|
show_hide: true,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
backend: Box::new(SplitOverlayBackend {
|
|
||||||
renderer: Box::new(StaticRenderer {
|
|
||||||
view: self.view.clone(),
|
|
||||||
}),
|
|
||||||
..Default::default()
|
|
||||||
}),
|
|
||||||
data: OpenVrOverlayData {
|
data: OpenVrOverlayData {
|
||||||
width: 0.002,
|
width: 0.002,
|
||||||
override_width: true,
|
override_width: true,
|
||||||
@@ -94,7 +88,9 @@ impl LinePool {
|
|||||||
image_dirty: true,
|
image_dirty: true,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
..Default::default()
|
..OverlayData::from_backend(Box::new(LineBackend {
|
||||||
|
view: self.view.clone(),
|
||||||
|
}))
|
||||||
};
|
};
|
||||||
data.state.z_order = Z_ORDER_LINES;
|
data.state.z_order = Z_ORDER_LINES;
|
||||||
data.state.dirty = true;
|
data.state.dirty = true;
|
||||||
@@ -177,29 +173,29 @@ impl LinePool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct StaticRenderer {
|
struct LineBackend {
|
||||||
view: Arc<ImageView>,
|
view: Arc<ImageView>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl OverlayRenderer for StaticRenderer {
|
impl OverlayBackend for LineBackend {
|
||||||
fn init(&mut self, _app: &mut AppState) -> anyhow::Result<()> {
|
fn init(&mut self, _: &mut AppState) -> anyhow::Result<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
fn pause(&mut self, _app: &mut AppState) -> anyhow::Result<()> {
|
fn pause(&mut self, _: &mut AppState) -> anyhow::Result<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
fn resume(&mut self, _app: &mut AppState) -> anyhow::Result<()> {
|
fn resume(&mut self, _: &mut AppState) -> anyhow::Result<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
fn should_render(&mut self, _app: &mut AppState) -> anyhow::Result<ShouldRender> {
|
fn should_render(&mut self, _: &mut AppState) -> anyhow::Result<ShouldRender> {
|
||||||
Ok(ShouldRender::Unable)
|
Ok(ShouldRender::Unable)
|
||||||
}
|
}
|
||||||
fn render(
|
fn render(
|
||||||
&mut self,
|
&mut self,
|
||||||
_app: &mut AppState,
|
_: &mut AppState,
|
||||||
_tgt: Arc<ImageView>,
|
_: Arc<ImageView>,
|
||||||
_buf: &mut CommandBuffers,
|
_: &mut CommandBuffers,
|
||||||
_alpha: f32,
|
_: f32,
|
||||||
) -> anyhow::Result<bool> {
|
) -> anyhow::Result<bool> {
|
||||||
Ok(false)
|
Ok(false)
|
||||||
}
|
}
|
||||||
@@ -209,6 +205,16 @@ impl OverlayRenderer for StaticRenderer {
|
|||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn on_hover(&mut self, _: &mut AppState, _: &PointerHit) -> Option<Haptics> {
|
||||||
|
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<glam::Affine2> {
|
||||||
|
None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn transition_layout(
|
pub fn transition_layout(
|
||||||
|
|||||||
@@ -204,11 +204,6 @@ pub fn openvr_run(
|
|||||||
|
|
||||||
state.tasks.retrieve_due(&mut due_tasks);
|
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() {
|
while let Some(task) = due_tasks.pop_front() {
|
||||||
match task {
|
match task {
|
||||||
TaskType::Overlay(sel, f) => {
|
TaskType::Overlay(sel, f) => {
|
||||||
@@ -230,8 +225,7 @@ pub fn openvr_run(
|
|||||||
|
|
||||||
overlays.add(OverlayData {
|
overlays.add(OverlayData {
|
||||||
state,
|
state,
|
||||||
backend,
|
..OverlayData::from_backend(backend)
|
||||||
..Default::default()
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
TaskType::DropOverlay(sel) => {
|
TaskType::DropOverlay(sel) => {
|
||||||
|
|||||||
@@ -486,11 +486,6 @@ pub fn openxr_run(
|
|||||||
)?;
|
)?;
|
||||||
// End layer submit
|
// 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);
|
notifications.submit_pending(&mut app);
|
||||||
|
|
||||||
app.tasks.retrieve_due(&mut due_tasks);
|
app.tasks.retrieve_due(&mut due_tasks);
|
||||||
@@ -515,8 +510,7 @@ pub fn openxr_run(
|
|||||||
|
|
||||||
overlays.add(OverlayData {
|
overlays.add(OverlayData {
|
||||||
state: overlay_state,
|
state: overlay_state,
|
||||||
backend: overlay_backend,
|
..OverlayData::from_backend(overlay_backend)
|
||||||
..Default::default()
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
TaskType::DropOverlay(sel) => {
|
TaskType::DropOverlay(sel) => {
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ use std::{
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
use anyhow::Ok;
|
|
||||||
use glam::{Affine2, Affine3A, Mat3A, Quat, Vec2, Vec3, Vec3A};
|
use glam::{Affine2, Affine3A, Mat3A, Quat, Vec2, Vec3, Vec3A};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use vulkano::{format::Format, image::view::ImageView};
|
use vulkano::{format::Format, image::view::ImageView};
|
||||||
@@ -17,16 +16,11 @@ use crate::{
|
|||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
common::snap_upright,
|
common::snap_upright,
|
||||||
input::{DummyInteractionHandler, Haptics, InteractionHandler, PointerHit},
|
input::{Haptics, PointerHit},
|
||||||
};
|
};
|
||||||
|
|
||||||
static OVERLAY_AUTO_INCREMENT: AtomicUsize = AtomicUsize::new(0);
|
static OVERLAY_AUTO_INCREMENT: AtomicUsize = AtomicUsize::new(0);
|
||||||
|
|
||||||
pub trait OverlayBackend: OverlayRenderer + InteractionHandler {
|
|
||||||
fn set_renderer(&mut self, renderer: Box<dyn OverlayRenderer>);
|
|
||||||
fn set_interaction(&mut self, interaction: Box<dyn InteractionHandler>);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Default)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Default)]
|
||||||
pub struct OverlayID(pub usize);
|
pub struct OverlayID(pub usize);
|
||||||
|
|
||||||
@@ -56,7 +50,6 @@ pub struct OverlayState {
|
|||||||
pub saved_transform: Option<Affine3A>,
|
pub saved_transform: Option<Affine3A>,
|
||||||
pub positioning: Positioning,
|
pub positioning: Positioning,
|
||||||
pub curvature: Option<f32>,
|
pub curvature: Option<f32>,
|
||||||
pub interaction_transform: Affine2,
|
|
||||||
pub birthframe: usize,
|
pub birthframe: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,32 +74,28 @@ impl Default for OverlayState {
|
|||||||
spawn_rotation: Quat::IDENTITY,
|
spawn_rotation: Quat::IDENTITY,
|
||||||
saved_transform: None,
|
saved_transform: None,
|
||||||
transform: Affine3A::IDENTITY,
|
transform: Affine3A::IDENTITY,
|
||||||
interaction_transform: Affine2::IDENTITY,
|
|
||||||
birthframe: 0,
|
birthframe: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct OverlayData<T>
|
pub struct OverlayData<T> {
|
||||||
where
|
|
||||||
T: Default,
|
|
||||||
{
|
|
||||||
pub state: OverlayState,
|
pub state: OverlayState,
|
||||||
pub backend: Box<dyn OverlayBackend>,
|
pub backend: Box<dyn OverlayBackend>,
|
||||||
pub primary_pointer: Option<usize>,
|
pub primary_pointer: Option<usize>,
|
||||||
pub data: T,
|
pub data: T,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Default for OverlayData<T>
|
impl<T> OverlayData<T>
|
||||||
where
|
where
|
||||||
T: Default,
|
T: Default,
|
||||||
{
|
{
|
||||||
fn default() -> Self {
|
pub fn from_backend(backend: Box<dyn OverlayBackend>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
state: OverlayState::default(),
|
state: OverlayState::default(),
|
||||||
backend: Box::<SplitOverlayBackend>::default(),
|
backend,
|
||||||
primary_pointer: None,
|
primary_pointer: None,
|
||||||
data: Default::default(),
|
data: T::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -166,7 +155,7 @@ impl OverlayState {
|
|||||||
app.input_state.pointers[hand].pose
|
app.input_state.pointers[hand].pose
|
||||||
}
|
}
|
||||||
Positioning::Anchored => app.anchor,
|
Positioning::Anchored => app.anchor,
|
||||||
Positioning::Static => return,
|
Positioning::FollowOverlay { .. } | Positioning::Static => return,
|
||||||
};
|
};
|
||||||
|
|
||||||
if hard_reset {
|
if hard_reset {
|
||||||
@@ -191,7 +180,7 @@ impl OverlayState {
|
|||||||
app.input_state.pointers[hand].pose
|
app.input_state.pointers[hand].pose
|
||||||
}
|
}
|
||||||
Positioning::Anchored => snap_upright(app.anchor, Vec3A::Y),
|
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);
|
self.saved_transform = Some(parent_transform.inverse() * self.transform);
|
||||||
@@ -307,7 +296,7 @@ pub enum ShouldRender {
|
|||||||
Unable,
|
Unable,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait OverlayRenderer {
|
pub trait OverlayBackend {
|
||||||
/// Called once, before the first frame is rendered
|
/// Called once, before the first frame is rendered
|
||||||
fn init(&mut self, app: &mut AppState) -> anyhow::Result<()>;
|
fn init(&mut self, app: &mut AppState) -> anyhow::Result<()>;
|
||||||
fn pause(&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.
|
/// Must be true if should_render was also true on the same frame.
|
||||||
fn frame_meta(&mut self) -> Option<FrameMeta>;
|
fn frame_meta(&mut self) -> Option<FrameMeta>;
|
||||||
}
|
|
||||||
|
|
||||||
pub struct FallbackRenderer;
|
fn on_hover(&mut self, app: &mut AppState, hit: &PointerHit) -> Option<Haptics>;
|
||||||
|
fn on_left(&mut self, app: &mut AppState, pointer: usize);
|
||||||
impl OverlayRenderer for FallbackRenderer {
|
fn on_pointer(&mut self, app: &mut AppState, hit: &PointerHit, pressed: bool);
|
||||||
fn init(&mut self, _app: &mut AppState) -> anyhow::Result<()> {
|
fn on_scroll(&mut self, app: &mut AppState, hit: &PointerHit, delta_y: f32, delta_x: f32);
|
||||||
Ok(())
|
fn get_interaction_transform(&mut self) -> Option<Affine2>;
|
||||||
}
|
|
||||||
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<ShouldRender> {
|
|
||||||
Ok(ShouldRender::Unable)
|
|
||||||
}
|
|
||||||
fn render(
|
|
||||||
&mut self,
|
|
||||||
_app: &mut AppState,
|
|
||||||
_tgt: Arc<ImageView>,
|
|
||||||
_buf: &mut CommandBuffers,
|
|
||||||
_alpha: f32,
|
|
||||||
) -> anyhow::Result<bool> {
|
|
||||||
Ok(false)
|
|
||||||
}
|
|
||||||
fn frame_meta(&mut self) -> Option<FrameMeta> {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// Boilerplate and dummies
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Default)]
|
#[derive(Clone, Copy, Debug, Default)]
|
||||||
pub enum Positioning {
|
pub enum Positioning {
|
||||||
@@ -377,74 +342,12 @@ pub enum Positioning {
|
|||||||
FollowHand { hand: usize, lerp: f32 },
|
FollowHand { hand: usize, lerp: f32 },
|
||||||
/// Normally follows hand, but paused due to interaction
|
/// Normally follows hand, but paused due to interaction
|
||||||
FollowHandPaused { hand: usize, lerp: f32 },
|
FollowHandPaused { hand: usize, lerp: f32 },
|
||||||
|
/// Follow another overlay
|
||||||
|
FollowOverlay { id: usize },
|
||||||
/// Stays in place, no recentering
|
/// Stays in place, no recentering
|
||||||
Static,
|
Static,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct SplitOverlayBackend {
|
|
||||||
pub renderer: Box<dyn OverlayRenderer>,
|
|
||||||
pub interaction: Box<dyn InteractionHandler>,
|
|
||||||
}
|
|
||||||
|
|
||||||
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<dyn OverlayRenderer>) {
|
|
||||||
self.renderer = renderer;
|
|
||||||
}
|
|
||||||
fn set_interaction(&mut self, interaction: Box<dyn InteractionHandler>) {
|
|
||||||
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<ShouldRender> {
|
|
||||||
self.renderer.should_render(app)
|
|
||||||
}
|
|
||||||
fn render(
|
|
||||||
&mut self,
|
|
||||||
app: &mut AppState,
|
|
||||||
tgt: Arc<ImageView>,
|
|
||||||
buf: &mut CommandBuffers,
|
|
||||||
alpha: f32,
|
|
||||||
) -> anyhow::Result<bool> {
|
|
||||||
self.renderer.render(app, tgt, buf, alpha)
|
|
||||||
}
|
|
||||||
fn frame_meta(&mut self) -> Option<FrameMeta> {
|
|
||||||
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<Haptics> {
|
|
||||||
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 {
|
pub fn ui_transform(extent: [u32; 2]) -> Affine2 {
|
||||||
let aspect = extent[0] as f32 / extent[1] as f32;
|
let aspect = extent[0] as f32 / extent[1] as f32;
|
||||||
let scale = if aspect < 1.0 {
|
let scale = if aspect < 1.0 {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use glam::{Vec2, vec2};
|
use glam::{Affine2, Vec2, vec2};
|
||||||
use vulkano::{command_buffer::CommandBufferUsage, image::view::ImageView};
|
use vulkano::{command_buffer::CommandBufferUsage, image::view::ImageView};
|
||||||
use wgui::{
|
use wgui::{
|
||||||
event::{
|
event::{
|
||||||
@@ -14,8 +14,8 @@ use wgui::{
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
backend::{
|
backend::{
|
||||||
input::{Haptics, InteractionHandler, PointerHit, PointerMode},
|
input::{Haptics, PointerHit, PointerMode},
|
||||||
overlay::{FrameMeta, OverlayBackend, OverlayRenderer, ShouldRender},
|
overlay::{FrameMeta, OverlayBackend, ShouldRender, ui_transform},
|
||||||
},
|
},
|
||||||
graphics::{CommandBuffers, ExtentExt},
|
graphics::{CommandBuffers, ExtentExt},
|
||||||
gui,
|
gui,
|
||||||
@@ -32,6 +32,7 @@ pub struct GuiPanel<S> {
|
|||||||
pub state: S,
|
pub state: S,
|
||||||
pub timers: Vec<GuiTimer>,
|
pub timers: Vec<GuiTimer>,
|
||||||
pub listeners: EventListenerCollection<AppState, S>,
|
pub listeners: EventListenerCollection<AppState, S>,
|
||||||
|
interaction_transform: Option<Affine2>,
|
||||||
context: WguiContext,
|
context: WguiContext,
|
||||||
timestep: Timestep,
|
timestep: Timestep,
|
||||||
}
|
}
|
||||||
@@ -62,6 +63,7 @@ impl<S> GuiPanel<S> {
|
|||||||
state,
|
state,
|
||||||
timers: vec![],
|
timers: vec![],
|
||||||
listeners,
|
listeners,
|
||||||
|
interaction_transform: None,
|
||||||
},
|
},
|
||||||
parser_result,
|
parser_result,
|
||||||
))
|
))
|
||||||
@@ -80,6 +82,7 @@ impl<S> GuiPanel<S> {
|
|||||||
state,
|
state,
|
||||||
timers: vec![],
|
timers: vec![],
|
||||||
listeners: EventListenerCollection::default(),
|
listeners: EventListenerCollection::default(),
|
||||||
|
interaction_transform: None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -98,88 +101,14 @@ impl<S> GuiPanel<S> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<S> OverlayBackend for GuiPanel<S> {
|
impl<S> OverlayBackend for GuiPanel<S> {
|
||||||
fn set_renderer(&mut self, _: Box<dyn OverlayRenderer>) {
|
|
||||||
log::debug!("Attempted to replace renderer on GuiPanel!");
|
|
||||||
}
|
|
||||||
fn set_interaction(&mut self, _: Box<dyn InteractionHandler>) {
|
|
||||||
log::debug!("Attempted to replace interaction layer on GuiPanel!");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<S> InteractionHandler for GuiPanel<S> {
|
|
||||||
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<Haptics> {
|
|
||||||
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<S> OverlayRenderer for GuiPanel<S> {
|
|
||||||
fn init(&mut self, _app: &mut AppState) -> anyhow::Result<()> {
|
fn init(&mut self, _app: &mut AppState) -> anyhow::Result<()> {
|
||||||
if self.layout.content_size.x * self.layout.content_size.y == 0.0 {
|
if self.layout.content_size.x * self.layout.content_size.y == 0.0 {
|
||||||
self.update_layout()?;
|
self.update_layout()?;
|
||||||
|
self.interaction_transform = Some(ui_transform([
|
||||||
|
//TODO: dynamic
|
||||||
|
self.layout.content_size.x as _,
|
||||||
|
self.layout.content_size.y as _,
|
||||||
|
]));
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -253,4 +182,76 @@ impl<S> OverlayRenderer for GuiPanel<S> {
|
|||||||
..Default::default()
|
..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<Haptics> {
|
||||||
|
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<Affine2> {
|
||||||
|
self.interaction_transform
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ where
|
|||||||
positioning: Positioning::Static,
|
positioning: Positioning::Static,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
backend: Box::new(panel),
|
..OverlayData::from_backend(Box::new(panel))
|
||||||
..Default::default()
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
45
wlx-overlay-s/src/overlays/bar.rs
Normal file
@@ -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<O>(app: &mut AppState) -> anyhow::Result<OverlayData<O>>
|
||||||
|
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))
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
use std::{collections::HashMap, rc::Rc};
|
use std::{collections::HashMap, rc::Rc};
|
||||||
|
|
||||||
use glam::{Affine2, Mat4, Vec2, Vec3, vec2, vec3a};
|
use glam::{Mat4, Vec2, Vec3, vec2, vec3a};
|
||||||
use wgui::{
|
use wgui::{
|
||||||
animation::{Animation, AnimationEasing},
|
animation::{Animation, AnimationEasing},
|
||||||
drawing::Color,
|
drawing::Color,
|
||||||
@@ -260,12 +260,6 @@ where
|
|||||||
|
|
||||||
panel.layout.update(vec2(2048., 2048.), 0.0)?;
|
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;
|
let width = layout.row_size * 0.05 * app.session.config.keyboard_scale;
|
||||||
|
|
||||||
Ok(OverlayData {
|
Ok(OverlayData {
|
||||||
@@ -277,11 +271,9 @@ where
|
|||||||
interactable: true,
|
interactable: true,
|
||||||
spawn_scale: width,
|
spawn_scale: width,
|
||||||
spawn_point: vec3a(0., -0.5, 0.),
|
spawn_point: vec3a(0., -0.5, 0.),
|
||||||
interaction_transform,
|
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
backend: Box::new(KeyboardBackend { panel }),
|
..OverlayData::from_backend(Box::new(KeyboardBackend { panel }))
|
||||||
..Default::default()
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,8 +12,8 @@ use wgui::{
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
backend::{
|
backend::{
|
||||||
input::{Haptics, InteractionHandler, PointerHit},
|
input::{Haptics, PointerHit},
|
||||||
overlay::{FrameMeta, OverlayBackend, OverlayRenderer, ShouldRender},
|
overlay::{FrameMeta, OverlayBackend, ShouldRender},
|
||||||
},
|
},
|
||||||
graphics::CommandBuffers,
|
graphics::CommandBuffers,
|
||||||
gui::panel::GuiPanel,
|
gui::panel::GuiPanel,
|
||||||
@@ -32,34 +32,6 @@ struct KeyboardBackend {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl OverlayBackend for KeyboardBackend {
|
impl OverlayBackend for KeyboardBackend {
|
||||||
fn set_interaction(&mut self, interaction: Box<dyn crate::backend::input::InteractionHandler>) {
|
|
||||||
self.panel.set_interaction(interaction);
|
|
||||||
}
|
|
||||||
fn set_renderer(&mut self, renderer: Box<dyn crate::backend::overlay::OverlayRenderer>) {
|
|
||||||
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<Haptics> {
|
|
||||||
self.panel.on_hover(app, hit)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl OverlayRenderer for KeyboardBackend {
|
|
||||||
fn init(&mut self, app: &mut AppState) -> anyhow::Result<()> {
|
fn init(&mut self, app: &mut AppState) -> anyhow::Result<()> {
|
||||||
self.panel.init(app)
|
self.panel.init(app)
|
||||||
}
|
}
|
||||||
@@ -91,6 +63,26 @@ impl OverlayRenderer for KeyboardBackend {
|
|||||||
);
|
);
|
||||||
Ok(())
|
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<Haptics> {
|
||||||
|
self.panel.on_hover(app, hit)
|
||||||
|
}
|
||||||
|
fn get_interaction_transform(&mut self) -> Option<glam::Affine2> {
|
||||||
|
self.panel.get_interaction_transform()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct KeyboardState {
|
struct KeyboardState {
|
||||||
|
|||||||
@@ -4,34 +4,34 @@ use std::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use futures::{Future, FutureExt};
|
use futures::{Future, FutureExt};
|
||||||
|
use glam::Affine2;
|
||||||
use vulkano::image::view::ImageView;
|
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::{
|
use crate::{
|
||||||
backend::{
|
backend::{
|
||||||
common::OverlaySelector,
|
common::OverlaySelector,
|
||||||
overlay::{
|
input::{Haptics, PointerHit},
|
||||||
ui_transform, FrameMeta, OverlayBackend, OverlayRenderer, OverlayState, ShouldRender,
|
overlay::{FrameMeta, OverlayBackend, OverlayState, ShouldRender, ui_transform},
|
||||||
SplitOverlayBackend,
|
|
||||||
},
|
|
||||||
task::TaskType,
|
task::TaskType,
|
||||||
},
|
},
|
||||||
graphics::CommandBuffers,
|
graphics::CommandBuffers,
|
||||||
state::{AppSession, AppState},
|
state::{AppSession, AppState},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::screen::ScreenRenderer;
|
use super::screen::backend::ScreenBackend;
|
||||||
type PinnedSelectorFuture = core::pin::Pin<
|
type PinnedSelectorFuture = core::pin::Pin<
|
||||||
Box<dyn Future<Output = Result<PipewireSelectScreenResult, wlx_capture::pipewire::AshpdError>>>,
|
Box<dyn Future<Output = Result<PipewireSelectScreenResult, wlx_capture::pipewire::AshpdError>>>,
|
||||||
>;
|
>;
|
||||||
|
|
||||||
pub struct MirrorRenderer {
|
pub struct MirrorBackend {
|
||||||
name: Arc<str>,
|
name: Arc<str>,
|
||||||
renderer: Option<ScreenRenderer>,
|
renderer: Option<ScreenBackend>,
|
||||||
selector: Option<PinnedSelectorFuture>,
|
selector: Option<PinnedSelectorFuture>,
|
||||||
last_extent: [u32; 3],
|
last_extent: [u32; 3],
|
||||||
|
interaction_transform: Option<Affine2>,
|
||||||
}
|
}
|
||||||
impl MirrorRenderer {
|
impl MirrorBackend {
|
||||||
pub fn new(name: Arc<str>) -> Self {
|
pub fn new(name: Arc<str>) -> Self {
|
||||||
let selector = Box::pin(pipewire_select_screen(None, false, false, false, false));
|
let selector = Box::pin(pipewire_select_screen(None, false, false, false, false));
|
||||||
Self {
|
Self {
|
||||||
@@ -39,11 +39,12 @@ impl MirrorRenderer {
|
|||||||
renderer: None,
|
renderer: None,
|
||||||
selector: Some(selector),
|
selector: Some(selector),
|
||||||
last_extent: [0; 3],
|
last_extent: [0; 3],
|
||||||
|
interaction_transform: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl OverlayRenderer for MirrorRenderer {
|
impl OverlayBackend for MirrorBackend {
|
||||||
fn init(&mut self, _app: &mut AppState) -> anyhow::Result<()> {
|
fn init(&mut self, _app: &mut AppState) -> anyhow::Result<()> {
|
||||||
Ok(())
|
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
|
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);
|
log::info!("{}: PipeWire node selected: {}", self.name.clone(), node_id);
|
||||||
let capture = PipewireCapture::new(self.name.clone(), node_id);
|
let capture = PipewireCapture::new(self.name.clone(), node_id);
|
||||||
self.renderer = Some(ScreenRenderer::new_raw(
|
self.renderer =
|
||||||
self.name.clone(),
|
Some(ScreenBackend::new_raw(self.name.clone(), Box::new(capture)));
|
||||||
Box::new(capture),
|
|
||||||
));
|
|
||||||
app.tasks.enqueue(TaskType::Overlay(
|
app.tasks.enqueue(TaskType::Overlay(
|
||||||
OverlaySelector::Name(self.name.clone()),
|
OverlaySelector::Name(self.name.clone()),
|
||||||
Box::new(|app, o| {
|
Box::new(|app, o| {
|
||||||
@@ -106,13 +105,7 @@ impl OverlayRenderer for MirrorRenderer {
|
|||||||
let extent = meta.extent;
|
let extent = meta.extent;
|
||||||
if self.last_extent != extent {
|
if self.last_extent != extent {
|
||||||
self.last_extent = extent;
|
self.last_extent = extent;
|
||||||
// resized
|
self.interaction_transform = Some(ui_transform([extent[0], extent[1]]));
|
||||||
app.tasks.enqueue(TaskType::Overlay(
|
|
||||||
OverlaySelector::Name(self.name.clone()),
|
|
||||||
Box::new(move |_app, o| {
|
|
||||||
o.interaction_transform = ui_transform([extent[0], extent[1]]);
|
|
||||||
}),
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -133,7 +126,17 @@ impl OverlayRenderer for MirrorRenderer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn frame_meta(&mut self) -> Option<FrameMeta> {
|
fn frame_meta(&mut self) -> Option<FrameMeta> {
|
||||||
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<Haptics> {
|
||||||
|
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<Affine2> {
|
||||||
|
self.interaction_transform
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -149,10 +152,7 @@ pub fn new_mirror(
|
|||||||
spawn_scale: 0.5 * session.config.desktop_view_scale,
|
spawn_scale: 0.5 * session.config.desktop_view_scale,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
let backend = Box::new(SplitOverlayBackend {
|
let backend = Box::new(MirrorBackend::new(name));
|
||||||
renderer: Box::new(MirrorRenderer::new(name)),
|
|
||||||
..Default::default()
|
|
||||||
});
|
|
||||||
|
|
||||||
(state, backend)
|
(state, backend)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
pub mod anchor;
|
pub mod anchor;
|
||||||
|
pub mod bar;
|
||||||
pub mod custom;
|
pub mod custom;
|
||||||
pub mod keyboard;
|
pub mod keyboard;
|
||||||
#[cfg(feature = "wayland")]
|
#[cfg(feature = "wayland")]
|
||||||
|
|||||||
256
wlx-overlay-s/src/overlays/screen/backend.rs
Normal file
@@ -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<Instant> = 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<str>,
|
||||||
|
capture: Box<dyn WlxCapture<WlxCaptureIn, WlxCaptureOut>>,
|
||||||
|
pipeline: Option<ScreenPipeline>,
|
||||||
|
cur_frame: Option<WlxCaptureOut>,
|
||||||
|
meta: Option<FrameMeta>,
|
||||||
|
mouse_transform: Affine2,
|
||||||
|
interaction_transform: Option<Affine2>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ScreenBackend {
|
||||||
|
pub fn new_raw(
|
||||||
|
name: Arc<str>,
|
||||||
|
capture: Box<dyn WlxCapture<WlxCaptureIn, WlxCaptureOut>>,
|
||||||
|
) -> 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<ShouldRender> {
|
||||||
|
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<ImageView>,
|
||||||
|
buf: &mut CommandBuffers,
|
||||||
|
alpha: f32,
|
||||||
|
) -> anyhow::Result<bool> {
|
||||||
|
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<FrameMeta> {
|
||||||
|
self.meta
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_hover(&mut self, app: &mut AppState, hit: &PointerHit) -> Option<Haptics> {
|
||||||
|
#[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<Affine2> {
|
||||||
|
self.interaction_transform
|
||||||
|
}
|
||||||
|
}
|
||||||
442
wlx-overlay-s/src/overlays/screen/capture.rs
Normal file
@@ -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<Vert2Uv>,
|
||||||
|
buf_vert: Subbuffer<[Vert2Uv]>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) struct ScreenPipeline {
|
||||||
|
mouse: MousePass,
|
||||||
|
pipeline: Arc<WGfxPipeline<Vert2Uv>>,
|
||||||
|
pass: WGfxPass<Vert2Uv>,
|
||||||
|
buf_alpha: Subbuffer<[f32]>,
|
||||||
|
extentf: [f32; 2],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ScreenPipeline {
|
||||||
|
pub(super) fn new(meta: &FrameMeta, app: &mut AppState) -> anyhow::Result<Self> {
|
||||||
|
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<WGfxPipeline<Vert2Uv>>,
|
||||||
|
extentf: [f32; 2],
|
||||||
|
buf_alpha: Subbuffer<[f32]>,
|
||||||
|
) -> anyhow::Result<WGfxPass<Vert2Uv>> {
|
||||||
|
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<WGfxPipeline<Vert2Uv>>,
|
||||||
|
extentf: [f32; 2],
|
||||||
|
buf_alpha: Subbuffer<[f32]>,
|
||||||
|
) -> anyhow::Result<MousePass> {
|
||||||
|
#[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<ImageView>,
|
||||||
|
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<str>,
|
||||||
|
gfx: Arc<WGfx>,
|
||||||
|
queue: Arc<Queue>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WlxCaptureIn {
|
||||||
|
pub(super) fn new(name: Arc<str>, 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<Image>,
|
||||||
|
format: FrameFormat,
|
||||||
|
mouse: Option<MouseMeta>,
|
||||||
|
}
|
||||||
|
|
||||||
|
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<Arc<Image>> {
|
||||||
|
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<WlxCaptureOut> {
|
||||||
|
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<T>
|
||||||
|
where
|
||||||
|
T: WlxCapture<(), WlxFrame>,
|
||||||
|
{
|
||||||
|
inner: T,
|
||||||
|
data: Option<WlxCaptureIn>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> MainThreadWlxCapture<T>
|
||||||
|
where
|
||||||
|
T: WlxCapture<(), WlxFrame>,
|
||||||
|
{
|
||||||
|
pub const fn new(inner: T) -> Self {
|
||||||
|
Self { inner, data: None }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> WlxCapture<WlxCaptureIn, WlxCaptureOut> for MainThreadWlxCapture<T>
|
||||||
|
where
|
||||||
|
T: WlxCapture<(), WlxFrame>,
|
||||||
|
{
|
||||||
|
fn init(
|
||||||
|
&mut self,
|
||||||
|
dmabuf_formats: &[DrmFormat],
|
||||||
|
user_data: WlxCaptureIn,
|
||||||
|
_: fn(&WlxCaptureIn, WlxFrame) -> Option<WlxCaptureOut>,
|
||||||
|
) {
|
||||||
|
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<WlxCaptureOut> {
|
||||||
|
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<WlxFrame> {
|
||||||
|
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<dyn WlxCapture<_, _>>
|
||||||
|
} else {
|
||||||
|
Box::new($capture) as Box<dyn WlxCapture<_, _>>
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) use new_wlx_capture;
|
||||||
115
wlx-overlay-s/src/overlays/screen/mod.rs
Normal file
@@ -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<wl_output::Transform> 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<str>, 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<ScreenBackend>)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_screens(app: &mut AppState) -> anyhow::Result<(ScreenCreateData, Option<XkbKeymap>)> {
|
||||||
|
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.")
|
||||||
|
}
|
||||||
134
wlx-overlay-s/src/overlays/screen/pw.rs
Normal file
@@ -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<String> /* 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<PipewireSelectScreenResult, wlx_capture::pipewire::AshpdError> {
|
||||||
|
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<dyn Error>> {
|
||||||
|
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<PwTokenMap, Box<dyn Error>> {
|
||||||
|
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)
|
||||||
|
}
|
||||||
164
wlx-overlay-s/src/overlays/screen/wl.rs
Normal file
@@ -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<Self> {
|
||||||
|
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<Self> {
|
||||||
|
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<ScreenBackend> {
|
||||||
|
let mut capture: Option<ScreenBackend> = 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 }
|
||||||
|
}
|
||||||
209
wlx-overlay-s/src/overlays/screen/x11.rs
Normal file
@@ -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<XshmScreen>, 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<ScreenCreateData> {
|
||||||
|
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<Item = &'a XshmScreen>,
|
||||||
|
) -> 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<ScreenCreateData> {
|
||||||
|
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 })
|
||||||
|
}
|
||||||
0
wlx-overlay-s/src/overlays/tooltip.rs
Normal file
@@ -10,7 +10,7 @@ use wgui::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
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},
|
gui::{panel::GuiPanel, timer::GuiTimer},
|
||||||
state::AppState,
|
state::AppState,
|
||||||
};
|
};
|
||||||
@@ -113,12 +113,10 @@ where
|
|||||||
spawn_scale: 0.115, //TODO:configurable
|
spawn_scale: 0.115, //TODO:configurable
|
||||||
spawn_point: app.session.config.watch_pos,
|
spawn_point: app.session.config.watch_pos,
|
||||||
spawn_rotation: app.session.config.watch_rot,
|
spawn_rotation: app.session.config.watch_rot,
|
||||||
interaction_transform: ui_transform([400, 200]),
|
|
||||||
positioning,
|
positioning,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
backend: Box::new(panel),
|
..OverlayData::from_backend(Box::new(panel))
|
||||||
..Default::default()
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,10 +14,10 @@ use wlx_capture::frame::{DmabufFrame, FourCC, FrameFormat, FramePlane};
|
|||||||
use crate::{
|
use crate::{
|
||||||
backend::{
|
backend::{
|
||||||
common::{OverlayContainer, OverlaySelector},
|
common::{OverlayContainer, OverlaySelector},
|
||||||
input::{self, InteractionHandler},
|
input::{self},
|
||||||
overlay::{
|
overlay::{
|
||||||
FrameMeta, OverlayData, OverlayID, OverlayRenderer, OverlayState, ShouldRender,
|
FrameMeta, OverlayBackend, OverlayData, OverlayID, OverlayState, ShouldRender,
|
||||||
SplitOverlayBackend, Z_ORDER_DASHBOARD, ui_transform,
|
Z_ORDER_DASHBOARD, ui_transform,
|
||||||
},
|
},
|
||||||
task::TaskType,
|
task::TaskType,
|
||||||
wayvr::{
|
wayvr::{
|
||||||
@@ -98,91 +98,12 @@ impl WayVRData {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct WayVRInteractionHandler {
|
|
||||||
context: Rc<RefCell<WayVRContext>>,
|
|
||||||
mouse_transform: Affine2,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl WayVRInteractionHandler {
|
|
||||||
pub const fn new(context: Rc<RefCell<WayVRContext>>, 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<input::Haptics> {
|
|
||||||
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 {
|
struct ImageData {
|
||||||
vk_image: Arc<Image>,
|
vk_image: Arc<Image>,
|
||||||
vk_image_view: Arc<ImageView>,
|
vk_image_view: Arc<ImageView>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct WayVRRenderer {
|
pub struct WayVRBackend {
|
||||||
pipeline: Arc<WGfxPipeline<Vert2Uv>>,
|
pipeline: Arc<WGfxPipeline<Vert2Uv>>,
|
||||||
pass: WGfxPass<Vert2Uv>,
|
pass: WGfxPass<Vert2Uv>,
|
||||||
buf_alpha: Subbuffer<[f32]>,
|
buf_alpha: Subbuffer<[f32]>,
|
||||||
@@ -190,9 +111,11 @@ pub struct WayVRRenderer {
|
|||||||
context: Rc<RefCell<WayVRContext>>,
|
context: Rc<RefCell<WayVRContext>>,
|
||||||
graphics: Arc<WGfx>,
|
graphics: Arc<WGfx>,
|
||||||
resolution: [u16; 2],
|
resolution: [u16; 2],
|
||||||
|
mouse_transform: Affine2,
|
||||||
|
interaction_transform: Option<Affine2>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WayVRRenderer {
|
impl WayVRBackend {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
app: &state::AppState,
|
app: &state::AppState,
|
||||||
wvr: Rc<RefCell<WayVRData>>,
|
wvr: Rc<RefCell<WayVRData>>,
|
||||||
@@ -234,6 +157,8 @@ impl WayVRRenderer {
|
|||||||
graphics: app.gfx.clone(),
|
graphics: app.gfx.clone(),
|
||||||
image: None,
|
image: None,
|
||||||
resolution,
|
resolution,
|
||||||
|
mouse_transform: Affine2::IDENTITY,
|
||||||
|
interaction_transform: Some(ui_transform([resolution[0] as _, resolution[1] as _])), //TODO:dynamic
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -592,7 +517,7 @@ where
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WayVRRenderer {
|
impl WayVRBackend {
|
||||||
fn ensure_software_data(
|
fn ensure_software_data(
|
||||||
&mut self,
|
&mut self,
|
||||||
data: &wayvr::egl_data::RenderSoftwarePixelsData,
|
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<()> {
|
fn init(&mut self, _app: &mut state::AppState) -> anyhow::Result<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -772,6 +697,73 @@ impl OverlayRenderer for WayVRRenderer {
|
|||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn on_hover(
|
||||||
|
&mut self,
|
||||||
|
_app: &mut state::AppState,
|
||||||
|
hit: &input::PointerHit,
|
||||||
|
) -> Option<input::Haptics> {
|
||||||
|
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<Affine2> {
|
||||||
|
self.interaction_transform
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
@@ -786,8 +778,6 @@ pub fn create_wayvr_display_overlay<O>(
|
|||||||
where
|
where
|
||||||
O: Default,
|
O: Default,
|
||||||
{
|
{
|
||||||
let transform = ui_transform([u32::from(display_width), u32::from(display_height)]);
|
|
||||||
|
|
||||||
let state = OverlayState {
|
let state = OverlayState {
|
||||||
name: format!("WayVR - {name}").into(),
|
name: format!("WayVR - {name}").into(),
|
||||||
keyboard_focus: Some(KeyboardFocus::WayVR),
|
keyboard_focus: Some(KeyboardFocus::WayVR),
|
||||||
@@ -796,24 +786,21 @@ where
|
|||||||
grabbable: true,
|
grabbable: true,
|
||||||
spawn_scale: display_scale,
|
spawn_scale: display_scale,
|
||||||
spawn_point: vec3a(0.0, -0.1, -1.0),
|
spawn_point: vec3a(0.0, -0.1, -1.0),
|
||||||
interaction_transform: transform,
|
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
let wayvr = app.get_wayvr()?;
|
let wayvr = app.get_wayvr()?;
|
||||||
|
|
||||||
let renderer = WayVRRenderer::new(app, wayvr, display_handle, [display_width, display_height])?;
|
let backend = Box::new(WayVRBackend::new(
|
||||||
let context = renderer.context.clone();
|
app,
|
||||||
|
wayvr,
|
||||||
let backend = Box::new(SplitOverlayBackend {
|
display_handle,
|
||||||
renderer: Box::new(renderer),
|
[display_width, display_height],
|
||||||
interaction: Box::new(WayVRInteractionHandler::new(context, Affine2::IDENTITY)),
|
)?);
|
||||||
});
|
|
||||||
|
|
||||||
Ok(OverlayData {
|
Ok(OverlayData {
|
||||||
state,
|
state,
|
||||||
backend,
|
..OverlayData::from_backend(backend)
|
||||||
..Default::default()
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ use idmap::IdMap;
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use smallvec::{SmallVec, smallvec};
|
use smallvec::{SmallVec, smallvec};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use vulkano::image::view::ImageView;
|
|
||||||
use wgui::{gfx::WGfx, renderer_vk::context::SharedContext as WSharedContext};
|
use wgui::{gfx::WGfx, renderer_vk::context::SharedContext as WSharedContext};
|
||||||
|
|
||||||
#[cfg(feature = "wayvr")]
|
#[cfg(feature = "wayvr")]
|
||||||
@@ -18,7 +17,7 @@ use crate::subsystem::osc::OscSender;
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
backend::{input::InputState, overlay::OverlayID, task::TaskContainer},
|
backend::{input::InputState, overlay::OverlayID, task::TaskContainer},
|
||||||
config::{AStrMap, GeneralConfig},
|
config::GeneralConfig,
|
||||||
config_io,
|
config_io,
|
||||||
graphics::WGfxExtras,
|
graphics::WGfxExtras,
|
||||||
overlays::toast::{DisplayMethod, ToastTopic},
|
overlays::toast::{DisplayMethod, ToastTopic},
|
||||||
@@ -39,7 +38,6 @@ pub struct AppState {
|
|||||||
pub input_state: InputState,
|
pub input_state: InputState,
|
||||||
pub screens: SmallVec<[ScreenMeta; 8]>,
|
pub screens: SmallVec<[ScreenMeta; 8]>,
|
||||||
pub anchor: Affine3A,
|
pub anchor: Affine3A,
|
||||||
pub sprites: AStrMap<Arc<ImageView>>,
|
|
||||||
pub toast_sound: &'static [u8],
|
pub toast_sound: &'static [u8],
|
||||||
|
|
||||||
#[cfg(feature = "osc")]
|
#[cfg(feature = "osc")]
|
||||||
@@ -86,7 +84,6 @@ impl AppState {
|
|||||||
input_state: InputState::new(),
|
input_state: InputState::new(),
|
||||||
screens: smallvec![],
|
screens: smallvec![],
|
||||||
anchor: Affine3A::IDENTITY,
|
anchor: Affine3A::IDENTITY,
|
||||||
sprites: AStrMap::new(),
|
|
||||||
toast_sound: toast_sound_wav,
|
toast_sound: toast_sound_wav,
|
||||||
|
|
||||||
#[cfg(feature = "osc")]
|
#[cfg(feature = "osc")]
|
||||||
|
|||||||