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
+27 -30
View File
@@ -18,11 +18,7 @@ import {
resolveConnectionOrigin,
buildOriginWsUrl,
} from "@/lib/connection-origin.ts";
import {
isFirefoxBrowser,
isPasteShortcut,
pasteTextToRemote,
} from "./guacamole-clipboard.ts";
import { isPasteShortcut, pasteTextToRemote } from "./guacamole-clipboard.ts";
import { getGuacamoleDisplaySize } from "./guacamole-display-size.ts";
import { bindPointerInput } from "./guacamole-pointer.ts";
import {
@@ -456,31 +452,32 @@ export const GuacamoleDisplay = forwardRef<
displayElement.setAttribute("tabindex", "0");
displayElement.style.outline = "none";
const useNativePasteFallback = isFirefoxBrowser();
if (useNativePasteFallback) {
displayElement.addEventListener(
"keydown",
(event) => {
if (isPasteShortcut(event)) {
event.stopImmediatePropagation();
}
},
true,
);
displayElement.addEventListener(
"paste",
(event) => {
if (clientRef.current !== client) return;
const text = event.clipboardData?.getData("text/plain");
if (!text) return;
event.preventDefault();
// Reading navigator.clipboard outside a user gesture is denied by Safari
// 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(
"keydown",
(event) => {
if (isPasteShortcut(event)) {
event.stopImmediatePropagation();
pasteTextToRemote(client, text);
},
true,
);
}
}
},
true,
);
displayElement.addEventListener(
"paste",
(event) => {
if (clientRef.current !== client) return;
const text = event.clipboardData?.getData("text/plain");
if (!text) return;
event.preventDefault();
event.stopImmediatePropagation();
pasteTextToRemote(client, text);
},
true,
);
display.onresize = () => {
if (!isMountedRef.current || clientRef.current !== client) return;
@@ -759,7 +756,7 @@ export const GuacamoleDisplay = forwardRef<
const syncClipboard = useCallback(() => {
const client = clientRef.current;
if (!client || isFirefoxBrowser() || !navigator.clipboard?.readText) return;
if (!client || !navigator.clipboard?.readText) return;
navigator.clipboard
.readText()
.then((text) => {
@@ -13,10 +13,6 @@ export interface GuacamoleClipboardClient {
sendKeyEvent(pressed: number, keysym: number): void;
}
export function isFirefoxBrowser(userAgent = navigator.userAgent): boolean {
return /(?:Firefox|FxiOS)\//.test(userAgent);
}
export function isPasteShortcut(
event: Pick<KeyboardEvent, "altKey" | "ctrlKey" | "key" | "metaKey">,
): boolean {
@@ -1,25 +1,11 @@
import { describe, expect, it, vi } from "vitest";
import {
isFirefoxBrowser,
isPasteShortcut,
pasteTextToRemote,
type GuacamoleClipboardClient,
} from "../../../features/guacamole/guacamole-clipboard.js";
describe("Guacamole Firefox clipboard fallback", () => {
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);
});
describe("Guacamole clipboard paste", () => {
it("recognizes Ctrl+V and Command+V without intercepting Alt+V", () => {
expect(
isPasteShortcut({