fix(ui): redesign overlay as top dock with 3 transparent cards and unified settings
This commit is contained in:
parent
10a2ea5343
commit
98dc90e85c
9 changed files with 896 additions and 1182 deletions
|
|
@ -14,7 +14,7 @@ RequestExecutionLevel user
|
|||
|
||||
!define MUI_FINISHPAGE_RUN "$INSTDIR\vrec-ui.exe"
|
||||
!define MUI_FINISHPAGE_RUN_PARAMETERS "--menu"
|
||||
!define MUI_FINISHPAGE_RUN_TEXT "Launch vrec Studio Overlay"
|
||||
!define MUI_FINISHPAGE_RUN_TEXT "Launch vrec"
|
||||
!insertmacro MUI_PAGE_FINISH
|
||||
|
||||
!insertmacro MUI_UNPAGE_CONFIRM
|
||||
|
|
@ -28,8 +28,7 @@ Section "vrec" SecMain
|
|||
|
||||
; Create Start Menu and Desktop shortcuts
|
||||
CreateDirectory "$SMPROGRAMS\vrec"
|
||||
CreateShortcut "$SMPROGRAMS\vrec\vrec Studio.lnk" "$INSTDIR\vrec-ui.exe" "--menu"
|
||||
CreateShortcut "$SMPROGRAMS\vrec\vrec Background Engine.lnk" "$INSTDIR\vrec-daemon.exe" ""
|
||||
CreateShortcut "$SMPROGRAMS\vrec\vrec.lnk" "$INSTDIR\vrec-ui.exe" "--menu"
|
||||
CreateShortcut "$DESKTOP\vrec.lnk" "$INSTDIR\vrec-ui.exe" "--menu"
|
||||
|
||||
; Register uninstaller with Windows Control Panel / Settings
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
#![cfg_attr(target_os = "windows", windows_subsystem = "windows")]
|
||||
|
||||
use vrec::capture::{Frame, FrameSource};
|
||||
use vrec::encoder::VideoEncoder;
|
||||
use vrec::ring::Packet;
|
||||
|
|
@ -99,9 +101,20 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
|||
|
||||
#[cfg(windows)]
|
||||
let listener = {
|
||||
let l = std::net::TcpListener::bind("127.0.0.1:42069")?;
|
||||
match std::net::TcpListener::bind("127.0.0.1:42069") {
|
||||
Ok(l) => {
|
||||
println!("Daemon listening on TCP IPC: 127.0.0.1:42069");
|
||||
l
|
||||
}
|
||||
Err(e) => {
|
||||
if vrec::ipc::query_status().is_ok() {
|
||||
println!("Another instance of vrec-daemon is already running. Exiting cleanly.");
|
||||
return Ok(());
|
||||
}
|
||||
std::thread::sleep(std::time::Duration::from_millis(500));
|
||||
std::net::TcpListener::bind("127.0.0.1:42069")?
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let cmd_tx_ipc = cmd_tx.clone();
|
||||
|
|
@ -114,8 +127,15 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
|||
for stream in listener.incoming() {
|
||||
match stream {
|
||||
Ok(mut stream) => {
|
||||
let _ = stream.set_read_timeout(Some(std::time::Duration::from_millis(1000)));
|
||||
let _ = stream.set_write_timeout(Some(std::time::Duration::from_millis(1000)));
|
||||
let cmd_tx_ipc = cmd_tx_ipc.clone();
|
||||
let is_recording_state_ipc = Arc::clone(&is_recording_state_ipc);
|
||||
let record_start_state_ipc = Arc::clone(&record_start_state_ipc);
|
||||
let replay_enabled_state_ipc = Arc::clone(&replay_enabled_state_ipc);
|
||||
let audio_muted_state_ipc = Arc::clone(&audio_muted_state_ipc);
|
||||
|
||||
std::thread::spawn(move || {
|
||||
let _ = stream.set_read_timeout(Some(std::time::Duration::from_millis(1500)));
|
||||
let _ = stream.set_write_timeout(Some(std::time::Duration::from_millis(1500)));
|
||||
let mut len_buf = [0u8; 4];
|
||||
if stream.read_exact(&mut len_buf).is_ok() {
|
||||
let len = u32::from_le_bytes(len_buf) as usize;
|
||||
|
|
@ -153,10 +173,17 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
|||
let mut cfg = vrec::config::VrecConfig::load();
|
||||
cfg.show_cursor = !cfg.show_cursor;
|
||||
let _ = cfg.save();
|
||||
println!("Cursor display toggled to: {}. Restarting daemon for new capture session...", cfg.show_cursor);
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
let session_type = env::var("XDG_SESSION_TYPE").unwrap_or_default();
|
||||
if session_type.to_lowercase() == "wayland" {
|
||||
println!("Cursor display toggled to: {}. Restarting Wayland session...", cfg.show_cursor);
|
||||
let _ = cmd_tx_ipc.send(Command::StopRecording);
|
||||
std::thread::sleep(std::time::Duration::from_millis(150));
|
||||
std::process::exit(0);
|
||||
}
|
||||
}
|
||||
println!("Cursor display toggled to: {}", cfg.show_cursor);
|
||||
},
|
||||
Command::CycleAudioMode => {
|
||||
let mut cfg = vrec::config::VrecConfig::load();
|
||||
|
|
@ -183,6 +210,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
|||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
Err(err) => {
|
||||
eprintln!("Socket error: {}", err);
|
||||
|
|
@ -253,7 +281,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
|||
Frame::Raw { width, height, .. } => (*width, *height),
|
||||
Frame::DmaBuf { width, height, .. } => (*width, *height),
|
||||
#[cfg(target_os = "windows")]
|
||||
Frame::D3D11Texture { .. } => (1920, 1080),
|
||||
Frame::D3D11Texture { width, height, .. } => (*width, *height),
|
||||
};
|
||||
|
||||
let _ = frame_tx.send(first_frame);
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
#![cfg_attr(target_os = "windows", windows_subsystem = "windows")]
|
||||
|
||||
use vrec::ipc::{Command, send_command, query_status};
|
||||
use vrec::overlay::show_notification;
|
||||
use std::env;
|
||||
|
|
@ -35,7 +37,8 @@ fn ensure_wayland_env() {
|
|||
}
|
||||
|
||||
fn get_daemon_cmd() -> std::process::Command {
|
||||
if let Ok(mut path) = std::env::current_exe() {
|
||||
#[allow(unused_mut)]
|
||||
let mut cmd = if let Ok(mut path) = std::env::current_exe() {
|
||||
path.pop();
|
||||
#[cfg(target_os = "windows")]
|
||||
let candidate = path.join("vrec-daemon.exe");
|
||||
|
|
@ -43,33 +46,102 @@ fn get_daemon_cmd() -> std::process::Command {
|
|||
let candidate = path.join("vrec-daemon");
|
||||
|
||||
if candidate.exists() {
|
||||
return std::process::Command::new(candidate);
|
||||
}
|
||||
}
|
||||
std::process::Command::new(candidate)
|
||||
} else {
|
||||
#[cfg(target_os = "windows")]
|
||||
return std::process::Command::new("vrec-daemon.exe");
|
||||
{
|
||||
std::process::Command::new("vrec-daemon.exe")
|
||||
}
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
return std::process::Command::new("vrec-daemon");
|
||||
{
|
||||
std::process::Command::new("vrec-daemon")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
std::process::Command::new("vrec-daemon.exe")
|
||||
}
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
{
|
||||
std::process::Command::new("vrec-daemon")
|
||||
}
|
||||
};
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
use std::os::windows::process::CommandExt;
|
||||
cmd.creation_flags(0x08000000); // CREATE_NO_WINDOW
|
||||
}
|
||||
|
||||
cmd
|
||||
}
|
||||
|
||||
fn get_ui_cmd() -> std::process::Command {
|
||||
#[allow(unused_mut)]
|
||||
let mut cmd = if let Ok(path) = std::env::current_exe() {
|
||||
std::process::Command::new(path)
|
||||
} else {
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
std::process::Command::new("vrec-ui.exe")
|
||||
}
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
{
|
||||
std::process::Command::new("vrec-ui")
|
||||
}
|
||||
};
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
use std::os::windows::process::CommandExt;
|
||||
cmd.creation_flags(0x08000000); // CREATE_NO_WINDOW
|
||||
}
|
||||
|
||||
cmd
|
||||
}
|
||||
|
||||
fn ensure_daemon_running() {
|
||||
if query_status().is_err() {
|
||||
println!("vrec-daemon not running. Auto-launching vrec-daemon...");
|
||||
let _ = get_daemon_cmd()
|
||||
.stdin(std::process::Stdio::null())
|
||||
.stdout(std::process::Stdio::null())
|
||||
.stderr(std::process::Stdio::null())
|
||||
.spawn();
|
||||
for _ in 0..20 {
|
||||
std::thread::sleep(std::time::Duration::from_millis(150));
|
||||
for _ in 0..15 {
|
||||
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||
if query_status().is_ok() {
|
||||
println!("vrec-daemon successfully connected.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn ensure_hotkeys_running() {
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
unsafe {
|
||||
use windows::Win32::Foundation::{CloseHandle, GetLastError, WIN32_ERROR};
|
||||
use windows::Win32::System::Threading::CreateMutexW;
|
||||
|
||||
if let Ok(handle) = CreateMutexW(None, false, windows::core::w!("Global\\vrec_hotkeys_single_instance")) {
|
||||
if GetLastError() == WIN32_ERROR(183) { // ERROR_ALREADY_EXISTS
|
||||
let _ = CloseHandle(handle);
|
||||
return;
|
||||
}
|
||||
let _ = CloseHandle(handle);
|
||||
}
|
||||
}
|
||||
|
||||
let _ = get_ui_cmd()
|
||||
.arg("--hotkeys")
|
||||
.stdin(std::process::Stdio::null())
|
||||
.stdout(std::process::Stdio::null())
|
||||
.stderr(std::process::Stdio::null())
|
||||
.spawn();
|
||||
}
|
||||
}
|
||||
|
||||
fn send_with_notification(cmd: Command, success_msg: &str) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
ensure_daemon_running();
|
||||
match send_command(cmd) {
|
||||
|
|
@ -135,6 +207,7 @@ fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
|||
}
|
||||
"--menu" => {
|
||||
ensure_daemon_running();
|
||||
ensure_hotkeys_running();
|
||||
vrec::overlay_egui::run_egui_overlay();
|
||||
return Ok(());
|
||||
}
|
||||
|
|
@ -197,10 +270,23 @@ fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
|||
}
|
||||
} else {
|
||||
ensure_daemon_running();
|
||||
ensure_hotkeys_running();
|
||||
vrec::overlay_egui::run_egui_overlay();
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
let _mutex = unsafe {
|
||||
use windows::Win32::Foundation::{GetLastError, WIN32_ERROR};
|
||||
use windows::Win32::System::Threading::CreateMutexW;
|
||||
|
||||
let handle = CreateMutexW(None, true, windows::core::w!("Global\\vrec_hotkeys_single_instance"));
|
||||
if GetLastError() == WIN32_ERROR(183) {
|
||||
return Ok(());
|
||||
}
|
||||
handle.ok()
|
||||
};
|
||||
|
||||
println!("Starting vrec UI/Hotkey process...");
|
||||
|
||||
let session_type = env::var("XDG_SESSION_TYPE").unwrap_or_else(|_| "x11".to_string());
|
||||
|
|
@ -223,7 +309,13 @@ fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
|||
println!("X11 session detected.");
|
||||
}
|
||||
|
||||
let manager = GlobalHotKeyManager::new()?;
|
||||
let manager = match GlobalHotKeyManager::new() {
|
||||
Ok(m) => m,
|
||||
Err(e) => {
|
||||
eprintln!("Failed to create global hotkey manager: {}", e);
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
let hotkey_menu = HotKey::new(Some(Modifiers::ALT), Code::KeyZ);
|
||||
let hotkey_save = HotKey::new(Some(Modifiers::CONTROL | Modifiers::SHIFT), Code::KeyR);
|
||||
let hotkey_record = HotKey::new(Some(Modifiers::CONTROL | Modifiers::SHIFT), Code::F9);
|
||||
|
|
@ -241,7 +333,7 @@ fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
|||
if let Ok(event) = receiver.try_recv() {
|
||||
if event.id == hotkey_menu.id() {
|
||||
ensure_daemon_running();
|
||||
vrec::overlay_egui::run_egui_overlay();
|
||||
let _ = get_ui_cmd().arg("--menu").spawn();
|
||||
} else if event.id == hotkey_save.id() {
|
||||
let _ = send_with_notification(Command::SaveReplay, "Replay saved");
|
||||
} else if event.id == hotkey_record.id() {
|
||||
|
|
|
|||
|
|
@ -35,6 +35,8 @@ pub enum Frame {
|
|||
#[cfg(target_os = "windows")]
|
||||
D3D11Texture {
|
||||
handle: usize,
|
||||
width: u32,
|
||||
height: u32,
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -16,8 +16,8 @@ pub struct WindowsCapture {
|
|||
context: ID3D11DeviceContext,
|
||||
duplication: IDXGIOutputDuplication,
|
||||
gpu_texture: ID3D11Texture2D,
|
||||
width: u32,
|
||||
height: u32,
|
||||
pub width: u32,
|
||||
pub height: u32,
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
|
|
@ -30,14 +30,40 @@ impl WindowsCapture {
|
|||
// Initialize COM library on capture thread
|
||||
let _ = CoInitializeEx(None, COINIT_MULTITHREADED);
|
||||
|
||||
// Create D3D11 hardware device
|
||||
// Create DXGI Factory to find the display adapter with active outputs
|
||||
let factory: IDXGIFactory1 = CreateDXGIFactory1()?;
|
||||
let mut chosen_adapter: Option<IDXGIAdapter1> = None;
|
||||
let mut chosen_output: Option<IDXGIOutput1> = None;
|
||||
|
||||
let mut a_idx = 0;
|
||||
while let Ok(adapter) = factory.EnumAdapters1(a_idx) {
|
||||
let mut o_idx = 0;
|
||||
while let Ok(output) = adapter.EnumOutputs(o_idx) {
|
||||
if let Ok(output1) = output.cast::<IDXGIOutput1>() {
|
||||
chosen_adapter = Some(adapter);
|
||||
chosen_output = Some(output1);
|
||||
break;
|
||||
}
|
||||
o_idx += 1;
|
||||
}
|
||||
if chosen_adapter.is_some() {
|
||||
break;
|
||||
}
|
||||
a_idx += 1;
|
||||
}
|
||||
|
||||
let (adapter, output1) = match (chosen_adapter, chosen_output) {
|
||||
(Some(a), Some(o)) => (a, o),
|
||||
_ => return Err("No active display output found for Windows desktop capture".into()),
|
||||
};
|
||||
|
||||
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,
|
||||
Some(&adapter),
|
||||
D3D_DRIVER_TYPE_UNKNOWN,
|
||||
HMODULE(std::ptr::null_mut()),
|
||||
D3D11_CREATE_DEVICE_BGRA_SUPPORT,
|
||||
Some(&[D3D_FEATURE_LEVEL_11_1, D3D_FEATURE_LEVEL_11_0]),
|
||||
|
|
@ -50,21 +76,14 @@ impl WindowsCapture {
|
|||
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;
|
||||
let desc = output1.GetDesc()?;
|
||||
let width = (desc.DesktopCoordinates.right - desc.DesktopCoordinates.left).unsigned_abs();
|
||||
let height = (desc.DesktopCoordinates.bottom - desc.DesktopCoordinates.top).unsigned_abs();
|
||||
|
||||
// Initialize Desktop Duplication
|
||||
let duplication = output1.DuplicateOutput(&device)?;
|
||||
|
||||
// Allocate a dedicated VRAM-resident GPU texture for 100% zero-copy capture
|
||||
// (Frame stays on GPU die, zero PCIe bus readback, 0% CPU consumption)
|
||||
let gpu_desc = D3D11_TEXTURE2D_DESC {
|
||||
Width: width,
|
||||
Height: height,
|
||||
|
|
@ -99,10 +118,17 @@ impl WindowsCapture {
|
|||
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(())
|
||||
let mut o_idx = 0;
|
||||
while let Ok(output) = adapter.EnumOutputs(o_idx) {
|
||||
if let Ok(output1) = output.cast::<IDXGIOutput1>() {
|
||||
if let Ok(dup) = output1.DuplicateOutput(&self.device) {
|
||||
self.duplication = dup;
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
o_idx += 1;
|
||||
}
|
||||
Err("Failed to reinitialize desktop duplication".into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -120,24 +146,26 @@ impl FrameSource for WindowsCapture {
|
|||
if let Some(resource) = desktop_resource {
|
||||
let texture: ID3D11Texture2D = resource.cast()?;
|
||||
|
||||
// Copy directly on the GPU from the desktop buffer to our persistent VRAM texture
|
||||
// Copy directly on the GPU from desktop buffer to persistent VRAM texture
|
||||
self.context.CopyResource(&self.gpu_texture, &texture);
|
||||
|
||||
// Immediately release the desktop frame back to the DWM compositor
|
||||
let _ = self.duplication.ReleaseFrame();
|
||||
|
||||
// Pass the VRAM texture handle directly to the hardware encoder (Zero CPU copy!)
|
||||
return Ok(Frame::D3D11Texture {
|
||||
handle: self.gpu_texture.as_raw() as usize,
|
||||
width: self.width,
|
||||
height: self.height,
|
||||
});
|
||||
}
|
||||
let _ = self.duplication.ReleaseFrame();
|
||||
}
|
||||
Err(e) if e.code() == DXGI_ERROR_WAIT_TIMEOUT => {
|
||||
// Display is idle (no new pixels), yield current texture without spinning CPU
|
||||
std::thread::sleep(std::time::Duration::from_millis(8));
|
||||
return Ok(Frame::D3D11Texture {
|
||||
handle: self.gpu_texture.as_raw() as usize,
|
||||
width: self.width,
|
||||
height: self.height,
|
||||
});
|
||||
}
|
||||
Err(e) if e.code() == DXGI_ERROR_ACCESS_LOST => {
|
||||
|
|
@ -154,6 +182,8 @@ impl FrameSource for WindowsCapture {
|
|||
|
||||
Ok(Frame::D3D11Texture {
|
||||
handle: self.gpu_texture.as_raw() as usize,
|
||||
width: self.width,
|
||||
height: self.height,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -336,7 +336,7 @@ impl VaapiEncoder {
|
|||
av_frame_free(&mut drm_frame);
|
||||
}
|
||||
#[cfg(target_os = "windows")]
|
||||
Frame::D3D11Texture { handle } => {
|
||||
Frame::D3D11Texture { handle, .. } => {
|
||||
println!("D3D11: Mock encoding D3D11 texture (Windows zero-copy). Handle: {}", handle);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
18
src/ipc.rs
18
src/ipc.rs
|
|
@ -87,11 +87,12 @@ pub fn send_command(cmd: Command) -> Result<(), Box<dyn std::error::Error + Send
|
|||
|
||||
#[cfg(windows)]
|
||||
{
|
||||
use std::net::TcpStream;
|
||||
match TcpStream::connect("127.0.0.1:42069") {
|
||||
use std::net::{SocketAddr, TcpStream};
|
||||
let addr: SocketAddr = "127.0.0.1:42069".parse().unwrap();
|
||||
match TcpStream::connect_timeout(&addr, Duration::from_millis(300)) {
|
||||
Ok(mut stream) => {
|
||||
let _ = stream.set_write_timeout(Some(Duration::from_millis(1000)));
|
||||
let _ = stream.set_read_timeout(Some(Duration::from_millis(1000)));
|
||||
let _ = stream.set_write_timeout(Some(Duration::from_millis(600)));
|
||||
let _ = stream.set_read_timeout(Some(Duration::from_millis(600)));
|
||||
if stream.write_all(&len_buf).is_ok() && stream.write_all(&payload).is_ok() {
|
||||
return Ok(());
|
||||
}
|
||||
|
|
@ -128,14 +129,15 @@ pub fn query_status() -> Result<DaemonStatus, Box<dyn std::error::Error + Send +
|
|||
|
||||
#[cfg(windows)]
|
||||
let stream_res = {
|
||||
use std::net::TcpStream;
|
||||
TcpStream::connect("127.0.0.1:42069")
|
||||
use std::net::{SocketAddr, TcpStream};
|
||||
let addr: SocketAddr = "127.0.0.1:42069".parse().unwrap();
|
||||
TcpStream::connect_timeout(&addr, Duration::from_millis(300))
|
||||
};
|
||||
|
||||
match stream_res {
|
||||
Ok(mut stream) => {
|
||||
let _ = stream.set_write_timeout(Some(Duration::from_millis(1000)));
|
||||
let _ = stream.set_read_timeout(Some(Duration::from_millis(1000)));
|
||||
let _ = stream.set_write_timeout(Some(Duration::from_millis(600)));
|
||||
let _ = stream.set_read_timeout(Some(Duration::from_millis(600)));
|
||||
|
||||
if stream.write_all(&len_buf).is_err() || stream.write_all(&payload).is_err() {
|
||||
continue;
|
||||
|
|
|
|||
|
|
@ -16,7 +16,31 @@ pub fn show_notification_overlay() {
|
|||
}
|
||||
|
||||
pub fn show_notification(message: &str) {
|
||||
#[cfg(unix)]
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
let msg = message.to_string();
|
||||
std::thread::spawn(move || {
|
||||
use std::os::windows::process::CommandExt;
|
||||
let script = format!(
|
||||
"[reflection.assembly]::loadwithpartialname('System.Windows.Forms') | Out-Null; \
|
||||
$notify = new-object system.windows.forms.notifyicon; \
|
||||
$notify.icon = [System.Drawing.SystemIcons]::Information; \
|
||||
$notify.visible = $true; \
|
||||
$notify.showballoontip(2000, 'vrec', '{}', [system.windows.forms.tooltipicon]::Info); \
|
||||
Start-Sleep -Seconds 2; \
|
||||
$notify.dispose()",
|
||||
msg.replace('\'', "''")
|
||||
);
|
||||
let _ = std::process::Command::new("powershell")
|
||||
.args(["-NoProfile", "-WindowStyle", "Hidden", "-Command", &script])
|
||||
.creation_flags(0x08000000)
|
||||
.output();
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
{
|
||||
if std::env::var("WAYLAND_DISPLAY").is_err() && std::env::var("DISPLAY").is_err() {
|
||||
return;
|
||||
}
|
||||
|
|
@ -45,14 +69,6 @@ pub fn show_notification(message: &str) {
|
|||
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#"
|
||||
window {
|
||||
|
|
@ -90,6 +106,7 @@ pub fn show_notification(message: &str) {
|
|||
|
||||
app.run_with_args(&[] as &[&str]);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn show_menu_overlay() {
|
||||
#[cfg(unix)]
|
||||
|
|
|
|||
1556
src/overlay_egui.rs
1556
src/overlay_egui.rs
File diff suppressed because it is too large
Load diff
Loading…
Add table
Reference in a new issue