WARNING: THIS SITE IS A MIRROR OF GITHUB.COM / IT CANNOT LOGIN OR REGISTER ACCOUNTS / THE CONTENTS ARE PROVIDED AS-IS / THIS SITE ASSUMES NO RESPONSIBILITY FOR ANY DISPLAYED CONTENT OR LINKS / IF YOU FOUND SOMETHING MAY NOT GOOD FOR EVERYONE, CONTACT ADMIN AT ilovescratch@foxmail.com
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
b753ce4
Update VSCode extension recommendations
richiemcilroy Dec 4, 2025
dd51fcc
Support different minimum crop sizes for screenshot mode
richiemcilroy Dec 4, 2025
0187998
Format VSCode extensions.json file
richiemcilroy Dec 4, 2025
acdd6cc
Add bottom margin to ViewAllButton in TargetMenuGrid
richiemcilroy Dec 4, 2025
97ad9ea
Refactor annotation config UI and remove presets dropdown
richiemcilroy Dec 4, 2025
9222156
try add clippy for windows
richiemcilroy Dec 4, 2025
2b0195c
Add LayersPanel and improve crop dialog logic
richiemcilroy Dec 4, 2025
1c349ea
Refactor crop dialog state in Header component
richiemcilroy Dec 4, 2025
c306d9e
Add keyboard shortcuts and focus pan to Preview
richiemcilroy Dec 4, 2025
0152089
Add layers panel toggle and improve aspect ratio constraints
richiemcilroy Dec 4, 2025
2a63eb0
Merge branch 'main' into screenshot-ux
richiemcilroy Dec 4, 2025
74816b6
Add prettyName to screenshot editor
richiemcilroy Dec 4, 2025
96aac80
Refactor popover state management in screenshot editor
richiemcilroy Dec 4, 2025
c52dcb6
Remove rounded class from PreviewCanvas element
richiemcilroy Dec 4, 2025
8245640
Set default border color to transparent black
richiemcilroy Dec 4, 2025
7d5371a
clippy + coderabbit
richiemcilroy Dec 4, 2025
cbbf665
clippy
richiemcilroy Dec 4, 2025
06d5dc8
Improve UI button hover/active styles and window shadow
richiemcilroy Dec 4, 2025
11daa79
Use clamp for output latency hint calculation
richiemcilroy Dec 4, 2025
293be48
Sync camera preview shape with project config
richiemcilroy Dec 4, 2025
6e87871
Return 0.0 for unsupported sample formats
richiemcilroy Dec 4, 2025
30254ed
Add support for more pixel formats on macOS and Windows
richiemcilroy Dec 4, 2025
d8a452f
Refactor Windows platform code for conciseness and clarity
richiemcilroy Dec 4, 2025
2b5d4bd
Add support for unsigned and 8-bit sample formats
richiemcilroy Dec 4, 2025
dd137a6
Refactor useScreenshotExport to use editorCtx object
richiemcilroy Dec 4, 2025
b6b7975
Refactor static slice and icon extraction logic
richiemcilroy Dec 4, 2025
5fde381
Remove UYVY422 mapping to DXGI_FORMAT_YUY2
richiemcilroy Dec 4, 2025
283e8ef
Remove incorrect mappings in ffmpeg_sample_format_for
richiemcilroy Dec 4, 2025
38a865c
Fix pixel format and plane count handling for frames
richiemcilroy Dec 4, 2025
cf869bc
Refactor memory and time handling in SinkFilter and AMMediaType
richiemcilroy Dec 5, 2025
a97d19e
Refactor frame data access and improve dimension checks
richiemcilroy Dec 5, 2025
cfd4d08
Fix memory handling in AMMediaType::into_inner
richiemcilroy Dec 5, 2025
3e24267
Refactor empty check to use is_empty()
richiemcilroy Dec 5, 2025
13986b5
Add recent items submenu to tray and improve image handling
richiemcilroy Dec 7, 2025
d2df462
Remove background overlay from target select screen
richiemcilroy Dec 7, 2025
cfd360e
clippy
richiemcilroy Dec 7, 2025
75c3a68
Document Rust Clippy workspace lint rules
richiemcilroy Dec 7, 2025
32401db
Fix Unicode truncation and fallback for file creation time
richiemcilroy Dec 7, 2025
1de769f
Refactor error handling with let-else chains
richiemcilroy Dec 7, 2025
187bc33
clippy
richiemcilroy Dec 7, 2025
6edcee7
clippy
richiemcilroy Dec 7, 2025
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
5 changes: 2 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -103,9 +103,8 @@ jobs:
settings:
- target: aarch64-apple-darwin
runner: macos-latest
# Windows can't take the disk usage lol
# - target: x86_64-pc-windows-msvc
# runner: windows-latest
- target: x86_64-pc-windows-msvc
runner: windows-latest
runs-on: ${{ matrix.settings.runner }}
permissions:
contents: read
Expand Down
2 changes: 1 addition & 1 deletion .vscode/extensions.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
"recommendations": ["tauri-apps.tauri-vscode", "rust-lang.rust-analyzer"]
"recommendations": ["biomejs.biome", "rust-lang.rust-analyzer"]
}
20 changes: 20 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,26 @@
- Runtime: Node 20, pnpm 10.x, Rust 1.88+, Docker for MySQL/MinIO.
- **NO COMMENTS**: Never add comments to code (`//`, `/* */`, `///`, `//!`, `#`, etc.). Code must be self-explanatory through naming, types, and structure. This applies to all languages (TypeScript, Rust, JavaScript, etc.).

