fix: restore RDP clipboard paste across browsers (#1331)

This commit is contained in:
ZacharyZcR
2026-08-25 00:56:26 +08:00
committed by GitHub
parent 81d79cc89b
commit dc47c4ca86
3 changed files with 28 additions and 49 deletions
@@ -18,11 +18,7 @@ import {
resolveConnectionOrigin, resolveConnectionOrigin,
buildOriginWsUrl, buildOriginWsUrl,
} from "@/lib/connection-origin.ts"; } from "@/lib/connection-origin.ts";
import { import { isPasteShortcut, pasteTextToRemote } from "./guacamole-clipboard.ts";
isFirefoxBrowser,
isPasteShortcut,
pasteTextToRemote,
} from "./guacamole-clipboard.ts";
import { getGuacamoleDisplaySize } from "./guacamole-display-size.ts"; import { getGuacamoleDisplaySize } from "./guacamole-display-size.ts";
import { bindPointerInput } from "./guacamole-pointer.ts"; import { bindPointerInput } from "./guacamole-pointer.ts";
import { import {
@@ -456,8 +452,10 @@ export const GuacamoleDisplay = forwardRef<
displayElement.setAttribute("tabindex", "0"); displayElement.setAttribute("tabindex", "0");
displayElement.style.outline = "none"; displayElement.style.outline = "none";
const useNativePasteFallback = isFirefoxBrowser(); // Reading navigator.clipboard outside a user gesture is denied by Safari
if (useNativePasteFallback) { // and commonly denied by Chromium. The paste event carries the text under
// the browser's normal permission model, so use it on every browser and
// replace the original shortcut with an ordered clipboard update + Ctrl+V.
displayElement.addEventListener( displayElement.addEventListener(
"keydown", "keydown",
(event) => { (event) => {
@@ -480,7 +478,6 @@ export const GuacamoleDisplay = forwardRef<
}, },
true, true,
); );
}
display.onresize = () => { display.onresize = () => {
if (!isMountedRef.current || clientRef.current !== client) return; if (!isMountedRef.current || clientRef.current !== client) return;
@@ -759,7 +756,7 @@ export const GuacamoleDisplay = forwardRef<
const syncClipboard = useCallback(() => { const syncClipboard = useCallback(() => {
const client = clientRef.current; const client = clientRef.current;
if (!client || isFirefoxBrowser() || !navigator.clipboard?.readText) return; if (!client || !navigator.clipboard?.readText) return;
navigator.clipboard navigator.clipboard
.readText() .readText()
.then((text) => { .then((text) => {
@@ -13,10 +13,6 @@ export interface GuacamoleClipboardClient {
sendKeyEvent(pressed: number, keysym: number): void; sendKeyEvent(pressed: number, keysym: number): void;
} }
export function isFirefoxBrowser(userAgent = navigator.userAgent): boolean {
return /(?:Firefox|FxiOS)\//.test(userAgent);
}
export function isPasteShortcut( export function isPasteShortcut(
event: Pick<KeyboardEvent, "altKey" | "ctrlKey" | "key" | "metaKey">, event: Pick<KeyboardEvent, "altKey" | "ctrlKey" | "key" | "metaKey">,
): boolean { ): boolean {
@@ -1,25 +1,11 @@
import { describe, expect, it, vi } from "vitest"; import { describe, expect, it, vi } from "vitest";
import { import {
isFirefoxBrowser,
isPasteShortcut, isPasteShortcut,
pasteTextToRemote, pasteTextToRemote,
type GuacamoleClipboardClient, type GuacamoleClipboardClient,
} from "../../../features/guacamole/guacamole-clipboard.js"; } from "../../../features/guacamole/guacamole-clipboard.js";
describe("Guacamole Firefox clipboard fallback", () => { describe("Guacamole clipboard paste", () => {
it("only enables the native paste path for Firefox", () => {
expect(
isFirefoxBrowser(
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:140.0) Gecko/20100101 Firefox/140.0",
),
).toBe(true);
expect(
isFirefoxBrowser(
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/140.0.0.0",
),
).toBe(false);
});
it("recognizes Ctrl+V and Command+V without intercepting Alt+V", () => { it("recognizes Ctrl+V and Command+V without intercepting Alt+V", () => {
expect( expect(
isPasteShortcut({ isPasteShortcut({