@electerm/electerm-react 2.7.9 → 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 +113 -4
- 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 +397 -52
- 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,22 +55,119 @@ 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}`
|
|
48
99
|
}
|
|
49
100
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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
|
+
)
|
|
146
|
+
}
|
|
147
|
+
|
|
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({
|
|
@@ -75,6 +182,9 @@ export default class VncSession extends RdpSession {
|
|
|
75
182
|
viewOnly = false,
|
|
76
183
|
scaleViewport = true,
|
|
77
184
|
clipViewport = false,
|
|
185
|
+
qualityLevel = 3, // 0-9, lower = faster performance
|
|
186
|
+
compressionLevel = 1, // 0-9, lower = faster performance
|
|
187
|
+
shared = true,
|
|
78
188
|
username,
|
|
79
189
|
password
|
|
80
190
|
} = tab
|
|
@@ -102,17 +212,35 @@ export default class VncSession extends RdpSession {
|
|
|
102
212
|
const { pid, port } = r
|
|
103
213
|
this.pid = pid
|
|
104
214
|
this.port = port
|
|
105
|
-
|
|
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()
|
|
106
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
|
|
107
230
|
const vncOpts = {
|
|
108
231
|
clipViewport,
|
|
109
232
|
scaleViewport,
|
|
110
233
|
viewOnly,
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
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
|
+
: {},
|
|
116
244
|
credentials: {}
|
|
117
245
|
}
|
|
118
246
|
if (username) {
|
|
@@ -121,11 +249,112 @@ export default class VncSession extends RdpSession {
|
|
|
121
249
|
if (password) {
|
|
122
250
|
vncOpts.credentials.password = password
|
|
123
251
|
}
|
|
252
|
+
const RFB = window.novnc.RFB
|
|
124
253
|
const rfb = new RFB(
|
|
125
254
|
this.getDom(),
|
|
126
255
|
wsUrl,
|
|
127
256
|
vncOpts
|
|
128
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
|
+
|
|
129
358
|
const events = [
|
|
130
359
|
'connect',
|
|
131
360
|
'disconnect',
|
|
@@ -134,21 +363,29 @@ export default class VncSession extends RdpSession {
|
|
|
134
363
|
'clipboard',
|
|
135
364
|
'bell',
|
|
136
365
|
'desktopname',
|
|
137
|
-
'capabilities'
|
|
366
|
+
'capabilities',
|
|
367
|
+
'screens',
|
|
368
|
+
'desktopsize'
|
|
138
369
|
]
|
|
139
370
|
for (const event of events) {
|
|
140
371
|
rfb.addEventListener(event, this[`on${window.capitalizeFirstLetter(event)}`])
|
|
141
372
|
}
|
|
373
|
+
rfb.scaleViewport = scaleViewport
|
|
374
|
+
rfb.clipViewport = clipViewport
|
|
142
375
|
this.rfb = rfb
|
|
143
376
|
}
|
|
144
377
|
|
|
145
378
|
onConnect = (event) => {
|
|
146
379
|
this.setStatus(statusMap.success)
|
|
380
|
+
// Capture the remote desktop size from the RFB connection
|
|
147
381
|
this.setState({
|
|
148
382
|
loading: false
|
|
149
383
|
})
|
|
150
384
|
}
|
|
151
385
|
|
|
386
|
+
onDesktopsize = (event) => {
|
|
387
|
+
}
|
|
388
|
+
|
|
152
389
|
onDisconnect = () => {
|
|
153
390
|
this.setStatus(statusMap.error)
|
|
154
391
|
}
|
|
@@ -192,10 +429,97 @@ export default class VncSession extends RdpSession {
|
|
|
192
429
|
this.setState({
|
|
193
430
|
name: event?.detail?.name || ''
|
|
194
431
|
})
|
|
432
|
+
this.checkScreens()
|
|
195
433
|
}
|
|
196
434
|
|
|
197
435
|
onCapabilities = (capabilities) => {
|
|
198
|
-
|
|
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
|
|
199
523
|
}
|
|
200
524
|
|
|
201
525
|
getDom = () => {
|
|
@@ -224,10 +548,8 @@ export default class VncSession extends RdpSession {
|
|
|
224
548
|
)
|
|
225
549
|
}
|
|
226
550
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
<Tag color='red' className='mg1l' variant='solid'>Beta</Tag>
|
|
230
|
-
)
|
|
551
|
+
handleSendCtrlAltDel = () => {
|
|
552
|
+
this.rfb?.sendCtrlAltDel()
|
|
231
553
|
}
|
|
232
554
|
|
|
233
555
|
renderConfirm () {
|
|
@@ -254,31 +576,54 @@ export default class VncSession extends RdpSession {
|
|
|
254
576
|
|
|
255
577
|
render () {
|
|
256
578
|
const { width: w, height: h } = this.props
|
|
257
|
-
const
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
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'
|
|
262
599
|
}
|
|
263
|
-
const { width, height, loading } = this.state
|
|
264
600
|
const divProps = {
|
|
265
|
-
style:
|
|
266
|
-
|
|
267
|
-
height: height + 'px'
|
|
268
|
-
}
|
|
601
|
+
style: remoteStyle,
|
|
602
|
+
className: 'vnc-session-wrap session-v-wrap'
|
|
269
603
|
}
|
|
604
|
+
const contrlProps = this.getControlProps()
|
|
270
605
|
return (
|
|
271
606
|
<Spin spinning={loading}>
|
|
272
607
|
<div
|
|
273
|
-
{...vncProps}
|
|
274
608
|
className='rdp-session-wrap pd1'
|
|
609
|
+
style={{
|
|
610
|
+
width: w + 'px',
|
|
611
|
+
height: h + 'px'
|
|
612
|
+
}}
|
|
275
613
|
>
|
|
276
614
|
{this.renderControl()}
|
|
277
|
-
<
|
|
278
|
-
{...
|
|
279
|
-
className='vnc-session-wrap session-v-wrap'
|
|
280
|
-
ref={this.domRef}
|
|
615
|
+
<RemoteFloatControl
|
|
616
|
+
{...contrlProps}
|
|
281
617
|
/>
|
|
618
|
+
<div
|
|
619
|
+
style={wrapperStyle}
|
|
620
|
+
className='vnc-scroll-wrapper'
|
|
621
|
+
>
|
|
622
|
+
<div
|
|
623
|
+
{...divProps}
|
|
624
|
+
ref={this.domRef}
|
|
625
|
+
/>
|
|
626
|
+
</div>
|
|
282
627
|
{this.renderConfirm()}
|
|
283
628
|
</div>
|
|
284
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
|
-
}
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
// import zmodem from 'zmodem-ts/dist/zmodem.mjs'
|
|
2
|
-
import Sentry from 'zmodem-ts/dist/zsentry.js'
|
|
3
|
-
export class AddonZmodem {
|
|
4
|
-
_disposables = []
|
|
5
|
-
|
|
6
|
-
activate (terminal) {
|
|
7
|
-
terminal.zmodemAttach = this.zmodemAttach
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
sendWebSocket = (octets) => {
|
|
11
|
-
const { socket } = this
|
|
12
|
-
if (socket && socket.readyState === WebSocket.OPEN) {
|
|
13
|
-
return socket.send(new Uint8Array(octets))
|
|
14
|
-
} else {
|
|
15
|
-
console.error('WebSocket is not open')
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
zmodemAttach = (ctx) => {
|
|
20
|
-
this.socket = ctx.socket
|
|
21
|
-
this.term = ctx.term
|
|
22
|
-
this.ctx = ctx
|
|
23
|
-
this.zsentry = new Sentry({
|
|
24
|
-
to_terminal: (octets) => {
|
|
25
|
-
if (ctx.onZmodem) {
|
|
26
|
-
this.term.write(String.fromCharCode.apply(String, octets))
|
|
27
|
-
}
|
|
28
|
-
},
|
|
29
|
-
sender: this.sendWebSocket,
|
|
30
|
-
on_retract: ctx.onzmodemRetract,
|
|
31
|
-
on_detect: ctx.onZmodemDetect
|
|
32
|
-
})
|
|
33
|
-
this.socket.binaryType = 'arraybuffer'
|
|
34
|
-
this.socket.addEventListener('message', this.handleWSMessage)
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
handleWSMessage = (evt) => {
|
|
38
|
-
if (typeof evt.data === 'string') {
|
|
39
|
-
if (this.ctx.onZmodem) {
|
|
40
|
-
this.term.write(evt.data)
|
|
41
|
-
}
|
|
42
|
-
} else {
|
|
43
|
-
this.zsentry.consume(evt.data)
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
dispose = () => {
|
|
48
|
-
this.socket && this.socket.removeEventListener('message', this.handleWSMessage)
|
|
49
|
-
this._disposables.forEach(d => d.dispose())
|
|
50
|
-
this._disposables.length = 0
|
|
51
|
-
this.term = null
|
|
52
|
-
this.zsentry = null
|
|
53
|
-
this.socket = null
|
|
54
|
-
}
|
|
55
|
-
}
|