## Rust Clippy Rules (Workspace Lints)
All Rust code must respect these workspace-level lints defined in `Cargo.toml`:

**Rust compiler lints:**
- `unused_must_use = "deny"` — Always handle `Result`/`Option` or types marked `#[must_use]`; never ignore them.

**Clippy lints (all denied):**
- `dbg_macro` — Never use `dbg!()` in code; use proper logging instead.
- `let_underscore_future` — Never write `let _ = async_fn()` which silently drops futures; await or explicitly handle them.
- `unchecked_duration_subtraction` — Use `saturating_sub` instead of `-` for `Duration` to avoid panics.
- `collapsible_if` — Merge nested `if` statements: use `if a && b { }` instead of `if a { if b { } }`.
- `clone_on_copy` — Don't call `.clone()` on `Copy` types; just copy them directly.
- `redundant_closure` — Use function references directly: `iter.map(foo)` instead of `iter.map(|x| foo(x))`.
- `ptr_arg` — Accept `&[T]` or `&str` instead of `&Vec<T>` or `&String` in function parameters.
- `len_zero` — Use `.is_empty()` instead of `.len() == 0` or `.len() > 0`.
- `let_unit_value` — Don't assign `()` to a variable: write `foo();` instead of `let _ = foo();` when return is unit.
- `unnecessary_lazy_evaluations` — Use `.unwrap_or(val)` instead of `.unwrap_or_else(|| val)` for cheap values.
- `needless_range_loop` — Use `for item in &collection` instead of `for i in 0..collection.len()` when index isn't needed.
- `manual_clamp` — Use `.clamp(min, max)` instead of manual `if` chains or `.min().max()` patterns.

## Testing
- TS/JS: Vitest where present (e.g., desktop). Name tests `*.test.ts(x)` near sources.
- Rust: `cargo test` per crate; tests in `src` or `tests`.
Expand Down
60 changes: 60 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,66 @@ Minimize `useEffect` usage: compute during render, handle logic in event handler
- Strict TypeScript; avoid `any`; leverage shared types
- Use Biome for linting/formatting; match existing formatting

## Rust Clippy Rules (Workspace Lints)
All Rust code must respect these workspace-level lints defined in `Cargo.toml`. Violating any of these will fail CI:

**Rust compiler lints:**
- `unused_must_use = "deny"` — Always handle `Result`/`Option` or types marked `#[must_use]`; never ignore them.

