Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
7352ca0
integrate cap-displays a bit
Brendonovich Aug 14, 2025
0dde2c1
start removing scap
Brendonovich Aug 14, 2025
b71dee4
windows actor
Brendonovich Aug 14, 2025
f7d6067
actors
Brendonovich Aug 15, 2025
3513e8b
windows actor
Brendonovich Aug 15, 2025
fb28fa4
move more stuff for windows
Brendonovich Aug 15, 2025
0eb9fa9
make windows actually build
Brendonovich Aug 15, 2025
93eead6
more stuff
Brendonovich Aug 15, 2025
8c13cf7
fix get_current_recording
Brendonovich Aug 15, 2025
c16a087
more windows impl
Brendonovich Aug 15, 2025
4f831c8
remove cap-media
Brendonovich Aug 15, 2025
de4eff5
fix early recording errors
Brendonovich Aug 18, 2025
63eb554
Merge branch 'main' into scap-crate
Brendonovich Aug 18, 2025
35fccda
improvements
oscartbeaumont Aug 18, 2025
d332d77
compile on Windows
oscartbeaumont Aug 18, 2025
f3c55aa
fix screen capture video info
Brendonovich Aug 19, 2025
8c7bc19
cpal
Brendonovich Aug 20, 2025
0b977ec
cpal ffmpeg
Brendonovich Aug 20, 2025
3f3a267
add config to create_capturer data callback
Brendonovich Aug 20, 2025
bb7c0dd
audio sender
Brendonovich Aug 20, 2025
fcfb3e4
mostly working on macos
Brendonovich Aug 20, 2025
9568eac
cursor bounds all good on macos
Brendonovich Aug 20, 2025
59aa5be
can_* functions
Brendonovich Aug 20, 2025
8e397ac
capability check fns
Brendonovich Aug 20, 2025
d5e330c
make it work on windows
Brendonovich Aug 21, 2025
a1a5965
bounds work properly on macos
Brendonovich Aug 21, 2025
bb9c6cc
fixup windows
Brendonovich Aug 21, 2025
a884916
macos audio capture
Brendonovich Aug 21, 2025
c189c81
don't drop audio capturer after starting recording -_-
Brendonovich Aug 21, 2025
90b62b7
move screen capture source to multiple files
Brendonovich Aug 21, 2025
98297d2
fix macos
Brendonovich Aug 21, 2025
88a237f
cargo fix
Brendonovich Aug 21, 2025
bd5427d
rename Screen to Display
Brendonovich Aug 21, 2025
2ba98fa
update tauri opener
Brendonovich Aug 21, 2025
f111ec8
formatting
Brendonovich Aug 21, 2025
cc58bcd
formatting
Brendonovich Aug 21, 2025
15ede68
types
Brendonovich Aug 21, 2025
22fe8df
format
Brendonovich Aug 21, 2025
7358adb
round recording crop to nearest 2 on windows
Brendonovich Aug 22, 2025
49d2c2e
fix on windows
Brendonovich Aug 22, 2025
3058804
use is_valid from windows-capture
Brendonovich Aug 22, 2025
8b773dc
license scap and camera crates as MIT
Brendonovich Aug 22, 2025
71f1d1b
fix intersects for macos and fix Display label
Brendonovich Aug 22, 2025
37dddab
Merge branch 'main' into scap-crate
Brendonovich Aug 22, 2025
10dcc5a
disable a11y lints for desktop
Brendonovich Aug 22, 2025
eb9d3bb
address coderabbit feedback
Brendonovich Aug 22, 2025
d4aa709
macos fail points
Brendonovich Aug 22, 2025
9d4a1c1
last of windows error handling i think
Brendonovich Aug 22, 2025
9750493
format
oscartbeaumont Aug 22, 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
363 changes: 225 additions & 138 deletions Cargo.lock

Large diffs are not rendered by default.

