Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ pnpm-lock.yaml
.output
.vinxi
native-deps*
apps/desktop/recordings-from-AppData-Roaming/
apps/storybook/storybook-static
.tinyb

Expand Down
21 changes: 21 additions & 0 deletions apps/desktop/register_protocol.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
$ExePath = Join-Path $PSScriptRoot "../../target/debug/cap-desktop.exe"
$Protocol = "cap"

if (-not (Test-Path $ExePath)) {
Write-Host "Error: cap-desktop.exe not found at $ExePath" -ForegroundColor Red
exit
}

Write-Host "Registering $($Protocol):// protocol to: $ExePath"

# HKEY_CLASSES_ROOT
$RegPath = "HKCU:\Software\Classes\$Protocol"
if (-not (Test-Path $RegPath)) { New-Item -Path $RegPath -Force }
New-ItemProperty -Path $RegPath -Name "URL Protocol" -Value "" -PropertyType String -Force

$ShellPath = "$RegPath\shell\open\command"
if (-not (Test-Path $ShellPath)) { New-Item -Path $ShellPath -Force }
Set-Item -Path $ShellPath -Value "`"$ExePath`" `"%1`""

Write-Host "✅ Protocol $($Protocol):// registered successfully!" -ForegroundColor Green
Write-Host "You can now test it by running: start $($Protocol)://record"
28 changes: 28 additions & 0 deletions apps/desktop/set_build_env.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#!/bin/bash
echo "Configuring Cap Desktop Build Environment..."

# 1. Disable VCPKG to prevent conflicts
unset VCPKG_ROOT
echo " - Unset VCPKG_ROOT"

# 2. Add CMake to PATH (VS Build Tools)
export PATH="/c/Program Files (x86)/Microsoft Visual Studio/2022/BuildTools/Common7/IDE/CommonExtensions/Microsoft/CMake/CMake/bin:$PATH"

# 3. Configure FFmpeg 6.1
export FFMPEG_DIR="C:/Tools/ffmpeg-6.1-shared"
export PATH="$FFMPEG_DIR/bin:$PATH"
export BINDGEN_EXTRA_CLANG_ARGS="-I$FFMPEG_DIR/include"
echo " - Configured FFmpeg 6.1 at $FFMPEG_DIR"

# 4. Configure Clang
export LIBCLANG_PATH="C:/Program Files/LLVM/bin"
echo " - Configured Clang at $LIBCLANG_PATH"

# 5. Ensure DLLs are present in target (Fixes STATUS_DLL_NOT_FOUND)
TARGET_DIR="../../../target/debug"
if [ -d "$TARGET_DIR" ]; then
echo " - Copying FFmpeg DLLs to target/debug..."
cp "$FFMPEG_DIR/bin/"*.dll "$TARGET_DIR/" 2>/dev/null || true
fi

echo "✅ Environment Ready! You can now run 'pnpm tauri dev'."
55 changes: 54 additions & 1 deletion apps/desktop/src-tauri/src/deeplink_actions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ use tracing::trace;

use crate::{App, ArcLock, recording::StartRecordingInputs, windows::ShowCapWindow};

use tauri_specta::Event;

#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum CaptureMode {
Expand All @@ -25,7 +27,10 @@ pub enum DeepLinkAction {
capture_system_audio: bool,
mode: RecordingMode,
},
StartDefaultRecording,
StopRecording,
PauseRecording,
ResumeRecording,
OpenEditor {
project_path: PathBuf,
},
Expand Down Expand Up @@ -87,7 +92,17 @@ impl TryFrom<&Url> for DeepLinkAction {
});
}

