Skip to content

Commit aa03b65

Browse files
feat(webapp): support dynamic resizing for RDP and VNC sessions (#1389)
1 parent aca08f0 commit aa03b65

13 files changed

+218
-135
lines changed

webapp/package-lock.json

Lines changed: 28 additions & 12 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

webapp/package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,9 @@
3030
"@angular/platform-browser-dynamic": "18.2.1",
3131
"@angular/router": "18.2.1",
3232
"@devolutions/icons": "4.0.8",
33-
"@devolutions/iron-remote-desktop": "^0.5.1",
34-
"@devolutions/iron-remote-desktop-rdp": "^0.2.0",
35-
"@devolutions/iron-remote-desktop-vnc": "^0.3.0",
33+
"@devolutions/iron-remote-desktop": "^0.5.3",
34+
"@devolutions/iron-remote-desktop-rdp": "^0.2.2",
35+
"@devolutions/iron-remote-desktop-vnc": "^0.3.1",
3636
"@devolutions/web-ssh-gui": "0.3.1",
3737
"@devolutions/web-telnet-gui": "0.2.19",
3838
"primeflex": "3.3.1",

webapp/src/client/app/modules/web-client/rdp/web-client-rdp.component.ts

Lines changed: 57 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,13 @@ import { UtilsService } from '@shared/services/utils.service';
2727
import { WebClientService } from '@shared/services/web-client.service';
2828
import { WebSessionService } from '@shared/services/web-session.service';
2929
import { MessageService } from 'primeng/api';
30-
import { EMPTY, from, Observable, of, Subject, throwError } from 'rxjs';
30+
import { debounceTime, EMPTY, from, Observable, of, Subject, Subscription, throwError } from 'rxjs';
3131
import { catchError, map, switchMap, takeUntil } from 'rxjs/operators';
3232
import '@devolutions/iron-remote-desktop/iron-remote-desktop.js';
3333
import { DVL_RDP_ICON, DVL_WARNING_ICON, JET_RDP_URL } from '@gateway/app.constants';
3434
import { AnalyticService, ProtocolString } from '@gateway/shared/services/analytic.service';
35+
import { WebSession } from '@shared/models/web-session.model';
36+
import { ComponentResizeObserverService } from '@shared/services/component-resize-observer.service';
3537

3638
enum UserIronRdpErrorKind {
3739
General = 0,
@@ -49,6 +51,7 @@ enum UserIronRdpErrorKind {
4951
})
5052
export class WebClientRdpComponent extends WebClientBaseComponent implements OnInit, AfterViewInit, OnDestroy {
5153
@Input() webSessionId: string;
54+
@Input() sessionsContainerElement: ElementRef;
5255
@Output() componentStatus: EventEmitter<ComponentStatus> = new EventEmitter<ComponentStatus>();
5356
@Output() sizeChange: EventEmitter<void> = new EventEmitter<void>();
5457

@@ -61,10 +64,12 @@ export class WebClientRdpComponent extends WebClientBaseComponent implements OnI
6164
formData: RdpFormDataInput;
6265
rdpError: { kind: string; backtrace: string };
6366
isFullScreenMode = false;
64-
showToolbarDiv = true;
6567
useUnicodeKeyboard = false;
6668
cursorOverrideActive = false;
6769

70+
dynamicResizeSupported = false;
71+
dynamicResizeEnabled = false;
72+
6873
leftToolbarButtons = [
6974
{
7075
label: 'Start',
@@ -79,6 +84,11 @@ export class WebClientRdpComponent extends WebClientBaseComponent implements OnI
7984
];
8085

8186
middleToolbarButtons = [
87+
{
88+
label: 'Full Screen',
89+
icon: 'dvl-icon dvl-icon-fullscreen',
90+
action: () => this.toggleFullscreen(),
91+
},
8292
{
8393
label: 'Fit to Screen',
8494
icon: 'dvl-icon dvl-icon-minimize',
@@ -111,34 +121,40 @@ export class WebClientRdpComponent extends WebClientBaseComponent implements OnI
111121
checkboxes = [
112122
{
113123
label: 'Unicode Keyboard Mode',
114-
default: this.useUnicodeKeyboard,
124+
value: this.useUnicodeKeyboard,
115125
onChange: () => {
116126
this.useUnicodeKeyboard = !this.useUnicodeKeyboard;
117127
this.setKeyboardUnicodeMode(this.useUnicodeKeyboard);
118128
},
129+
enabled: () => true,
130+
},
131+
{
132+
label: 'Dynamic Resize',
133+
value: this.dynamicResizeEnabled,
134+
onChange: () => this.toggleDynamicResize(),
135+
enabled: () => this.dynamicResizeSupported,
119136
},
120137
];
121138

122139
protected removeElement = new Subject();
123140
private remoteClientEventListener: (event: Event) => void;
124141
private remoteClient: UserInteraction;
125142

143+
private componentResizeObserverDisconnect?: () => void;
144+
private dynamicComponentResizeSubscription?: Subscription;
145+
126146
constructor(
127147
private renderer: Renderer2,
128148
protected utils: UtilsService,
129149
protected gatewayAlertMessageService: GatewayAlertMessageService,
130150
private webSessionService: WebSessionService,
131151
private webClientService: WebClientService,
152+
private componentResizeService: ComponentResizeObserverService,
132153
protected analyticService: AnalyticService,
133154
) {
134155
super(gatewayAlertMessageService, analyticService);
135156
}
136157

137-
@HostListener('document:mousemove', ['$event'])
138-
onMouseMove(event: MouseEvent): void {
139-
this.handleSessionToolbarDisplay(event);
140-
}
141-
142158
@HostListener('document:fullscreenchange')
143159
onFullScreenChange(): void {
144160
this.handleOnFullScreenEvent();
@@ -155,6 +171,10 @@ export class WebClientRdpComponent extends WebClientBaseComponent implements OnI
155171
ngOnDestroy(): void {
156172
this.removeRemoteClientListener();
157173
this.removeWebClientGuiElement();
174+
175+
this.dynamicComponentResizeSubscription?.unsubscribe();
176+
this.componentResizeObserverDisconnect?.();
177+
158178
super.ngOnDestroy();
159179
}
160180

@@ -182,11 +202,7 @@ export class WebClientRdpComponent extends WebClientBaseComponent implements OnI
182202
}
183203

184204
scaleTo(scale: ScreenScale): void {
185-
if (scale === ScreenScale.Full) {
186-
this.toggleFullscreen();
187-
} else {
188-
this.remoteClient.setScale(scale.valueOf());
189-
}
205+
this.remoteClient.setScale(scale.valueOf());
190206
}
191207

192208
setKeyboardUnicodeMode(useUnicode: boolean): void {
@@ -203,6 +219,30 @@ export class WebClientRdpComponent extends WebClientBaseComponent implements OnI
203219
this.cursorOverrideActive = !this.cursorOverrideActive;
204220
}
205221

222+
toggleDynamicResize(): void {
223+
const RESIZE_DEBOUNCE_TIME = 100;
224+
225+
this.dynamicResizeEnabled = !this.dynamicResizeEnabled;
226+
227+
if (this.dynamicResizeEnabled) {
228+
this.componentResizeObserverDisconnect = this.componentResizeService.observe(
229+
this.sessionsContainerElement.nativeElement,
230+
);
231+
232+
this.dynamicComponentResizeSubscription = this.componentResizeService.resize$
233+
.pipe(debounceTime(RESIZE_DEBOUNCE_TIME))
234+
.subscribe(({ width, height }) => {
235+
if (!this.isFullScreenMode) {
236+
height -= WebSession.TOOLBAR_SIZE;
237+
}
238+
this.remoteClient.resize(width, height);
239+
});
240+
} else {
241+
this.dynamicComponentResizeSubscription?.unsubscribe();
242+
this.componentResizeObserverDisconnect?.();
243+
}
244+
}
245+
206246
removeWebClientGuiElement(): void {
207247
this.removeElement.pipe(takeUntil(this.destroyed$)).subscribe({
208248
next: (): void => {
@@ -236,21 +276,11 @@ export class WebClientRdpComponent extends WebClientBaseComponent implements OnI
236276
}
237277
}
238278

239-
private handleSessionToolbarDisplay(event: MouseEvent): void {
240-
if (!document.fullscreenElement) {
241-
return;
242-
}
243-
244-
if (event.clientY === 0) {
245-
this.showToolbarDiv = true;
246-
} else if (event.clientY > 44) {
247-
this.showToolbarDiv = false;
248-
}
249-
}
250-
251279
private toggleFullscreen(): void {
252280
this.isFullScreenMode = !this.isFullScreenMode;
253281
!document.fullscreenElement ? this.enterFullScreen() : this.exitFullScreen();
282+
283+
this.scaleTo(ScreenScale.Full);
254284
}
255285

256286
private async enterFullScreen(): Promise<void> {
@@ -259,8 +289,7 @@ export class WebClientRdpComponent extends WebClientBaseComponent implements OnI
259289
}
260290

261291
try {
262-
const sessionContainerElement = this.sessionContainerElement.nativeElement;
263-
await sessionContainerElement.requestFullscreen();
292+
await this.sessionsContainerElement.nativeElement.requestFullscreen();
264293
} catch (err) {
265294
this.isFullScreenMode = false;
266295
console.error(`Error attempting to enable fullscreen mode: ${err.message} (${err.name})`);
@@ -277,7 +306,6 @@ export class WebClientRdpComponent extends WebClientBaseComponent implements OnI
277306

278307
private handleExitFullScreenEvent(): void {
279308
this.isFullScreenMode = false;
280-
this.showToolbarDiv = true;
281309

282310
const sessionContainerElement = this.sessionContainerElement.nativeElement;
283311
const sessionToolbarElement = sessionContainerElement.querySelector('#sessionToolbar');
@@ -394,6 +422,7 @@ export class WebClientRdpComponent extends WebClientBaseComponent implements OnI
394422

395423
if (connectionParameters.enableDisplayControl) {
396424
configBuilder.withExtension(displayControl(true));
425+
this.dynamicResizeSupported = true;
397426
}
398427

399428
if (connectionParameters.preConnectionBlob != null) {

webapp/src/client/app/modules/web-client/vnc/web-client-vnc.component.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
[middleButtons]="middleToolbarButtons"
1414
[middleToggleButtons]="middleToolbarToggleButtons"
1515
[rightButtons]="rightToolbarButtons"
16+
[checkboxes]="checkboxes"
1617
[sliders]="sliders">
1718
</session-toolbar>
1819

0 commit comments

Comments
 (0)