@electerm/electerm-react 2.5.16 → 2.7.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/constants.js +1 -1
- package/client/common/create-title.jsx +2 -2
- package/client/common/download.jsx +14 -14
- package/client/components/bookmark-form/common/encodes.js +1 -0
- package/client/components/bookmark-form/config/common-fields.js +29 -2
- package/client/components/common/zoom-control.jsx +58 -0
- package/client/components/rdp/rdp-session.jsx +264 -198
- package/client/components/sftp/sftp.styl +1 -1
- package/client/components/shortcuts/shortcut-handler.js +84 -0
- package/client/components/sys-menu/zoom.jsx +4 -31
- package/client/components/terminal/terminal.jsx +51 -45
- package/client/components/web/address-bar.jsx +35 -6
- package/client/components/web/web-auth-modal.jsx +69 -0
- package/client/components/web/web-session.jsx +60 -4
- package/client/views/index.pug +0 -2
- package/package.json +1 -1
- package/client/entry/rle.js +0 -2
- package/client/entry/rle.wasm +0 -0
|
@@ -8,10 +8,8 @@ import {
|
|
|
8
8
|
} from '../../common/constants'
|
|
9
9
|
import {
|
|
10
10
|
Spin,
|
|
11
|
-
// Button,
|
|
12
11
|
Select
|
|
13
12
|
} from 'antd'
|
|
14
|
-
import { notification } from '../common/notification'
|
|
15
13
|
import {
|
|
16
14
|
ReloadOutlined,
|
|
17
15
|
EditOutlined
|
|
@@ -22,6 +20,25 @@ import resolutions from './resolutions'
|
|
|
22
20
|
|
|
23
21
|
const { Option } = Select
|
|
24
22
|
|
|
23
|
+
// IronRDP WASM module imports — loaded dynamically
|
|
24
|
+
async function loadWasmModule () {
|
|
25
|
+
if (window.ironRdp) return
|
|
26
|
+
console.debug('[RDP-CLIENT] Loading IronRDP WASM module...')
|
|
27
|
+
const mod = await import('ironrdp-wasm')
|
|
28
|
+
window.ironRdp = {
|
|
29
|
+
wasmInit: mod.default,
|
|
30
|
+
wasmSetup: mod.setup,
|
|
31
|
+
SessionBuilder: mod.SessionBuilder,
|
|
32
|
+
DesktopSize: mod.DesktopSize,
|
|
33
|
+
InputTransaction: mod.InputTransaction,
|
|
34
|
+
DeviceEvent: mod.DeviceEvent,
|
|
35
|
+
Extension: mod.Extension
|
|
36
|
+
}
|
|
37
|
+
await window.ironRdp.wasmInit()
|
|
38
|
+
window.ironRdp.wasmSetup('info')
|
|
39
|
+
console.debug('[RDP-CLIENT] IronRDP WASM module loaded and initialized')
|
|
40
|
+
}
|
|
41
|
+
|
|
25
42
|
export default class RdpSession extends PureComponent {
|
|
26
43
|
constructor (props) {
|
|
27
44
|
const id = `rdp-reso-${props.tab.host}`
|
|
@@ -30,10 +47,9 @@ export default class RdpSession extends PureComponent {
|
|
|
30
47
|
this.canvasRef = createRef()
|
|
31
48
|
this.state = {
|
|
32
49
|
loading: false,
|
|
33
|
-
bitmapProps: {},
|
|
34
|
-
aspectRatio: 4 / 3,
|
|
35
50
|
...resObj
|
|
36
51
|
}
|
|
52
|
+
this.session = null
|
|
37
53
|
}
|
|
38
54
|
|
|
39
55
|
componentDidMount () {
|
|
@@ -41,14 +57,24 @@ export default class RdpSession extends PureComponent {
|
|
|
41
57
|
}
|
|
42
58
|
|
|
43
59
|
componentWillUnmount () {
|
|
44
|
-
this.
|
|
45
|
-
delete this.socket
|
|
60
|
+
this.cleanup()
|
|
46
61
|
}
|
|
47
62
|
|
|
48
|
-
|
|
49
|
-
|
|
63
|
+
cleanup = () => {
|
|
64
|
+
console.debug('[RDP-CLIENT] cleanup() called')
|
|
65
|
+
if (this.session) {
|
|
66
|
+
try {
|
|
67
|
+
this.session.shutdown()
|
|
68
|
+
console.debug('[RDP-CLIENT] session.shutdown() called')
|
|
69
|
+
} catch (e) {
|
|
70
|
+
console.debug('[RDP-CLIENT] session.shutdown() error:', e)
|
|
71
|
+
}
|
|
72
|
+
this.session = null
|
|
73
|
+
}
|
|
50
74
|
}
|
|
51
75
|
|
|
76
|
+
runInitScript = () => {}
|
|
77
|
+
|
|
52
78
|
setStatus = status => {
|
|
53
79
|
const id = this.props.tab?.id
|
|
54
80
|
this.props.editTab(id, {
|
|
@@ -56,7 +82,7 @@ export default class RdpSession extends PureComponent {
|
|
|
56
82
|
})
|
|
57
83
|
}
|
|
58
84
|
|
|
59
|
-
remoteInit = async (
|
|
85
|
+
remoteInit = async () => {
|
|
60
86
|
this.setState({
|
|
61
87
|
loading: true
|
|
62
88
|
})
|
|
@@ -80,237 +106,279 @@ export default class RdpSession extends PureComponent {
|
|
|
80
106
|
termType: type,
|
|
81
107
|
...tab
|
|
82
108
|
})
|
|
109
|
+
|
|
110
|
+
console.debug('[RDP-CLIENT] Creating RDP session term, host=', tab.host, 'port=', tab.port)
|
|
83
111
|
const r = await createTerm(opts)
|
|
84
112
|
.catch(err => {
|
|
85
113
|
const text = err.message
|
|
86
114
|
handleErr({ message: text })
|
|
87
115
|
})
|
|
88
|
-
this.setState({
|
|
89
|
-
loading: false
|
|
90
|
-
})
|
|
91
116
|
if (!r) {
|
|
117
|
+
this.setState({ loading: false })
|
|
92
118
|
this.setStatus(statusMap.error)
|
|
119
|
+
console.error('[RDP-CLIENT] createTerm failed')
|
|
93
120
|
return
|
|
94
121
|
}
|
|
95
|
-
|
|
122
|
+
|
|
96
123
|
const {
|
|
97
124
|
pid, port
|
|
98
125
|
} = r
|
|
99
126
|
this.pid = pid
|
|
127
|
+
console.debug('[RDP-CLIENT] Term created, pid=', pid, 'port=', port)
|
|
128
|
+
|
|
129
|
+
// Build the WebSocket proxy address for IronRDP WASM
|
|
100
130
|
const hs = server
|
|
101
131
|
? server.replace(/https?:\/\//, '')
|
|
102
132
|
: `${host}:${port}`
|
|
103
133
|
const pre = server.startsWith('https') ? 'wss' : 'ws'
|
|
104
134
|
const { width, height } = this.state
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
fName = 'bitmap_decompress_15'
|
|
119
|
-
break
|
|
120
|
-
case 16:
|
|
121
|
-
fName = 'bitmap_decompress_16'
|
|
122
|
-
break
|
|
123
|
-
case 24:
|
|
124
|
-
fName = 'bitmap_decompress_24'
|
|
125
|
-
break
|
|
126
|
-
case 32:
|
|
127
|
-
fName = 'bitmap_decompress_32'
|
|
128
|
-
break
|
|
129
|
-
default:
|
|
130
|
-
throw new Error('invalid bitmap data format')
|
|
135
|
+
// IronRDP connects to the proxy address, which then proxies via RDCleanPath
|
|
136
|
+
// The WebSocket URL includes the pid and token for auth
|
|
137
|
+
const proxyAddress = `${pre}://${hs}/rdp/${pid}?token=${tokenElecterm}&width=${width}&height=${height}`
|
|
138
|
+
console.debug('[RDP-CLIENT] Proxy address:', proxyAddress)
|
|
139
|
+
|
|
140
|
+
// Load WASM module if not already loaded
|
|
141
|
+
try {
|
|
142
|
+
await loadWasmModule()
|
|
143
|
+
} catch (e) {
|
|
144
|
+
console.error('[RDP-CLIENT] Failed to load WASM module:', e)
|
|
145
|
+
this.setState({ loading: false })
|
|
146
|
+
this.setStatus(statusMap.error)
|
|
147
|
+
return
|
|
131
148
|
}
|
|
132
|
-
const rle = window.Module
|
|
133
|
-
const input = new Uint8Array(bitmap.data.data)
|
|
134
|
-
const inputPtr = rle._malloc(input.length)
|
|
135
|
-
const inputHeap = new Uint8Array(rle.HEAPU8.buffer, inputPtr, input.length)
|
|
136
|
-
inputHeap.set(input)
|
|
137
|
-
|
|
138
|
-
const outputWidth = bitmap.destRight - bitmap.destLeft + 1
|
|
139
|
-
const outputHeight = bitmap.destBottom - bitmap.destTop + 1
|
|
140
|
-
const ouputSize = outputWidth * outputHeight * 4
|
|
141
|
-
const outputPtr = rle._malloc(ouputSize)
|
|
142
|
-
|
|
143
|
-
const outputHeap = new Uint8Array(rle.HEAPU8.buffer, outputPtr, ouputSize)
|
|
144
|
-
|
|
145
|
-
rle.ccall(fName,
|
|
146
|
-
'number',
|
|
147
|
-
['number', 'number', 'number', 'number', 'number', 'number', 'number', 'number'],
|
|
148
|
-
[outputHeap.byteOffset, outputWidth, outputHeight, bitmap.width, bitmap.height, inputHeap.byteOffset, input.length]
|
|
149
|
-
)
|
|
150
149
|
|
|
151
|
-
|
|
150
|
+
this.setStatus(statusMap.success)
|
|
152
151
|
|
|
153
|
-
|
|
154
|
-
|
|
152
|
+
// Connect using IronRDP SessionBuilder
|
|
153
|
+
try {
|
|
154
|
+
const canvas = this.canvasRef.current
|
|
155
|
+
if (!canvas) {
|
|
156
|
+
console.error('[RDP-CLIENT] Canvas ref not available')
|
|
157
|
+
this.setState({ loading: false })
|
|
158
|
+
return
|
|
159
|
+
}
|
|
155
160
|
|
|
156
|
-
|
|
157
|
-
|
|
161
|
+
const rdpHost = tab.host
|
|
162
|
+
const rdpPort = tab.port || 3389
|
|
163
|
+
const destination = `${rdpHost}:${rdpPort}`
|
|
164
|
+
const username = tab.username || ''
|
|
165
|
+
const password = tab.password || ''
|
|
166
|
+
|
|
167
|
+
console.debug('[RDP-CLIENT] Building IronRDP session...')
|
|
168
|
+
console.debug('[RDP-CLIENT] destination:', destination)
|
|
169
|
+
console.debug('[RDP-CLIENT] username:', username)
|
|
170
|
+
console.debug('[RDP-CLIENT] proxyAddress:', proxyAddress)
|
|
171
|
+
console.debug('[RDP-CLIENT] desktopSize:', width, 'x', height)
|
|
172
|
+
|
|
173
|
+
const desktopSize = new window.ironRdp.DesktopSize(width, height)
|
|
174
|
+
const enableCredsspExt = new window.ironRdp.Extension('enable_credssp', false)
|
|
175
|
+
|
|
176
|
+
const builder = new window.ironRdp.SessionBuilder()
|
|
177
|
+
builder.username(username)
|
|
178
|
+
builder.password(password)
|
|
179
|
+
builder.destination(destination)
|
|
180
|
+
builder.proxyAddress(proxyAddress)
|
|
181
|
+
builder.authToken('none')
|
|
182
|
+
builder.desktopSize(desktopSize)
|
|
183
|
+
builder.renderCanvas(canvas)
|
|
184
|
+
builder.extension(enableCredsspExt)
|
|
185
|
+
|
|
186
|
+
// Cursor style callback
|
|
187
|
+
builder.setCursorStyleCallbackContext(canvas)
|
|
188
|
+
builder.setCursorStyleCallback(function (style) {
|
|
189
|
+
canvas.style.cursor = style || 'default'
|
|
190
|
+
})
|
|
158
191
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
192
|
+
console.debug('[RDP-CLIENT] Calling builder.connect()...')
|
|
193
|
+
this.session = await builder.connect()
|
|
194
|
+
|
|
195
|
+
const ds = this.session.desktopSize()
|
|
196
|
+
console.debug('[RDP-CLIENT] Connected! Desktop:', ds.width, 'x', ds.height)
|
|
197
|
+
|
|
198
|
+
// Update canvas size to match actual desktop size
|
|
199
|
+
canvas.width = ds.width
|
|
200
|
+
canvas.height = ds.height
|
|
201
|
+
|
|
202
|
+
this.setState({
|
|
203
|
+
loading: false
|
|
204
|
+
})
|
|
169
205
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
206
|
+
canvas.focus()
|
|
207
|
+
|
|
208
|
+
// Run the session event loop (renders frames, handles protocol)
|
|
209
|
+
console.debug('[RDP-CLIENT] Starting session.run() event loop')
|
|
210
|
+
this.session.run().then((info) => {
|
|
211
|
+
console.debug('[RDP-CLIENT] Session ended:', info.reason())
|
|
212
|
+
this.onSessionEnd()
|
|
213
|
+
}).catch((e) => {
|
|
214
|
+
console.error('[RDP-CLIENT] Session error:', this.formatError(e))
|
|
215
|
+
this.onSessionEnd()
|
|
216
|
+
})
|
|
217
|
+
} catch (e) {
|
|
218
|
+
console.error('[RDP-CLIENT] Connection failed:', this.formatError(e))
|
|
219
|
+
this.setState({ loading: false })
|
|
220
|
+
this.setStatus(statusMap.error)
|
|
177
221
|
}
|
|
178
222
|
}
|
|
179
223
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
224
|
+
formatError = (e) => {
|
|
225
|
+
if (e && typeof e === 'object' && '__wbg_ptr' in e) {
|
|
226
|
+
try {
|
|
227
|
+
const kindNames = {
|
|
228
|
+
0: 'General',
|
|
229
|
+
1: 'WrongPassword',
|
|
230
|
+
2: 'LogonFailure',
|
|
231
|
+
3: 'AccessDenied',
|
|
232
|
+
4: 'RDCleanPath',
|
|
233
|
+
5: 'ProxyConnect',
|
|
234
|
+
6: 'NegotiationFailure'
|
|
235
|
+
}
|
|
236
|
+
const kind = e.kind ? e.kind() : 'Unknown'
|
|
237
|
+
const bt = e.backtrace ? e.backtrace() : ''
|
|
238
|
+
return `[${kindNames[kind] || kind}] ${bt}`
|
|
239
|
+
} catch (_) {}
|
|
188
240
|
}
|
|
241
|
+
return e?.message || e?.toString() || String(e)
|
|
189
242
|
}
|
|
190
243
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
clientY
|
|
196
|
-
} = e
|
|
197
|
-
const {
|
|
198
|
-
left,
|
|
199
|
-
top
|
|
200
|
-
} = e.target.getBoundingClientRect()
|
|
201
|
-
const x = clientX - left
|
|
202
|
-
const y = clientY - top
|
|
203
|
-
const keyCode = this.getKeyCode(e)
|
|
204
|
-
const action = type.startsWith('key')
|
|
205
|
-
? 'sendKeyEventScancode'
|
|
206
|
-
: type === 'mousewheel'
|
|
207
|
-
? 'sendWheelEvent'
|
|
208
|
-
: 'sendPointerEvent'
|
|
209
|
-
const pressed = type === 'mousedown' || type === 'keydown'
|
|
210
|
-
let params = []
|
|
211
|
-
if (type.startsWith('mouse') || type.startsWith('context')) {
|
|
212
|
-
params = [x, y, keyCode, pressed]
|
|
213
|
-
} else if (type === 'wheel') {
|
|
214
|
-
const isHorizontal = false
|
|
215
|
-
const delta = isHorizontal ? e.deltaX : e.deltaY
|
|
216
|
-
const step = Math.round(Math.abs(delta) * 15 / 8)
|
|
217
|
-
params = [x, y, step, delta > 0, isHorizontal]
|
|
218
|
-
} else if (type === 'keydown' || type === 'keyup') {
|
|
219
|
-
params = [keyCode, pressed]
|
|
220
|
-
}
|
|
221
|
-
this.socket.send(JSON.stringify({
|
|
222
|
-
action,
|
|
223
|
-
params
|
|
224
|
-
}))
|
|
244
|
+
onSessionEnd = () => {
|
|
245
|
+
console.debug('[RDP-CLIENT] onSessionEnd called')
|
|
246
|
+
this.session = null
|
|
247
|
+
this.setStatus(statusMap.error)
|
|
225
248
|
}
|
|
226
249
|
|
|
227
|
-
|
|
228
|
-
let { data } = msg
|
|
229
|
-
data = JSON.parse(data)
|
|
230
|
-
if (data.action === 'session-rdp-connected') {
|
|
231
|
-
return this.setState({
|
|
232
|
-
loading: false
|
|
233
|
-
})
|
|
234
|
-
}
|
|
250
|
+
setupInputHandlers = () => {
|
|
235
251
|
const canvas = this.canvasRef.current
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
252
|
+
if (!canvas || this._inputHandlersSetup) return
|
|
253
|
+
this._inputHandlersSetup = true
|
|
254
|
+
console.debug('[RDP-CLIENT] Setting up input handlers')
|
|
255
|
+
|
|
256
|
+
canvas.addEventListener('keydown', (e) => {
|
|
257
|
+
e.preventDefault()
|
|
258
|
+
e.stopPropagation()
|
|
259
|
+
if (!this.session) return
|
|
260
|
+
const scancode = this.getScancode(e.code)
|
|
261
|
+
if (scancode === null) return
|
|
262
|
+
try {
|
|
263
|
+
const event = window.ironRdp.DeviceEvent.keyPressed(scancode)
|
|
264
|
+
const tx = new window.ironRdp.InputTransaction()
|
|
265
|
+
tx.addEvent(event)
|
|
266
|
+
this.session.applyInputs(tx)
|
|
267
|
+
} catch (err) {
|
|
268
|
+
console.error('[RDP-CLIENT] Key press error:', err)
|
|
269
|
+
}
|
|
270
|
+
})
|
|
246
271
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
272
|
+
canvas.addEventListener('keyup', (e) => {
|
|
273
|
+
e.preventDefault()
|
|
274
|
+
e.stopPropagation()
|
|
275
|
+
if (!this.session) return
|
|
276
|
+
const scancode = this.getScancode(e.code)
|
|
277
|
+
if (scancode === null) return
|
|
278
|
+
try {
|
|
279
|
+
const event = window.ironRdp.DeviceEvent.keyReleased(scancode)
|
|
280
|
+
const tx = new window.ironRdp.InputTransaction()
|
|
281
|
+
tx.addEvent(event)
|
|
282
|
+
this.session.applyInputs(tx)
|
|
283
|
+
} catch (err) {
|
|
284
|
+
console.error('[RDP-CLIENT] Key release error:', err)
|
|
285
|
+
}
|
|
286
|
+
})
|
|
287
|
+
|
|
288
|
+
canvas.addEventListener('mousemove', (e) => {
|
|
289
|
+
if (!this.session) return
|
|
290
|
+
try {
|
|
291
|
+
const rect = canvas.getBoundingClientRect()
|
|
292
|
+
const scaleX = canvas.width / rect.width
|
|
293
|
+
const scaleY = canvas.height / rect.height
|
|
294
|
+
const x = Math.round((e.clientX - rect.left) * scaleX)
|
|
295
|
+
const y = Math.round((e.clientY - rect.top) * scaleY)
|
|
296
|
+
const event = window.ironRdp.DeviceEvent.mouseMove(x, y)
|
|
297
|
+
const tx = new window.ironRdp.InputTransaction()
|
|
298
|
+
tx.addEvent(event)
|
|
299
|
+
this.session.applyInputs(tx)
|
|
300
|
+
} catch (err) {
|
|
301
|
+
// suppress frequent mouse errors
|
|
302
|
+
}
|
|
303
|
+
})
|
|
304
|
+
|
|
305
|
+
canvas.addEventListener('mousedown', (e) => {
|
|
306
|
+
e.preventDefault()
|
|
307
|
+
canvas.focus()
|
|
308
|
+
if (!this.session) return
|
|
309
|
+
try {
|
|
310
|
+
const event = window.ironRdp.DeviceEvent.mouseButtonPressed(e.button)
|
|
311
|
+
const tx = new window.ironRdp.InputTransaction()
|
|
312
|
+
tx.addEvent(event)
|
|
313
|
+
this.session.applyInputs(tx)
|
|
314
|
+
} catch (err) {
|
|
315
|
+
console.error('[RDP-CLIENT] Mouse down error:', err)
|
|
316
|
+
}
|
|
317
|
+
})
|
|
318
|
+
|
|
319
|
+
canvas.addEventListener('mouseup', (e) => {
|
|
320
|
+
e.preventDefault()
|
|
321
|
+
if (!this.session) return
|
|
322
|
+
try {
|
|
323
|
+
const event = window.ironRdp.DeviceEvent.mouseButtonReleased(e.button)
|
|
324
|
+
const tx = new window.ironRdp.InputTransaction()
|
|
325
|
+
tx.addEvent(event)
|
|
326
|
+
this.session.applyInputs(tx)
|
|
327
|
+
} catch (err) {
|
|
328
|
+
console.error('[RDP-CLIENT] Mouse up error:', err)
|
|
329
|
+
}
|
|
330
|
+
})
|
|
331
|
+
|
|
332
|
+
canvas.addEventListener('wheel', (e) => {
|
|
333
|
+
e.preventDefault()
|
|
334
|
+
if (!this.session) return
|
|
335
|
+
try {
|
|
336
|
+
if (e.deltaY !== 0) {
|
|
337
|
+
const amount = e.deltaY > 0 ? -1 : 1
|
|
338
|
+
const event = window.ironRdp.DeviceEvent.wheelRotations(true, amount, 1)
|
|
339
|
+
const tx = new window.ironRdp.InputTransaction()
|
|
340
|
+
tx.addEvent(event)
|
|
341
|
+
this.session.applyInputs(tx)
|
|
342
|
+
}
|
|
343
|
+
if (e.deltaX !== 0) {
|
|
344
|
+
const amount = e.deltaX > 0 ? -1 : 1
|
|
345
|
+
const event = window.ironRdp.DeviceEvent.wheelRotations(false, amount, 1)
|
|
346
|
+
const tx = new window.ironRdp.InputTransaction()
|
|
347
|
+
tx.addEvent(event)
|
|
348
|
+
this.session.applyInputs(tx)
|
|
349
|
+
}
|
|
350
|
+
} catch (err) {
|
|
351
|
+
console.error('[RDP-CLIENT] Wheel error:', err)
|
|
352
|
+
}
|
|
353
|
+
}, { passive: false })
|
|
354
|
+
|
|
355
|
+
canvas.addEventListener('contextmenu', (e) => e.preventDefault())
|
|
250
356
|
}
|
|
251
357
|
|
|
252
|
-
|
|
253
|
-
|
|
358
|
+
// Get PS/2 scancode from keyboard event code using existing code-scan module
|
|
359
|
+
getScancode = (code) => {
|
|
360
|
+
const sc = scanCode({ code })
|
|
361
|
+
return sc !== undefined ? sc : null
|
|
254
362
|
}
|
|
255
363
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
this.handleReInit()
|
|
364
|
+
handleEditResolutions = () => {
|
|
365
|
+
window.store.toggleResolutionEdit()
|
|
259
366
|
}
|
|
260
367
|
|
|
261
368
|
handleReInit = () => {
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
]
|
|
268
|
-
}))
|
|
269
|
-
this.setState({
|
|
270
|
-
loading: true
|
|
271
|
-
})
|
|
369
|
+
console.debug('[RDP-CLIENT] handleReInit called')
|
|
370
|
+
this.cleanup()
|
|
371
|
+
this.props.reloadTab(
|
|
372
|
+
this.props.tab
|
|
373
|
+
)
|
|
272
374
|
}
|
|
273
375
|
|
|
274
376
|
handleReload = () => {
|
|
275
|
-
this.closeMsg()
|
|
276
377
|
this.props.reloadTab(
|
|
277
378
|
this.props.tab
|
|
278
379
|
)
|
|
279
380
|
}
|
|
280
381
|
|
|
281
|
-
handleEditResolutions = () => {
|
|
282
|
-
window.store.toggleResolutionEdit()
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
oncloseSocket = () => {
|
|
286
|
-
// this.setStatus(
|
|
287
|
-
// statusMap.error
|
|
288
|
-
// )
|
|
289
|
-
// this.warningKey = `open${Date.now()}`
|
|
290
|
-
// notification.warning({
|
|
291
|
-
// key: this.warningKey,
|
|
292
|
-
// message: e('socketCloseTip'),
|
|
293
|
-
// duration: 30,
|
|
294
|
-
// description: (
|
|
295
|
-
// <div className='pd2y'>
|
|
296
|
-
// <Button
|
|
297
|
-
// className='mg1r'
|
|
298
|
-
// type='primary'
|
|
299
|
-
// onClick={this.handleClickClose}
|
|
300
|
-
// >
|
|
301
|
-
// {m('close')}
|
|
302
|
-
// </Button>
|
|
303
|
-
// <Button
|
|
304
|
-
// icon={<ReloadOutlined />}
|
|
305
|
-
// onClick={this.handleReload}
|
|
306
|
-
// >
|
|
307
|
-
// {m('reload')}
|
|
308
|
-
// </Button>
|
|
309
|
-
// </div>
|
|
310
|
-
// )
|
|
311
|
-
// })
|
|
312
|
-
}
|
|
313
|
-
|
|
314
382
|
getAllRes = () => {
|
|
315
383
|
return [
|
|
316
384
|
...this.props.resolutions,
|
|
@@ -389,6 +457,11 @@ export default class RdpSession extends PureComponent {
|
|
|
389
457
|
)
|
|
390
458
|
}
|
|
391
459
|
|
|
460
|
+
componentDidUpdate () {
|
|
461
|
+
// Set up native input handlers after canvas is rendered
|
|
462
|
+
this.setupInputHandlers()
|
|
463
|
+
}
|
|
464
|
+
|
|
392
465
|
render () {
|
|
393
466
|
const { width: w, height: h } = this.props
|
|
394
467
|
const rdpProps = {
|
|
@@ -401,13 +474,6 @@ export default class RdpSession extends PureComponent {
|
|
|
401
474
|
const canvasProps = {
|
|
402
475
|
width,
|
|
403
476
|
height,
|
|
404
|
-
onMouseDown: this.handleCanvasEvent,
|
|
405
|
-
onMouseUp: this.handleCanvasEvent,
|
|
406
|
-
onMouseMove: this.handleCanvasEvent,
|
|
407
|
-
onKeyDown: this.handleCanvasEvent,
|
|
408
|
-
onKeyUp: this.handleCanvasEvent,
|
|
409
|
-
onWheel: this.handleCanvasEvent,
|
|
410
|
-
onContextMenu: this.handleCanvasEvent,
|
|
411
477
|
tabIndex: 0
|
|
412
478
|
}
|
|
413
479
|
return (
|
|
@@ -167,7 +167,6 @@
|
|
|
167
167
|
|
|
168
168
|
.sftp-has-pager
|
|
169
169
|
.sftp-table-content
|
|
170
|
-
position static
|
|
171
170
|
padding-bottom 42px
|
|
172
171
|
.pager-wrap
|
|
173
172
|
position absolute
|
|
@@ -175,6 +174,7 @@
|
|
|
175
174
|
left 0
|
|
176
175
|
width 100%
|
|
177
176
|
background var(--main)
|
|
177
|
+
height 40px
|
|
178
178
|
border-top 1px solid var(--main-darker)
|
|
179
179
|
|
|
180
180
|
.file-header-context-menu
|
|
@@ -3,6 +3,87 @@ import shortcutsDefaultsGen from './shortcuts-defaults.js'
|
|
|
3
3
|
import {
|
|
4
4
|
isMacJs
|
|
5
5
|
} from '../../common/constants'
|
|
6
|
+
import keyControlPressed from '../../common/key-control-pressed.js'
|
|
7
|
+
|
|
8
|
+
function sendInputData (ctx, data) {
|
|
9
|
+
if (!data) return
|
|
10
|
+
if (ctx.attachAddon && ctx.attachAddon._sendData) {
|
|
11
|
+
ctx.attachAddon._sendData(data)
|
|
12
|
+
}
|
|
13
|
+
// if (!ctx.onData) return
|
|
14
|
+
// if (splitChars) {
|
|
15
|
+
// for (const ch of data) {
|
|
16
|
+
// ctx.onData(ch)
|
|
17
|
+
// }
|
|
18
|
+
// return
|
|
19
|
+
// }
|
|
20
|
+
// ctx.onData(data)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function getSelectionReplaceInfo (term) {
|
|
24
|
+
if (!term || !term.hasSelection()) return null
|
|
25
|
+
if (term.buffer?.active?.type === 'alternate') return null
|
|
26
|
+
const getPos = term.getSelectionPosition?.bind(term)
|
|
27
|
+
if (!getPos) return null
|
|
28
|
+
const pos = getPos()
|
|
29
|
+
if (!pos || !pos.start || !pos.end) return null
|
|
30
|
+
const buffer = term.buffer.active
|
|
31
|
+
const cursorY = buffer.cursorY
|
|
32
|
+
if (pos.start.y !== cursorY || pos.end.y !== cursorY) return null
|
|
33
|
+
const startX = Math.min(pos.start.x, pos.end.x)
|
|
34
|
+
const endX = Math.max(pos.start.x, pos.end.x)
|
|
35
|
+
if (startX === endX) return null
|
|
36
|
+
return {
|
|
37
|
+
startX,
|
|
38
|
+
endX,
|
|
39
|
+
cursorX: buffer.cursorX
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function handleTerminalSelectionReplace (event, ctx) {
|
|
44
|
+
if (
|
|
45
|
+
!ctx.term ||
|
|
46
|
+
!ctx.term.hasSelection() ||
|
|
47
|
+
keyControlPressed(event)
|
|
48
|
+
) {
|
|
49
|
+
return false
|
|
50
|
+
}
|
|
51
|
+
const { key } = event
|
|
52
|
+
const isBackspace = key === 'Backspace'
|
|
53
|
+
const isDelete = key === 'Delete'
|
|
54
|
+
const isPrintable = key && key.length === 1
|
|
55
|
+
if (!isBackspace && !isDelete && !isPrintable) return false
|
|
56
|
+
|
|
57
|
+
if (event && event.preventDefault) {
|
|
58
|
+
event.preventDefault()
|
|
59
|
+
}
|
|
60
|
+
if (event && event.stopPropagation) {
|
|
61
|
+
event.stopPropagation()
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const info = getSelectionReplaceInfo(ctx.term)
|
|
65
|
+
if (!info) return false
|
|
66
|
+
|
|
67
|
+
const { startX, endX, cursorX } = info
|
|
68
|
+
const move = startX - cursorX
|
|
69
|
+
if (move > 0) {
|
|
70
|
+
sendInputData(ctx, '\x1b[C'.repeat(move))
|
|
71
|
+
} else if (move < 0) {
|
|
72
|
+
sendInputData(ctx, '\x1b[D'.repeat(-move))
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const delCount = endX - startX
|
|
76
|
+
if (delCount > 0) {
|
|
77
|
+
sendInputData(ctx, '\x1b[3~'.repeat(delCount))
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (isPrintable) {
|
|
81
|
+
sendInputData(ctx, key)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
ctx.term.clearSelection()
|
|
85
|
+
return true
|
|
86
|
+
}
|
|
6
87
|
|
|
7
88
|
function buildConfig (config, filter = d => d) {
|
|
8
89
|
const defs = shortcutsDefaultsGen().filter(filter)
|
|
@@ -65,6 +146,9 @@ export function shortcutExtend (Cls) {
|
|
|
65
146
|
if (isInAntdInput()) {
|
|
66
147
|
return
|
|
67
148
|
}
|
|
149
|
+
if (handleTerminalSelectionReplace(event, this)) {
|
|
150
|
+
return false
|
|
151
|
+
}
|
|
68
152
|
if (
|
|
69
153
|
this.term &&
|
|
70
154
|
key === 'Backspace' &&
|