match url.domain() {
if url.scheme() == "cap" {
return match url.host_str() {
Some("record") => Ok(Self::StartDefaultRecording),
Some("stop") => Ok(Self::StopRecording),
Some("pause") => Ok(Self::PauseRecording),
Some("resume") => Ok(Self::ResumeRecording),
_ => Err(ActionParseFromUrlError::Invalid),
};
}

match url.host_str() {
Some(v) if v != "action" => Err(ActionParseFromUrlError::NotAction),
_ => Err(ActionParseFromUrlError::Invalid),
}?;
Expand Down Expand Up @@ -143,9 +158,47 @@ impl DeepLinkAction {
.await
.map(|_| ())
}
DeepLinkAction::StartDefaultRecording => {
let state = app.state::<ArcLock<App>>();
let displays = cap_recording::screen_capture::list_displays();

if let Some((display, _)) = displays.into_iter().next() {
let capture_target = ScreenCaptureTarget::Display { id: display.id };
let inputs = StartRecordingInputs {
mode: RecordingMode::Studio,
capture_target,
capture_system_audio: true,
organization_id: None,
};

crate::recording::start_recording(app.clone(), state, inputs)
.await
.map(|_| ())
} else {
Err("No display found".to_string())
}
}
DeepLinkAction::StopRecording => {
crate::recording::stop_recording(app.clone(), app.state()).await
}
DeepLinkAction::PauseRecording => {
let state = app.state::<ArcLock<App>>();
let state_read = state.read().await;
if let Some(recording) = state_read.current_recording() {
recording.pause().await.map_err(|e| e.to_string())?;
crate::recording::RecordingEvent::Paused.emit(app).ok();
}
Ok(())
}
DeepLinkAction::ResumeRecording => {
let state = app.state::<ArcLock<App>>();
let state_read = state.read().await;
if let Some(recording) = state_read.current_recording() {
recording.resume().await.map_err(|e| e.to_string())?;
crate::recording::RecordingEvent::Resumed.emit(app).ok();
}
Ok(())
}
DeepLinkAction::OpenEditor { project_path } => {
crate::open_project_from_path(Path::new(&project_path), app.clone())
}
Expand Down
2 changes: 2 additions & 0 deletions apps/desktop/src-tauri/src/export.rs
Original file line number Diff line number Diff line change
Expand Up @@ -367,6 +367,7 @@ pub async fn generate_export_preview(
&render_constants.device,
&render_constants.queue,
render_constants.is_software_adapter,
render_constants.is_software_adapter,
);

let frame = frame_renderer
Expand Down Expand Up @@ -510,6 +511,7 @@ pub async fn generate_export_preview_fast(
&editor.render_constants.device,
&editor.render_constants.queue,
editor.render_constants.is_software_adapter,
editor.render_constants.is_software_adapter,
);

let frame = frame_renderer
Expand Down
86 changes: 65 additions & 21 deletions apps/desktop/src-tauri/src/gpu_context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,41 +49,85 @@ static GPU: OnceCell<Option<SharedGpuContext>> = OnceCell::const_new();

pub async fn get_shared_gpu() -> Option<&'static SharedGpuContext> {
GPU.get_or_init(|| async {
let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor::default());
let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor {
backends: wgpu::Backends::all(),
flags: wgpu::InstanceFlags::default()
| wgpu::InstanceFlags::ALLOW_UNDERLYING_NONCOMPLIANT_ADAPTER,
..Default::default()
});

let hardware_adapter = instance
.request_adapter(&wgpu::RequestAdapterOptions {
power_preference: wgpu::PowerPreference::HighPerformance,
force_fallback_adapter: false,
compatible_surface: None,
})
.await
.ok();
let adapters = instance.enumerate_adapters(wgpu::Backends::all());

let (adapter, is_software_adapter) = if let Some(adapter) = hardware_adapter {
for adapter in &adapters {
let info = adapter.get_info();
tracing::info!(
adapter_name = adapter.get_info().name,
adapter_backend = ?adapter.get_info().backend,
"Found GPU adapter: {} (Vendor: 0x{:04X}, Backend: {:?}, Type: {:?}, LUID: {:?})",
info.name,
info.vendor,
info.backend,
info.device_type,
info.device
);
}

let (adapter, is_software_adapter) = if let Some(hardware_adapter) = adapters
.iter()
.find(|a| {
let info = a.get_info();
// Prefer discrete GPU on Dx12 if available for zero-copy
info.device_type == wgpu::DeviceType::DiscreteGpu
&& info.backend == wgpu::Backend::Dx12
&& info.name != "Microsoft Basic Render Driver"
})
.or_else(|| {
// Secondary check for any hardware GPU on Dx12
adapters.iter().find(|a| {
let info = a.get_info();
info.device_type != wgpu::DeviceType::Cpu
&& info.backend == wgpu::Backend::Dx12
&& info.name != "Microsoft Basic Render Driver"
})
})
.or_else(|| {
// Tertiary: try hardware on any backend (might have been missed by Dx12)
adapters.iter().find(|a| {
let info = a.get_info();
info.device_type != wgpu::DeviceType::Cpu
&& info.name != "Microsoft Basic Render Driver"
&& !info.name.contains("WARP")
})
}) {
let info = hardware_adapter.get_info();
tracing::info!(
adapter_name = info.name,
adapter_backend = ?info.backend,
"Using hardware GPU adapter for shared context"
);
(adapter, false)
(hardware_adapter.clone(), false)
} else {
tracing::warn!("No hardware GPU adapter found, attempting software fallback for shared context");
let software_adapter = instance
tracing::warn!(
"No clear hardware GPU adapter found via enumeration, attempting fallback"
);
let fallback_adapter = instance
.request_adapter(&wgpu::RequestAdapterOptions {
power_preference: wgpu::PowerPreference::LowPower,
force_fallback_adapter: true,
power_preference: wgpu::PowerPreference::HighPerformance,
force_fallback_adapter: false,
compatible_surface: None,
})
.await
.ok()?;

let info = fallback_adapter.get_info();
let is_software = info.device_type == wgpu::DeviceType::Cpu
|| info.name == "Microsoft Basic Render Driver";

tracing::info!(
adapter_name = software_adapter.get_info().name,
adapter_backend = ?software_adapter.get_info().backend,
"Using software adapter for shared context (CPU rendering - performance may be reduced)"
adapter_name = info.name,
adapter_backend = ?info.backend,
is_software = is_software,
"Using fallback GPU adapter for shared context"
);
(software_adapter, true)
(fallback_adapter, is_software)
};

let (device, queue) = adapter
Expand Down
Loading