diff --git a/src/ui/features/guacamole/GuacamoleApp.tsx b/src/ui/features/guacamole/GuacamoleApp.tsx
index a978557d..4c9f80a6 100644
--- a/src/ui/features/guacamole/GuacamoleApp.tsx
+++ b/src/ui/features/guacamole/GuacamoleApp.tsx
@@ -17,6 +17,7 @@ import {
logActivity,
isElectron,
} from "@/main-axios.ts";
+import { readConfiguredDimension } from "@/features/guacamole/guacamole-display-size.ts";
import { resolveConnectionOrigin } from "@/lib/connection-origin.ts";
import { useTranslation } from "react-i18next";
import { AlertCircle, RefreshCw } from "lucide-react";
@@ -362,7 +363,15 @@ const GuacamoleAppInner = React.forwardRef<
}
const resolvedProtocol = resolvedProtocolForConnect;
- const configuredDpi = Number(hostConfig.guacamoleConfig?.dpi);
+ const configuredDpi = readConfiguredDimension(
+ hostConfig.guacamoleConfig?.dpi,
+ );
+ const configuredWidth = readConfiguredDimension(
+ hostConfig.guacamoleConfig?.width,
+ );
+ const configuredHeight = readConfiguredDimension(
+ hostConfig.guacamoleConfig?.height,
+ );
return (
@@ -400,10 +409,9 @@ const GuacamoleAppInner = React.forwardRef<
token,
protocol: resolvedProtocol,
type: resolvedProtocol,
- dpi:
- Number.isFinite(configuredDpi) && configuredDpi > 0
- ? configuredDpi
- : undefined,
+ width: configuredWidth,
+ height: configuredHeight,
+ dpi: configuredDpi,
}}
isVisible={isVisible}
touchMode={touchMode}
diff --git a/src/ui/features/guacamole/GuacamoleDisplay.tsx b/src/ui/features/guacamole/GuacamoleDisplay.tsx
index 671fa6f7..74e14856 100644
--- a/src/ui/features/guacamole/GuacamoleDisplay.tsx
+++ b/src/ui/features/guacamole/GuacamoleDisplay.tsx
@@ -69,6 +69,10 @@ export const GuacamoleDisplay = forwardRef<
ref,
) {
const { t } = useTranslation();
+ // The host config pins the session resolution; without it the display follows
+ // the container.
+ const hasConfiguredSize =
+ connectionConfig.width != null && connectionConfig.height != null;
const containerRef = useRef(null);
const displayRef = useRef(null);
const displayElementRef = useRef(null);
@@ -493,7 +497,10 @@ export const GuacamoleDisplay = forwardRef<
isConnectingRef.current = false;
setIsReady(true);
onConnect?.();
- if (containerRef.current) {
+ // A configured resolution is the size the session should render at;
+ // resizing it to the container would discard it. rescaleDisplay still
+ // fits that fixed display into whatever space is available.
+ if (!hasConfiguredSize && containerRef.current) {
const rect = containerRef.current.getBoundingClientRect();
const size = getGuacamoleDisplaySize(
rect.width,
@@ -601,6 +608,7 @@ export const GuacamoleDisplay = forwardRef<
connectionConfig.protocol,
connectionConfig.type,
connectionConfig.dpi,
+ hasConfiguredSize,
touchMode,
t,
]);
@@ -675,15 +683,17 @@ export const GuacamoleDisplay = forwardRef<
resizeTimeoutRef.current = setTimeout(() => {
if (clientRef.current && containerRef.current) {
const rect = containerRef.current.getBoundingClientRect();
- const size = getGuacamoleDisplaySize(
- rect.width,
- rect.height,
- connectionConfig.protocol ?? connectionConfig.type,
- window.devicePixelRatio,
- connectionConfig.dpi,
- );
if (rect.width > 0 && rect.height > 0) {
- clientRef.current.sendSize(size.width, size.height);
+ if (!hasConfiguredSize) {
+ const size = getGuacamoleDisplaySize(
+ rect.width,
+ rect.height,
+ connectionConfig.protocol ?? connectionConfig.type,
+ window.devicePixelRatio,
+ connectionConfig.dpi,
+ );
+ clientRef.current.sendSize(size.width, size.height);
+ }
rescaleDisplay(true);
}
}
@@ -699,6 +709,7 @@ export const GuacamoleDisplay = forwardRef<
connectionConfig.dpi,
connectionConfig.protocol,
connectionConfig.type,
+ hasConfiguredSize,
rescaleDisplay,
]);
diff --git a/src/ui/features/guacamole/guacamole-display-size.ts b/src/ui/features/guacamole/guacamole-display-size.ts
index a16a3475..27e34f3a 100644
--- a/src/ui/features/guacamole/guacamole-display-size.ts
+++ b/src/ui/features/guacamole/guacamole-display-size.ts
@@ -1,6 +1,15 @@
const DEFAULT_RDP_DPI = 96;
const MAX_DEVICE_PIXEL_RATIO = 3;
+/**
+ * Reads a guacamoleConfig display field. The UI stores these as strings, and
+ * leaves them empty when the size should follow the browser window.
+ */
+export function readConfiguredDimension(value: unknown): number | undefined {
+ const parsed = Number(value);
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : undefined;
+}
+
export interface GuacamoleDisplaySize {
width: number;
height: number;
diff --git a/src/ui/tests/features/guacamole/guacamole-display-size.test.ts b/src/ui/tests/features/guacamole/guacamole-display-size.test.ts
index b208acd7..71c336bf 100644
--- a/src/ui/tests/features/guacamole/guacamole-display-size.test.ts
+++ b/src/ui/tests/features/guacamole/guacamole-display-size.test.ts
@@ -1,5 +1,8 @@
import { describe, expect, it } from "vitest";
-import { getGuacamoleDisplaySize } from "../../../features/guacamole/guacamole-display-size";
+import {
+ getGuacamoleDisplaySize,
+ readConfiguredDimension,
+} from "../../../features/guacamole/guacamole-display-size";
describe("getGuacamoleDisplaySize", () => {
it("requests native pixels and matching DPI for HiDPI RDP", () => {
@@ -36,4 +39,29 @@ describe("getGuacamoleDisplaySize", () => {
pixelRatio: 3,
});
});
+
+ it("uses a configured resolution in place of the container size", () => {
+ expect(
+ getGuacamoleDisplaySize(800, 600, "rdp", 1, undefined),
+ ).toMatchObject({ width: 800, height: 600 });
+ expect(
+ getGuacamoleDisplaySize(1920, 1080, "rdp", 1, undefined),
+ ).toMatchObject({ width: 1920, height: 1080 });
+ });
+});
+
+describe("readConfiguredDimension", () => {
+ it("accepts the strings the host editor stores", () => {
+ expect(readConfiguredDimension("1920")).toBe(1920);
+ expect(readConfiguredDimension(1080)).toBe(1080);
+ });
+
+ it("treats an unset or unusable value as 'follow the container'", () => {
+ expect(readConfiguredDimension("")).toBeUndefined();
+ expect(readConfiguredDimension(undefined)).toBeUndefined();
+ expect(readConfiguredDimension(null)).toBeUndefined();
+ expect(readConfiguredDimension("auto")).toBeUndefined();
+ expect(readConfiguredDimension("0")).toBeUndefined();
+ expect(readConfiguredDimension("-1080")).toBeUndefined();
+ });
});