Skip to content

Commit b4b623a

Browse files
committed
Improve screenshot editor image handling and target selection
1 parent 44d96d4 commit b4b623a

8 files changed

Lines changed: 64 additions & 32 deletions

File tree

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ pub struct ScreenshotEditorInstance {
3232
pub config_tx: watch::Sender<ProjectConfiguration>,
3333
pub path: PathBuf,
3434
pub pretty_name: String,
35+
pub image_width: u32,
36+
pub image_height: u32,
3537
}
3638

3739
impl ScreenshotEditorInstance {
@@ -300,6 +302,8 @@ impl ScreenshotEditorInstances {
300302
config_tx,
301303
path: path.clone(),
302304
pretty_name: recording_meta.pretty_name.clone(),
305+
image_width: width,
306+
image_height: height,
303307
});
304308

305309
// Spawn render loop
@@ -429,6 +433,8 @@ pub struct SerializedScreenshotEditorInstance {
429433
pub path: PathBuf,
430434
pub config: Option<ProjectConfiguration>,
431435
pub pretty_name: String,
436+
pub image_width: u32,
437+
pub image_height: u32,
432438
}
433439

434440
#[tauri::command]
@@ -459,6 +465,8 @@ pub async fn create_screenshot_editor_instance(
459465
path: instance.path.clone(),
460466
config: Some(config),
461467
pretty_name: instance.pretty_name.clone(),
468+
image_width: instance.image_width,
469+
image_height: instance.image_height,
462470
})
463471
}
464472

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

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -697,15 +697,12 @@ pub fn create_tray(app: &AppHandle) -> tauri::Result<()> {
697697
Ok(TrayItem::TakeScreenshot) => {
698698
let app = app.clone();
699699
tokio::spawn(async move {
700-
use cap_recording::screen_capture::{ScreenCaptureTarget, list_displays};
700+
use cap_recording::screen_capture::ScreenCaptureTarget;
701+
use scap_targets::Display;
701702

702-
let displays = list_displays();
703-
let Some((display, _)) = displays.into_iter().next() else {
704-
tracing::error!("No displays found for screenshot");
705-
return;
706-
};
707-
708-
let target = ScreenCaptureTarget::Display { id: display.id };
703+
let display =
704+
Display::get_containing_cursor().unwrap_or_else(Display::primary);
705+
let target = ScreenCaptureTarget::Display { id: display.id() };
709706

710707
match recording::take_screenshot(app.clone(), target).await {
711708
Ok(path) => {

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

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -645,6 +645,7 @@ function Page() {
645645
await commands.openTargetSelectOverlays(
646646
{ variant: "display", id: target.id },
647647
null,
648+
"display",
648649
);
649650
setOptions("targetMode", "display");
650651
};
@@ -659,6 +660,7 @@ function Page() {
659660
await commands.openTargetSelectOverlays(
660661
{ variant: "window", id: target.id },
661662
null,
663+
"window",
662664
);
663665
setOptions("targetMode", "window");
664666

@@ -686,7 +688,7 @@ function Page() {
686688
};
687689
const targetMode = __CAP__?.initialTargetMode ?? null;
688690
if (targetMode) {
689-
await commands.openTargetSelectOverlays(null, null);
691+
await commands.openTargetSelectOverlays(null, null, targetMode);
690692
setOptions({ targetMode });
691693
} else {
692694
setOptions({ targetMode });
@@ -720,7 +722,11 @@ function Page() {
720722
const newTargetMode = event.payload.target_mode;
721723
const displayId = event.payload.display_id;
722724
if (newTargetMode) {
723-
await commands.openTargetSelectOverlays(null, displayId);
725+
await commands.openTargetSelectOverlays(
726+
null,
727+
displayId,
728+
newTargetMode,
729+
);
724730
setOptions({ targetMode: newTargetMode });
725731
} else {
726732
setOptions({ targetMode: newTargetMode });
@@ -859,11 +865,11 @@ function Page() {
859865
if (isRecording()) return;
860866
const nextMode = rawOptions.targetMode === mode ? null : mode;
861867
if (nextMode) {
862-
await commands.openTargetSelectOverlays(null, null);
868+
await commands.openTargetSelectOverlays(null, null, nextMode);
863869
setOptions("targetMode", nextMode);
864870
} else {
865871
setOptions("targetMode", nextMode);
866-
commands.closeTargetSelectOverlays();
872+
await commands.closeTargetSelectOverlays();
867873
}
868874
};
869875

apps/desktop/src/routes/screenshot-editor/Editor.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,13 @@ function Dialogs() {
162162
useScreenshotEditorContext();
163163

164164
const path = () => editorInstance()?.path ?? "";
165+
const imagePath = () => {
166+
const p = path();
167+
if (p.endsWith(".cap")) {
168+
return `${p}/original.png`;
169+
}
170+
return p;
171+
};
165172

166173
return (
167174
<Dialog.Root
@@ -414,7 +421,7 @@ function Dialogs() {
414421
<img
415422
class="w-full h-full pointer-events-none select-none"
416423
alt="screenshot"
417-
src={convertFileSrc(path())}
424+
src={convertFileSrc(imagePath())}
418425
/>
419426
</Cropper>
420427
</div>

apps/desktop/src/routes/screenshot-editor/Header.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ import { useScreenshotExport } from "./useScreenshotExport";
3333

3434
export function Header() {
3535
const ctx = useScreenshotEditorContext();
36-
const { setDialog, project, originalImageSize } = ctx;
36+
const { setDialog, project, originalImageSize, isImageFileReady } = ctx;
3737
const path = () => ctx.editorInstance()?.path ?? "";
3838

3939
const { exportImage, isExporting } = useScreenshotExport();
@@ -99,7 +99,7 @@ export function Header() {
9999
});
100100
};
101101

102-
const isCropDisabled = () => !originalImageSize();
102+
const isCropDisabled = () => !originalImageSize() || !isImageFileReady();
103103

104104
return (
105105
<div

apps/desktop/src/routes/screenshot-editor/context.tsx

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,7 @@ function createScreenshotEditorContext() {
190190
height: number;
191191
} | null>(null);
192192
const [isRenderReady, setIsRenderReady] = createSignal(false);
193+
const [isImageFileReady, setIsImageFileReady] = createSignal(false);
193194
let wsRef: WebSocket | null = null;
194195

195196
const [editorInstance] = createResource(async () => {
@@ -202,18 +203,20 @@ function createScreenshotEditorContext() {
202203
}
203204
}
204205

206+
setOriginalImageSize({
207+
width: instance.imageWidth,
208+
height: instance.imageHeight,
209+
});
210+
205211
const hasReceivedWebSocketFrame = { value: false };
206212

207213
if (instance.path) {
208-
const loadImage = (imagePath: string) => {
214+
const loadImage = (imagePath: string, retryCount = 0) => {
209215
const img = new Image();
210216
img.crossOrigin = "anonymous";
211217
img.src = convertFileSrc(imagePath);
212218
img.onload = async () => {
213-
setOriginalImageSize({
214-
width: img.naturalWidth,
215-
height: img.naturalHeight,
216-
});
219+
setIsImageFileReady(true);
217220
if (hasReceivedWebSocketFrame.value) {
218221
return;
219222
}
@@ -240,21 +243,18 @@ function createScreenshotEditorContext() {
240243
);
241244
}
242245
};
246+
img.onerror = () => {
247+
if (retryCount < 10) {
248+
setTimeout(() => loadImage(imagePath, retryCount + 1), 200);
249+
}
250+
};
243251
return img;
244252
};
245253

246254
const pathStr = instance.path;
247255
const isCapDir = pathStr.endsWith(".cap");
248-
249-
if (isCapDir) {
250-
const originalPath = `${pathStr}/original.png`;
251-
const img = loadImage(originalPath);
252-
img.onerror = () => {
253-
loadImage(pathStr);
254-
};
255-
} else {
256-
loadImage(pathStr);
257-
}
256+
const imagePath = isCapDir ? `${pathStr}/original.png` : pathStr;
257+
loadImage(imagePath);
258258
}
259259

260260
const ws = new WebSocket(instance.framesSocketUrl);
@@ -660,6 +660,7 @@ function createScreenshotEditorContext() {
660660
latestFrame,
661661
originalImageSize,
662662
isRenderReady,
663+
isImageFileReady,
663664
editorInstance,
664665
};
665666
}

apps/desktop/src/routes/target-select-overlay.tsx

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -395,7 +395,7 @@ function Inner() {
395395
setOptions({
396396
targetMode: "area",
397397
});
398-
commands.openTargetSelectOverlays(null, null);
398+
commands.openTargetSelectOverlays(null, null, "area");
399399
}}
400400
>
401401
Adjust recording area
@@ -883,6 +883,19 @@ function RecordingControls(props: {
883883
}));
884884
const setCamera = createCameraMutation();
885885

886+
onMount(() => {
887+
if (rawOptions.micName) {
888+
setMicInput
889+
.mutateAsync(rawOptions.micName)
890+
.catch((error) => console.error("Failed to set mic input:", error));
891+
}
892+
893+
if (rawOptions.cameraID && "ModelID" in rawOptions.cameraID)
894+
setCamera.mutate({ ModelID: rawOptions.cameraID.ModelID });
895+
else if (rawOptions.cameraID && "DeviceID" in rawOptions.cameraID)
896+
setCamera.mutate({ DeviceID: rawOptions.cameraID.DeviceID });
897+
});
898+
886899
const selectedCamera = createMemo(() => {
887900
if (!rawOptions.cameraID) return null;
888901
return findCamera(cameras(), rawOptions.cameraID) ?? null;

apps/desktop/src/utils/tauri.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -533,7 +533,7 @@ export type ScreenCaptureTarget = { variant: "window"; id: WindowId } | { varian
533533
export type ScreenMovementSpring = { stiffness: number; damping: number; mass: number }
534534
export type SegmentRecordings = { display: Video; camera: Video | null; mic: Audio | null; system_audio: Audio | null }
535535
export type SerializedEditorInstance = { framesSocketUrl: string; recordingDuration: number; savedProjectConfig: ProjectConfiguration; recordings: ProjectRecordingsMeta; path: string }
536-
export type SerializedScreenshotEditorInstance = { framesSocketUrl: string; path: string; config: ProjectConfiguration | null; prettyName: string }
536+
export type SerializedScreenshotEditorInstance = { framesSocketUrl: string; path: string; config: ProjectConfiguration | null; prettyName: string; imageWidth: number; imageHeight: number }
537537
export type SetCaptureAreaPending = boolean
538538
export type ShadowConfiguration = { size: number; opacity: number; blur: number }
539539
export type SharingMeta = { id: string; link: string }

0 commit comments

Comments
 (0)