Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
a49b55a
no NSPanel for main, fix traffic lights, editor header height
ItsEeleeya May 5, 2026
49ba3b6
Bump versions for tauri, tauri-cli, objc2-app-kit
ItsEeleeya May 6, 2026
75ba1b3
Fix theme change, rename AppTheme to Appearance
ItsEeleeya May 7, 2026
5faa712
Correctly display crop options context menu onMouseDown
ItsEeleeya May 7, 2026
fca4ebf
Add more associated file info like UTTypeConformsTo and UTTypeIdentif…
ItsEeleeya May 7, 2026
33bab32
Use non activating style mask on NSPanels and don't make them focused.
ItsEeleeya May 7, 2026
5fea9e1
no NSPanel for main, fix traffic lights, editor header height
ItsEeleeya May 5, 2026
8eb3360
Bump versions for tauri, tauri-cli, objc2-app-kit
ItsEeleeya May 6, 2026
46b007b
Fix theme change, rename AppTheme to Appearance
ItsEeleeya May 7, 2026
ed3c86b
Correctly display crop options context menu onMouseDown
ItsEeleeya May 7, 2026
819f9d9
Add more associated file info like UTTypeConformsTo and UTTypeIdentif…
ItsEeleeya May 7, 2026
4a1fad4
Use non activating style mask on NSPanels and don't make them focused.
ItsEeleeya May 7, 2026
4f19b49
Merge branch 'next-base-improvements' of https://github.com/ItsEeleey…
ItsEeleeya May 8, 2026
750783a
Merge branch 'main' into next-base-improvements
ItsEeleeya May 9, 2026
2b0a417
update tauri-build
ItsEeleeya May 9, 2026
3b39cf9
Use `data-tauri-drag-region="deep"`
ItsEeleeya May 9, 2026
7643967
Merge branch 'main' into next-base-improvements
ItsEeleeya May 11, 2026
038ba02
Merge remote-tracking branch 'upstream/main' into next-base-improvements
ItsEeleeya May 12, 2026
79c5f56
Rename ShowCapWindow to CapWindow
ItsEeleeya May 12, 2026
31f364f
Move display math into display_utils.rs, remove now unused is_system_…
ItsEeleeya May 12, 2026
b92ed38
Better window auto show handling
ItsEeleeya May 12, 2026
2827347
Merge remote-tracking branch 'upstream/main' into next-base-improvements
ItsEeleeya May 12, 2026
aea51c7
clippy
ItsEeleeya May 12, 2026
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
780 changes: 515 additions & 265 deletions Cargo.lock

Large diffs are not rendered by default.

20 changes: 10 additions & 10 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@ members = [
]