8 changes: 4 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ members = ["apps/cli", "apps/desktop/src-tauri", "crates/*"]
anyhow = { version = "1.0.86" }
# This includes a currently-unreleased fix that ensures the audio stream is actually
# stopped and released on drop on macOS
cpal = { git = "https://github.com/RustAudio/cpal", rev = "f43d36e55494993bbbde3299af0c53e5cdf4d4cf" }
# cpal = { git = "https://github.com/RustAudio/cpal", rev = "f43d36e55494993bbbde3299af0c53e5cdf4d4cf" }
cpal = "0.16"
ffmpeg = { package = "ffmpeg-next", git = "https://github.com/CapSoftware/rust-ffmpeg", rev = "49db1fede112" }
ffmpeg-sys-next = "7.1.0"
tokio = { version = "1.39.3", features = [
"macros",
"process",
Expand Down Expand Up @@ -43,7 +43,7 @@ sentry = { version = "0.34.0", features = [
tracing = "0.1.41"
futures = "0.3.31"

cidre = { git = "https://github.com/CapSoftware/cidre", rev = "517d097ae438", features = [
cidre = { git = "https://github.com/CapSoftware/cidre", rev = "bf84b67079a8", features = [
"macos_13_0",
"cv",
"cf",
Expand Down Expand Up @@ -79,6 +79,6 @@ debug = true

[patch.crates-io]
screencapturekit = { git = "https://github.com/CapSoftware/screencapturekit-rs", rev = "7ff1e103742e56c8f6c2e940b5e52684ed0bed69" } # branch = "cap-main"
cidre = { git = "https://github.com/CapSoftware/cidre", rev = "517d097ae438" }
cidre = { git = "https://github.com/CapSoftware/cidre", rev = "bf84b67079a8" }
# https://github.com/gfx-rs/wgpu/pull/7550
# wgpu = { git = "https://github.com/gfx-rs/wgpu", rev = "cd41a6e32a6239b65d1cecbeccde6a43a100914a" }
8 changes: 7 additions & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
Copyright (c) 2023-present Cap Software, Inc.


Portions of this software are licensed as follows:

- All code residing in the `cap-camera*` and `scap-*` families of crates is licensed under the MIT License (see LICENSE-MIT).
- All third party components are licensed under the original license provided by the owner of the applicable component
- All other content not mentioned above is available under the AGPLv3 license as defined below

GNU AFFERO GENERAL PUBLIC LICENSE
Version 3, 19 November 2007

Expand Down
21 changes: 21 additions & 0 deletions LICENSE-MIT
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2023 Cap Software, Inc.

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
2 changes: 1 addition & 1 deletion apps/cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ cap-flags = { path = "../../crates/flags" }
cap-recording = { path = "../../crates/recording" }
cap-export = { path = "../../crates/export" }
cap-camera = { path = "../../crates/camera" }
cap-displays = { path = "../../crates/displays" }
serde = { workspace = true }
serde_json = "1.0.133"
tokio.workspace = true
scap.workspace = true
uuid = { version = "1.11.1", features = ["v4"] }
ffmpeg = { workspace = true }
tracing.workspace = true
Expand Down
10 changes: 5 additions & 5 deletions apps/cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ use std::{
};

use cap_export::ExporterBase;
use cap_media::{feeds::CameraFeed, sources::get_target_fps};
use cap_project::XY;
use cap_recording::feeds::CameraFeed;
use clap::{Args, Parser, Subcommand};
use record::RecordStart;
use serde_json::json;
Expand Down Expand Up @@ -77,7 +77,7 @@ async fn main() -> Result<(), String> {
}
Commands::Record(RecordArgs { command, args }) => match command {
Some(RecordCommands::Screens) => {
let screens = cap_media::sources::list_screens();
let screens = cap_recording::screen_capture::list_displays();

for (i, (screen, target)) in screens.iter().enumerate() {
println!(
Expand All @@ -89,12 +89,12 @@ screen {}:
i,
screen.id,
screen.name,
get_target_fps(target).unwrap()
target.refresh_rate()
);
}
}
Some(RecordCommands::Windows) => {
let windows = cap_media::sources::list_windows();
let windows = cap_recording::screen_capture::list_windows();

for (i, (window, target)) in windows.iter().enumerate() {
println!(
Expand All @@ -106,7 +106,7 @@ window {}:
i,
window.id,
window.name,
get_target_fps(target).unwrap()
target.display().unwrap().refresh_rate()
);
}
}
Expand Down
20 changes: 10 additions & 10 deletions apps/cli/src/record.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use std::{env::current_dir, path::PathBuf, sync::Arc};

use cap_camera::ModelID;
use cap_media::sources::ScreenCaptureTarget;
use cap_displays::{DisplayId, WindowId};
use cap_recording::screen_capture::ScreenCaptureTarget;
use clap::Args;
use std::{env::current_dir, path::PathBuf, sync::Arc};
use tokio::{io::AsyncBufReadExt, sync::Mutex};
use uuid::Uuid;

Expand All @@ -29,16 +29,16 @@ pub struct RecordStart {

impl RecordStart {
pub async fn run(self) -> Result<(), String> {
let (target_info, _) = match (self.target.screen, self.target.window) {
(Some(id), _) => cap_media::sources::list_screens()
let target_info = match (self.target.screen, self.target.window) {
(Some(id), _) => cap_recording::screen_capture::list_displays()
.into_iter()
.find(|s| s.0.id == id)
.map(|(s, t)| (ScreenCaptureTarget::Screen { id: s.id }, t))
.map(|(s, _)| ScreenCaptureTarget::Display { id: s.id })
.ok_or(format!("Screen with id '{id}' not found")),
(_, Some(id)) => cap_media::sources::list_windows()
(_, Some(id)) => cap_recording::screen_capture::list_windows()
.into_iter()
.find(|s| s.0.id == id)
.map(|(s, t)| (ScreenCaptureTarget::Window { id: s.id }, t))
.map(|(s, _)| ScreenCaptureTarget::Window { id: s.id })
.ok_or(format!("Window with id '{id}' not found")),
_ => Err("No target specified".to_string()),
}?;
Expand Down Expand Up @@ -90,8 +90,8 @@ impl RecordStart {
struct RecordTargets {
/// ID of the screen to capture
#[arg(long, group = "target")]
screen: Option<u32>,
screen: Option<DisplayId>,
/// ID of the window to capture
#[arg(long, group = "target")]
window: Option<u32>,
window: Option<WindowId>,
}
4 changes: 2 additions & 2 deletions apps/desktop/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,11 @@
"@tauri-apps/plugin-fs": "^2.4.1",
"@tauri-apps/plugin-http": "^2.5.1",
"@tauri-apps/plugin-notification": "^2.3.0",
"@tauri-apps/plugin-opener": "^2.4.0",
"@tauri-apps/plugin-opener": "^2.5.0",
"@tauri-apps/plugin-os": "^2.3.0",
"@tauri-apps/plugin-process": "2.3.0",
"@tauri-apps/plugin-shell": "^2.3.0",
"@tauri-apps/plugin-store": "^2.3.0",
"@tauri-apps/plugin-store": "^2.4.0",
"@tauri-apps/plugin-updater": "^2.9.0",
"@ts-rest/core": "^3.52.1",
"@types/react-tooltip": "^4.2.4",
Expand Down
2 changes: 1 addition & 1 deletion apps/desktop/src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,13 @@ tauri-plugin-deep-link = "2.2.0"
tauri-plugin-clipboard-manager = "2.2.1"
tauri-plugin-opener = "2.2.6"

scap = { workspace = true }
serde = { workspace = true }
serde_json = "1.0.111"
specta.workspace = true
specta-typescript = "0.0.7"
tokio.workspace = true
uuid = { version = "1.10.0", features = ["v4"] }
scap.workspace = true
image = "0.25.2"
mp4 = "0.14.0"
futures-intrusive = "0.5.0"
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
@@ -1,4 +1,4 @@
use cap_media::feeds::{AudioInputSamples, AudioInputSamplesReceiver};
use cap_recording::feeds::{AudioInputSamples, AudioInputSamplesReceiver};
use cpal::{SampleFormat, StreamInstant};
use keyed_priority_queue::KeyedPriorityQueue;
use serde::{Deserialize, Serialize};
Expand Down
2 changes: 1 addition & 1 deletion apps/desktop/src-tauri/src/camera.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use anyhow::Context;
use cap_media::feeds::RawCameraFrame;
use cap_recording::feeds::RawCameraFrame;
use ffmpeg::{
format::{self, Pixel},
frame,
Expand Down
6 changes: 4 additions & 2 deletions apps/desktop/src-tauri/src/camera_legacy.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
use cap_media::{feeds::RawCameraFrame, frame_ws::WSFrame};
use cap_recording::feeds::RawCameraFrame;
use flume::Sender;
use tokio_util::sync::CancellationToken;

use crate::frame_ws::{WSFrame, create_frame_ws};

pub async fn create_camera_preview_ws() -> (Sender<RawCameraFrame>, u16, CancellationToken) {
let (camera_tx, mut _camera_rx) = flume::bounded::<RawCameraFrame>(4);
let (_camera_tx, camera_rx) = flume::bounded::<WSFrame>(4);
Expand Down Expand Up @@ -64,7 +66,7 @@ pub async fn create_camera_preview_ws() -> (Sender<RawCameraFrame>, u16, Cancell
}
});
// _shutdown needs to be kept alive to keep the camera ws running
let (camera_ws_port, _shutdown) = cap_media::frame_ws::create_frame_ws(camera_rx.clone()).await;
let (camera_ws_port, _shutdown) = create_frame_ws(camera_rx.clone()).await;

(camera_tx, camera_ws_port, _shutdown)
}
14 changes: 6 additions & 8 deletions apps/desktop/src-tauri/src/deeplink_actions.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
use std::path::{Path, PathBuf};

use cap_recording::RecordingMode;
use cap_recording::{RecordingMode, feeds::DeviceOrModelID, sources::ScreenCaptureTarget};
use serde::{Deserialize, Serialize};
use std::path::{Path, PathBuf};
use tauri::{AppHandle, Manager, Url};
use tracing::trace;

Expand All @@ -21,7 +20,7 @@ pub enum CaptureMode {
pub enum DeepLinkAction {
StartRecording {
capture_mode: CaptureMode,
camera: Option<cap_media::feeds::DeviceOrModelID>,
camera: Option<DeviceOrModelID>,
mic_label: Option<String>,
capture_system_audio: bool,
mode: RecordingMode,
Expand Down Expand Up @@ -121,14 +120,13 @@ impl DeepLinkAction {
crate::set_camera_input(app.clone(), state.clone(), camera_preview, camera).await?;
crate::set_mic_input(state.clone(), mic_label).await?;

use cap_media::sources::ScreenCaptureTarget;
let capture_target: ScreenCaptureTarget = match capture_mode {
CaptureMode::Screen(name) => cap_media::sources::list_screens()
CaptureMode::Screen(name) => cap_recording::screen_capture::list_displays()
.into_iter()
.find(|(s, _)| s.name == name)
.map(|(s, _)| ScreenCaptureTarget::Screen { id: s.id })
.map(|(s, _)| ScreenCaptureTarget::Display { id: s.id })
.ok_or(format!("No screen with name \"{}\"", &name))?,
CaptureMode::Window(name) => cap_media::sources::list_windows()
CaptureMode::Window(name) => cap_recording::screen_capture::list_windows()
.into_iter()
.find(|(w, _)| w.name == name)
.map(|(w, _)| ScreenCaptureTarget::Window { id: w.id })
Expand Down
55 changes: 51 additions & 4 deletions apps/desktop/src-tauri/src/editor_window.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,34 @@
use std::{collections::HashMap, ops::Deref, path::PathBuf, sync::Arc};

use cap_editor::EditorInstance;
use tauri::{Manager, Runtime, Window, ipc::CommandArg};
use tokio::sync::RwLock;
use tokio_util::sync::CancellationToken;

use crate::{
create_editor_instance_impl,
frame_ws::{WSFrame, create_frame_ws},
};

pub struct EditorInstance {
inner: Arc<cap_editor::EditorInstance>,
pub ws_port: u16,
pub ws_shutdown_token: CancellationToken,
}

impl EditorInstance {
pub async fn dispose(&self) {
self.inner.dispose().await;

self.ws_shutdown_token.cancel();
}
}

use crate::create_editor_instance_impl;
impl Deref for EditorInstance {
type Target = Arc<cap_editor::EditorInstance>;

fn deref(&self) -> &Self::Target {
&self.inner
}
}

#[derive(Clone)]
pub struct EditorInstances(Arc<RwLock<HashMap<String, Arc<EditorInstance>>>>);
Expand Down Expand Up @@ -64,8 +88,31 @@ impl EditorInstances {

match instances.entry(window.label().to_string()) {
Entry::Vacant(entry) => {
let instance = create_editor_instance_impl(window.app_handle(), path).await?;
let (frame_tx, frame_rx) = flume::bounded(4);

let (ws_port, ws_shutdown_token) = create_frame_ws(frame_rx).await;
let instance = create_editor_instance_impl(
window.app_handle(),
path,
Box::new(move |frame| {
let _ = frame_tx.send(WSFrame {
data: frame.data,
width: frame.width,
height: frame.height,
stride: frame.padded_bytes_per_row,
});
}),
)
.await?;

let instance = Arc::new(EditorInstance {
inner: instance.clone(),
ws_port,
ws_shutdown_token,
});

entry.insert(instance.clone());

Ok(instance)
}
Entry::Occupied(entry) => Ok(entry.get().clone()),
Expand Down
16 changes: 9 additions & 7 deletions apps/desktop/src-tauri/src/fake_window.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
use cap_media::platform::Bounds;
use cap_displays::bounds::LogicalBounds;
use std::{collections::HashMap, sync::Arc, time::Duration};
use tauri::{AppHandle, Manager, WebviewWindow};
use tokio::{sync::RwLock, time::sleep};

pub struct FakeWindowBounds(pub Arc<RwLock<HashMap<String, HashMap<String, Bounds>>>>);
pub struct FakeWindowBounds(pub Arc<RwLock<HashMap<String, HashMap<String, LogicalBounds>>>>);

#[tauri::command]
#[specta::specta]
pub async fn set_fake_window_bounds(
window: tauri::Window,
name: String,
bounds: Bounds,
bounds: LogicalBounds,
state: tauri::State<'_, FakeWindowBounds>,
) -> Result<(), String> {
let mut state = state.0.write().await;
Expand Down Expand Up @@ -70,10 +70,12 @@ pub fn spawn_fake_window_listener(app: AppHandle, window: WebviewWindow) {
let mut ignore = true;

for bounds in windows.values() {
let x_min = (window_position.x as f64) + bounds.x * scale_factor;
let x_max = (window_position.x as f64) + (bounds.x + bounds.width) * scale_factor;
let y_min = (window_position.y as f64) + bounds.y * scale_factor;
let y_max = (window_position.y as f64) + (bounds.y + bounds.height) * scale_factor;
let x_min = (window_position.x as f64) + bounds.position().x() * scale_factor;
let x_max = (window_position.x as f64)
+ (bounds.position().x() + bounds.size().width()) * scale_factor;
let y_min = (window_position.y as f64) + bounds.position().y() * scale_factor;
let y_max = (window_position.y as f64)
+ (bounds.position().y() + bounds.size().height()) * scale_factor;

if mouse_position.x >= x_min
&& mouse_position.x <= x_max
Expand Down
File renamed without changes.
Loading
Loading