fix(linux): repair unresponsive UI on startup and full-screen panels

Linux users reported the window UI (including native title bar buttons)
couldn't receive clicks until manually maximizing and restoring the
window. Root causes: (1) Tauri webview did not acquire focus on startup
so first clicks were consumed by X11/Wayland click-to-activate
(Tauri #10746, wry #637); (2) GTK surface input region failed to
renegotiate on the visible:false + show() path under some
WebKitGTK/compositor combinations.

- Add linux_fix::nudge_main_window helper that performs set_focus plus
  a ±1px no-op resize after window show, with a 500ms reconciliation
  readback to compensate for dropped resize requests on slow
  compositors.
- Wire the helper into every window re-show path: normal startup,
  deeplink, single_instance, tray show_main, and lightweight exit.
- Set WEBKIT_DISABLE_COMPOSITING_MODE=1 at startup to avoid resize
  crashes and Wayland surface negotiation issues.
- Remove data-tauri-drag-region on Linux from App.tsx header and the
  shared FullScreenPanel (used by all provider/MCP/workspace forms)
  to avoid Tauri #13440 in Wayland sessions. Extract drag-region
  constants to src/lib/platform.ts for reuse.

All Rust changes are gated by #[cfg(target_os = "linux")]; frontend
changes preserve macOS/Windows behavior via runtime isLinux() checks.
Known limitation: tiling Wayland compositors ignore set_size, so
GDK_BACKEND=x11 remains the user-side workaround.
This commit is contained in:
Jason
2026-04-07 17:53:20 +08:00
parent 3a16462261
commit 64c068415e
9 changed files with 219 additions and 25 deletions
+17 -10
View File
@@ -40,7 +40,12 @@ import { useLastValidValue } from "@/hooks/useLastValidValue";
import { extractErrorMessage } from "@/utils/errorUtils";
import { isTextEditableTarget } from "@/utils/domUtils";
import { cn } from "@/lib/utils";
import { isWindows, isLinux } from "@/lib/platform";
import {
isWindows,
isLinux,
DRAG_REGION_ATTR,
DRAG_REGION_STYLE,
} from "@/lib/platform";
import { AppSwitcher } from "@/components/AppSwitcher";
import { ProviderList } from "@/components/providers/ProviderList";
import { AddProviderDialog } from "@/components/providers/AddProviderDialog";
@@ -876,11 +881,13 @@ function App() {
className="flex flex-col h-screen overflow-hidden bg-background text-foreground selection:bg-primary/30"
style={{ overflowX: "hidden", paddingTop: CONTENT_TOP_OFFSET }}
>
<div
className="fixed top-0 left-0 right-0 z-[60]"
data-tauri-drag-region
style={{ WebkitAppRegion: "drag", height: DRAG_BAR_HEIGHT } as any}
/>
{DRAG_BAR_HEIGHT > 0 && (
<div
className="fixed top-0 left-0 right-0 z-[60]"
data-tauri-drag-region
style={{ WebkitAppRegion: "drag", height: DRAG_BAR_HEIGHT } as any}
/>
)}
{showEnvBanner && envConflicts.length > 0 && (
<EnvWarningBanner
conflicts={envConflicts}
@@ -908,10 +915,10 @@ function App() {
<header
className="fixed z-50 w-full transition-all duration-300 bg-background/80 backdrop-blur-md"
data-tauri-drag-region
{...DRAG_REGION_ATTR}
style={
{
WebkitAppRegion: "drag",
...DRAG_REGION_STYLE,
top: DRAG_BAR_HEIGHT,
height: HEADER_HEIGHT,
} as any
@@ -919,8 +926,8 @@ function App() {
>
<div
className="flex h-full items-center justify-between gap-2 px-6"
data-tauri-drag-region
style={{ WebkitAppRegion: "drag" } as any}
{...DRAG_REGION_ATTR}
style={{ ...DRAG_REGION_STYLE } as any}
>
<div
className="flex items-center gap-1"
+23 -15
View File
@@ -3,7 +3,12 @@ import { createPortal } from "react-dom";
import { motion, AnimatePresence } from "framer-motion";
import { ArrowLeft } from "lucide-react";
import { Button } from "@/components/ui/button";
import { isWindows, isLinux } from "@/lib/platform";
import {
isWindows,
isLinux,
DRAG_REGION_ATTR,
DRAG_REGION_STYLE,
} from "@/lib/platform";
import { isTextEditableTarget } from "@/utils/domUtils";
interface FullScreenPanelProps {
@@ -82,24 +87,27 @@ export const FullScreenPanel: React.FC<FullScreenPanelProps> = ({
className="fixed inset-0 z-[60] flex flex-col"
style={{ backgroundColor: "hsl(var(--background))" }}
>
{/* Drag region - match App.tsx */}
<div
data-tauri-drag-region
style={
{
WebkitAppRegion: "drag",
height: DRAG_BAR_HEIGHT,
} as React.CSSProperties
}
/>
{/* Drag region - match App.tsx. Linux 上 DRAG_BAR_HEIGHT=0
直接跳过整个元素;macOS 保留 28px 拖拽占位。 */}
{DRAG_BAR_HEIGHT > 0 && (
<div
data-tauri-drag-region
style={
{
WebkitAppRegion: "drag",
height: DRAG_BAR_HEIGHT,
} as React.CSSProperties
}
/>
)}
{/* Header - match App.tsx */}
<div
className="flex-shrink-0 flex items-center"
data-tauri-drag-region
{...DRAG_REGION_ATTR}
style={
{
WebkitAppRegion: "drag",
...DRAG_REGION_STYLE,
backgroundColor: "hsl(var(--background))",
height: HEADER_HEIGHT,
} as React.CSSProperties
@@ -107,8 +115,8 @@ export const FullScreenPanel: React.FC<FullScreenPanelProps> = ({
>
<div
className="px-6 w-full flex items-center gap-4"
data-tauri-drag-region
style={{ WebkitAppRegion: "drag" } as React.CSSProperties}
{...DRAG_REGION_ATTR}
style={{ ...DRAG_REGION_STYLE } as React.CSSProperties}
>
<Button
type="button"
+17
View File
@@ -29,3 +29,20 @@ export const isLinux = (): boolean => {
return false;
}
};
// Linux 上禁用所有 drag region,规避 Wayland 下 gtk_window_begin_move_drag
// 相关的窗口事件异常(Tauri #13440)。macOS 上保留原有拖动行为;Windows
// 项目原本就不依赖这个。
//
// 这些常量设计为通过 JSX 属性 spread 消费(`{...DRAG_REGION_ATTR}`),
// 因为 `data-tauri-drag-region` 是 wry 侧的 attribute 存在性检测,必须
// 完全不渲染属性才算禁用;空字符串或 "false" 仍会触发。
export const DRAG_REGION_ENABLED = !isLinux();
export const DRAG_REGION_ATTR: Record<string, unknown> = DRAG_REGION_ENABLED
? { "data-tauri-drag-region": true }
: {};
export const DRAG_REGION_STYLE: Record<string, unknown> = DRAG_REGION_ENABLED
? { WebkitAppRegion: "drag" }
: {};