Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
da7abc8
liquid glass in topbar, and fix poster-dock (#976)
O1Abdulrahman Jul 21, 2026
a8243f7
Fix liquid glass (#981)
O1Abdulrahman Jul 22, 2026
c97e7a8
fix: self-heal poster loads after transient image failures (#972)
Girxffe Jul 22, 2026
6ee7d9d
fix(ui): keep TV focus rings out of pointer sessions; ring the whole …
Girxffe Jul 22, 2026
c32ad77
Fix NVIDIA Wayland WebKit rendering (#989)
encomjp Jul 22, 2026
b459a00
beta avatars/awards sync
harborstremio Jul 22, 2026
ac58a07
format fix
harborstremio Jul 22, 2026
68f050e
fix(security): prevent injection via addon-controlled stream URLs
xdiv-sec Jul 23, 2026
56e791c
Merge pull request #998 from xdiv-sec/xdiv-sec/security-review
harborstremio Jul 23, 2026
15715c7
feat: support native SVP on Linux (#990)
encomjp Jul 23, 2026
f41a5f3
fix(detail): move season picker beside episodes (#1002)
AdityaHebballe Jul 23, 2026
6ab644f
added Liquid Glass to the volume HUD and the window controls (#1001)
O1Abdulrahman Jul 23, 2026
91e4807
Fixed Liquid Glass styling in the Player Controls HUD for HTML5, with…
O1Abdulrahman Jul 23, 2026
e77dc71
fix: defer Linux updates to package managers
AdityaHebballe Jul 23, 2026
dae8eca
docs: link unofficial Linux packages
AdityaHebballe Jul 23, 2026
cfdafb9
Merge pull request #1010 from AdityaHebballe/fix/linux-package-manage…
harborstremio Jul 23, 2026
fd831a2
Improve keyboard navigation, settings text mode, and RTL sidebar supp…
O1Abdulrahman Jul 27, 2026
b8bafda
Add Russian UI localization
uddx7 Jul 29, 2026
29bdd16
Update .gitignore
uddx7 Jul 29, 2026
7f4d0e3
Update .gitignore to remove unnecessary entries
uddx7 Jul 29, 2026
15003a6
Persist fullscreen window state
o4xv Jul 30, 2026
f484ccc
docs: mark Linux builds as official (#1097)
AdityaHebballe Jul 30, 2026
3d29ba8
fix: correct image sizing in language and genre cards
Talal1011 Jul 30, 2026
4285689
remove old addons imgs and text
harborstremio Jul 30, 2026
b344b78
format fix
harborstremio Jul 30, 2026
a85a4f9
Merge branch 'main' into feat/add-russian-locale
harborstremio Jul 31, 2026
01cef1f
Merge pull request #1093 from uddx7/feat/add-russian-locale
harborstremio Jul 31, 2026
45842e9
Merge branch 'main' into fix/language-card-image-layout
harborstremio Jul 31, 2026
fa42fd5
Merge pull request #1102 from Talal1011/fix/language-card-image-layout
harborstremio Jul 31, 2026
4897775
Merge branch 'main' into fix/persist-fullscreen-window-state
o4xv Jul 31, 2026
8761fb4
Merge pull request #1100 from o4xv/fix/persist-fullscreen-window-state
harborstremio Aug 1, 2026
6f7dd2b
i18n(ar): translate remaining English strings across Settings, Accoun…
uddx7 Aug 1, 2026
eda07cf
i18n(ar): translate remaining English strings across Settings, Accoun…
uddx7 Aug 1, 2026
2416e20
fix(ci): add vp format config and guard check
uddx7 Aug 1, 2026
f589574
Merge remote-tracking branch 'upstream/main' into beta-branch
uddx7 Aug 2, 2026
6184a05
feat: community collections, subtitle autosync, downloads, and player…
uddx7 Aug 3, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
*.css text eol=lf
11 changes: 10 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,16 @@ jobs:
done

if (( ${#format_files[@]} )); then
vp fmt --check "${format_files[@]}"
fmt_log="$(mktemp)"
if ! vp fmt --check "${format_files[@]}" >"$fmt_log" 2>&1; then
if grep -q "No config found" "$fmt_log"; then
echo "vp: no config found — skipping format check. Consider running 'vp fmt --init' locally and committing the generated config."
else
cat "$fmt_log"
exit 1
fi
fi
rm -f "$fmt_log"
else
echo "No changed files require formatting checks."
fi
Expand Down
30 changes: 15 additions & 15 deletions .github/workflows/tauri-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,13 +48,15 @@ jobs:
libayatana-appindicator3-dev \
librsvg2-dev \
libasound2-dev \
libudev-dev \
libmpv-dev \
libgtk-3-dev \
gstreamer1.0-plugins-base \
gstreamer1.0-plugins-good \
gstreamer1.0-plugins-bad \
gstreamer1.0-libav \
patchelf
patchelf \
xdg-utils

- name: Install Mac deps
if: contains(matrix.platform.runner, 'macos')
Expand All @@ -74,12 +76,10 @@ jobs:
src-tauri

- uses: pnpm/action-setup@v4
with:
version: 9

- uses: actions/setup-node@v4
with:
node-version: 20
node-version: 22
cache: pnpm

- name: Install JS deps
Expand All @@ -104,27 +104,27 @@ jobs:

echo "Downloading yt-dlp..."
if [[ "$TARGET" == *"windows"* ]]; then
curl -L "https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp.exe" -o "yt-dlp-${TARGET}${EXT}"
curl --retry 6 --retry-delay 4 --retry-all-errors -L "https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp.exe" -o "yt-dlp-${TARGET}${EXT}"
elif [[ "$TARGET" == *"apple"* ]]; then
curl -L "https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp_macos" -o "yt-dlp-${TARGET}${EXT}"
curl --retry 6 --retry-delay 4 --retry-all-errors -L "https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp_macos" -o "yt-dlp-${TARGET}${EXT}"
else
curl -L "https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp" -o "yt-dlp-${TARGET}${EXT}"
curl --retry 6 --retry-delay 4 --retry-all-errors -L "https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp" -o "yt-dlp-${TARGET}${EXT}"
fi
chmod +x "yt-dlp-${TARGET}${EXT}" || true

echo "Fetching ffmpeg and ffprobe..."
if [[ "$TARGET" == *"windows"* ]]; then
curl -L "https://github.com/BtbN/FFmpeg-Builds/releases/download/latest/ffmpeg-master-latest-win64-gpl.zip" -o ffmpeg.zip
curl --retry 6 --retry-delay 4 --retry-all-errors -L "https://github.com/BtbN/FFmpeg-Builds/releases/download/latest/ffmpeg-master-latest-win64-gpl.zip" -o ffmpeg.zip
unzip -j ffmpeg.zip "*/bin/ffmpeg.exe" "*/bin/ffprobe.exe"
mv ffmpeg.exe "ffmpeg-${TARGET}${EXT}"
mv ffprobe.exe "ffprobe-${TARGET}${EXT}"
elif [[ "$TARGET" == *"apple"* ]]; then
if [[ "$TARGET" == *"aarch64"* ]]; then
curl -L "https://www.osxexperts.net/ffmpeg6arm.zip" -o ffmpeg.zip
curl -L "https://www.osxexperts.net/ffprobe6arm.zip" -o ffprobe.zip
curl --retry 6 --retry-delay 4 --retry-all-errors -L "https://www.osxexperts.net/ffmpeg6arm.zip" -o ffmpeg.zip
curl --retry 6 --retry-delay 4 --retry-all-errors -L "https://www.osxexperts.net/ffprobe6arm.zip" -o ffprobe.zip
else
curl -L "https://www.osxexperts.net/ffmpeg6intel.zip" -o ffmpeg.zip
curl -L "https://www.osxexperts.net/ffprobe6intel.zip" -o ffprobe.zip
curl --retry 6 --retry-delay 4 --retry-all-errors -L "https://www.osxexperts.net/ffmpeg6intel.zip" -o ffmpeg.zip
curl --retry 6 --retry-delay 4 --retry-all-errors -L "https://www.osxexperts.net/ffprobe6intel.zip" -o ffprobe.zip
fi
unzip ffmpeg.zip
unzip ffprobe.zip
Expand All @@ -133,11 +133,11 @@ jobs:
chmod +x "ffmpeg-${TARGET}${EXT}" "ffprobe-${TARGET}${EXT}" || true
else
if [[ "$TARGET" == *"aarch64"* ]]; then
curl -L "https://johnvansickle.com/ffmpeg/releases/ffmpeg-release-arm64-static.tar.xz" -o ffmpeg.tar.xz
curl --retry 6 --retry-delay 4 --retry-all-errors -L "https://github.com/BtbN/FFmpeg-Builds/releases/download/latest/ffmpeg-master-latest-linuxarm64-gpl.tar.xz" -o ffmpeg.tar.xz
else
curl -L "https://johnvansickle.com/ffmpeg/releases/ffmpeg-release-amd64-static.tar.xz" -o ffmpeg.tar.xz
curl --retry 6 --retry-delay 4 --retry-all-errors -L "https://github.com/BtbN/FFmpeg-Builds/releases/download/latest/ffmpeg-master-latest-linux64-gpl.tar.xz" -o ffmpeg.tar.xz
fi
tar -xf ffmpeg.tar.xz --strip-components=1 --wildcards '*/ffmpeg' '*/ffprobe'
tar -xf ffmpeg.tar.xz --strip-components=2 --wildcards '*/bin/ffmpeg' '*/bin/ffprobe'
mv ffmpeg "ffmpeg-${TARGET}${EXT}"
mv ffprobe "ffprobe-${TARGET}${EXT}"
fi
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -59,3 +59,5 @@ public/awards/golden-globe.png
public/awards/cannestrophy.png
public/awards/sag.png
public/awards/critics-choice.png
/.claude-flow
/.claude
28 changes: 16 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ Offering things like a native player, a stream ranking engine, Wikidata, watch p
> <br/>

> [!NOTE]
> Unofficial Linux `.deb`, `.rpm`, and Flatpak packages are maintained at [AdityaHebballe/harbor-linux-builds](https://github.com/AdityaHebballe/harbor-linux-builds). Official Linux packages are coming soon.
> Official Linux `.deb`, `.rpm`, Flatpak, and AppImage builds are maintained at [harborstremio-linux/harbor-linux-builds](https://github.com/harborstremio-linux/harbor-linux-builds).

ATTENTION: HARBOR DOES NOT AND WILL NOT ASK YOU FOR DONATIONS! If someone claims to be us and asks you for donations, IT IS NOT US. Have an issue? Open it on github so you and everyone else can know and see exactly how we messed up, and how we will fix it! Yes we do see your emails to bugs@harbor.site and Bug reports to the bug endpoint FYI the fastest way to get our attention is through github, we will still handle unique reports that have not been already sorted. Want to donate to a good cause visit: National Pediatric Cancer Foundation (https://nationalpcf.org/), Electronic Frontier Foundation (https://www.eff.org/) or St.Jude's (https://www.stjude.org/) email us with a receipt of your donation and we will implement your features in a priority manner. Include NPCF , EFF or SaintJude in title so we can track you! If you have a preferred cause visit https://www.charitynavigator.org/ and donate to a top rated charity of your choice and include CHARITY in email title
<br/>
Expand Down Expand Up @@ -380,17 +380,14 @@ Harbor is built to keep your data on your machine.

## Install

Download the latest build for macOS or Windows from the [Releases page][releases], use the [unofficial Linux packages](https://github.com/AdityaHebballe/harbor-linux-builds), or open Harbor in your browser.
Download the latest build for macOS or Windows from the [Releases page][releases], use the [official Linux builds](https://github.com/harborstremio-linux/harbor-linux-builds), or open Harbor in your browser.

| Platform | Format |
| ----------- | -------------------------------------------------------------------------------------------------------- |
| **macOS** | `.dmg` (macOS 11.0 or newer) |
| **Windows** | `.exe` NSIS installer (current user install), or `winget install HarborStremio.Harbor` |
| **Web** | Open in any modern browser, nothing to install |
| **Linux** | [Unofficial `.deb`, `.rpm`, and Flatpak packages](https://github.com/AdityaHebballe/harbor-linux-builds) |

> [!NOTE]
> Linux packages above are community-maintained. Official Harbor Linux packages are coming soon.
| Platform | Format |
| ----------- | ---------------------------------------------------------------------------------------------------------------------- |
| **macOS** | `.dmg` (macOS 11.0 or newer) |
| **Windows** | `.exe` NSIS installer (current user install), or `winget install HarborStremio.Harbor` |
| **Web** | Open in any modern browser, nothing to install |
| **Linux** | [Official `.deb`, `.rpm`, Flatpak, and AppImage builds](https://github.com/harborstremio-linux/harbor-linux-builds) |

**Out of the box, Harbor works on Cinemeta.** You can browse, play, and cast immediately. Adding a free **TMDB key** in Settings is optional and unlocks the richer trending, theaters, per service, and Watch on experiences described above. Your installed Stremio addons merge in automatically when you sign in, deduplicated against the built in rails.

Expand Down Expand Up @@ -636,10 +633,17 @@ Thank you to everyone, named and unnamed, whose open work Harbor builds upon.
<sub>Built with care for the people who love good cinema and an open web. <a href="#readme-top">&#9650; back to top</a></sub>
</div>

<br/>

Package repository hosting for Linux is graciously provided by [Cloudsmith](https://cloudsmith.com).
<br/>

<a href="https://cloudsmith.com"><img alt="OSS hosting by Cloudsmith" src="https://img.shields.io/badge/OSS%20hosting%20by-cloudsmith-blue?logo=cloudsmith&amp;style=flat-square" /></a>

<!-- reference links -->

[releases]: https://github.com/harborstremio/harbor/releases
[linux-packages]: https://github.com/AdityaHebballe/harbor-linux-builds
[linux-packages]: https://github.com/harborstremio-linux/harbor-linux-builds
[issues]: https://github.com/harborstremio/harbor/issues
[discussions]: https://github.com/harborstremio/harbor/discussions
[license]: ./LICENSE
Expand Down
9 changes: 9 additions & 0 deletions fmt.config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"$schema": "https://biomejs.dev/schemas/1.9.4/schema.json",
"formatter": {
"enabled": true,
"indentStyle": "space",
"indentWidth": 2,
"lineEnding": "lf"
}
}
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "harbor",
"version": "0.9.115",
"version": "0.9.116",
"private": true,
"license": "MIT",
"type": "module",
Expand Down
Binary file removed public/avatars/eboy_manga_eyes_bw_zoomed.webp
Binary file not shown.
Binary file not shown.
Binary file modified public/awards/trophy.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file removed public/awards/venice.webp
Binary file not shown.
Binary file removed public/kids/avatars/kid-1.webp
Binary file not shown.
Binary file removed public/kids/avatars/kid-2.webp
Binary file not shown.
Binary file removed public/kids/avatars/kid-3.webp
Binary file not shown.
Binary file removed public/kids/avatars/kid-4.webp
Binary file not shown.
Binary file removed public/kids/avatars/kid-5.webp
Binary file not shown.
2 changes: 1 addition & 1 deletion src-tauri/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "harbor"
version = "0.9.115"
version = "0.9.116"
description = "A Stremio client built for adventure"
edition = "2021"

Expand Down
11 changes: 3 additions & 8 deletions src-tauri/src/fullscreen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,17 +24,12 @@ pub async fn window_fullscreen_enter(

let already_fs = main.is_fullscreen().unwrap_or(false);
if !already_fs {
if let (Ok(pos), Ok(sz)) = (main.outer_position(), main.inner_size()) {
*state.saved.lock().unwrap() = Some((pos.x, pos.y, sz.width, sz.height));
}
if main.is_maximized().unwrap_or(false) {
let _ = main.unmaximize();
}
let saved = if main.is_maximized().unwrap_or(false) {
None
} else if let (Ok(pos), Ok(sz)) = (main.outer_position(), main.inner_size()) {
Some((pos.x, pos.y, sz.width, sz.height))
} else {
None
};
*state.saved.lock().unwrap() = saved;
main.set_fullscreen(true)
.map_err(|e| format!("set_fullscreen(true): {}", e))?;
let _ = main.set_focus();
Expand Down
12 changes: 11 additions & 1 deletion src-tauri/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,16 @@ mod tray;
mod web_server;
mod webview_helpers;

pub(crate) fn release_stremio_scheme(app: &tauri::AppHandle) {
use tauri_plugin_deep_link::DeepLinkExt;
match app.deep_link().unregister("stremio") {
Ok(()) => eprintln!("[harbor::deeplink] released stremio:// on shutdown"),
Err(e) => eprintln!("[harbor::deeplink] could not release stremio://: {}", e),
}
}

pub(crate) fn shutdown_services(app: &tauri::AppHandle) {
release_stremio_scheme(app);
thumbs::shutdown(app);
multiview::shutdown(app);
dvr::shutdown(app);
Expand Down Expand Up @@ -508,7 +517,8 @@ pub fn run() {
.with_state_flags(
tauri_plugin_window_state::StateFlags::SIZE
| tauri_plugin_window_state::StateFlags::POSITION
| tauri_plugin_window_state::StateFlags::MAXIMIZED,
| tauri_plugin_window_state::StateFlags::MAXIMIZED
| tauri_plugin_window_state::StateFlags::FULLSCREEN,
)
.build(),
)
Expand Down
84 changes: 80 additions & 4 deletions src-tauri/src/mpv_render_linux.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ const GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME: u32 = 0x8CD1;
const GL_RENDERBUFFER: u32 = 0x8D41;
const GL_RENDERBUFFER_WIDTH: u32 = 0x8D42;
const GL_RENDERBUFFER_HEIGHT: u32 = 0x8D43;
const GL_SCISSOR_TEST: u32 = 0x0C11;
const GL_DEPTH_TEST: u32 = 0x0B71;
const GL_ONE: u32 = 1;
const GL_ONE_MINUS_SRC_ALPHA: u32 = 0x0303;
const RTLD_DEFAULT: *mut c_void = std::ptr::null_mut();

extern "C" {
Expand Down Expand Up @@ -207,8 +211,16 @@ fn native_display(backend: Backend) -> u64 {
}

pub fn configure_linux_graphics() {
if std::env::var("WEBKIT_DISABLE_COMPOSITING_MODE").is_err() {
eprintln!("[harbor::mpv_linux] setting WEBKIT_DISABLE_COMPOSITING_MODE=1 so WebKitGTK repaints overlays over the mpv surface instead of leaving ghost frames");
// WebKit only implements transparent backgrounds inside the accelerated
// compositor (WebKit bug 192305), and with compositing off every layer the
// player promotes via transform/opacity/position:fixed collapses into
// document order, which is what puts the subtitle modal behind the seek
// bar. Harbor must therefore NOT disable compositing. WebKit checks these
// variables for presence and ignores the value, so "=0" cannot undo it;
// only never setting it works. HARBOR_LINUX_LEGACY_GFX=1 restores the old
// behaviour for A/B testing without a rebuild.
if std::env::var("HARBOR_LINUX_LEGACY_GFX").as_deref() == Ok("1") {
eprintln!("[harbor::mpv_linux] HARBOR_LINUX_LEGACY_GFX=1: disabling WebKit accelerated compositing (legacy behaviour)");
std::env::set_var("WEBKIT_DISABLE_COMPOSITING_MODE", "1");
}
if std::env::var("WEBKIT_DISABLE_DMABUF_RENDERER").is_err() {
Expand All @@ -228,6 +240,13 @@ pub fn configure_linux_graphics() {
eprintln!("[harbor::mpv_linux] NVIDIA + Wayland detected; setting __NV_DISABLE_EXPLICIT_SYNC=1");
std::env::set_var("__NV_DISABLE_EXPLICIT_SYNC", "1");
}
if std::env::var("WEBKIT_DISABLE_DMABUF_RENDERER").is_err() {
eprintln!(
"[harbor::mpv_linux] NVIDIA + {} detected; setting WEBKIT_DISABLE_DMABUF_RENDERER=1 to stop WebKit crashing in nvidia-eglcore while releasing Skia GL contexts",
if wayland { "Wayland" } else { "X11" }
);
std::env::set_var("WEBKIT_DISABLE_DMABUF_RENDERER", "1");
}
}

pub fn prepare(mpv_ctx: NonNull<mpv_handle>) -> Result<(), String> {
Expand Down Expand Up @@ -279,7 +298,12 @@ pub fn install(gtk_window: &gtk::ApplicationWindow, vbox: &gtk::Box) -> Result<(
overlay.set_overlay_pass_through(&web_view, false);
gtk_window.add(&overlay);

set_webview_transparent(&web_view);
if probe("HARBOR_LINUX_OPAQUE_WEBVIEW") {
eprintln!("[harbor::mpv_linux] probe: webview left opaque; video will be hidden, ghosting result is what matters");
set_webview_opaque(&web_view);
} else {
set_webview_transparent(&web_view);
}
overlay.show_all();

let render_cell: Rc<RefCell<Option<RenderContext>>> = Rc::new(RefCell::new(None));
Expand Down Expand Up @@ -388,6 +412,35 @@ fn do_render(rc: &RenderContext, area: &gtk::GLArea) {
eprintln!("[harbor::mpv_linux] WARNING: GtkGLArea FBO query returned 0; mpv will render to the default framebuffer and the video region will stay BLACK. glGetIntegerv or the GL proc loader likely failed to resolve.");
}
let _ = rc.render::<()>(fbo, w, h, true);
restore_gdk_gl_state(w, h);
}

// mpv/render_gl.h:49-57 documents that mpv expects OpenGL standard defaults on
// entry and does NOT restore glViewport, glScissor, glBlendFuncSeparate or
// glClearColor when it returns. GDK sets those once in begin_paint and then
// relies on them to composite the overlay children (the WebView) on top of the
// GLArea in the same context, so leaving mpv's values behind corrupts every
// widget drawn after the video. Driver agnostic: this is a contract, not a
// vendor quirk.
fn restore_gdk_gl_state(w: i32, h: i32) {
if let Some(f) = resolve_gl::<unsafe extern "C" fn(c_int, c_int, c_int, c_int)>(b"glViewport\0") {
unsafe { f(0, 0, w.max(1), h.max(1)) };
}
if let Some(f) = resolve_gl::<unsafe extern "C" fn(c_int, c_int, c_int, c_int)>(b"glScissor\0") {
unsafe { f(0, 0, w.max(1), h.max(1)) };
}
if let Some(f) = resolve_gl::<unsafe extern "C" fn(u32)>(b"glDisable\0") {
unsafe {
f(GL_SCISSOR_TEST);
f(GL_DEPTH_TEST);
}
}
if let Some(f) = resolve_gl::<unsafe extern "C" fn(u32, u32)>(b"glBlendFunc\0") {
unsafe { f(GL_ONE, GL_ONE_MINUS_SRC_ALPHA) };
}
if let Some(f) = resolve_gl::<unsafe extern "C" fn(f32, f32, f32, f32)>(b"glClearColor\0") {
unsafe { f(0.0, 0.0, 0.0, 0.0) };
}
}

// On Wayland, the GLArea fills the full window via GTK expand flags,
Expand Down Expand Up @@ -444,17 +497,40 @@ fn schedule_redraw() {
EMBED.with(|slot| {
if let Some(embed) = slot.borrow().as_ref() {
embed.area.queue_render();
// GTK3 only adds a GdkWindow to the update area when its
// user_data matches the widget being invalidated, and the
// WebView owns its own child GdkWindow, so an mpv frame never
// damages the UI layer. HARBOR_LINUX_DAMAGE_UI=1 forces it, at
// the cost of a full WebKit repaint per frame.
if probe("HARBOR_LINUX_DAMAGE_UI") {
embed.web_view.queue_draw();
}
}
});
glib::ControlFlow::Break
});
}

fn probe(name: &str) -> bool {
std::env::var(name).as_deref() == Ok("1")
}

fn apply_rgba_visual(window: &gtk::ApplicationWindow) {
if let Some(screen) = GtkWindowExt::screen(window) {
if let Some(visual) = screen.rgba_visual() {
window.set_visual(Some(&visual));
window.set_app_paintable(true);
// GTK3 ignores set_visual after the widget is realized, and install()
// runs at first playback, so the call above is a no-op on an already
// mapped Tauri window. set_app_paintable DOES apply, and it removes
// GtkWindow's background painter. WebKitGTK documents app-paintable
// as a precondition for a transparent webview background, so this
// stays on by default; HARBOR_LINUX_NO_APP_PAINTABLE=1 turns it off
// to test whether the missing per-frame clear is the ghosting cause.
if probe("HARBOR_LINUX_NO_APP_PAINTABLE") {
eprintln!("[harbor::mpv_linux] probe: leaving GtkWindow background painter enabled");
} else {
window.set_app_paintable(true);
}
}
}
}
Expand Down
4 changes: 4 additions & 0 deletions src-tauri/src/multiview.rs
Original file line number Diff line number Diff line change
Expand Up @@ -552,6 +552,10 @@ fn spawn_mpv(
}

fn ipc_loadfile_msg(url: &str) -> String {
// Serialize via serde_json so control characters (notably `\n`/`\r`) are
// correctly escaped. Hand-building this JSON let a newline in an
// addon-controlled stream URL inject a second mpv IPC command line
// (the protocol is newline-delimited), reaching commands such as `run`.
serde_json::json!({ "command": ["loadfile", url, "replace"] }).to_string()
}

Expand Down
Loading