Skip to content

Commit 06301f7

Browse files
Merge branch 'main' into optimistic-presigned-urls
2 parents f4af36c + 55ff581 commit 06301f7

35 files changed

Lines changed: 989 additions & 212 deletions

.claude/settings.local.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
"allow": [
44
"Bash(pnpm typecheck:*)",
55
"Bash(pnpm lint:*)",
6-
"Bash(pnpm build:*)"
6+
"Bash(pnpm build:*)",
7+
"Bash(cargo check:*)"
78
],
89
"deny": [],
910
"ask": []

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

apps/desktop/src-tauri/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "cap-desktop"
3-
version = "0.3.72"
3+
version = "0.3.73"
44
description = "Beautiful screen recordings, owned by you."
55
authors = ["you"]
66
edition = "2024"

apps/desktop/src-tauri/src/api.rs

Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,12 @@ use serde::{Deserialize, Serialize};
55
use serde_json::json;
66
use tauri::AppHandle;
77

8-
use crate::web_api::ManagerExt;
8+
use crate::web_api::{AuthedApiError, ManagerExt};
99

10-
pub async fn upload_multipart_initiate(app: &AppHandle, video_id: &str) -> Result<String, String> {
10+
pub async fn upload_multipart_initiate(
11+
app: &AppHandle,
12+
video_id: &str,
13+
) -> Result<String, AuthedApiError> {
1114
#[derive(Deserialize)]
1215
#[serde(rename_all = "camelCase")]
1316
pub struct Response {
@@ -32,14 +35,12 @@ pub async fn upload_multipart_initiate(app: &AppHandle, video_id: &str) -> Resul
3235
.text()
3336
.await
3437
.unwrap_or_else(|_| "<no response body>".to_string());
35-
return Err(format!(
36-
"api/upload_multipart_initiate/{status}: {error_body}"
37-
));
38+
return Err(format!("api/upload_multipart_initiate/{status}: {error_body}").into());
3839
}
3940

4041
resp.json::<Response>()
4142
.await
42-
.map_err(|err| format!("api/upload_multipart_initiate/response: {err}"))
43+
.map_err(|err| format!("api/upload_multipart_initiate/response: {err}").into())
4344
.map(|data| data.upload_id)
4445
}
4546

@@ -48,7 +49,7 @@ pub async fn upload_multipart_presign_part(
4849
video_id: &str,
4950
upload_id: &str,
5051
part_number: u32,
51-
) -> Result<String, String> {
52+
) -> Result<String, AuthedApiError> {
5253
#[derive(Deserialize)]
5354
#[serde(rename_all = "camelCase")]
5455
pub struct Response {
@@ -74,14 +75,12 @@ pub async fn upload_multipart_presign_part(
7475
.text()
7576
.await
7677
.unwrap_or_else(|_| "<no response body>".to_string());
77-
return Err(format!(
78-
"api/upload_multipart_presign_part/{status}: {error_body}"
79-
));
78+
return Err(format!("api/upload_multipart_presign_part/{status}: {error_body}").into());
8079
}
8180

8281
resp.json::<Response>()
8382
.await
84-
.map_err(|err| format!("api/upload_multipart_presign_part/response: {err}"))
83+
.map_err(|err| format!("api/upload_multipart_presign_part/response: {err}").into())
8584
.map(|data| data.presigned_url)
8685
}
8786

@@ -112,7 +111,7 @@ pub async fn upload_multipart_complete(
112111
upload_id: &str,
113112
parts: &[UploadedPart],
114113
meta: Option<S3VideoMeta>,
115-
) -> Result<Option<String>, String> {
114+
) -> Result<Option<String>, AuthedApiError> {
116115
#[derive(Serialize)]
117116
#[serde(rename_all = "camelCase")]
118117
pub struct MultipartCompleteRequest<'a> {
@@ -148,14 +147,12 @@ pub async fn upload_multipart_complete(
148147
.text()
149148
.await
150149
.unwrap_or_else(|_| "<no response body>".to_string());
151-
return Err(format!(
152-
"api/upload_multipart_complete/{status}: {error_body}"
153-
));
150+
return Err(format!("api/upload_multipart_complete/{status}: {error_body}").into());
154151
}
155152

156153
resp.json::<Response>()
157154
.await
158-
.map_err(|err| format!("api/upload_multipart_complete/response: {err}"))
155+
.map_err(|err| format!("api/upload_multipart_complete/response: {err}").into())
159156
.map(|data| data.location)
160157
}
161158

@@ -177,7 +174,10 @@ pub struct PresignedS3PutRequest {
177174
pub meta: Option<S3VideoMeta>,
178175
}
179176

180-
pub async fn upload_signed(app: &AppHandle, body: PresignedS3PutRequest) -> Result<String, String> {
177+
pub async fn upload_signed(
178+
app: &AppHandle,
179+
body: PresignedS3PutRequest,
180+
) -> Result<String, AuthedApiError> {
181181
#[derive(Deserialize)]
182182
struct Data {
183183
url: String,
@@ -202,12 +202,12 @@ pub async fn upload_signed(app: &AppHandle, body: PresignedS3PutRequest) -> Resu
202202
.text()
203203
.await
204204
.unwrap_or_else(|_| "<no response body>".to_string());
205-
return Err(format!("api/upload_signed/{status}: {error_body}"));
205+
return Err(format!("api/upload_signed/{status}: {error_body}").into());
206206
}
207207

208208
resp.json::<Response>()
209209
.await
210-
.map_err(|err| format!("api/upload_signed/response: {err}"))
210+
.map_err(|err| format!("api/upload_signed/response: {err}").into())
211211
.map(|data| data.presigned_put_data.url)
212212
}
213213

@@ -216,7 +216,7 @@ pub async fn desktop_video_progress(
216216
video_id: &str,
217217
uploaded: u64,
218218
total: u64,
219-
) -> Result<(), String> {
219+
) -> Result<(), AuthedApiError> {
220220
let resp = app
221221
.authed_api_request("/api/desktop/video/progress", |client, url| {
222222
client.post(url).json(&json!({
@@ -235,7 +235,7 @@ pub async fn desktop_video_progress(
235235
.text()
236236
.await
237237
.unwrap_or_else(|_| "<no response body>".to_string());
238-
return Err(format!("api/desktop_video_progress/{status}: {error_body}"));
238+
return Err(format!("api/desktop_video_progress/{status}: {error_body}").into());
239239
}
240240

241241
Ok(())

apps/desktop/src-tauri/src/auth.rs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,3 @@ impl AuthStore {
118118
store.save().map_err(|e| e.to_string())
119119
}
120120
}
121-
122-
#[derive(specta::Type, serde::Serialize, tauri_specta::Event, Debug, Clone, serde::Deserialize)]
123-
pub struct AuthenticationInvalid;

apps/desktop/src-tauri/src/deeplink_actions.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,9 @@ impl DeepLinkAction {
138138
mode,
139139
};
140140

141-
crate::recording::start_recording(app.clone(), state, inputs).await
141+
crate::recording::start_recording(app.clone(), state, inputs)
142+
.await
143+
.map(|_| ())
142144
}
143145
DeepLinkAction::StopRecording => {
144146
crate::recording::stop_recording(app.clone(), app.state()).await

apps/desktop/src-tauri/src/general_settings.rs

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use crate::window_exclusion::WindowExclusion;
12
use serde::{Deserialize, Serialize};
23
use serde_json::json;
34
use specta::Type;
@@ -39,6 +40,24 @@ impl MainWindowRecordingStartBehaviour {
3940
}
4041
}
4142

43+
const DEFAULT_EXCLUDED_WINDOW_TITLES: &[&str] = &[
44+
"Cap",
45+
"Cap Settings",
46+
"Cap Recording Controls",
47+
"Cap Camera",
48+
];
49+
50+
pub fn default_excluded_windows() -> Vec<WindowExclusion> {
51+
DEFAULT_EXCLUDED_WINDOW_TITLES
52+
.iter()
53+
.map(|title| WindowExclusion {
54+
bundle_identifier: None,
55+
owner_name: None,
56+
window_title: Some((*title).to_string()),
57+
})
58+
.collect()
59+
}
60+
4261
// When adding fields here, #[serde(default)] defines the value to use for existing configurations,
4362
// and `Default::default` defines the value to use for new configurations.
4463
// Things that affect the user experience should only be enabled by default for new configurations.
@@ -99,6 +118,8 @@ pub struct GeneralSettingsStore {
99118
pub post_deletion_behaviour: PostDeletionBehaviour,
100119
#[serde(default = "default_enable_new_uploader", skip_serializing_if = "no")]
101120
pub enable_new_uploader: bool,
121+
#[serde(default = "default_excluded_windows")]
122+
pub excluded_windows: Vec<WindowExclusion>,
102123
}
103124

104125
fn default_enable_native_camera_preview() -> bool {
@@ -111,7 +132,7 @@ fn default_enable_new_recording_flow() -> bool {
111132
}
112133

113134
fn default_enable_new_uploader() -> bool {
114-
cfg!(debug_assertions)
135+
true
115136
}
116137

117138
fn no(_: &bool) -> bool {
@@ -162,6 +183,7 @@ impl Default for GeneralSettingsStore {
162183
enable_new_recording_flow: default_enable_new_recording_flow(),
163184
post_deletion_behaviour: PostDeletionBehaviour::DoNothing,
164185
enable_new_uploader: default_enable_new_uploader(),
186+
excluded_windows: default_excluded_windows(),
165187
}
166188
}
167189
}
@@ -231,3 +253,9 @@ pub fn init(app: &AppHandle) {
231253

232254
println!("GeneralSettingsState managed");
233255
}
256+
257+
#[tauri::command]
258+
#[specta::specta]
259+
pub fn get_default_excluded_windows() -> Vec<WindowExclusion> {
260+
default_excluded_windows()
261+
}

apps/desktop/src-tauri/src/hotkeys.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -146,9 +146,9 @@ async fn handle_hotkey(app: AppHandle, action: HotkeyAction) -> Result<(), Strin
146146
Ok(())
147147
}
148148
HotkeyAction::StopRecording => recording::stop_recording(app.clone(), app.state()).await,
149-
HotkeyAction::RestartRecording => {
150-
recording::restart_recording(app.clone(), app.state()).await
151-
}
149+
HotkeyAction::RestartRecording => recording::restart_recording(app.clone(), app.state())
150+
.await
151+
.map(|_| ()),
152152
HotkeyAction::OpenRecordingPicker => {
153153
let _ = RequestOpenRecordingPicker { target_mode: None }.emit(&app);
154154
Ok(())

apps/desktop/src-tauri/src/lib.rs

Lines changed: 24 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,11 @@ mod tray;
2525
mod upload;
2626
mod upload_legacy;
2727
mod web_api;
28+
mod window_exclusion;
2829
mod windows;
2930

3031
use audio::AppSounds;
31-
use auth::{AuthStore, AuthenticationInvalid, Plan};
32+
use auth::{AuthStore, Plan};
3233
use camera::CameraPreviewState;
3334
use cap_editor::{EditorInstance, EditorState};
3435
use cap_project::{
@@ -87,6 +88,7 @@ use tokio::sync::Mutex;
8788
use tokio::sync::{RwLock, oneshot};
8889
use tracing::{error, trace, warn};
8990
use upload::{create_or_get_video, upload_image, upload_video};
91+
use web_api::AuthedApiError;
9092
use web_api::ManagerExt as WebManagerExt;
9193
use windows::{CapWindowId, EditorWindowIds, ShowCapWindow, set_window_transparent};
9294

@@ -1084,7 +1086,7 @@ async fn upload_exported_video(
10841086

10851087
channel.send(UploadProgress { progress: 0.0 }).ok();
10861088

1087-
let s3_config = async {
1089+
let s3_config = match async {
10881090
let video_id = match mode {
10891091
UploadMode::Initial { pre_created_video } => {
10901092
if let Some(pre_created) = pre_created_video {
@@ -1094,7 +1096,7 @@ async fn upload_exported_video(
10941096
}
10951097
UploadMode::Reupload => {
10961098
let Some(sharing) = meta.sharing.clone() else {
1097-
return Err("No sharing metadata found".to_string());
1099+
return Err("No sharing metadata found".into());
10981100
};
10991101

11001102
Some(sharing.id)
@@ -1110,7 +1112,13 @@ async fn upload_exported_video(
11101112
)
11111113
.await
11121114
}
1113-
.await?;
1115+
.await
1116+
{
1117+
Ok(data) => data,
1118+
Err(AuthedApiError::InvalidAuthentication) => return Ok(UploadResult::NotAuthenticated),
1119+
Err(AuthedApiError::UpgradeRequired) => return Ok(UploadResult::UpgradeRequired),
1120+
Err(err) => return Err(err.to_string()),
1121+
};
11141122

11151123
let screenshot_path = meta.project_path.join("screenshots/display.jpg");
11161124
meta.upload = Some(UploadMeta::SinglePartUpload {
@@ -1154,17 +1162,20 @@ async fn upload_exported_video(
11541162
NotificationType::ShareableLinkCopied.send(&app);
11551163
Ok(UploadResult::Success(uploaded_video.link))
11561164
}
1165+
Err(AuthedApiError::UpgradeRequired) => Ok(UploadResult::UpgradeRequired),
11571166
Err(e) => {
11581167
error!("Failed to upload video: {e}");
11591168

11601169
NotificationType::UploadFailed.send(&app);
11611170

1162-
meta.upload = Some(UploadMeta::Failed { error: e.clone() });
1171+
meta.upload = Some(UploadMeta::Failed {
1172+
error: e.to_string(),
1173+
});
11631174
meta.save_for_project()
11641175
.map_err(|e| error!("Failed to save recording meta: {e}"))
11651176
.ok();
11661177

1167-
Err(e)
1178+
Err(e.to_string().into())
11681179
}
11691180
}
11701181
}
@@ -1597,16 +1608,10 @@ async fn check_upgraded_and_update(app: AppHandle) -> Result<bool, String> {
15971608
.await
15981609
.map_err(|e| {
15991610
println!("Failed to fetch plan: {e}");
1600-
format!("Failed to fetch plan: {e}")
1611+
e.to_string()
16011612
})?;
16021613

16031614
println!("Plan fetch response status: {}", response.status());
1604-
if response.status() == reqwest::StatusCode::UNAUTHORIZED {
1605-
println!("Unauthorized response, clearing auth store");
1606-
AuthStore::set(&app, None).map_err(|e| e.to_string())?;
1607-
return Ok(false);
1608-
}
1609-
16101615
let plan_data = response.json::<serde_json::Value>().await.map_err(|e| {
16111616
println!("Failed to parse plan response: {e}");
16121617
format!("Failed to parse plan response: {e}")
@@ -1914,6 +1919,8 @@ pub async fn run(recording_logging_handle: LoggingHandle) {
19141919
recording::list_capture_displays,
19151920
recording::list_displays_with_thumbnails,
19161921
recording::list_windows_with_thumbnails,
1922+
windows::refresh_window_content_protection,
1923+
general_settings::get_default_excluded_windows,
19171924
take_screenshot,
19181925
list_audio_devices,
19191926
close_recordings_overlay_window,
@@ -1998,7 +2005,6 @@ pub async fn run(recording_logging_handle: LoggingHandle) {
19982005
RequestOpenSettings,
19992006
RequestScreenCapturePrewarm,
20002007
NewNotification,
2001-
AuthenticationInvalid,
20022008
audio_meter::AudioInputLevelChange,
20032009
captions::DownloadProgress,
20042010
recording::RecordingEvent,
@@ -2014,7 +2020,8 @@ pub async fn run(recording_logging_handle: LoggingHandle) {
20142020
.typ::<hotkeys::HotkeysStore>()
20152021
.typ::<general_settings::GeneralSettingsStore>()
20162022
.typ::<recording_settings::RecordingSettingsStore>()
2017-
.typ::<cap_flags::Flags>();
2023+
.typ::<cap_flags::Flags>()
2024+
.typ::<crate::window_exclusion::WindowExclusion>();
20182025

20192026
#[cfg(debug_assertions)]
20202027
specta_builder
@@ -2114,7 +2121,7 @@ pub async fn run(recording_logging_handle: LoggingHandle) {
21142121
CapWindowId::CaptureArea.label().as_str(),
21152122
CapWindowId::Camera.label().as_str(),
21162123
CapWindowId::RecordingsOverlay.label().as_str(),
2117-
CapWindowId::InProgressRecording.label().as_str(),
2124+
CapWindowId::RecordingControls.label().as_str(),
21182125
CapWindowId::Upgrade.label().as_str(),
21192126
])
21202127
.map_label(|label| match label {
@@ -2548,7 +2555,7 @@ async fn resume_uploads(app: AppHandle) -> Result<(), String> {
25482555
error!("Error completing resumed upload for video: {error}");
25492556

25502557
if let Ok(mut meta) = RecordingMeta::load_for_project(&recording_dir).map_err(|err| error!("Error loading project metadata: {err}")) {
2551-
meta.upload = Some(UploadMeta::Failed { error });
2558+
meta.upload = Some(UploadMeta::Failed { error: error.to_string() });
25522559
meta.save_for_project().map_err(|err| error!("Error saving project metadata: {err}")).ok();
25532560
}
25542561
})

0 commit comments

Comments
 (0)