[workspace.dependencies]
anyhow = { version = "1.0.86" }
anyhow = { version = "1.0.102" }
# 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/CapSoftware/cpal", rev = "3cc779a7b4ca" }
ffmpeg = { package = "ffmpeg-next", git = "https://github.com/CapSoftware/rust-ffmpeg", rev = "49db1fede112" }
tokio = { version = "1.39.3", features = [
tokio = { version = "1.52.2", features = [
"macros",
"process",
"fs",
Expand All @@ -22,7 +22,7 @@ tokio = { version = "1.39.3", features = [
"rt-multi-thread",
"time",
] }
tauri = { version = "2.5.0", features = ["specta"] }
tauri = { version = "2.11.0", features = ["specta"] }
specta = { version = "=2.0.0-rc.20", features = [
"derive",
"serde_json",
Expand All @@ -37,18 +37,18 @@ nokhwa = { git = "https://github.com/CapSoftware/nokhwa", rev = "b9c8079e82e2",
"serialize",
] }
nokhwa-bindings-macos = { git = "https://github.com/CapSoftware/nokhwa", rev = "b9c8079e82e2" }
wgpu = { version = "25.0.0", features = ["wgpu-core"] }
wgpu-hal = "25.0.0"
wgpu-core = "25.0.0"
flume = "0.11.0"
wgpu = { version = "25.0.2", features = ["wgpu-core"] }
wgpu-hal = "25.0.2"
wgpu-core = "25.0.2"
flume = "0.11.1"
thiserror = "1.0"
sentry = { version = "0.42.0", features = [
"anyhow",
"backtrace",
"debug-images",
] }
tracing = "0.1.41"
futures = "0.3.31"
tracing = "0.1.44"
futures = "0.3.32"
aho-corasick = "1.1.4"

cidre = { git = "https://github.com/CapSoftware/cidre", rev = "bf84b67079a8", features = [
Expand All @@ -72,7 +72,7 @@ windows = "0.60.0"
windows-core = "0.60"
windows-sys = "0.59.0"
windows-capture = "1.5.0"
percent-encoding = "2.3.1"
percent-encoding = "2.3.2"
sysinfo = "0.32"

# TODO: Reenable these: https://github.com/CapSoftware/Cap/issues/859
Expand Down
4 changes: 2 additions & 2 deletions apps/desktop/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
"@solidjs/router": "^0.14.2",
"@solidjs/start": "^1.1.3",
"@tanstack/solid-query": "^5.51.21",
"@tauri-apps/api": "2.8.0",
"@tauri-apps/api": "2.11.0",
"@tauri-apps/plugin-clipboard-manager": "^2.3.0",
"@tauri-apps/plugin-deep-link": "^2.4.1",
"@tauri-apps/plugin-dialog": "^2.4.0",
Expand Down Expand Up @@ -77,7 +77,7 @@
"@fontsource/geist-sans": "^5.0.3",
"@webgpu/types": "^0.1.44",
"@iconify/json": "^2.2.239",
"@tauri-apps/cli": ">=2.1.0",
"@tauri-apps/cli": ">=2.11.0",
"@total-typescript/ts-reset": "^0.6.1",
"@types/dom-webcodecs": "^0.1.11",
"@types/uuid": "^9.0.8",
Expand Down
4 changes: 2 additions & 2 deletions apps/desktop/src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ name = "cap_desktop_lib"
crate-type = ["lib", "cdylib", "staticlib"]

[build-dependencies]
tauri-build = { version = "2.1.0", features = [] }
tauri-build = { version = "2.6.1", features = [] }

[target.'cfg(target_os = "macos")'.build-dependencies]
swift-rs = { version = "1.0.6", features = ["build"] }
Expand Down Expand Up @@ -126,7 +126,7 @@ aho-corasick.workspace = true
[target.'cfg(target_os = "macos")'.dependencies]
core-graphics = "0.24.0"
core-foundation = "0.10.0"
objc2-app-kit = { version = "0.3.0", features = [
objc2-app-kit = { version = "0.3.2", features = [
"NSWindow",
"NSResponder",
"NSHapticFeedback",
Expand Down
1 change: 1 addition & 0 deletions apps/desktop/src-tauri/capabilities/default.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
"core:webview:default",
"core:webview:allow-create-webview-window",
"core:app:allow-version",
"core:app:allow-set-app-theme",
"shell:default",
"core:image:default",
"dialog:default",
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
Expand Up @@ -316,7 +316,7 @@ fn wait_for_shutdown_signal(runtime: &Runtime, receiver: oneshot::Receiver<()>,
// crashes after ~4-5 toggle cycles.
//
// Pause/Resume hide/show the window while keeping GPU resources alive.
// See the comment in windows.rs ShowCapWindow::Camera for the critical detail
// See the comment in windows.rs CapWindow::Camera for the critical detail
// about avoiding order_front_regardless().
#[derive(Clone)]
enum ReconfigureEvent {
Expand Down
4 changes: 2 additions & 2 deletions apps/desktop/src-tauri/src/deeplink_actions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use std::path::{Path, PathBuf};
use tauri::{AppHandle, Manager, Url};
use tracing::trace;

use crate::{App, ArcLock, recording::StartRecordingInputs, windows::ShowCapWindow};
use crate::{App, ArcLock, recording::StartRecordingInputs, windows::CapWindow};

#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
Expand Down Expand Up @@ -151,7 +151,7 @@ impl DeepLinkAction {
crate::open_project_from_path(Path::new(&project_path), app.clone())
}
DeepLinkAction::OpenSettings { page } => {
crate::show_window(app.clone(), ShowCapWindow::Settings { page }).await
crate::show_window(app.clone(), CapWindow::Settings { page }).await
}
}
}
Expand Down
168 changes: 168 additions & 0 deletions apps/desktop/src-tauri/src/display_utils.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
use scap_targets::Display;
use tauri::{PhysicalPosition, PhysicalSize};

// Credits: tauri-plugin-window-state
pub trait MonitorExt {
fn intersects(
&self,
position: PhysicalPosition<i32>,
size: PhysicalSize<u32>,
scale: f64,
) -> bool;

fn intersects_window(&self, window: tauri::Window) -> tauri::Result<bool>;
}

impl MonitorExt for Display {
fn intersects_window(&self, window: tauri::Window) -> tauri::Result<bool> {
Ok(self.intersects(
window.outer_position()?,
window.outer_size()?,
window.scale_factor()?,
))
}

fn intersects(
&self,
position: PhysicalPosition<i32>,
size: PhysicalSize<u32>,
_scale: f64,
) -> bool {
#[cfg(target_os = "macos")]
{
let Some(bounds) = self.raw_handle().logical_bounds() else {
return false;
};

let left = (bounds.position().x() * _scale) as i32;
let right = left + (bounds.size().width() * _scale) as i32;
let top = (bounds.position().y() * _scale) as i32;
let bottom = top + (bounds.size().height() * _scale) as i32;

[
(position.x, position.y),
(position.x + size.width as i32, position.y),
(position.x, position.y + size.height as i32),
(
position.x + size.width as i32,
position.y + size.height as i32,
),
]
.into_iter()
.any(|(x, y)| x >= left && x < right && y >= top && y < bottom)
}

#[cfg(windows)]
{
let Some(bounds) = self.raw_handle().physical_bounds() else {
return false;
};

let left = bounds.position().x() as i32;
let right = left + bounds.size().width() as i32;
let top = bounds.position().y() as i32;
let bottom = top + bounds.size().height() as i32;

[
(position.x, position.y),
(position.x + size.width as i32, position.y),
(position.x, position.y + size.height as i32),
(
position.x + size.width as i32,
position.y + size.height as i32,
),
]
.into_iter()
.any(|(x, y)| x >= left && x < right && y >= top && y < bottom)
}
}
}

const DEFAULT_FALLBACK_DISPLAY_WIDTH: f64 = 1920.0;
const DEFAULT_FALLBACK_DISPLAY_HEIGHT: f64 = 1080.0;
pub struct CursorMonitorInfo {
pub x: f64,
pub y: f64,
pub width: f64,
pub height: f64,
}

impl CursorMonitorInfo {
pub fn get() -> Self {
let display = Display::get_containing_cursor().unwrap_or_else(Display::primary);
let bounds = display.raw_handle().logical_bounds();
let (x, y, width, height) = bounds
.map(|b| {
(
b.position().x(),
b.position().y(),
b.size().width(),
b.size().height(),
)
})
.unwrap_or((
0.0,
0.0,
DEFAULT_FALLBACK_DISPLAY_WIDTH,
DEFAULT_FALLBACK_DISPLAY_HEIGHT,
));

Self {
x,
y,
width,
height,
}
}

pub fn center_position(&self, window_width: f64, window_height: f64) -> (f64, f64) {
let pos_x = self.x + (self.width - window_width) / 2.0;
let pos_y = self.y + (self.height - window_height) / 2.0;
(pos_x, pos_y)
}

pub fn bottom_center_position(
&self,
window_width: f64,
window_height: f64,
offset_y: f64,
) -> (f64, f64) {
let pos_x = self.x + (self.width - window_width) / 2.0;
let pos_y = self.y + self.height - window_height - offset_y;
(pos_x, pos_y)
}

pub fn from_window(window: &tauri::WebviewWindow) -> Self {
let window_pos = window
.outer_position()
.ok()
.map(|p| (p.x as f64, p.y as f64))
.unwrap_or((0.0, 0.0));

for display in Display::list() {
if let Some(bounds) = display.raw_handle().logical_bounds() {
let (x, y, width, height) = (
bounds.position().x(),
bounds.position().y(),
bounds.size().width(),
bounds.size().height(),
);

if window_pos.0 >= x
&& window_pos.0 < x + width
&& window_pos.1 >= y
&& window_pos.1 < y + height
{
return Self {
x,
y,
width,
height,
};
}
}
}

Self::get()
}
}
2 changes: 1 addition & 1 deletion apps/desktop/src-tauri/src/fake_window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -317,7 +317,7 @@ pub fn spawn_fake_window_listener(app: AppHandle, window: WebviewWindow) {
let focused = window.is_focused().unwrap_or(false);
if !ignore {
if !focused {
window.set_focus().ok();
// window.set_focus().ok();
}
} else if focused {
window.set_ignore_cursor_events(ignore).ok();
Expand Down
18 changes: 14 additions & 4 deletions apps/desktop/src-tauri/src/general_settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -136,8 +136,8 @@ pub struct GeneralSettingsStore {
pub disable_auto_open_links: bool,
#[serde(default = "default_true")]
pub has_completed_startup: bool,
#[serde(default)]
pub theme: AppTheme,
#[serde(default, alias = "theme")]
pub appearance: Appearance,
#[serde(default)]
pub commercial_license: Option<CommercialLicense>,
#[serde(default)]
Expand Down Expand Up @@ -251,7 +251,7 @@ impl Default for GeneralSettingsStore {
enable_notifications: true,
disable_auto_open_links: false,
has_completed_startup: false,
theme: AppTheme::System,
appearance: Appearance::System,
commercial_license: None,
last_version: None,
window_transparency: false,
Expand Down Expand Up @@ -285,13 +285,23 @@ impl Default for GeneralSettingsStore {

#[derive(Default, Debug, Copy, Clone, Serialize, Deserialize, Type)]
#[serde(rename_all = "camelCase")]
pub enum AppTheme {
pub enum Appearance {
#[default]
System,
Light,
Dark,
}

impl Into<Option<tauri::Theme>> for Appearance {
fn into(self) -> Option<tauri::Theme> {
match self {
Self::Light => Some(tauri::Theme::Light),
Self::Dark => Some(tauri::Theme::Dark),
Self::System => None,
}
}
}

impl GeneralSettingsStore {
pub fn get(app: &AppHandle<Wry>) -> Result<Option<Self>, String> {
match app.store("store").map(|s| s.get("general_settings")) {
Expand Down
8 changes: 4 additions & 4 deletions apps/desktop/src-tauri/src/hotkeys.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use crate::{
RequestOpenRecordingPicker, RequestStartRecording, recording,
recording_settings::{RecordingSettingsStore, RecordingTargetMode},
tray,
windows::ShowCapWindow,
windows::CapWindow,
};
use cap_recording::screen_capture::ScreenCaptureTarget;
use global_hotkey::HotKeyState;
Expand Down Expand Up @@ -102,7 +102,7 @@ pub fn init(app: &AppHandle) {
if shortcut.key == Code::Comma && shortcut.mods == Modifiers::META {
let app = app.clone();
tokio::spawn(async move {
let _ = ShowCapWindow::Settings { page: None }.show(&app).await;
let _ = CapWindow::Settings { page: None }.show(&app).await;
});
}

Expand Down Expand Up @@ -212,7 +212,7 @@ async fn handle_hotkey(app: AppHandle, action: HotkeyAction) -> Result<(), Strin

match recording::take_screenshot(app.clone(), target).await {
Ok(path) => {
let _ = ShowCapWindow::ScreenshotEditor { path }.show(&app).await;
let _ = CapWindow::ScreenshotEditor { path }.show(&app).await;
Ok(())
}
Err(e) => Err(format!("Failed to take screenshot: {e}")),
Expand All @@ -229,7 +229,7 @@ async fn handle_hotkey(app: AppHandle, action: HotkeyAction) -> Result<(), Strin

match recording::take_screenshot(app.clone(), target).await {
Ok(path) => {
let _ = ShowCapWindow::ScreenshotEditor { path }.show(&app).await;
let _ = CapWindow::ScreenshotEditor { path }.show(&app).await;
Ok(())
}
Err(e) => Err(format!("Failed to take screenshot: {e}")),
Expand Down
Loading
Loading