Skip to content

Commit b54ddcc

Browse files
committed
new recording picker hotkeys
1 parent 2515763 commit b54ddcc

File tree

11 files changed

+189
-72
lines changed

11 files changed

+189
-72
lines changed

apps/desktop/src-tauri/capabilities/default.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
"core:window:allow-set-theme",
4242
"core:window:allow-set-progress-bar",
4343
"core:window:allow-set-effects",
44+
"core:window:allow-set-ignore-cursor-events",
4445
"core:webview:default",
4546
"core:webview:allow-create-webview-window",
4647
"core:app:allow-version",

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

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
use crate::{RequestStartRecording, recording, windows::ShowCapWindow};
1+
use crate::{
2+
RequestOpenRecordingPicker, RequestStartRecording, recording,
3+
recording_settings::RecordingTargetMode, windows::ShowCapWindow,
4+
};
25
use global_hotkey::HotKeyState;
36
use serde::{Deserialize, Serialize};
47
use specta::Type;
@@ -49,6 +52,13 @@ pub enum HotkeyAction {
4952
StopRecording,
5053
RestartRecording,
5154
// TakeScreenshot,
55+
OpenRecordingPicker,
56+
OpenRecordingPickerDisplay,
57+
OpenRecordingPickerWindow,
58+
OpenRecordingPickerArea,
59+
// Needed for deserialization of deprecated actions
60+
#[serde(other)]
61+
Other,
5262
}
5363

5464
#[derive(Serialize, Deserialize, Type, Default)]
@@ -139,6 +149,32 @@ async fn handle_hotkey(app: AppHandle, action: HotkeyAction) -> Result<(), Strin
139149
HotkeyAction::RestartRecording => {
140150
recording::restart_recording(app.clone(), app.state()).await
141151
}
152+
HotkeyAction::OpenRecordingPicker => {
153+
let _ = RequestOpenRecordingPicker { target_mode: None }.emit(&app);
154+
Ok(())
155+
}
156+
HotkeyAction::OpenRecordingPickerDisplay => {
157+
let _ = RequestOpenRecordingPicker {
158+
target_mode: Some(RecordingTargetMode::Display),
159+
}
160+
.emit(&app);
161+
Ok(())
162+
}
163+
HotkeyAction::OpenRecordingPickerWindow => {
164+
let _ = RequestOpenRecordingPicker {
165+
target_mode: Some(RecordingTargetMode::Window),
166+
}
167+
.emit(&app);
168+
Ok(())
169+
}
170+
HotkeyAction::OpenRecordingPickerArea => {
171+
let _ = RequestOpenRecordingPicker {
172+
target_mode: Some(RecordingTargetMode::Area),
173+
}
174+
.emit(&app);
175+
Ok(())
176+
}
177+
HotkeyAction::Other => Ok(()),
142178
}
143179
}
144180

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

Lines changed: 44 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,10 @@ use upload::{S3UploadMeta, create_or_get_video, upload_image, upload_video};
8585
use web_api::ManagerExt as WebManagerExt;
8686
use windows::{CapWindowId, EditorWindowIds, ShowCapWindow, set_window_transparent};
8787

88-
use crate::{camera::CameraPreviewManager, recording_settings::RecordingSettingsStore};
88+
use crate::{
89+
camera::CameraPreviewManager,
90+
recording_settings::{RecordingSettingsStore, RecordingTargetMode},
91+
};
8992
use crate::{recording::start_recording, upload::build_video_meta};
9093