**Clippy lints (all denied — code MUST NOT contain these patterns):**
- `dbg_macro` — Never use `dbg!()` in code; use proper logging (`tracing::debug!`, etc.) instead.
- `let_underscore_future` — Never write `let _ = async_fn()` which silently drops futures; await or explicitly handle them.
- `unchecked_duration_subtraction` — Use `duration.saturating_sub(other)` instead of `duration - other` to avoid panics on underflow.
- `collapsible_if` — Merge nested `if` statements: write `if a && b { }` instead of `if a { if b { } }`.
- `clone_on_copy` — Don't call `.clone()` on `Copy` types (integers, bools, etc.); just copy them directly.
- `redundant_closure` — Use function references directly: `iter.map(foo)` instead of `iter.map(|x| foo(x))`.
- `ptr_arg` — Accept `&[T]` or `&str` instead of `&Vec<T>` or `&String` in function parameters for flexibility.
- `len_zero` — Use `.is_empty()` instead of `.len() == 0` or `.len() > 0` / `.len() != 0`.
- `let_unit_value` — Don't assign `()` to a variable: write `foo();` instead of `let _ = foo();` or `let x = foo();` when return is unit.
- `unnecessary_lazy_evaluations` — Use `.unwrap_or(val)` instead of `.unwrap_or_else(|| val)` when the default is a simple/cheap value.
- `needless_range_loop` — Use `for item in &collection` or `for (i, item) in collection.iter().enumerate()` instead of `for i in 0..collection.len()`.
- `manual_clamp` — Use `value.clamp(min, max)` instead of manual `if` chains or `.min(max).max(min)` patterns.

**Examples of violations to avoid:**

```rust
dbg!(value);
let _ = some_async_function();
let duration = duration_a - duration_b;
if condition {
if other_condition {
do_something();
}
}
let x = 5.clone();
vec.iter().map(|x| process(x))
fn example(v: &Vec<i32>) { }
if vec.len() == 0 { }
let _ = returns_unit();
option.unwrap_or_else(|| 42)
for i in 0..vec.len() { println!("{}", vec[i]); }
value.min(max).max(min)
```

**Correct alternatives:**

```rust
tracing::debug!(?value);
some_async_function().await;
let duration = duration_a.saturating_sub(duration_b);
if condition && other_condition {
do_something();
}
let x = 5;
vec.iter().map(process)
fn example(v: &[i32]) { }
if vec.is_empty() { }
returns_unit();
option.unwrap_or(42)
for item in &vec { println!("{}", item); }
value.clamp(min, max)
```

## Security & Privacy Considerations

### Data Handling
Expand Down
2 changes: 1 addition & 1 deletion apps/desktop/src-tauri/src/audio_meter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,6 @@ fn samples_to_f64(samples: &MicrophoneSamples) -> impl Iterator<Item = f64> + us
SampleFormat::F64 => f64::from_ne_bytes([
data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7],
]),
_ => todo!(),
_ => 0.0,
})
}
6 changes: 3 additions & 3 deletions apps/desktop/src-tauri/src/camera.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,9 @@ pub enum CameraPreviewShape {

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Type)]
pub struct CameraPreviewState {
size: f32,
shape: CameraPreviewShape,
mirrored: bool,
pub size: f32,
pub shape: CameraPreviewShape,
pub mirrored: bool,
}

