On Linux, Tauri uses WebKitGTK as its webview, which intercepts trackpad pinch gestures at the native level and applies them as page zoom. Calling `preventDefault()` on `wheel` events from JavaScript does not stop this, because WebKit handles the magnification through a separate gesture pipeline that bypasses the DOM event system. The approach below intercepts the pinch gesture at the GTK layer before WebKit sees it, then re-dispatches it as a synthetic `WheelEvent` with `ctrlKey: true`. This is the same shape of event that browsers fire natively for trackpad pinch on Chrome/Firefox, so the React frontend code can stay platform-agnostic and use a single `wheel` event handler everywhere. The Rust side attaches a `gtk::GestureZoom` controller to the GTK window in the `Capture` propagation phase and claims the gesture, which prevents WebKit from also processing it. It then emits Tauri events containing the scale factor and the cursor position. ```toml # src-tauri/Cargo.toml [target."cfg(target_os = \"linux\")".dependencies] gtk = "0.18" ``` ```rust ``#[cfg(target_os = "linux")] fn setup_linux_pinch(window: &tauri::WebviewWindow) { use gtk::prelude::*; use gtk::{EventSequenceState, PropagationPhase}; use tauri::{Emitter, Manager}; let gtk_window = window.gtk_window().expect("gtk window"); let app_handle = window.app_handle().clone(); let gesture = gtk::GestureZoom::new(>k_window); gesture.set_propagation_phase(PropagationPhase::Capture); let ah = app_handle.clone(); gesture.connect_begin(move |gesture, _seq| { gesture.set_state(EventSequenceState::Claimed); let (x, y) = gesture.bounding_box_center().unwrap_or((0.0, 0.0)); let _ = ah.emit("pinch-begin", (x, y)); }); let ah = app_handle.clone(); gesture.connect_scale_changed(move |_, scale| { let _ = ah.emit("pinch-scale", scale); }); unsafe { gtk_window.set_data("zoom-gesture", gesture); } } #[cfg_attr(mobile, tauri::mobile_entry_point)] pub fn run() { tauri::Builder::default() .setup(|app| { #[cfg(target_os = "linux")] { use tauri::Manager; let window = app.get_webview_window("main").unwrap(); setup_linux_pinch(&window); } Ok(()) }) .plugin(tauri_plugin_opener::init()) .invoke_handler(tauri::generate_handler![greet]) .run(tauri::generate_context!()) .expect("error while running tauri application"); } ``` On the JavaScript side, a small adapter listens for the Tauri events and re-dispatches them as synthetic `WheelEvent`s. The adapter checks for `window.__TAURI_INTERNALS__` and bails out silently when running in a plain browser, where the events would not fire and `@tauri-apps/api/event` would fail to initialize. ```ts // src/tauriPinchAdapter.ts export async function setupTauriPinchAdapter() { // Bail out if not running inside Tauri if (typeof window === 'undefined' || !('__TAURI_INTERNALS__' in window)) { return; } // Dynamic import so the @tauri-apps/api code isn't even loaded in the browser const { listen } = await import('@tauri-apps/api/event'); let pinchOriginX = 0; let pinchOriginY = 0; let lastScale = 1; await listen<[number, number]>('pinch-begin', (event) => { const [x, y] = event.payload; pinchOriginX = x; pinchOriginY = y; lastScale = 1; }); await listen('pinch-scale', (event) => { const currentScale = event.payload; const scaleRatio = currentScale / lastScale; lastScale = currentScale; const deltaY = -Math.log(scaleRatio) * 100; const target = document.elementFromPoint(pinchOriginX, pinchOriginY) ?? document.body; const wheelEvent = new WheelEvent('wheel', { deltaY, ctrlKey: true, clientX: pinchOriginX, clientY: pinchOriginY, bubbles: true, cancelable: true, }); target.dispatchEvent(wheelEvent); }); } ``` ```tsx // src/main.tsx import React from "react"; import ReactDOM from "react-dom/client"; import App from "./App"; import { setupTauriPinchAdapter } from "./tauriPinchAdapter"; setupTauriPinchAdapter(); ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render( , ); ``` With the adapter in place, any component that wants to handle zoom can use a plain `wheel` event listener with `ctrlKey` detection. The same code works in the browser, on Linux Tauri (via the adapter), and on other platforms where the browser-style events fire natively. Notes on the other platforms: - **Windows (WebView2):** set the env var `WEBVIEW2_ADDITIONAL_BROWSER_ARGUMENTS=--disable-pinch` before launching the app. Pinch then fires as a regular `wheel` event with `ctrlKey: true` and the adapter is not needed. - **macOS (WKWebView):** access the webview from Rust with `with_webview` and set `allowsMagnification = false`. The Linux adapter is not needed. - The sensitivity constants (`100` in the adapter, `0.01` in the wheel handler) are tuned to roughly match browser pinch feel. Adjust if the zoom speed feels off.