mirror of
https://github.com/zhom/donutbrowser.git
synced 2026-06-11 17:27:54 +02:00
Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e98d02a585 | |||
| afa2326584 | |||
| d25d8549e4 | |||
| 662b370ed0 |
@@ -1,6 +1,24 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
|
||||||
|
## v0.25.2 (2026-06-02)
|
||||||
|
|
||||||
|
### Refactoring
|
||||||
|
|
||||||
|
- cleanup
|
||||||
|
|
||||||
|
### Documentation
|
||||||
|
|
||||||
|
- update CHANGELOG.md and README.md for v0.25.1 [skip ci] (#412)
|
||||||
|
|
||||||
|
### Maintenance
|
||||||
|
|
||||||
|
- chore: simplify linux repo publish
|
||||||
|
- chore: version bump
|
||||||
|
- chore: copy
|
||||||
|
- chore: update flake.nix for v0.25.1 [skip ci] (#413)
|
||||||
|
|
||||||
|
|
||||||
## v0.25.1 (2026-06-01)
|
## v0.25.1 (2026-06-01)
|
||||||
|
|
||||||
### Maintenance
|
### Maintenance
|
||||||
|
|||||||
@@ -46,7 +46,7 @@
|
|||||||
|
|
||||||
| | Apple Silicon | Intel |
|
| | Apple Silicon | Intel |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| **DMG** | [Download](https://github.com/zhom/donutbrowser/releases/download/v0.25.1/Donut_0.25.1_aarch64.dmg) | [Download](https://github.com/zhom/donutbrowser/releases/download/v0.25.1/Donut_0.25.1_x64.dmg) |
|
| **DMG** | [Download](https://github.com/zhom/donutbrowser/releases/download/v0.25.2/Donut_0.25.2_aarch64.dmg) | [Download](https://github.com/zhom/donutbrowser/releases/download/v0.25.2/Donut_0.25.2_x64.dmg) |
|
||||||
|
|
||||||
Or install via Homebrew:
|
Or install via Homebrew:
|
||||||
|
|
||||||
@@ -56,15 +56,15 @@ brew install --cask donut
|
|||||||
|
|
||||||
### Windows
|
### Windows
|
||||||
|
|
||||||
[Download Windows Installer (x64)](https://github.com/zhom/donutbrowser/releases/download/v0.25.1/Donut_0.25.1_x64-setup.exe) · [Portable (x64)](https://github.com/zhom/donutbrowser/releases/download/v0.25.1/Donut_0.25.1_x64-portable.zip)
|
[Download Windows Installer (x64)](https://github.com/zhom/donutbrowser/releases/download/v0.25.2/Donut_0.25.2_x64-setup.exe) · [Portable (x64)](https://github.com/zhom/donutbrowser/releases/download/v0.25.2/Donut_0.25.2_x64-portable.zip)
|
||||||
|
|
||||||
### Linux
|
### Linux
|
||||||
|
|
||||||
| Format | x86_64 | ARM64 |
|
| Format | x86_64 | ARM64 |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| **deb** | [Download](https://github.com/zhom/donutbrowser/releases/download/v0.25.1/Donut_0.25.1_amd64.deb) | [Download](https://github.com/zhom/donutbrowser/releases/download/v0.25.1/Donut_0.25.1_arm64.deb) |
|
| **deb** | [Download](https://github.com/zhom/donutbrowser/releases/download/v0.25.2/Donut_0.25.2_amd64.deb) | [Download](https://github.com/zhom/donutbrowser/releases/download/v0.25.2/Donut_0.25.2_arm64.deb) |
|
||||||
| **rpm** | [Download](https://github.com/zhom/donutbrowser/releases/download/v0.25.1/Donut-0.25.1-1.x86_64.rpm) | [Download](https://github.com/zhom/donutbrowser/releases/download/v0.25.1/Donut-0.25.1-1.aarch64.rpm) |
|
| **rpm** | [Download](https://github.com/zhom/donutbrowser/releases/download/v0.25.2/Donut-0.25.2-1.x86_64.rpm) | [Download](https://github.com/zhom/donutbrowser/releases/download/v0.25.2/Donut-0.25.2-1.aarch64.rpm) |
|
||||||
| **AppImage** | [Download](https://github.com/zhom/donutbrowser/releases/download/v0.25.1/Donut_0.25.1_amd64.AppImage) | [Download](https://github.com/zhom/donutbrowser/releases/download/v0.25.1/Donut_0.25.1_aarch64.AppImage) |
|
| **AppImage** | [Download](https://github.com/zhom/donutbrowser/releases/download/v0.25.2/Donut_0.25.2_amd64.AppImage) | [Download](https://github.com/zhom/donutbrowser/releases/download/v0.25.2/Donut_0.25.2_aarch64.AppImage) |
|
||||||
<!-- install-links-end -->
|
<!-- install-links-end -->
|
||||||
|
|
||||||
Or install via package manager:
|
Or install via package manager:
|
||||||
|
|||||||
@@ -96,17 +96,17 @@
|
|||||||
pkgConfigPath = lib.makeSearchPath "lib/pkgconfig" (
|
pkgConfigPath = lib.makeSearchPath "lib/pkgconfig" (
|
||||||
pkgConfigLibs ++ map lib.getDev pkgConfigLibs
|
pkgConfigLibs ++ map lib.getDev pkgConfigLibs
|
||||||
);
|
);
|
||||||
releaseVersion = "0.25.1";
|
releaseVersion = "0.25.2";
|
||||||
releaseAppImage =
|
releaseAppImage =
|
||||||
if system == "x86_64-linux" then
|
if system == "x86_64-linux" then
|
||||||
pkgs.fetchurl {
|
pkgs.fetchurl {
|
||||||
url = "https://github.com/zhom/donutbrowser/releases/download/v0.25.1/Donut_0.25.1_amd64.AppImage";
|
url = "https://github.com/zhom/donutbrowser/releases/download/v0.25.2/Donut_0.25.2_amd64.AppImage";
|
||||||
hash = "sha256-+wtKVCYUjDgXyL96oCqHC0ekWHIe9pLjn1RLBfWHamA=";
|
hash = "sha256-awESxsKfrSJFMAGbTasbXjL8UnF58ziLnS8Ee0phgb8=";
|
||||||
}
|
}
|
||||||
else if system == "aarch64-linux" then
|
else if system == "aarch64-linux" then
|
||||||
pkgs.fetchurl {
|
pkgs.fetchurl {
|
||||||
url = "https://github.com/zhom/donutbrowser/releases/download/v0.25.1/Donut_0.25.1_aarch64.AppImage";
|
url = "https://github.com/zhom/donutbrowser/releases/download/v0.25.2/Donut_0.25.2_aarch64.AppImage";
|
||||||
hash = "sha256-fEmf8OzYG3XoEHwOVLh1mONDcJEGeW3d4bb3y//6gPs=";
|
hash = "sha256-zOUWnvf+5stknWomHwYRUw2TR0aS4/XeiVySBjHuJLA=";
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
null;
|
null;
|
||||||
|
|||||||
+1
-1
@@ -2,7 +2,7 @@
|
|||||||
"name": "donutbrowser",
|
"name": "donutbrowser",
|
||||||
"private": true,
|
"private": true,
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"version": "0.25.2",
|
"version": "0.25.3",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "next dev --turbopack -p 12341",
|
"dev": "next dev --turbopack -p 12341",
|
||||||
|
|||||||
Generated
+1
-1
@@ -1784,7 +1784,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "donutbrowser"
|
name = "donutbrowser"
|
||||||
version = "0.25.2"
|
version = "0.25.3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aes 0.9.1",
|
"aes 0.9.1",
|
||||||
"aes-gcm",
|
"aes-gcm",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "donutbrowser"
|
name = "donutbrowser"
|
||||||
version = "0.25.2"
|
version = "0.25.3"
|
||||||
description = "Simple Yet Powerful Anti-Detect Browser"
|
description = "Simple Yet Powerful Anti-Detect Browser"
|
||||||
authors = ["zhom@github"]
|
authors = ["zhom@github"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|||||||
@@ -138,6 +138,46 @@ impl WayfernManager {
|
|||||||
fingerprint
|
fingerprint
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Derive the on-screen window size Chromium should open at, from the stored
|
||||||
|
/// fingerprint. `Wayfern.setFingerprint` only spoofs what the page *reports*
|
||||||
|
/// for `windowOuterWidth`/`screenWidth`/etc.; it does not move or resize the
|
||||||
|
/// real top-level window. Without `--window-size` the OS window keeps
|
||||||
|
/// Chromium's default, so the visible window contradicts the reported
|
||||||
|
/// dimensions — a detectable mismatch. We pass `--window-size` so the actual
|
||||||
|
/// window matches the fingerprint.
|
||||||
|
///
|
||||||
|
/// Keys are the camelCase fields Wayfern uses in its fingerprint
|
||||||
|
/// (`windowOuterWidth`, `screenAvailWidth`, …) — NOT the dotted
|
||||||
|
/// Camoufox-style keys. Preference order, matching how the fingerprint
|
||||||
|
/// describes the window:
|
||||||
|
/// 1. `windowOuterWidth` / `windowOuterHeight` — the real window size.
|
||||||
|
/// 2. `screenAvailWidth` / `screenAvailHeight` — usable screen area.
|
||||||
|
/// 3. `screenWidth` / `screenHeight` — full screen.
|
||||||
|
///
|
||||||
|
/// Returns `None` when the fingerprint carries no usable dimensions, leaving
|
||||||
|
/// Chromium's default untouched. The fingerprint JSON may be the bare object
|
||||||
|
/// or the legacy `{ "fingerprint": {...} }` wrapper.
|
||||||
|
fn window_size_from_fingerprint(fingerprint_json: &str) -> Option<(u32, u32)> {
|
||||||
|
let parsed: serde_json::Value = serde_json::from_str(fingerprint_json).ok()?;
|
||||||
|
let fp = parsed.get("fingerprint").unwrap_or(&parsed);
|
||||||
|
let obj = fp.as_object()?;
|
||||||
|
|
||||||
|
// Accept both numeric and stringified numbers (Wayfern emits numbers, but a
|
||||||
|
// CDP echo or older saved fingerprint may stringify them).
|
||||||
|
let read = |key: &str| -> Option<u32> {
|
||||||
|
let v = obj.get(key)?;
|
||||||
|
v.as_u64()
|
||||||
|
.or_else(|| v.as_str().and_then(|s| s.trim().parse::<u64>().ok()))
|
||||||
|
.filter(|n| *n > 0)
|
||||||
|
.map(|n| n as u32)
|
||||||
|
};
|
||||||
|
let pair = |w: &str, h: &str| -> Option<(u32, u32)> { Some((read(w)?, read(h)?)) };
|
||||||
|
|
||||||
|
pair("windowOuterWidth", "windowOuterHeight")
|
||||||
|
.or_else(|| pair("screenAvailWidth", "screenAvailHeight"))
|
||||||
|
.or_else(|| pair("screenWidth", "screenHeight"))
|
||||||
|
}
|
||||||
|
|
||||||
async fn wait_for_cdp_ready(
|
async fn wait_for_cdp_ready(
|
||||||
&self,
|
&self,
|
||||||
port: u16,
|
port: u16,
|
||||||
@@ -618,6 +658,18 @@ impl WayfernManager {
|
|||||||
|
|
||||||
if headless {
|
if headless {
|
||||||
args.push("--headless=new".to_string());
|
args.push("--headless=new".to_string());
|
||||||
|
} else if let Some((w, h)) = config
|
||||||
|
.fingerprint
|
||||||
|
.as_deref()
|
||||||
|
.and_then(Self::window_size_from_fingerprint)
|
||||||
|
{
|
||||||
|
// Size the real OS window to match the fingerprint so the visible window
|
||||||
|
// agrees with the reported windowOuterWidth/screen dimensions. Anchor at
|
||||||
|
// 0,0 so the window also fits within the spoofed screen origin. Skipped in
|
||||||
|
// headless mode, where there is no on-screen window.
|
||||||
|
log::info!("Sizing Wayfern window to fingerprint dimensions: {w}x{h}");
|
||||||
|
args.push(format!("--window-size={w},{h}"));
|
||||||
|
args.push("--window-position=0,0".to_string());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
@@ -1198,3 +1250,72 @@ impl WayfernManager {
|
|||||||
lazy_static::lazy_static! {
|
lazy_static::lazy_static! {
|
||||||
static ref WAYFERN_MANAGER: WayfernManager = WayfernManager::new();
|
static ref WAYFERN_MANAGER: WayfernManager = WayfernManager::new();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn window_size_prefers_outer_window_dimensions() {
|
||||||
|
// Field names + values mirror a real Wayfern fingerprint (camelCase).
|
||||||
|
let fp = r#"{"windowOuterWidth": 1268, "windowOuterHeight": 764,
|
||||||
|
"windowInnerWidth": 1253, "windowInnerHeight": 630,
|
||||||
|
"screenAvailWidth": 1280, "screenAvailHeight": 775,
|
||||||
|
"screenWidth": 1280, "screenHeight": 800}"#;
|
||||||
|
assert_eq!(
|
||||||
|
WayfernManager::window_size_from_fingerprint(fp),
|
||||||
|
Some((1268, 764))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn window_size_falls_back_to_avail_then_full_screen() {
|
||||||
|
let avail = r#"{"screenAvailWidth": 1280, "screenAvailHeight": 775,
|
||||||
|
"screenWidth": 1280, "screenHeight": 800}"#;
|
||||||
|
assert_eq!(
|
||||||
|
WayfernManager::window_size_from_fingerprint(avail),
|
||||||
|
Some((1280, 775))
|
||||||
|
);
|
||||||
|
|
||||||
|
let full = r#"{"screenWidth": 2560, "screenHeight": 1440}"#;
|
||||||
|
assert_eq!(
|
||||||
|
WayfernManager::window_size_from_fingerprint(full),
|
||||||
|
Some((2560, 1440))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn window_size_handles_wrapper_and_stringified_numbers() {
|
||||||
|
let wrapped = r#"{"fingerprint": {"windowOuterWidth": "1366", "windowOuterHeight": "768"}}"#;
|
||||||
|
assert_eq!(
|
||||||
|
WayfernManager::window_size_from_fingerprint(wrapped),
|
||||||
|
Some((1366, 768))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn window_size_none_when_missing_or_invalid() {
|
||||||
|
// No dimensions at all.
|
||||||
|
assert_eq!(
|
||||||
|
WayfernManager::window_size_from_fingerprint(r#"{"userAgent": "x"}"#),
|
||||||
|
None
|
||||||
|
);
|
||||||
|
// A width with no matching height is not a usable pair.
|
||||||
|
assert_eq!(
|
||||||
|
WayfernManager::window_size_from_fingerprint(r#"{"windowOuterWidth": 1268}"#),
|
||||||
|
None
|
||||||
|
);
|
||||||
|
// Zero is rejected as a degenerate size.
|
||||||
|
assert_eq!(
|
||||||
|
WayfernManager::window_size_from_fingerprint(
|
||||||
|
r#"{"windowOuterWidth": 0, "windowOuterHeight": 0}"#
|
||||||
|
),
|
||||||
|
None
|
||||||
|
);
|
||||||
|
// Not valid JSON.
|
||||||
|
assert_eq!(
|
||||||
|
WayfernManager::window_size_from_fingerprint("not json"),
|
||||||
|
None
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://schema.tauri.app/config/2",
|
"$schema": "https://schema.tauri.app/config/2",
|
||||||
"productName": "Donut",
|
"productName": "Donut",
|
||||||
"version": "0.25.2",
|
"version": "0.25.3",
|
||||||
"identifier": "com.donutbrowser",
|
"identifier": "com.donutbrowser",
|
||||||
"build": {
|
"build": {
|
||||||
"beforeDevCommand": "pnpm copy-proxy-binary && pnpm dev",
|
"beforeDevCommand": "pnpm copy-proxy-binary && pnpm dev",
|
||||||
|
|||||||
Reference in New Issue
Block a user