feat(windows): Implement Windows DXGI Desktop Duplication capture and packaging definitions (AUR, RPM, Flatpak, GitHub Actions)

This commit is contained in:
katR 2026-09-03 07:45:45 +05:30
parent 45eaf2840d
commit 0d73a17815
12 changed files with 547 additions and 48 deletions

127
.github/workflows/release.yml vendored Normal file
View file

@ -0,0 +1,127 @@
name: Release & Packaging
on:
push:
tags:
- 'v*'
workflow_dispatch:
permissions:
contents: write
jobs:
build-linux:
name: Build Linux Binaries & Packages
runs-on: ubuntu-24.04
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Install System Dependencies
run: |
sudo apt-get update
sudo apt-get install -y \
pkg-config clang libva-dev \
libavcodec-dev libavformat-dev libavutil-dev libswresample-dev libswscale-dev \
libpipewire-0.3-dev libgtk-3-dev libgtk-layer-shell-dev libxcb1-dev libasound2-dev
- name: Setup Rust toolchain
uses: dtolnay/rust-toolchain@stable
- name: Build Release Binaries
run: cargo build --release
- name: Package Linux Tarball
run: |
mkdir -p dist/vrec-linux-x86_64
cp target/release/vrec-daemon target/release/vrec-ui dist/vrec-linux-x86_64/
cp packaging/vrec.desktop dist/vrec-linux-x86_64/
cp README.md dist/vrec-linux-x86_64/ 2>/dev/null || true
tar -czvf dist/vrec-linux-x86_64.tar.gz -C dist vrec-linux-x86_64
- name: Package Standalone AppImage
run: |
mkdir -p AppDir/usr/bin AppDir/usr/share/applications AppDir/usr/share/icons/hicolor/scalable/apps
cp target/release/vrec-ui AppDir/usr/bin/
cp target/release/vrec-daemon AppDir/usr/bin/
cp packaging/vrec.desktop AppDir/usr/share/applications/
# Create AppRun launcher
cat > AppDir/AppRun << 'EOF'
#!/bin/sh
HERE="$(dirname "$(readlink -f "${0}")")"
exec "${HERE}/usr/bin/vrec-ui" "$@"
EOF
chmod +x AppDir/AppRun
# Generate dummy icon
touch AppDir/usr/share/icons/hicolor/scalable/apps/media-record.svg
touch AppDir/media-record.svg
ln -s usr/share/applications/vrec.desktop AppDir/vrec.desktop
# Download appimagetool and build
wget -q https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-x86_64.AppImage -O appimagetool
chmod +x appimagetool
./appimagetool --appimage-extract-and-run AppDir dist/vrec-x86_64.AppImage || true
- name: Upload Linux Artifacts
uses: actions/upload-artifact@v4
with:
name: linux-packages
path: |
dist/*.tar.gz
dist/*.AppImage
packaging/aur/PKGBUILD
packaging/rpm/vrec.spec
packaging/flatpak/com.vrec.Vrec.yaml
build-windows:
name: Build Windows Binaries (.exe)
runs-on: windows-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup Rust toolchain
uses: dtolnay/rust-toolchain@stable
- name: Build Release Binaries (Windows)
run: cargo build --release
- name: Package Windows Zip
shell: pwsh
run: |
New-Item -ItemType Directory -Force -Path dist/vrec-windows-x64
Copy-Item target/release/vrec-daemon.exe dist/vrec-windows-x64/
Copy-Item target/release/vrec-ui.exe dist/vrec-windows-x64/
Compress-Archive -Path dist/vrec-windows-x64 -DestinationPath dist/vrec-windows-x64.zip
- name: Upload Windows Artifacts
uses: actions/upload-artifact@v4
with:
name: windows-package
path: dist/vrec-windows-x64.zip
publish-release:
name: Publish GitHub Release
needs: [build-linux, build-windows]
if: startsWith(github.ref, 'refs/tags/v')
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Download all built artifacts
uses: actions/download-artifact@v4
with:
path: artifacts
- name: Create GitHub Release
uses: softprops/action-gh-release@v2
with:
files: |
artifacts/linux-packages/*
artifacts/windows-package/*
draft: false
prerelease: false
generate_release_notes: true

1
Cargo.lock generated
View file

@ -2321,6 +2321,7 @@ dependencies = [
"serde",
"serde_json",
"tokio",
"windows",
"xcb",
]

View file

@ -4,24 +4,38 @@ version = "0.1.0"
edition = "2024"
[dependencies]
ashpd = { version = "0.13.13", features = ["screencast"] }
crossbeam-channel = "0.5.16"
ffmpeg-next = "9.0.0"
ffmpeg-sys-next = "9.0.0"
gdk = "0.18.2"
glib = "0.22.9"
global-hotkey = "0.8.0"
gtk-layer-shell = "0.8.2"
gtk = { version = "0.18.2", package = "gtk" }
libc = "0.2.189"
memmap2 = "0.9.11"
mp4 = "0.14.0"
pipewire = "0.10.1"
ringbuf = "0.5.1"
serde = { version = "1.0.229", features = ["derive"] }
serde_json = "1.0.151"
tokio = { version = "1.53.1", features = ["rt", "macros", "rt-multi-thread"] }
xcb = { version = "1.7.1", features = ["shm", "xfixes"] }
dirs = "6.0.0"
cpal = "0.18.2"
rubato = "5.0.0"
[target.'cfg(target_os = "linux")'.dependencies]
ashpd = { version = "0.13.13", features = ["screencast"] }
gtk-layer-shell = "0.8.2"
pipewire = "0.10.1"
xcb = { version = "1.7.1", features = ["shm", "xfixes"] }
memmap2 = "0.9.11"
[target.'cfg(target_os = "windows")'.dependencies]
windows = { version = "0.62.2", features = [
"Win32_Foundation",
"Win32_Graphics_Direct3D",
"Win32_Graphics_Direct3D11",
"Win32_Graphics_Dxgi",
"Win32_Graphics_Dxgi_Common",
"Win32_System_Com",
"Win32_UI_WindowsAndMessaging",
"Win32_System_Threading",
] }

31
packaging/aur/PKGBUILD Normal file
View file

@ -0,0 +1,31 @@
# Maintainer: oiupoyt
pkgname=vrec-git
pkgver=0.1.0
pkgrel=1
pkgdesc="High-performance GPU hardware screen recorder and instant replay overlay for Wayland and X11"
arch=('x86_64')
url="https://github.com/oiupoyt/vrec"
license=('MIT' 'Apache-2.0')
depends=('ffmpeg' 'pipewire' 'gtk3' 'gtk-layer-shell' 'libpulse' 'libva' 'libxcb')
makedepends=('cargo' 'git' 'pkgconf' 'clang')
provides=('vrec')
conflicts=('vrec')
source=("git+https://github.com/oiupoyt/vrec.git")
sha256sums=('SKIP')
pkgver() {
cd "$srcdir/vrec"
git describe --long --tags 2>/dev/null | sed 's/^v//;s/\([^-]*-g\)/r\1/;s/-/./g' || echo "0.1.0"
}
build() {
cd "$srcdir/vrec"
cargo build --release --locked
}
package() {
cd "$srcdir/vrec"
install -Dm755 target/release/vrec-daemon "$pkgdir/usr/bin/vrec-daemon"
install -Dm755 target/release/vrec-ui "$pkgdir/usr/bin/vrec-ui"
install -Dm644 packaging/vrec.desktop "$pkgdir/usr/share/applications/vrec.desktop"
}

View file

@ -0,0 +1,35 @@
app-id: com.vrec.Vrec
runtime: org.gnome.Platform
runtime-version: '46'
sdk: org.gnome.Sdk
sdk-extensions:
- org.freedesktop.Sdk.Extension.rust-stable
command: vrec-ui
finish-args:
- --share=ipc
- --socket=fallback-x11
- --socket=wayland
- --socket=pulseaudio
- --filesystem=xdg-videos
- --filesystem=xdg-run/pipewire-0:ro
- --device=all
- --talk-name=org.freedesktop.portal.Desktop
- --talk-name=org.freedesktop.portal.ScreenCast
build-options:
append-path: /usr/lib/sdk/rust-stable/bin
env:
CARGO_HOME: /run/build/vrec/cargo
modules:
- name: vrec
buildsystem: simple
build-commands:
- cargo build --release --verbose
- install -Dm755 target/release/vrec-daemon /app/bin/vrec-daemon
- install -Dm755 target/release/vrec-ui /app/bin/vrec-ui
- install -Dm644 packaging/vrec.desktop /app/share/applications/com.vrec.Vrec.desktop
sources:
- type: git
url: https://github.com/oiupoyt/vrec.git
branch: main

46
packaging/rpm/vrec.spec Normal file
View file

@ -0,0 +1,46 @@
Name: vrec
Version: 0.1.0
Release: 1%{?dist}
Summary: High-performance GPU hardware screen recorder and instant replay overlay
License: MIT
URL: https://github.com/oiupoyt/vrec
Source0: %{name}-%{version}.tar.gz
BuildRequires: cargo
BuildRequires: rust
BuildRequires: ffmpeg-devel
BuildRequires: pipewire-devel
BuildRequires: gtk3-devel
BuildRequires: gtk-layer-shell-devel
BuildRequires: libxcb-devel
BuildRequires: clang-devel
Requires: ffmpeg-free
Requires: pipewire
Requires: gtk3
Requires: gtk-layer-shell
%description
High-performance GPU hardware screen recorder and instant replay overlay for Wayland and X11.
%prep
%autosetup
%build
cargo build --release
%install
rm -rf $RPM_BUILD_ROOT
install -D -m 0755 target/release/vrec-daemon %{buildroot}%{_bindir}/vrec-daemon
install -D -m 0755 target/release/vrec-ui %{buildroot}%{_bindir}/vrec-ui
install -D -m 0644 packaging/vrec.desktop %{buildroot}%{_datadir}/applications/vrec.desktop
%files
%{_bindir}/vrec-daemon
%{_bindir}/vrec-ui
%{_datadir}/applications/vrec.desktop
%changelog
* Wed Sep 02 2026 oiupoyt - 0.1.0-1
- Initial release of vrec

12
packaging/vrec.desktop Normal file
View file

@ -0,0 +1,12 @@
[Desktop Entry]
Type=Application
Name=vrec Screen Recorder
GenericName=Screen Recorder & Instant Replay
Comment=High-performance GPU hardware screen recorder and instant replay overlay
Exec=vrec-ui --menu
Icon=media-record
Terminal=false
Categories=AudioVideo;Recorder;
Keywords=screen;recorder;replay;capture;game;shadowplay;
StartupNotify=false
EOF

View file

@ -8,7 +8,6 @@ use ringbuf::traits::{RingBuffer, Consumer, Observer};
use crossbeam_channel::bounded;
use std::thread;
use std::env;
use std::os::unix::net::UnixListener;
use std::io::{Read, Write};
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use std::sync::Arc;
@ -21,12 +20,36 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let mut source: Box<dyn FrameSource> = if std::env::args().any(|a| a == "--mock") {
println!("Initializing MOCK capture...");
Box::new(vrec::capture::mock::MockCapture::new())
} else if cfg!(target_os = "windows") {
#[cfg(target_os = "windows")]
{
println!("Initializing Windows DXGI capture...");
Box::new(vrec::capture::windows::WindowsCapture::new()?)
}
#[cfg(not(target_os = "windows"))]
{
unreachable!()
}
} else if session_type.to_lowercase() == "wayland" {
println!("Initializing Wayland capture...");
Box::new(vrec::capture::wayland::WaylandCapture::new().await?)
#[cfg(target_os = "linux")]
{
println!("Initializing Wayland capture...");
Box::new(vrec::capture::wayland::WaylandCapture::new().await?)
}
#[cfg(not(target_os = "linux"))]
{
unreachable!()
}
} else {
println!("Initializing X11 capture...");
Box::new(vrec::capture::x11::X11Capture::new()?)
#[cfg(target_os = "linux")]
{
println!("Initializing X11 capture...");
Box::new(vrec::capture::x11::X11Capture::new()?)
}
#[cfg(not(target_os = "linux"))]
{
unreachable!()
}
};
let first_frame = source.next_frame()?;
@ -235,10 +258,21 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
}
});
let socket_path = format!("{}/vrec.sock", env::var("XDG_RUNTIME_DIR").unwrap_or_else(|_| "/tmp".to_string()));
let _ = std::fs::remove_file(&socket_path);
let listener = UnixListener::bind(&socket_path)?;
println!("Daemon listening on IPC socket: {}", socket_path);
#[cfg(unix)]
let listener = {
let socket_path = format!("{}/vrec.sock", env::var("XDG_RUNTIME_DIR").unwrap_or_else(|_| "/tmp".to_string()));
let _ = std::fs::remove_file(&socket_path);
let l = std::os::unix::net::UnixListener::bind(&socket_path)?;
println!("Daemon listening on IPC socket: {}", socket_path);
l
};
#[cfg(windows)]
let listener = {
let l = std::net::TcpListener::bind("127.0.0.1:42069")?;
println!("Daemon listening on TCP IPC: 127.0.0.1:42069");
l
};
for stream in listener.incoming() {
match stream {

View file

@ -1,23 +1,30 @@
use std::os::unix::io::RawFd;
#[cfg(unix)]
pub type NativeFd = std::os::unix::io::RawFd;
#[cfg(not(unix))]
pub type NativeFd = i32;
#[cfg(target_os = "linux")]
pub mod wayland;
#[cfg(target_os = "linux")]
pub mod wayland_stream;
#[cfg(target_os = "linux")]
pub mod x11;
#[cfg(target_os = "windows")]
pub mod windows;
#[derive(Debug)]
pub enum Frame {
/// Zero-copy DMA-BUF file descriptor. Used by Wayland/PipeWire.
/// Zero-copy DMA-BUF file descriptor. Used by Wayland/PipeWire on Linux.
DmaBuf {
width: u32,
height: u32,
format: u32, modifier: u64,
fd: RawFd,
format: u32,
modifier: u64,
fd: NativeFd,
stride: u32,
offset: u32,
},
/// Raw memory buffer. Used by X11 fallback.
/// Raw memory buffer. Used by X11, Windows fallback, and mock capture.
Raw {
width: u32,
height: u32,
@ -27,12 +34,13 @@ pub enum Frame {
/// Zero-copy DirectX 11 Texture handle. Used by Windows DXGI capture.
#[cfg(target_os = "windows")]
D3D11Texture {
handle: usize, // Placeholder for ID3D11Texture2D raw pointer
handle: usize,
},
}
pub trait FrameSource: Send {
fn next_frame(&mut self) -> Result<Frame, Box<dyn std::error::Error + Send + Sync>>;
}
pub mod mock;
pub mod audio;

View file

@ -1,8 +1,21 @@
#[cfg(target_os = "windows")]
use windows::{
core::Interface,
Win32::Foundation::*,
Win32::Graphics::Direct3D::*,
Win32::Graphics::Direct3D11::*,
Win32::Graphics::Dxgi::Common::*,
Win32::Graphics::Dxgi::*,
Win32::System::Com::*,
};
use crate::capture::{Frame, FrameSource};
#[cfg(target_os = "windows")]
pub struct WindowsCapture {
device: ID3D11Device,
context: ID3D11DeviceContext,
duplication: IDXGIOutputDuplication,
staging_texture: ID3D11Texture2D,
width: u32,
height: u32,
}
@ -10,18 +23,149 @@ pub struct WindowsCapture {
#[cfg(target_os = "windows")]
impl WindowsCapture {
pub fn new() -> Result<Self, Box<dyn std::error::Error + Send + Sync>> {
// Mock DXGI Desktop Duplication initialization
Ok(Self {
width: 1920,
height: 1080,
})
unsafe {
// Initialize COM library on capture thread
let _ = CoInitializeEx(None, COINIT_MULTITHREADED);
// Create D3D11 hardware device
let mut device: Option<ID3D11Device> = None;
let mut context: Option<ID3D11DeviceContext> = None;
let mut feature_level = D3D_FEATURE_LEVEL_11_0;
D3D11CreateDevice(
None,
D3D_DRIVER_TYPE_HARDWARE,
None,
D3D11_CREATE_DEVICE_BGRA_SUPPORT,
Some(&[D3D_FEATURE_LEVEL_11_1, D3D_FEATURE_LEVEL_11_0]),
D3D11_SDK_VERSION,
Some(&mut device),
Some(&mut feature_level),
Some(&mut context),
)?;
let device = device.ok_or("Failed to create D3D11 device")?;
let context = context.ok_or("Failed to create D3D11 context")?;
// Retrieve DXGI Output for the primary display
let dxgi_device: IDXGIDevice = device.cast()?;
let adapter = dxgi_device.GetAdapter()?;
let output = adapter.EnumOutputs(0)?;
let output1: IDXGIOutput1 = output.cast()?;
let desc = output.GetDesc()?;
let width = (desc.DesktopCoordinates.right - desc.DesktopCoordinates.left) as u32;
let height = (desc.DesktopCoordinates.bottom - desc.DesktopCoordinates.top) as u32;
// Initialize Desktop Duplication
let duplication = output1.DuplicateOutput(&device)?;
// Allocate a staging texture for CPU mapping fallback
let staging_desc = D3D11_TEXTURE2D_DESC {
Width: width,
Height: height,
MipLevels: 1,
ArraySize: 1,
Format: DXGI_FORMAT_B8G8R8A8_UNORM,
SampleDesc: DXGI_SAMPLE_DESC { Count: 1, Quality: 0 },
Usage: D3D11_USAGE_STAGING,
BindFlags: 0,
CPUAccessFlags: D3D11_CPU_ACCESS_READ.0 as u32,
MiscFlags: 0,
};
let mut staging_texture: Option<ID3D11Texture2D> = None;
device.CreateTexture2D(&staging_desc, None, Some(&mut staging_texture))?;
let staging_texture = staging_texture.ok_or("Failed to create staging texture")?;
println!("Windows DXGI Desktop Duplication active: {}x{}", width, height);
Ok(Self {
device,
context,
duplication,
staging_texture,
width,
height,
})
}
}
fn reinit_duplication(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
unsafe {
let dxgi_device: IDXGIDevice = self.device.cast()?;
let adapter = dxgi_device.GetAdapter()?;
let output = adapter.EnumOutputs(0)?;
let output1: IDXGIOutput1 = output.cast()?;
self.duplication = output1.DuplicateOutput(&self.device)?;
Ok(())
}
}
}
#[cfg(target_os = "windows")]
impl FrameSource for WindowsCapture {
fn next_frame(&mut self) -> Result<Frame, Box<dyn std::error::Error + Send + Sync>> {
// DXGI Desktop Duplication API capture loop goes here
Ok(Frame::D3D11Texture { handle: 0 })
unsafe {
let mut frame_info = DXGI_OUTDUPL_FRAME_INFO::default();
let mut desktop_resource: Option<IDXGIResource> = None;
for _ in 0..5 {
match self.duplication.AcquireNextFrame(150, &mut frame_info, &mut desktop_resource) {
Ok(()) => {
if let Some(resource) = desktop_resource {
let texture: ID3D11Texture2D = resource.cast()?;
// Copy to CPU staging texture
self.context.CopyResource(&self.staging_texture, &texture);
let _ = self.duplication.ReleaseFrame();
let mut mapped = D3D11_MAPPED_SUBRESOURCE::default();
self.context.Map(&self.staging_texture, 0, D3D11_MAP_READ, 0, Some(&mut mapped))?;
let row_pitch = mapped.RowPitch as usize;
let mut data = vec![0u8; (self.width * self.height * 4) as usize];
let src_ptr = mapped.pData as *const u8;
for y in 0..(self.height as usize) {
let src_row = std::slice::from_raw_parts(src_ptr.add(y * row_pitch), (self.width * 4) as usize);
let dst_offset = y * (self.width * 4) as usize;
data[dst_offset..dst_offset + (self.width * 4) as usize].copy_from_slice(src_row);
}
self.context.Unmap(&self.staging_texture, 0);
return Ok(Frame::Raw {
width: self.width,
height: self.height,
stride: self.width * 4,
data,
});
}
let _ = self.duplication.ReleaseFrame();
}
Err(e) if e.code() == DXGI_ERROR_WAIT_TIMEOUT => {
std::thread::sleep(std::time::Duration::from_millis(8));
continue;
}
Err(e) if e.code() == DXGI_ERROR_ACCESS_LOST => {
println!("DXGI access lost (display mode or fullscreen switch), reacquiring...");
let _ = self.reinit_duplication();
std::thread::sleep(std::time::Duration::from_millis(50));
continue;
}
Err(e) => {
return Err(Box::new(e));
}
}
}
Ok(Frame::Raw {
width: self.width,
height: self.height,
stride: self.width * 4,
data: vec![0u8; (self.width * self.height * 4) as usize],
})
}
}
}

View file

@ -22,28 +22,50 @@ pub struct DaemonStatus {
}
pub fn send_command(cmd: Command) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
use std::env;
use std::os::unix::net::UnixStream;
use std::io::Write;
let socket_path = format!("{}/vrec.sock", env::var("XDG_RUNTIME_DIR").unwrap_or_else(|_| "/tmp".to_string()));
let mut stream = UnixStream::connect(&socket_path)?;
let payload = serde_json::to_vec(&cmd)?;
let len_buf = (payload.len() as u32).to_le_bytes();
stream.write_all(&len_buf)?;
stream.write_all(&payload)?;
#[cfg(unix)]
{
use std::os::unix::net::UnixStream;
let socket_path = format!("{}/vrec.sock", std::env::var("XDG_RUNTIME_DIR").unwrap_or_else(|_| "/tmp".to_string()));
let mut stream = UnixStream::connect(&socket_path)?;
stream.write_all(&len_buf)?;
stream.write_all(&payload)?;
}
#[cfg(windows)]
{
use std::net::TcpStream;
let mut stream = TcpStream::connect("127.0.0.1:42069")?;
stream.write_all(&len_buf)?;
stream.write_all(&payload)?;
}
Ok(())
}
pub fn query_status() -> Result<DaemonStatus, Box<dyn std::error::Error + Send + Sync>> {
use std::env;
use std::os::unix::net::UnixStream;
use std::io::{Read, Write};
let socket_path = format!("{}/vrec.sock", env::var("XDG_RUNTIME_DIR").unwrap_or_else(|_| "/tmp".to_string()));
let mut stream = UnixStream::connect(&socket_path)?;
let payload = serde_json::to_vec(&Command::GetStatus)?;
let len_buf = (payload.len() as u32).to_le_bytes();
#[cfg(unix)]
let mut stream = {
use std::os::unix::net::UnixStream;
let socket_path = format!("{}/vrec.sock", std::env::var("XDG_RUNTIME_DIR").unwrap_or_else(|_| "/tmp".to_string()));
UnixStream::connect(&socket_path)?
};
#[cfg(windows)]
let mut stream = {
use std::net::TcpStream;
TcpStream::connect("127.0.0.1:42069")?
};
stream.write_all(&len_buf)?;
stream.write_all(&payload)?;

View file

@ -3,6 +3,7 @@ use gtk::{
Application, ApplicationWindow, Box, Button, ComboBoxText, CssProvider, Entry, Label,
Orientation, Stack, StackTransitionType, StyleContext, Switch,
};
#[cfg(target_os = "linux")]
use gtk_layer_shell::{Layer, LayerShell};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
@ -11,6 +12,7 @@ use crate::config::VrecConfig;
use crate::ipc::{self, Command};
pub fn show_notification_overlay() {
#[cfg(unix)]
if std::env::var("WAYLAND_DISPLAY").is_err() && std::env::var("DISPLAY").is_err() {
return;
}
@ -29,11 +31,22 @@ pub fn show_notification_overlay() {
.default_height(70)
.build();
window.init_layer_shell();
window.set_layer(Layer::Overlay);
window.set_namespace("vrec-notification");
window.set_layer_shell_margin(gtk_layer_shell::Edge::Top, 30);
window.set_anchor(gtk_layer_shell::Edge::Top, true);
#[cfg(target_os = "linux")]
{
window.init_layer_shell();
window.set_layer(Layer::Overlay);
window.set_namespace("vrec-notification");
window.set_layer_shell_margin(gtk_layer_shell::Edge::Top, 30);
window.set_anchor(gtk_layer_shell::Edge::Top, true);
}
#[cfg(not(target_os = "linux"))]
{
window.set_decorated(false);
window.set_keep_above(true);
window.set_skip_taskbar_hint(true);
window.set_position(gtk::WindowPosition::Center);
}
let css_provider = CssProvider::new();
let css = r#"
@ -74,6 +87,7 @@ pub fn show_notification_overlay() {
}
pub fn show_menu_overlay() {
#[cfg(unix)]
if std::env::var("WAYLAND_DISPLAY").is_err() && std::env::var("DISPLAY").is_err() {
eprintln!("Error: No display server detected (WAYLAND_DISPLAY and DISPLAY are unset).");
return;
@ -96,12 +110,23 @@ pub fn show_menu_overlay() {
.default_height(220)
.build();
window.init_layer_shell();
window.set_layer(Layer::Overlay);
window.set_namespace("vrec-hud");
window.set_keyboard_interactivity(true);
window.set_layer_shell_margin(gtk_layer_shell::Edge::Top, 70);
window.set_anchor(gtk_layer_shell::Edge::Top, true);
#[cfg(target_os = "linux")]
{
window.init_layer_shell();
window.set_layer(Layer::Overlay);
window.set_namespace("vrec-hud");
window.set_keyboard_interactivity(true);
window.set_layer_shell_margin(gtk_layer_shell::Edge::Top, 70);
window.set_anchor(gtk_layer_shell::Edge::Top, true);
}
#[cfg(not(target_os = "linux"))]
{
window.set_decorated(false);
window.set_keep_above(true);
window.set_skip_taskbar_hint(true);
window.set_position(gtk::WindowPosition::Center);
}
// Main Stack (Allows toggling between HUD cards and Inline Settings with NO extra windows!)
let stack = Stack::new();