impl Default for CameraPreviewState {
Expand Down
52 changes: 24 additions & 28 deletions apps/desktop/src-tauri/src/captions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,30 +115,28 @@
if mixed_samples.is_empty() {
mixed_samples = audio.samples().to_vec();
channel_count = audio.channels() as usize;
} else {
if audio.channels() as usize != channel_count {
log::info!(
"Channel count mismatch: {} vs {}, mixing to mono",
channel_count,
audio.channels()
);

if channel_count > 1 {
let mono_samples = convert_to_mono(&mixed_samples, channel_count);
mixed_samples = mono_samples;
channel_count = 1;
}
} else if audio.channels() as usize != channel_count {
log::info!(
"Channel count mismatch: {} vs {}, mixing to mono",
channel_count,
audio.channels()
);

let samples = if audio.channels() > 1 {
convert_to_mono(audio.samples(), audio.channels() as usize)
} else {
audio.samples().to_vec()
};
if channel_count > 1 {
let mono_samples = convert_to_mono(&mixed_samples, channel_count);
mixed_samples = mono_samples;
channel_count = 1;
}

mix_samples(&mut mixed_samples, &samples);
let samples = if audio.channels() > 1 {
convert_to_mono(audio.samples(), audio.channels() as usize)
} else {
mix_samples(&mut mixed_samples, audio.samples());
}
audio.samples().to_vec()
};

mix_samples(&mut mixed_samples, &samples);
} else {
mix_samples(&mut mixed_samples, audio.samples());
}
}
Err(e) => {
Expand Down Expand Up @@ -1012,13 +1010,11 @@
std::thread::spawn(move || {
use std::io::BufRead;
let reader = std::io::BufReader::new(stderr);
for line in reader.lines() {
if let Ok(line) = line {
if line.starts_with("STDERR:") {
log::info!("[WhisperX] {}", &line[7..]);
} else {
log::info!("[WhisperX stderr] {}", line);
}
for line in reader.lines().flatten() {

Check warning on line 1013 in apps/desktop/src-tauri/src/captions.rs

View workflow job for this annotation

GitHub Actions / Clippy (aarch64-apple-darwin, macos-latest)

`flatten()` will run forever if the iterator repeatedly produces an `Err`

warning: `flatten()` will run forever if the iterator repeatedly produces an `Err` --> apps/desktop/src-tauri/src/captions.rs:1013:36 | 1013 | for line in reader.lines().flatten() { | ^^^^^^^^^ help: replace with: `map_while(Result::ok)` | note: this expression returning a `std::io::Lines` may produce an infinite number of `Err` in case of a read error --> apps/desktop/src-tauri/src/captions.rs:1013:21 | 1013 | for line in reader.lines().flatten() { | ^^^^^^^^^^^^^^ = help: for further information visit https://rust-lang.github.io/rust-clippy/rust-1.91.0/index.html#lines_filter_map_ok = note: `#[warn(clippy::lines_filter_map_ok)]` on by default
if let Some(stripped) = line.strip_prefix("STDERR:") {
log::info!("[WhisperX] {}", stripped);
} else {
log::info!("[WhisperX stderr] {}", line);
}
}
});
Expand Down
2 changes: 1 addition & 1 deletion apps/desktop/src-tauri/src/export.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::{FramesRendered, get_video_metadata};
use cap_export::ExporterBase;
use cap_project::{RecordingMeta, XY};
use cap_project::RecordingMeta;
use serde::Deserialize;
use specta::Type;
use std::path::PathBuf;
Expand Down
27 changes: 23 additions & 4 deletions apps/desktop/src-tauri/src/recording.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ use cap_fail::fail;
use cap_project::CursorMoveEvent;
use cap_project::cursor::SHORT_CURSOR_SHAPE_DEBOUNCE_MS;
use cap_project::{
CursorClickEvent, InstantRecordingMeta, MultipleSegments, Platform, ProjectConfiguration,
RecordingMeta, RecordingMetaInner, SharingMeta, StudioRecordingMeta, StudioRecordingStatus,
TimelineConfiguration, TimelineSegment, UploadMeta, ZoomMode, ZoomSegment,
cursor::CursorEvents,
CameraShape, CursorClickEvent, InstantRecordingMeta, MultipleSegments, Platform,
ProjectConfiguration, RecordingMeta, RecordingMetaInner, SharingMeta, StudioRecordingMeta,
StudioRecordingStatus, TimelineConfiguration, TimelineSegment, UploadMeta, ZoomMode,
ZoomSegment, cursor::CursorEvents,
};
use cap_recording::feeds::camera::CameraFeedLock;
#[cfg(target_os = "macos")]
Expand Down Expand Up @@ -42,6 +42,7 @@ use tauri_plugin_dialog::{DialogExt, MessageDialogBuilder};
use tauri_specta::Event;
use tracing::*;

use crate::camera::{CameraPreviewManager, CameraPreviewShape};
use crate::web_api::AuthedApiError;
use crate::{
App, CurrentRecordingChanged, MutableState, NewStudioRecordingAdded, RecordingState,
Expand Down Expand Up @@ -1680,6 +1681,24 @@ fn project_config_from_recording(

let mut config = default_config.unwrap_or_default();

let camera_preview_manager = CameraPreviewManager::new(app);
if let Ok(camera_preview_state) = camera_preview_manager.get_state() {
match camera_preview_state.shape {
CameraPreviewShape::Round => {
config.camera.shape = CameraShape::Square;
config.camera.rounding = 100.0;
}
CameraPreviewShape::Square => {
config.camera.shape = CameraShape::Square;
config.camera.rounding = 25.0;
}
CameraPreviewShape::Full => {
config.camera.shape = CameraShape::Source;
config.camera.rounding = 25.0;
}
}
}

let timeline_segments = recordings
.segments
.iter()
Expand Down
50 changes: 42 additions & 8 deletions apps/desktop/src-tauri/src/screenshot_editor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ pub struct ScreenshotEditorInstance {
pub ws_shutdown_token: CancellationToken,
pub config_tx: watch::Sender<ProjectConfiguration>,
pub path: PathBuf,
pub pretty_name: String,
}

impl ScreenshotEditorInstance {
Expand Down Expand Up @@ -140,8 +141,31 @@ impl ScreenshotEditorInstances {
let rgba_img: image::RgbaImage = rgb_img.convert();
(rgba_img.into_raw(), width, height)
} else {
let img =
image::open(&path).map_err(|e| format!("Failed to open image: {e}"))?;
let image_path = if path.is_dir() {
let original = path.join("original.png");
if original.exists() {
original
} else {
std::fs::read_dir(&path)
.ok()
.and_then(|dir| {
dir.flatten()
.find(|e| {
e.path().extension().and_then(|s| s.to_str())
== Some("png")
})
.map(|e| e.path())
})
.ok_or_else(|| {
format!("No PNG file found in directory: {:?}", path)
})?
}
} else {
path.clone()
};

let img = image::open(&image_path)
.map_err(|e| format!("Failed to open image: {e}"))?;
let (w, h) = img.dimensions();

if w > MAX_DIMENSION || h > MAX_DIMENSION {
Expand All @@ -156,15 +180,22 @@ impl ScreenshotEditorInstances {
}
};

// Try to load existing meta if in a .cap directory
let (recording_meta, loaded_config) = if let Some(parent) = path.parent() {
let cap_dir = if path.extension().and_then(|s| s.to_str()) == Some("cap") {
Some(path.clone())
} else if let Some(parent) = path.parent() {
if parent.extension().and_then(|s| s.to_str()) == Some("cap") {
let meta = RecordingMeta::load_for_project(parent).ok();
let config = ProjectConfiguration::load(parent).ok();
(meta, config)
Some(parent.to_path_buf())
} else {
(None, None)
None
}
} else {
None
};

let (recording_meta, loaded_config) = if let Some(cap_dir) = &cap_dir {
let meta = RecordingMeta::load_for_project(cap_dir).ok();
let config = ProjectConfiguration::load(cap_dir).ok();
(meta, config)
} else {
(None, None)
};
Expand Down Expand Up @@ -264,6 +295,7 @@ impl ScreenshotEditorInstances {
ws_shutdown_token,
config_tx,
path: path.clone(),
pretty_name: recording_meta.pretty_name.clone(),
});

// Spawn render loop
Expand Down Expand Up @@ -375,6 +407,7 @@ pub struct SerializedScreenshotEditorInstance {
pub frames_socket_url: String,
pub path: PathBuf,
pub config: Option<ProjectConfiguration>,
pub pretty_name: String,
}

#[tauri::command]
Expand Down Expand Up @@ -404,6 +437,7 @@ pub async fn create_screenshot_editor_instance(
frames_socket_url: format!("ws://localhost:{}", instance.ws_port),
path: instance.path.clone(),
config: Some(config),
pretty_name: instance.pretty_name.clone(),
})
}

Expand Down
Loading
Loading