9194
#[allow(clippy::large_enum_variant)]
@@ -307,6 +310,11 @@ pub struct RequestStartRecording {
307310
#[derive(Deserialize, specta::Type, Serialize, tauri_specta::Event, Debug, Clone)]
308311
pub struct RequestNewScreenshot;
309312

313+
#[derive(Deserialize, specta::Type, Serialize, tauri_specta::Event, Debug, Clone)]
314+
pub struct RequestOpenRecordingPicker {
315+
pub target_mode: Option<RecordingTargetMode>,
316+
}
317+
310318
#[derive(Deserialize, specta::Type, Serialize, tauri_specta::Event, Debug, Clone)]
311319
pub struct RequestOpenSettings {
312320
page: String,
@@ -1902,6 +1910,7 @@ pub async fn run(recording_logging_handle: LoggingHandle) {
19021910
RecordingStarted,
19031911
RecordingStopped,
19041912
RequestStartRecording,
1913+
RequestOpenRecordingPicker,
19051914
RequestNewScreenshot,
19061915
RequestOpenSettings,
19071916
NewNotification,
@@ -1912,7 +1921,7 @@ pub async fn run(recording_logging_handle: LoggingHandle) {
19121921
recording::RecordingEvent,
19131922
RecordingDeleted,
19141923
target_select_overlay::TargetUnderCursor,
1915-
hotkeys::OnEscapePress
1924+
hotkeys::OnEscapePress,
19161925
])
19171926
.error_handling(tauri_specta::ErrorHandlingMode::Throw)
19181927
.typ::<ProjectConfiguration>()
@@ -1973,7 +1982,13 @@ pub async fn run(recording_logging_handle: LoggingHandle) {
19731982
.map(PathBuf::from)
19741983
else {
19751984
let app = app.clone();
1976-
tokio::spawn(async move { ShowCapWindow::Main.show(&app).await });
1985+
tokio::spawn(async move {
1986+
ShowCapWindow::Main {
1987+
init_target_mode: None,
1988+
}
1989+
.show(&app)
1990+
.await
1991+
});
19771992
return;
19781993
};
19791994

@@ -2112,7 +2127,11 @@ pub async fn run(recording_logging_handle: LoggingHandle) {
21122127
} else {
21132128
println!("Permissions granted, showing main window");
21142129

2115-
let _ = ShowCapWindow::Main.show(&app).await;
2130+
let _ = ShowCapWindow::Main {
2131+
init_target_mode: None,
2132+
}
2133+
.show(&app)
2134+
.await;
21162135
}
21172136
}
21182137
});
@@ -2121,7 +2140,7 @@ pub async fn run(recording_logging_handle: LoggingHandle) {
21212140

21222141
tray::create_tray(&app).unwrap();
21232142

2124-
RequestStartRecording::listen_any_spawn(&app, |event, app| async move {
2143+
RequestStartRecording::listen_any_spawn(&app, async |event, app| {
21252144
if CapWindowId::Main.get(&app).is_some() {
21262145
return;
21272146
};
@@ -2150,13 +2169,15 @@ pub async fn run(recording_logging_handle: LoggingHandle) {
21502169
.await;
21512170
});
21522171

2153-
RequestNewScreenshot::listen_any_spawn(&app, |_, app| async move {
2154-
if let Err(e) = take_screenshot(app.clone(), app.state()).await {
2155-
eprintln!("Failed to take screenshot: {e}");
2172+
RequestOpenRecordingPicker::listen_any_spawn(&app, async |event, app| {
2173+
let _ = ShowCapWindow::Main {
2174+
init_target_mode: event.target_mode,
21562175
}
2176+
.show(&app)
2177+
.await;
21572178
});
21582179

2159-
RequestOpenSettings::listen_any_spawn(&app, |payload, app| async move {
2180+
RequestOpenSettings::listen_any_spawn(&app, async |payload, app| {
21602181
let _ = ShowCapWindow::Settings {
21612182
page: Some(payload.page),
21622183
}
@@ -2181,6 +2202,15 @@ pub async fn run(recording_logging_handle: LoggingHandle) {
21812202
match window_id {
21822203
CapWindowId::Main => {
21832204
let app = app.clone();
2205+
2206+
for (id, window) in app.webview_windows() {
2207+
if let Ok(CapWindowId::TargetSelectOverlay { .. }) =
2208+
CapWindowId::from_str(&id)
2209+
{
2210+
let _ = window.close();
2211+
}
2212+
}
2213+
21842214
tokio::spawn(async move {
21852215
let state = app.state::<ArcLock<App>>();
21862216
let app_state = &mut *state.write().await;
@@ -2306,7 +2336,11 @@ pub async fn run(recording_logging_handle: LoggingHandle) {
23062336
} else {
23072337
let handle = _handle.clone();
23082338
tokio::spawn(async move {
2309-
let _ = ShowCapWindow::Main.show(&handle).await;
2339+
let _ = ShowCapWindow::Main {
2340+
init_target_mode: None,
2341+
}
2342+
.show(&handle)
2343+
.await;
23102344
});
23112345
}
23122346
}

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -634,7 +634,11 @@ pub async fn delete_recording(app: AppHandle, state: MutableState<'_, App>) -> R
634634
match settings.post_deletion_behaviour {
635635
PostDeletionBehaviour::DoNothing => {}
636636
PostDeletionBehaviour::ReopenRecordingWindow => {
637-
let _ = ShowCapWindow::Main.show(&app).await;
637+
let _ = ShowCapWindow::Main {
638+
init_target_mode: None,
639+
}
640+
.show(&app)
641+
.await;
638642
}
639643
}
640644
}

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ use serde_json::json;
33
use tauri::{AppHandle, Wry};
44
use tauri_plugin_store::StoreExt;
55

6-
#[derive(serde::Serialize, serde::Deserialize, specta::Type, Debug, Clone)]
6+
#[derive(serde::Serialize, serde::Deserialize, specta::Type, Debug, Clone, Copy)]
7+
#[serde(rename_all = "camelCase")]
78
pub enum RecordingTargetMode {
89
Display,
910
Window,

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,11 @@ pub fn create_tray(app: &AppHandle) -> tauri::Result<()> {
102102
Ok(TrayItem::OpenCap) => {
103103
let app = app.clone();
104104
tokio::spawn(async move {
105-
let _ = ShowCapWindow::Main.show(&app).await;
105+
let _ = ShowCapWindow::Main {
106+
init_target_mode: None,
107+
}
108+
.show(&app)
109+
.await;
106110
});
107111
}
108112
Ok(TrayItem::TakeScreenshot) => {

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

Lines changed: 59 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ use crate::{
2323
App, ArcLock, fake_window,
2424
general_settings::{AppTheme, GeneralSettingsStore},
2525
permissions,
26+
recording_settings::RecordingTargetMode,
2627
target_select_overlay::WindowFocusManager,
2728
};
2829

@@ -177,15 +178,29 @@ impl CapWindowId {
177178
#[derive(Clone, Type, Deserialize)]
178179
pub enum ShowCapWindow {
179180
Setup,
180-
Main,
181-
Settings { page: Option<String> },
182-
Editor { project_path: PathBuf },
181+
Main {
182+
init_target_mode: Option<RecordingTargetMode>,
183+
},
184+
Settings {
185+
page: Option<String>,
186+
},
187+
Editor {
188+
project_path: PathBuf,
189+
},
183190
RecordingsOverlay,
184-
WindowCaptureOccluder { screen_id: DisplayId },
185-
TargetSelectOverlay { display_id: DisplayId },
186-
CaptureArea { screen_id: DisplayId },
191+
WindowCaptureOccluder {
192+
screen_id: DisplayId,
193+
},
194+
TargetSelectOverlay {
195+
display_id: DisplayId,
196+
},
197+
CaptureArea {
198+
screen_id: DisplayId,
199+
},
187200
Camera,
188-
InProgressRecording { countdown: Option<u32> },
201+
InProgressRecording {
202+
countdown: Option<u32>,
203+
},
189204
Upgrade,
190205
ModeSelect,
191206
}
@@ -223,35 +238,43 @@ impl ShowCapWindow {
223238
.maximizable(false)
224239
.shadow(true)
225240
.build()?,
226-
Self::Main => {
227-
if permissions::do_permissions_check(false).necessary_granted() {
228-
let new_recording_flow = GeneralSettingsStore::get(app)
229-
.ok()
230-
.flatten()
231-
.map(|s| s.enable_new_recording_flow)
232-
.unwrap_or_default();
233-
234-
let window = self
235-
.window_builder(app, if new_recording_flow { "/new-main" } else { "/" })
236-
.resizable(false)
237-
.maximized(false)
238-
.maximizable(false)
239-
.minimizable(false)
240-
.always_on_top(true)
241-
.visible_on_all_workspaces(true)
242-
.content_protected(true)
243-
.center()
244-
.build()?;
241+
Self::Main { init_target_mode } => {
242+
if !permissions::do_permissions_check(false).necessary_granted() {
243+
return Box::pin(Self::Setup.show(app)).await;
244+
}
245245

246-
if new_recording_flow {
247-
#[cfg(target_os = "macos")]
248-
crate::platform::set_window_level(window.as_ref().window(), 50);
249-
}
246+
let new_recording_flow = GeneralSettingsStore::get(app)
247+
.ok()
248+
.flatten()
249+
.map(|s| s.enable_new_recording_flow)
250+
.unwrap_or_default();
250251

251-
window
252-
} else {
253-
Box::pin(Self::Setup.show(app)).await?
252+
let window = self
253+
.window_builder(app, if new_recording_flow { "/new-main" } else { "/" })
254+
.resizable(false)
255+
.maximized(false)
256+
.maximizable(false)
257+
.minimizable(false)
258+
.always_on_top(true)
259+
.visible_on_all_workspaces(true)
260+
.content_protected(true)
261+
.center()
262+
.initialization_script(format!(
263+
"
264+
window.__CAP__ = window.__CAP__ ?? {{}};
265+
window.__CAP__.initialTargetMode = {}
266+
",
267+
serde_json::to_string(init_target_mode)
268+
.expect("Failed to serialize initial target mode")
269+
))
270+
.build()?;
271+
272+
if new_recording_flow {
273+
#[cfg(target_os = "macos")]
274+
crate::platform::set_window_level(window.as_ref().window(), 50);
254275
}
276+
277+
window
255278
}
256279
Self::TargetSelectOverlay { display_id } => {
257280
let Some(display) = scap_targets::Display::from_id(display_id) else {
@@ -271,7 +294,8 @@ impl ShowCapWindow {
271294
.always_on_top(true)
272295
.visible_on_all_workspaces(true)
273296
.skip_taskbar(true)
274-
.transparent(true);
297+
.transparent(true)
298+
.visible(false);
275299

276300
#[cfg(target_os = "macos")]
277301
{
@@ -700,7 +724,7 @@ impl ShowCapWindow {
700724
pub fn id(&self, app: &AppHandle) -> CapWindowId {
701725
match self {
702726
ShowCapWindow::Setup => CapWindowId::Setup,
703-
ShowCapWindow::Main => CapWindowId::Main,
727+
ShowCapWindow::Main { .. } => CapWindowId::Main,
704728
ShowCapWindow::Settings { .. } => CapWindowId::Settings,
705729
ShowCapWindow::Editor { project_path } => {
706730
let state = app.state::<EditorWindowIds>();

apps/desktop/src/routes/(window-chrome)/new-main/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ function Page() {
120120

121121
onMount(async () => {
122122
// We don't want the target select overlay on launch
123-
setOptions({ targetMode: null });
123+
setOptions({ targetMode: window.__CAP__.initialTargetMode });
124124

125125
// Enforce window size with multiple safeguards
126126
const currentWindow = getCurrentWindow();

0 commit comments

Comments
 (0)