@electerm/electerm-react 2.7.8 → 2.8.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/client/common/pre.js +38 -11
- package/client/components/bookmark-form/config/rdp.js +1 -0
- package/client/components/bookmark-form/config/vnc.js +5 -0
- package/client/components/common/remote-float-control.jsx +79 -0
- package/client/components/common/remote-float-control.styl +28 -0
- package/client/components/layout/layout.jsx +2 -1
- package/client/components/main/main.jsx +3 -6
- package/client/components/main/term-fullscreen.styl +1 -10
- package/client/components/rdp/rdp-session.jsx +131 -17
- package/client/components/rdp/resolutions.js +6 -0
- package/client/components/session/session.jsx +4 -3
- package/client/components/session/session.styl +18 -5
- package/client/components/session/sessions.jsx +2 -1
- package/client/components/shortcuts/shortcut-control.jsx +5 -3
- package/client/components/shortcuts/shortcut-handler.js +4 -2
- package/client/components/terminal/attach-addon-custom.js +13 -0
- package/client/components/terminal/event-emitter.js +27 -0
- package/client/components/terminal/terminal.jsx +10 -297
- package/client/components/terminal/zmodem-client.js +385 -0
- package/client/components/terminal-info/data-cols-parser.jsx +3 -2
- package/client/components/terminal-info/network.jsx +3 -2
- package/client/components/vnc/vnc-session.jsx +398 -62
- package/client/css/basic.styl +3 -0
- package/client/store/event.js +2 -2
- package/client/store/init-state.js +1 -1
- package/package.json +1 -1
- package/client/common/byte-format.js +0 -14
- package/client/components/main/term-fullscreen-control.jsx +0 -21
- package/client/components/terminal/xterm-zmodem.js +0 -55
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import { createRef } from 'react'
|
|
2
|
-
import RdpSession from '../rdp/rdp-session'
|
|
1
|
+
import { PureComponent, createRef } from 'react'
|
|
3
2
|
import { createTerm } from '../terminal/terminal-apis'
|
|
4
3
|
import deepCopy from 'json-deep-copy'
|
|
5
4
|
import clone from '../../common/to-simple-obj'
|
|
@@ -9,30 +8,41 @@ import {
|
|
|
9
8
|
} from '../../common/constants'
|
|
10
9
|
import {
|
|
11
10
|
Spin,
|
|
12
|
-
|
|
11
|
+
Select
|
|
13
12
|
} from 'antd'
|
|
13
|
+
import {
|
|
14
|
+
ReloadOutlined
|
|
15
|
+
} from '@ant-design/icons'
|
|
14
16
|
import message from '../common/message'
|
|
15
17
|
import Modal from '../common/modal'
|
|
16
|
-
import * as ls from '../../common/safe-local-storage'
|
|
17
18
|
import { copy } from '../../common/clipboard'
|
|
18
|
-
import resolutions from '../rdp/resolutions'
|
|
19
|
-
import RFB from '@novnc/novnc/core/rfb'
|
|
20
19
|
import VncForm from './vnc-form'
|
|
20
|
+
import RemoteFloatControl from '../common/remote-float-control'
|
|
21
|
+
|
|
22
|
+
// noVNC module imports — loaded dynamically
|
|
23
|
+
async function loadVncModule () {
|
|
24
|
+
if (window.novnc) return
|
|
25
|
+
console.debug('[VNC-CLIENT] Loading noVNC module...')
|
|
26
|
+
const mod = await import('@novnc/novnc/core/rfb')
|
|
27
|
+
window.novnc = {
|
|
28
|
+
RFB: mod.default
|
|
29
|
+
}
|
|
30
|
+
console.debug('[VNC-CLIENT] noVNC module loaded')
|
|
31
|
+
}
|
|
21
32
|
|
|
33
|
+
const { Option } = Select
|
|
22
34
|
const e = window.translate
|
|
23
35
|
|
|
24
|
-
export default class VncSession extends
|
|
36
|
+
export default class VncSession extends PureComponent {
|
|
25
37
|
constructor (props) {
|
|
26
|
-
const id = `vnc-reso-${props.tab.host}`
|
|
27
|
-
const resObj = ls.getItemJSON(id, resolutions[0])
|
|
28
38
|
super(props)
|
|
29
39
|
this.state = {
|
|
30
40
|
types: [],
|
|
31
41
|
showConfirm: false,
|
|
32
42
|
loading: false,
|
|
33
|
-
aspectRatio: 4 / 3,
|
|
34
43
|
name: '',
|
|
35
|
-
|
|
44
|
+
screens: [],
|
|
45
|
+
currentScreen: '0'
|
|
36
46
|
}
|
|
37
47
|
}
|
|
38
48
|
|
|
@@ -45,33 +55,125 @@ export default class VncSession extends RdpSession {
|
|
|
45
55
|
componentWillUnmount () {
|
|
46
56
|
this.rfb && this.rfb.disconnect()
|
|
47
57
|
delete this.rfb
|
|
58
|
+
clearTimeout(this.timer)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
setStatus = status => {
|
|
62
|
+
const id = this.props.tab?.id
|
|
63
|
+
this.props.editTab(id, {
|
|
64
|
+
status
|
|
65
|
+
})
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
getScreenSize = () => {
|
|
69
|
+
const { screens, currentScreen } = this.state
|
|
70
|
+
const currentScreenData = screens.find(s => s.id === currentScreen)
|
|
71
|
+
return {
|
|
72
|
+
remoteWidth: currentScreenData ? currentScreenData.width : null,
|
|
73
|
+
remoteHeight: currentScreenData ? currentScreenData.height : null
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
calcCanvasSize = () => {
|
|
78
|
+
const { width, height } = this.props
|
|
79
|
+
return {
|
|
80
|
+
width: width - 10,
|
|
81
|
+
height: height - 80
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
buildWsUrl = (port, type = 'rdp', extra = '') => {
|
|
86
|
+
const { host, tokenElecterm } = this.props.config
|
|
87
|
+
const { id } = this.props.tab
|
|
88
|
+
if (window.et.buildWsUrl) {
|
|
89
|
+
return window.et.buildWsUrl(
|
|
90
|
+
host,
|
|
91
|
+
port,
|
|
92
|
+
tokenElecterm,
|
|
93
|
+
id,
|
|
94
|
+
type,
|
|
95
|
+
extra
|
|
96
|
+
)
|
|
97
|
+
}
|
|
98
|
+
return `ws://${host}:${port}/${type}/${id}?token=${tokenElecterm}${extra}`
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
getControlProps = (options = {}) => {
|
|
102
|
+
const {
|
|
103
|
+
screens,
|
|
104
|
+
currentScreen
|
|
105
|
+
} = this.state
|
|
106
|
+
const {
|
|
107
|
+
fixedPosition = true,
|
|
108
|
+
showExitFullscreen = true,
|
|
109
|
+
className = ''
|
|
110
|
+
} = options
|
|
111
|
+
|
|
112
|
+
return {
|
|
113
|
+
isFullScreen: this.props.fullscreen,
|
|
114
|
+
onSendCtrlAltDel: this.handleSendCtrlAltDel,
|
|
115
|
+
screens: screens.length > 1 ? screens : [],
|
|
116
|
+
currentScreen,
|
|
117
|
+
onSelectScreen: this.handleSelectScreen,
|
|
118
|
+
fixedPosition,
|
|
119
|
+
showExitFullscreen,
|
|
120
|
+
className
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
renderControl = () => {
|
|
125
|
+
const contrlProps = this.getControlProps({
|
|
126
|
+
fixedPosition: false,
|
|
127
|
+
showExitFullscreen: false,
|
|
128
|
+
className: 'mg1l'
|
|
129
|
+
})
|
|
130
|
+
return (
|
|
131
|
+
<div className='pd1 fix session-v-info'>
|
|
132
|
+
<div className='fleft'>
|
|
133
|
+
<ReloadOutlined
|
|
134
|
+
onClick={this.handleReInit}
|
|
135
|
+
className='mg2r mg1l pointer'
|
|
136
|
+
/>
|
|
137
|
+
{this.renderScreensSelect()}
|
|
138
|
+
{this.renderInfo()}
|
|
139
|
+
</div>
|
|
140
|
+
<div className='fright'>
|
|
141
|
+
{this.props.fullscreenIcon()}
|
|
142
|
+
<RemoteFloatControl {...contrlProps} />
|
|
143
|
+
</div>
|
|
144
|
+
</div>
|
|
145
|
+
)
|
|
48
146
|
}
|
|
49
147
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
148
|
+
renderScreensSelect = () => {
|
|
149
|
+
const {
|
|
150
|
+
screens,
|
|
151
|
+
currentScreen
|
|
152
|
+
} = this.state
|
|
153
|
+
if (screens.length <= 1) {
|
|
154
|
+
return null
|
|
155
|
+
}
|
|
156
|
+
return (
|
|
157
|
+
<Select
|
|
158
|
+
value={currentScreen}
|
|
159
|
+
onChange={this.handleSelectScreen}
|
|
160
|
+
className='mg2r'
|
|
161
|
+
popupMatchSelectWidth={false}
|
|
162
|
+
>
|
|
163
|
+
{
|
|
164
|
+
screens.map(s => (
|
|
165
|
+
<Option key={s.id} value={s.id}>{s.name}</Option>
|
|
166
|
+
))
|
|
167
|
+
}
|
|
168
|
+
</Select>
|
|
169
|
+
)
|
|
170
|
+
}
|
|
64
171
|
|
|
65
172
|
remoteInit = async (term = this.term) => {
|
|
66
173
|
this.setState({
|
|
67
174
|
loading: true
|
|
68
175
|
})
|
|
69
176
|
const { config } = this.props
|
|
70
|
-
const {
|
|
71
|
-
host,
|
|
72
|
-
tokenElecterm,
|
|
73
|
-
server = ''
|
|
74
|
-
} = config
|
|
75
177
|
const { id } = this.props
|
|
76
178
|
const tab = window.store.applyProfile(deepCopy(this.props.tab || {}))
|
|
77
179
|
const {
|
|
@@ -80,6 +182,9 @@ export default class VncSession extends RdpSession {
|
|
|
80
182
|
viewOnly = false,
|
|
81
183
|
scaleViewport = true,
|
|
82
184
|
clipViewport = false,
|
|
185
|
+
qualityLevel = 3, // 0-9, lower = faster performance
|
|
186
|
+
compressionLevel = 1, // 0-9, lower = faster performance
|
|
187
|
+
shared = true,
|
|
83
188
|
username,
|
|
84
189
|
password
|
|
85
190
|
} = tab
|
|
@@ -107,21 +212,35 @@ export default class VncSession extends RdpSession {
|
|
|
107
212
|
const { pid, port } = r
|
|
108
213
|
this.pid = pid
|
|
109
214
|
this.port = port
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
215
|
+
|
|
216
|
+
// Load VNC module if not already loaded
|
|
217
|
+
try {
|
|
218
|
+
await loadVncModule()
|
|
219
|
+
} catch (e) {
|
|
220
|
+
console.error('[VNC-CLIENT] Failed to load VNC module:', e)
|
|
221
|
+
this.setState({ loading: false })
|
|
222
|
+
this.setStatus(statusMap.error)
|
|
223
|
+
return
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const { width, height } = this.calcCanvasSize()
|
|
227
|
+
const wsUrl = this.buildWsUrl(port, 'vnc', `&width=${width}&height=${height}`)
|
|
228
|
+
// When scaleViewport is false, we don't set fixed dimensions on the canvas
|
|
229
|
+
// so it can render at the actual remote screen size
|
|
116
230
|
const vncOpts = {
|
|
117
231
|
clipViewport,
|
|
118
232
|
scaleViewport,
|
|
119
233
|
viewOnly,
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
234
|
+
qualityLevel, // JPEG quality 0-9, lower = faster
|
|
235
|
+
compressionLevel, // Compression 0-9, lower = faster
|
|
236
|
+
shared, // Allow shared connections
|
|
237
|
+
style: scaleViewport
|
|
238
|
+
? {
|
|
239
|
+
width: width + 'px',
|
|
240
|
+
height: height + 'px',
|
|
241
|
+
overflow: 'hidden'
|
|
242
|
+
}
|
|
243
|
+
: {},
|
|
125
244
|
credentials: {}
|
|
126
245
|
}
|
|
127
246
|
if (username) {
|
|
@@ -130,11 +249,112 @@ export default class VncSession extends RdpSession {
|
|
|
130
249
|
if (password) {
|
|
131
250
|
vncOpts.credentials.password = password
|
|
132
251
|
}
|
|
252
|
+
const RFB = window.novnc.RFB
|
|
133
253
|
const rfb = new RFB(
|
|
134
254
|
this.getDom(),
|
|
135
255
|
wsUrl,
|
|
136
256
|
vncOpts
|
|
137
257
|
)
|
|
258
|
+
|
|
259
|
+
// Monkey patch _handleExtendedDesktopSize to capture screen info
|
|
260
|
+
const originalHandleExtendedDesktopSize = rfb._handleExtendedDesktopSize
|
|
261
|
+
rfb._handleExtendedDesktopSize = function () {
|
|
262
|
+
try {
|
|
263
|
+
// Wait for header
|
|
264
|
+
if (this._sock.rQwait('ExtendedDesktopSize', 4)) {
|
|
265
|
+
return false
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
const numberOfScreens = this._sock.rQpeek8()
|
|
269
|
+
const bytes = 4 + (numberOfScreens * 16)
|
|
270
|
+
if (this._sock.rQwait('ExtendedDesktopSize', bytes)) {
|
|
271
|
+
return false
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
const firstUpdate = !this._supportsSetDesktopSize
|
|
275
|
+
this._supportsSetDesktopSize = true
|
|
276
|
+
|
|
277
|
+
if (firstUpdate) {
|
|
278
|
+
this._requestRemoteResize()
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
const num = this._sock.rQshift8() // number-of-screens
|
|
282
|
+
this._sock.rQskipBytes(3) // padding
|
|
283
|
+
|
|
284
|
+
const screens = []
|
|
285
|
+
for (let i = 0; i < num; i += 1) {
|
|
286
|
+
const idBytes = this._sock.rQshiftBytes(4)
|
|
287
|
+
const id = (idBytes[0] << 24 | idBytes[1] << 16 | idBytes[2] << 8 | idBytes[3]) >>> 0
|
|
288
|
+
|
|
289
|
+
const x = this._sock.rQshift16() // x-position
|
|
290
|
+
const y = this._sock.rQshift16() // y-position
|
|
291
|
+
const w = this._sock.rQshift16() // width
|
|
292
|
+
const h = this._sock.rQshift16() // height
|
|
293
|
+
|
|
294
|
+
const flagsBytes = this._sock.rQshiftBytes(4)
|
|
295
|
+
const flags = (flagsBytes[0] << 24 | flagsBytes[1] << 16 | flagsBytes[2] << 8 | flagsBytes[3]) >>> 0
|
|
296
|
+
|
|
297
|
+
screens.push({ id, x, y, width: w, height: h, flags })
|
|
298
|
+
|
|
299
|
+
if (i === 0) {
|
|
300
|
+
this._screenID = idBytes
|
|
301
|
+
this._screenFlags = flagsBytes
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// Store the screens in a custom property and dispatch event
|
|
306
|
+
this.screens = screens
|
|
307
|
+
this.dispatchEvent(new CustomEvent('screens', { detail: screens }))
|
|
308
|
+
|
|
309
|
+
// Handle the resize feedback logic from original code
|
|
310
|
+
if (this._FBU.x === 1 && this._FBU.y !== 0) {
|
|
311
|
+
let msg = ''
|
|
312
|
+
switch (this._FBU.y) {
|
|
313
|
+
case 1: msg = 'Resize is administratively prohibited'; break
|
|
314
|
+
case 2: msg = 'Out of resources'; break
|
|
315
|
+
case 3: msg = 'Invalid screen layout'; break
|
|
316
|
+
default: msg = 'Unknown reason'; break
|
|
317
|
+
}
|
|
318
|
+
console.warn('Server did not accept the resize request: ' + msg)
|
|
319
|
+
} else {
|
|
320
|
+
this._resize(this._FBU.width, this._FBU.height)
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
return true
|
|
324
|
+
} catch (e) {
|
|
325
|
+
console.error('Error in patched _handleExtendedDesktopSize', e)
|
|
326
|
+
return originalHandleExtendedDesktopSize.call(this)
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// Monkey patch autoscale to support single screen scaling
|
|
331
|
+
const originalAutoscale = rfb._display.autoscale
|
|
332
|
+
rfb._display.autoscale = (containerWidth, containerHeight) => {
|
|
333
|
+
const { currentScreen, screens } = this.state
|
|
334
|
+
|
|
335
|
+
if (currentScreen === 'all' || !screens.length) {
|
|
336
|
+
return originalAutoscale.call(rfb._display, containerWidth, containerHeight)
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
const screen = screens.find(s => s.id === currentScreen)
|
|
340
|
+
if (!screen) {
|
|
341
|
+
return originalAutoscale.call(rfb._display, containerWidth, containerHeight)
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
let scaleRatio = 1.0
|
|
345
|
+
if (containerWidth > 0 && containerHeight > 0) {
|
|
346
|
+
const targetAspectRatio = containerWidth / containerHeight
|
|
347
|
+
const screenAspectRatio = screen.width / screen.height
|
|
348
|
+
|
|
349
|
+
if (screenAspectRatio >= targetAspectRatio) {
|
|
350
|
+
scaleRatio = containerWidth / screen.width
|
|
351
|
+
} else {
|
|
352
|
+
scaleRatio = containerHeight / screen.height
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
rfb._display._rescale(scaleRatio)
|
|
356
|
+
}
|
|
357
|
+
|
|
138
358
|
const events = [
|
|
139
359
|
'connect',
|
|
140
360
|
'disconnect',
|
|
@@ -143,21 +363,29 @@ export default class VncSession extends RdpSession {
|
|
|
143
363
|
'clipboard',
|
|
144
364
|
'bell',
|
|
145
365
|
'desktopname',
|
|
146
|
-
'capabilities'
|
|
366
|
+
'capabilities',
|
|
367
|
+
'screens',
|
|
368
|
+
'desktopsize'
|
|
147
369
|
]
|
|
148
370
|
for (const event of events) {
|
|
149
371
|
rfb.addEventListener(event, this[`on${window.capitalizeFirstLetter(event)}`])
|
|
150
372
|
}
|
|
373
|
+
rfb.scaleViewport = scaleViewport
|
|
374
|
+
rfb.clipViewport = clipViewport
|
|
151
375
|
this.rfb = rfb
|
|
152
376
|
}
|
|
153
377
|
|
|
154
378
|
onConnect = (event) => {
|
|
155
379
|
this.setStatus(statusMap.success)
|
|
380
|
+
// Capture the remote desktop size from the RFB connection
|
|
156
381
|
this.setState({
|
|
157
382
|
loading: false
|
|
158
383
|
})
|
|
159
384
|
}
|
|
160
385
|
|
|
386
|
+
onDesktopsize = (event) => {
|
|
387
|
+
}
|
|
388
|
+
|
|
161
389
|
onDisconnect = () => {
|
|
162
390
|
this.setStatus(statusMap.error)
|
|
163
391
|
}
|
|
@@ -201,10 +429,97 @@ export default class VncSession extends RdpSession {
|
|
|
201
429
|
this.setState({
|
|
202
430
|
name: event?.detail?.name || ''
|
|
203
431
|
})
|
|
432
|
+
this.checkScreens()
|
|
204
433
|
}
|
|
205
434
|
|
|
206
435
|
onCapabilities = (capabilities) => {
|
|
207
|
-
|
|
436
|
+
setTimeout(this.checkScreens, 1000)
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
processScreens = (screens) => {
|
|
440
|
+
if (!screens || !screens.length) {
|
|
441
|
+
return []
|
|
442
|
+
}
|
|
443
|
+
return screens.map((s, i) => ({
|
|
444
|
+
name: `Screen ${i + 1}`,
|
|
445
|
+
...s,
|
|
446
|
+
id: s.id !== undefined ? s.id : i
|
|
447
|
+
}))
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
onScreens = (event) => {
|
|
451
|
+
const screensData = event.detail
|
|
452
|
+
if (screensData && screensData.length) {
|
|
453
|
+
const screens = this.processScreens(screensData)
|
|
454
|
+
this.setState({
|
|
455
|
+
screens,
|
|
456
|
+
currentScreen: screens[0].id
|
|
457
|
+
})
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
checkScreens = () => {
|
|
462
|
+
// Attempt to detect screens from noVNC
|
|
463
|
+
let screens = this.rfb?.screens
|
|
464
|
+
if (!screens && this.rfb?.capabilities?.screens) {
|
|
465
|
+
screens = this.rfb.capabilities.screens
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
if (screens && screens.length) {
|
|
469
|
+
this.setState({
|
|
470
|
+
screens: this.processScreens(screens)
|
|
471
|
+
})
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
componentDidUpdate (prevProps, prevState) {
|
|
476
|
+
if (
|
|
477
|
+
prevProps.width !== this.props.width ||
|
|
478
|
+
prevProps.height !== this.props.height ||
|
|
479
|
+
prevState.currentScreen !== this.state.currentScreen
|
|
480
|
+
) {
|
|
481
|
+
this.updateViewLayout()
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
updateViewLayout = () => {
|
|
486
|
+
const { currentScreen, screens } = this.state
|
|
487
|
+
const { scaleViewport = true, clipViewport = false } = this.props.tab
|
|
488
|
+
const container = this.domRef.current ? this.domRef.current.firstElementChild : null
|
|
489
|
+
|
|
490
|
+
if (!this.rfb || !container) {
|
|
491
|
+
return
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
// Force re-apply scaleViewport to trigger autoscale logic (if enabled)
|
|
495
|
+
// or reset to 1.0 (if disabled)
|
|
496
|
+
this.rfb.scaleViewport = scaleViewport
|
|
497
|
+
this.rfb.clipViewport = clipViewport
|
|
498
|
+
|
|
499
|
+
// Explicitly trigger autoscale to ensure our patched logic runs
|
|
500
|
+
if (scaleViewport && this.rfb._display) {
|
|
501
|
+
this.rfb._display.autoscale(container.clientWidth, container.clientHeight)
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
// Handle scrolling to the selected screen
|
|
505
|
+
this.timer = setTimeout(() => {
|
|
506
|
+
if (currentScreen === 'all') {
|
|
507
|
+
container.scrollTo(0, 0)
|
|
508
|
+
} else {
|
|
509
|
+
const screen = screens.find(s => s.id === currentScreen)
|
|
510
|
+
if (screen) {
|
|
511
|
+
const scale = this.rfb._display.scale
|
|
512
|
+
container.scrollTo(screen.x * scale, screen.y * scale)
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
}, 50)
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
handleSelectScreen = (id) => {
|
|
519
|
+
this.setState({
|
|
520
|
+
currentScreen: id
|
|
521
|
+
})
|
|
522
|
+
// updateViewLayout will be called by componentDidUpdate
|
|
208
523
|
}
|
|
209
524
|
|
|
210
525
|
getDom = () => {
|
|
@@ -233,10 +548,8 @@ export default class VncSession extends RdpSession {
|
|
|
233
548
|
)
|
|
234
549
|
}
|
|
235
550
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
<Tag color='red' className='mg1l' variant='solid'>Beta</Tag>
|
|
239
|
-
)
|
|
551
|
+
handleSendCtrlAltDel = () => {
|
|
552
|
+
this.rfb?.sendCtrlAltDel()
|
|
240
553
|
}
|
|
241
554
|
|
|
242
555
|
renderConfirm () {
|
|
@@ -263,31 +576,54 @@ export default class VncSession extends RdpSession {
|
|
|
263
576
|
|
|
264
577
|
render () {
|
|
265
578
|
const { width: w, height: h } = this.props
|
|
266
|
-
const
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
579
|
+
const { loading } = this.state
|
|
580
|
+
const { remoteWidth, remoteHeight } = this.getScreenSize()
|
|
581
|
+
const { scaleViewport = true } = this.props.tab
|
|
582
|
+
// When not in scale mode, we need a wrapper with container size,
|
|
583
|
+
// and the inner div should have remote screen size to show scrollbars
|
|
584
|
+
const isScaled = scaleViewport
|
|
585
|
+
|
|
586
|
+
const {
|
|
587
|
+
width: innerWidth,
|
|
588
|
+
height: innerHeight
|
|
589
|
+
} = this.calcCanvasSize()
|
|
590
|
+
const wrapperStyle = {
|
|
591
|
+
width: innerWidth + 'px',
|
|
592
|
+
height: innerHeight + 'px',
|
|
593
|
+
overflow: isScaled ? 'hidden' : 'auto'
|
|
594
|
+
}
|
|
595
|
+
const remoteStyle = {
|
|
596
|
+
width: isScaled ? '100%' : remoteWidth + 'px',
|
|
597
|
+
height: isScaled ? '100%' : remoteHeight + 'px',
|
|
598
|
+
overflow: 'hidden'
|
|
271
599
|
}
|
|
272
|
-
const { width, height, loading } = this.state
|
|
273
600
|
const divProps = {
|
|
274
|
-
style:
|
|
275
|
-
|
|
276
|
-
height: height + 'px'
|
|
277
|
-
}
|
|
601
|
+
style: remoteStyle,
|
|
602
|
+
className: 'vnc-session-wrap session-v-wrap'
|
|
278
603
|
}
|
|
604
|
+
const contrlProps = this.getControlProps()
|
|
279
605
|
return (
|
|
280
606
|
<Spin spinning={loading}>
|
|
281
607
|
<div
|
|
282
|
-
{...vncProps}
|
|
283
608
|
className='rdp-session-wrap pd1'
|
|
609
|
+
style={{
|
|
610
|
+
width: w + 'px',
|
|
611
|
+
height: h + 'px'
|
|
612
|
+
}}
|
|
284
613
|
>
|
|
285
614
|
{this.renderControl()}
|
|
286
|
-
<
|
|
287
|
-
{...
|
|
288
|
-
className='vnc-session-wrap session-v-wrap'
|
|
289
|
-
ref={this.domRef}
|
|
615
|
+
<RemoteFloatControl
|
|
616
|
+
{...contrlProps}
|
|
290
617
|
/>
|
|
618
|
+
<div
|
|
619
|
+
style={wrapperStyle}
|
|
620
|
+
className='vnc-scroll-wrapper'
|
|
621
|
+
>
|
|
622
|
+
<div
|
|
623
|
+
{...divProps}
|
|
624
|
+
ref={this.domRef}
|
|
625
|
+
/>
|
|
626
|
+
</div>
|
|
291
627
|
{this.renderConfirm()}
|
|
292
628
|
</div>
|
|
293
629
|
</Spin>
|
package/client/css/basic.styl
CHANGED
package/client/store/event.js
CHANGED
|
@@ -37,8 +37,8 @@ export default Store => {
|
|
|
37
37
|
window.dispatchEvent(new Event('resize'))
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
Store.prototype.
|
|
41
|
-
window.store.
|
|
40
|
+
Store.prototype.toggleSessFullscreen = function (fullscreen) {
|
|
41
|
+
window.store.fullscreen = fullscreen
|
|
42
42
|
setTimeout(window.store.triggerResize, 500)
|
|
43
43
|
}
|
|
44
44
|
}
|
|
@@ -193,7 +193,7 @@ export default () => {
|
|
|
193
193
|
height: 500,
|
|
194
194
|
isMaximized: window.pre.runSync('isMaximized'),
|
|
195
195
|
hasNodePty: window.pre.runSync('nodePtyCheck'),
|
|
196
|
-
|
|
196
|
+
fullscreen: false,
|
|
197
197
|
hideDelKeyTip: ls.getItem(dismissDelKeyTipLsKey) === 'y',
|
|
198
198
|
tabsHeight: 36,
|
|
199
199
|
|
package/package.json
CHANGED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
export function formatBytes (sizeInKB) {
|
|
2
|
-
if (!sizeInKB) {
|
|
3
|
-
return ''
|
|
4
|
-
}
|
|
5
|
-
const units = ['KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
|
|
6
|
-
let index = 0
|
|
7
|
-
|
|
8
|
-
while (sizeInKB >= 1024 && index < units.length - 1) {
|
|
9
|
-
sizeInKB /= 1024
|
|
10
|
-
index++
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
return `${sizeInKB.toFixed(2)} ${units[index]}`
|
|
14
|
-
}
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* btns
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { FullscreenExitOutlined } from '@ant-design/icons'
|
|
6
|
-
import './term-fullscreen.styl'
|
|
7
|
-
|
|
8
|
-
export default function TermFullscreenControl (props) {
|
|
9
|
-
const handleExitFullscreen = () => {
|
|
10
|
-
window.store.toggleTermFullscreen(false)
|
|
11
|
-
}
|
|
12
|
-
if (!props.terminalFullScreen) {
|
|
13
|
-
return null
|
|
14
|
-
}
|
|
15
|
-
return (
|
|
16
|
-
<FullscreenExitOutlined
|
|
17
|
-
className='mg1r icon-info font16 pointer spliter term-fullscreen-control'
|
|
18
|
-
onClick={handleExitFullscreen}
|
|
19
|
-
/>
|
|
20
|
-
)
|
|
21
|
-
}
|