mirror of
https://github.com/rustdesk/rustdesk.git
synced 2026-09-11 15:01:02 +03:00
fix(terminal): send SGR mouse wheel reports with the button codes app… (#15817)
* fix(terminal): send SGR mouse wheel reports with the button codes apps expect xterm.dart 4.0.0 encodes the wheel buttons as 64+4..64+7 rather than 64+0..64+3, so the low bits land on the modifier field and every wheel report the terminal emits reads as wheel-with-Shift. Strict full-screen applications reject the modified event, which is why neither the mouse wheel nor the trackpad scrolls anything once the peer application takes over the alternate screen. Install a mouse handler that keeps every upstream reporting decision and only re-encodes the wheel buttons as 64..67. Non-wheel reports pass through untouched, and the emitted bytes stay identical once upstream ships the same fix, so this can be dropped without a behavior change. Upstream: TerminalStudio/xterm.dart#238 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(terminal): correct the wheel report row, drop the wasted report build Address review feedback on the wheel button fix: - The X10/utf row was encoded as `32 + y + 1` while y is already 1-based, so every normal-mode report pointed one row too low and the `y > limit` guard disagreed with what it emitted. - Gate the wheel path on `mouseMode.reportScroll` and the button state instead of building and discarding a full report string from `defaultMouseHandler` on every scroll tick. This also makes the hardcoded SGR 'M' provably right, since a wheel release now returns before the report is built. - Derive the wire code as `id - 4` and drop `_wheelButtonId`, whose `default` branch was unreachable and defeated enum exhaustiveness. - Assign `mouseHandler` after construction so the `Terminal(...)` line stays untouched. Cover the utf, urxvt, null-byte overflow and click-only branches, and assert that TerminalModel actually installs the handler. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -3,6 +3,7 @@ import 'dart:async';
|
||||
import 'package:flutter_hbb/models/model.dart';
|
||||
import 'package:flutter_hbb/models/terminal_model.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:xterm/xterm.dart';
|
||||
|
||||
class _FakeFFI implements FFI {
|
||||
@override
|
||||
@@ -48,4 +49,20 @@ void main() {
|
||||
expect(clearedCtrlLock, isFalse);
|
||||
expect(model.debugBufferedInputCount, 0);
|
||||
});
|
||||
|
||||
test('builds its terminal with the wheel button fix', () {
|
||||
final model = TerminalModel(_FakeFFI());
|
||||
addTearDown(model.dispose);
|
||||
|
||||
final captured = <String>[];
|
||||
model.terminal.onOutput = captured.add;
|
||||
model.terminal.write('\x1b[?1000h\x1b[?1006h');
|
||||
model.terminal.mouseInput(
|
||||
TerminalMouseButton.wheelUp,
|
||||
TerminalMouseButtonState.down,
|
||||
const CellOffset(10, 5),
|
||||
);
|
||||
|
||||
expect(captured.single, '\x1b[<64;11;6M');
|
||||
});
|
||||
}
|
||||
|
||||
114
flutter/test/terminal_mouse_handler_test.dart
Normal file
114
flutter/test/terminal_mouse_handler_test.dart
Normal file
@@ -0,0 +1,114 @@
|
||||
import 'package:flutter_hbb/models/terminal_mouse_handler.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:xterm/xterm.dart';
|
||||
|
||||
void main() {
|
||||
late Terminal terminal;
|
||||
late List<String> output;
|
||||
|
||||
setUp(() {
|
||||
output = <String>[];
|
||||
terminal = Terminal(mouseHandler: const WheelButtonFixMouseHandler())
|
||||
..onOutput = output.add;
|
||||
});
|
||||
|
||||
String? report(
|
||||
TerminalMouseButton button, [
|
||||
TerminalMouseButtonState state = TerminalMouseButtonState.down,
|
||||
CellOffset position = const CellOffset(10, 5),
|
||||
]) {
|
||||
output.clear();
|
||||
terminal.mouseInput(button, state, position);
|
||||
return output.isEmpty ? null : output.single;
|
||||
}
|
||||
|
||||
test('reports SGR wheel buttons without the Shift modifier bit', () {
|
||||
terminal.write('\x1b[?1000h\x1b[?1006h');
|
||||
|
||||
expect(report(TerminalMouseButton.wheelUp), '\x1b[<64;11;6M');
|
||||
expect(report(TerminalMouseButton.wheelDown), '\x1b[<65;11;6M');
|
||||
expect(report(TerminalMouseButton.wheelLeft), '\x1b[<66;11;6M');
|
||||
expect(report(TerminalMouseButton.wheelRight), '\x1b[<67;11;6M');
|
||||
});
|
||||
|
||||
test('reports normal-encoding wheel buttons in the 64..67 range', () {
|
||||
terminal.write('\x1b[?1000h');
|
||||
|
||||
expect(
|
||||
report(TerminalMouseButton.wheelUp),
|
||||
'\x1b[M${String.fromCharCode(32 + 64)}'
|
||||
'${String.fromCharCode(32 + 11)}${String.fromCharCode(32 + 6)}',
|
||||
);
|
||||
expect(
|
||||
report(TerminalMouseButton.wheelDown),
|
||||
'\x1b[M${String.fromCharCode(32 + 65)}'
|
||||
'${String.fromCharCode(32 + 11)}${String.fromCharCode(32 + 6)}',
|
||||
);
|
||||
});
|
||||
|
||||
test('reports utf-encoding wheel buttons beyond the normal-mode range', () {
|
||||
terminal.write('\x1b[?1000h\x1b[?1005h');
|
||||
|
||||
expect(
|
||||
report(
|
||||
TerminalMouseButton.wheelDown,
|
||||
TerminalMouseButtonState.down,
|
||||
const CellOffset(400, 300),
|
||||
),
|
||||
'\x1b[M${String.fromCharCode(32 + 65)}'
|
||||
'${String.fromCharCode(32 + 401)}${String.fromCharCode(32 + 301)}',
|
||||
);
|
||||
});
|
||||
|
||||
test('reports urxvt-encoding wheel buttons shifted by 32', () {
|
||||
terminal.write('\x1b[?1000h\x1b[?1015h');
|
||||
|
||||
expect(report(TerminalMouseButton.wheelUp), '\x1b[96;11;6M');
|
||||
expect(report(TerminalMouseButton.wheelDown), '\x1b[97;11;6M');
|
||||
});
|
||||
|
||||
test('sends a null byte for coordinates past the encoding limit', () {
|
||||
terminal.write('\x1b[?1000h');
|
||||
|
||||
expect(
|
||||
report(
|
||||
TerminalMouseButton.wheelUp,
|
||||
TerminalMouseButtonState.down,
|
||||
const CellOffset(300, 300),
|
||||
),
|
||||
'\x1b[M${String.fromCharCode(32 + 64)}\x00\x00',
|
||||
);
|
||||
});
|
||||
|
||||
test('leaves non-wheel buttons to the upstream handler', () {
|
||||
terminal.write('\x1b[?1000h\x1b[?1006h');
|
||||
|
||||
expect(report(TerminalMouseButton.left), '\x1b[<0;11;6M');
|
||||
expect(report(TerminalMouseButton.middle), '\x1b[<1;11;6M');
|
||||
expect(
|
||||
report(TerminalMouseButton.right, TerminalMouseButtonState.up),
|
||||
'\x1b[<2;11;6m',
|
||||
);
|
||||
});
|
||||
|
||||
test('stays silent when the peer has not enabled mouse reporting', () {
|
||||
expect(report(TerminalMouseButton.wheelDown), isNull);
|
||||
expect(report(TerminalMouseButton.left), isNull);
|
||||
});
|
||||
|
||||
test('stays silent for the wheel in click-only mode', () {
|
||||
terminal.write('\x1b[?9h\x1b[?1006h');
|
||||
|
||||
expect(report(TerminalMouseButton.wheelDown), isNull);
|
||||
expect(report(TerminalMouseButton.left), '\x1b[<0;11;6M');
|
||||
});
|
||||
|
||||
test('does not report wheel button releases', () {
|
||||
terminal.write('\x1b[?1000h\x1b[?1006h');
|
||||
|
||||
expect(
|
||||
report(TerminalMouseButton.wheelDown, TerminalMouseButtonState.up),
|
||||
isNull,
|
||||
);
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user