Skip to content

Commit e80860c

Browse files
feat(desktop): hidden 8-click logo gesture to open DevTools (agentscope-ai#5805)
1 parent ae36f4e commit e80860c

7 files changed

Lines changed: 71 additions & 6 deletions

File tree

console/src-tauri/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ semver = "1"
2525
base64 = "0.22"
2626
minisign-verify = "0.2"
2727
sha2 = "0.10"
28-
tauri = { version = "2.10.3", features = ["tray-icon"] }
28+
tauri = { version = "2.10.3", features = ["tray-icon", "devtools"] }
2929
tauri-plugin-log = "2"
3030
tauri-plugin-shell = "2"
3131
tauri-plugin-dialog = "2"

console/src-tauri/capabilities/default.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,16 @@
44
"description": "enables the default permissions",
55
"windows": ["main"],
66
"remote": {
7-
"urls": ["http://127.0.0.1:*"]
7+
"urls": ["http://127.0.0.1:*", "http://localhost:*"]
88
},
99
"permissions": [
1010
"backend",
1111
"backend-download",
1212
"core:default",
13+
"core:webview:allow-internal-toggle-devtools",
1314
"desktop-updater-check",
1415
"desktop-updater-install",
16+
"devtools",
1517
"dialog:allow-save",
1618
"open-external-link",
1719
"tray"

console/src-tauri/icons/icon.png

21.2 KB
Loading
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
[[permission]]
2+
identifier = "devtools"
3+
description = "Allows the frontend to open DevTools via the hidden 8-click logo gesture."
4+
commands.allow = ["open_devtools"]

console/src-tauri/src/lib.rs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,15 @@ mod external_link;
66
mod updates;
77
mod tray;
88

9-
use tauri::{Manager, RunEvent, WindowEvent};
9+
use tauri::{Manager, RunEvent, WebviewWindow, WindowEvent};
10+
11+
/// Opens the WebView DevTools. Gated by the hidden 8-click logo gesture in the
12+
/// frontend so end users cannot open DevTools via the default context menu or
13+
/// keyboard shortcuts in production builds.
14+
#[tauri::command]
15+
fn open_devtools(window: WebviewWindow) {
16+
window.open_devtools();
17+
}
1018

1119
#[cfg_attr(mobile, tauri::mobile_entry_point)]
1220
/// Build the desktop app, wire native plugins/commands, and stop the backend on exit.
@@ -16,6 +24,7 @@ pub fn run() {
1624
.plugin(tauri_plugin_dialog::init())
1725
.plugin(tauri_plugin_updater::Builder::new().build())
1826
.invoke_handler(tauri::generate_handler![
27+
open_devtools,
1928
backend_download::download_backend_file,
2029
backend::backend_port,
2130
backend::backend_startup_error,

console/src/App.tsx

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import { languageApi } from "./api/modules/language";
3737
import { useUploadLimitStore } from "./stores/uploadLimitStore";
3838
import { getApiUrl, getApiToken, clearAuthToken } from "./api/config";
3939
import CloseWindowPrompt from "./tauri/CloseWindowPrompt";
40+
import { isTauri } from "@tauri-apps/api/core";
4041
import "./styles/layout.css";
4142
import "./styles/form-override.css";
4243

@@ -168,6 +169,16 @@ function AppInner() {
168169
};
169170
}, [i18n]);
170171

172+
// Disable the default browser context menu in the Tauri desktop build so
173+
// users cannot open DevTools via right-click. DevTools is still available
174+
// through the hidden 8-click logo gesture handled in Header.tsx.
175+
useEffect(() => {
176+
if (!isTauri()) return;
177+
const preventContextMenu = (e: MouseEvent) => e.preventDefault();
178+
window.addEventListener("contextmenu", preventContextMenu);
179+
return () => window.removeEventListener("contextmenu", preventContextMenu);
180+
}, []);
181+
171182
// Wait for plugins to load before rendering routes that might be patched
172183
if (pluginsLoading) {
173184
return null;

console/src/layouts/Header.tsx

Lines changed: 42 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,13 @@
1-
import { Layout, Space, Badge, Spin, Tooltip, Dropdown, Popover } from "antd";
1+
import {
2+
Layout,
3+
Space,
4+
Badge,
5+
Spin,
6+
Tooltip,
7+
Dropdown,
8+
Popover,
9+
message,
10+
} from "antd";
211
import type { MenuProps } from "antd";
312
import LanguageSwitcher, {
413
LANGUAGE_LIST,
@@ -23,7 +32,8 @@ import {
2332
compareVersions,
2433
} from "./constants";
2534
import { useTheme } from "../contexts/ThemeContext";
26-
import { useState, useEffect } from "react";
35+
import { useState, useEffect, useRef } from "react";
36+
import { invoke } from "@tauri-apps/api/core";
2737
import { Slot } from "../plugins/registry/Slot";
2838
import ReactMarkdown from "react-markdown";
2939
import remarkGfm from "remark-gfm";
@@ -80,6 +90,7 @@ export default function Header() {
8090
const [latestVersion, setLatestVersion] = useState<string>("");
8191
const [updateModalOpen, setUpdateModalOpen] = useState(false);
8292
const [updateMarkdown, setUpdateMarkdown] = useState<string>("");
93+
const logoClicksRef = useRef<number[]>([]);
8394

8495
useEffect(() => {
8596
api
@@ -88,6 +99,34 @@ export default function Header() {
8899
.catch(() => {});
89100
}, []);
90101

102+
// Hidden gesture: 8 rapid clicks on the logo within 3 seconds toggles DevTools
103+
// in the Tauri desktop build. This keeps DevTools inaccessible via the default
104+
// context menu or keyboard shortcuts while still allowing support/debugging.
105+
const handleLogoClick = () => {
106+
if (!onDesktop) return;
107+
const now = Date.now();
108+
const windowStart = now - 3000;
109+
logoClicksRef.current = logoClicksRef.current.filter(
110+
(time) => time > windowStart,
111+
);
112+
logoClicksRef.current.push(now);
113+
if (logoClicksRef.current.length >= 8) {
114+
logoClicksRef.current = [];
115+
invoke("open_devtools")
116+
.then(() => message.success("DevTools opened"))
117+
.catch((err: unknown) => {
118+
const errMsg =
119+
err instanceof Error
120+
? err.message
121+
: typeof err === "string"
122+
? err
123+
: JSON.stringify(err);
124+
console.error("Failed to open DevTools:", errMsg);
125+
message.error(`DevTools error: ${errMsg}`);
126+
});
127+
}
128+
};
129+
91130
// Web-only PyPI fallback: desktop path is owned by DesktopUpdateContext.
92131
useEffect(() => {
93132
if (onDesktop) return;
@@ -297,7 +336,7 @@ export default function Header() {
297336
return (
298337
<>
299338
<AntHeader className={styles.header}>
300-
<div className={styles.logoWrapper}>
339+
<div className={styles.logoWrapper} onClick={handleLogoClick}>
301340
{/*
302341
Slot lets a plugin replace the brand logo (e.g. a per-agent
303342
branding override). When no plugin registers a replacement —

0 commit comments

Comments
 (0)