feat(windows): Implement Windows DXGI Desktop Duplication capture and packaging definitions (AUR, RPM, Flatpak, GitHub Actions)
This commit is contained in:
parent
45eaf2840d
commit
0d73a17815
12 changed files with 547 additions and 48 deletions
127
.github/workflows/release.yml
vendored
Normal file
127
.github/workflows/release.yml
vendored
Normal 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
1
Cargo.lock
generated
|
|
@ -2321,6 +2321,7 @@ dependencies = [
|
|||
"serde",
|
||||
"serde_json",
|
||||
"tokio",
|
||||
"windows",
|
||||
"xcb",
|
||||
]
|
||||
|
||||
|
|
|
|||
24
Cargo.toml
24
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",
|
||||
] }
|
||||
|
|
|
|||
31
packaging/aur/PKGBUILD
Normal file
31
packaging/aur/PKGBUILD
Normal 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"
|
||||
}
|
||||
35
packaging/flatpak/com.vrec.Vrec.yaml
Normal file
35
packaging/flatpak/com.vrec.Vrec.yaml
Normal 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
46
packaging/rpm/vrec.spec
Normal 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
12
packaging/vrec.desktop
Normal 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
|
||||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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],
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
42
src/ipc.rs
42
src/ipc.rs
|
|
@ -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)?;
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue