From 0d73a17815c097429aa606cb9612efec749a8c45 Mon Sep 17 00:00:00 2001 From: oiupoyt Date: Thu, 3 Sep 2026 07:45:45 +0530 Subject: [PATCH] feat(windows): Implement Windows DXGI Desktop Duplication capture and packaging definitions (AUR, RPM, Flatpak, GitHub Actions) --- .github/workflows/release.yml | 127 +++++++++++++++++++++ Cargo.lock | 1 + Cargo.toml | 24 +++- packaging/aur/PKGBUILD | 31 ++++++ packaging/flatpak/com.vrec.Vrec.yaml | 35 ++++++ packaging/rpm/vrec.spec | 46 ++++++++ packaging/vrec.desktop | 12 ++ src/bin/vrec-daemon.rs | 52 +++++++-- src/capture/mod.rs | 20 +++- src/capture/windows.rs | 158 +++++++++++++++++++++++++-- src/ipc.rs | 42 +++++-- src/overlay.rs | 47 ++++++-- 12 files changed, 547 insertions(+), 48 deletions(-) create mode 100644 .github/workflows/release.yml create mode 100644 packaging/aur/PKGBUILD create mode 100644 packaging/flatpak/com.vrec.Vrec.yaml create mode 100644 packaging/rpm/vrec.spec create mode 100644 packaging/vrec.desktop diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..3b7884c --- /dev/null +++ b/.github/workflows/release.yml @@ -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 diff --git a/Cargo.lock b/Cargo.lock index 7178797..49a6d02 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2321,6 +2321,7 @@ dependencies = [ "serde", "serde_json", "tokio", + "windows", "xcb", ] diff --git a/Cargo.toml b/Cargo.toml index de96316..92d0fb3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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", +] } diff --git a/packaging/aur/PKGBUILD b/packaging/aur/PKGBUILD new file mode 100644 index 0000000..617683a --- /dev/null +++ b/packaging/aur/PKGBUILD @@ -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" +} diff --git a/packaging/flatpak/com.vrec.Vrec.yaml b/packaging/flatpak/com.vrec.Vrec.yaml new file mode 100644 index 0000000..a1ef811 --- /dev/null +++ b/packaging/flatpak/com.vrec.Vrec.yaml @@ -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 diff --git a/packaging/rpm/vrec.spec b/packaging/rpm/vrec.spec new file mode 100644 index 0000000..84c3050 --- /dev/null +++ b/packaging/rpm/vrec.spec @@ -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 diff --git a/packaging/vrec.desktop b/packaging/vrec.desktop new file mode 100644 index 0000000..d46b2aa --- /dev/null +++ b/packaging/vrec.desktop @@ -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 diff --git a/src/bin/vrec-daemon.rs b/src/bin/vrec-daemon.rs index de648d5..bb513e5 100644 --- a/src/bin/vrec-daemon.rs +++ b/src/bin/vrec-daemon.rs @@ -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> { let mut source: Box = 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> { } }); - 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 { diff --git a/src/capture/mod.rs b/src/capture/mod.rs index 39c47d6..4cbb601 100644 --- a/src/capture/mod.rs +++ b/src/capture/mod.rs @@ -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>; } + pub mod mock; pub mod audio; diff --git a/src/capture/windows.rs b/src/capture/windows.rs index f803503..19472b9 100644 --- a/src/capture/windows.rs +++ b/src/capture/windows.rs @@ -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> { - // 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 = None; + let mut context: Option = 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 = 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> { + 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> { - // 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 = 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], + }) + } } } diff --git a/src/ipc.rs b/src/ipc.rs index 7cde227..ebf75d5 100644 --- a/src/ipc.rs +++ b/src/ipc.rs @@ -22,28 +22,50 @@ pub struct DaemonStatus { } pub fn send_command(cmd: Command) -> Result<(), Box> { - 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> { - 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)?; diff --git a/src/overlay.rs b/src/overlay.rs index d0f9da9..528c5c5 100644 --- a/src/overlay.rs +++ b/src/overlay.rs @@ -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();