Skip to content

Commit ca7b487

Browse files
authored
feat, trackpad speed (rustdesk#11680)
* feat, trackpad speed Signed-off-by: fufesou <linlong1266@gmail.com> * comments Signed-off-by: fufesou <linlong1266@gmail.com> * Trackpad speed, user default value Signed-off-by: fufesou <linlong1266@gmail.com> --------- Signed-off-by: fufesou <linlong1266@gmail.com>
1 parent 9475743 commit ca7b487

57 files changed

Lines changed: 325 additions & 9 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

flutter/lib/common/widgets/dialog.dart

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1623,6 +1623,28 @@ customImageQualityDialog(SessionID sessionId, String id, FFI ffi) async {
16231623
msgBoxCommon(ffi.dialogManager, 'Custom Image Quality', content, [btnClose]);
16241624
}
16251625

1626+
trackpadSpeedDialog(SessionID sessionId, FFI ffi) async {
1627+
int initSpeed = ffi.inputModel.trackpadSpeed;
1628+
final curSpeed = SimpleWrapper(initSpeed);
1629+
final btnClose = dialogButton('Close', onPressed: () async {
1630+
if (curSpeed.value <= kMaxTrackpadSpeed &&
1631+
curSpeed.value >= kMinTrackpadSpeed &&
1632+
curSpeed.value != initSpeed) {
1633+
await bind.sessionSetTrackpadSpeed(
1634+
sessionId: sessionId, value: curSpeed.value);
1635+
await ffi.inputModel.updateTrackpadSpeed();
1636+
}
1637+
ffi.dialogManager.dismissAll();
1638+
});
1639+
msgBoxCommon(
1640+
ffi.dialogManager,
1641+
'Trackpad speed',
1642+
TrackpadSpeedWidget(
1643+
value: curSpeed,
1644+
),
1645+
[btnClose]);
1646+
}
1647+
16261648
void deleteConfirmDialog(Function onSubmit, String title) async {
16271649
gFFI.dialogManager.show(
16281650
(setState, close, context) {

flutter/lib/common/widgets/setting_widgets.dart

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,3 +248,93 @@ List<(String, String)> otherDefaultSettings() {
248248

249249
return v;
250250
}
251+
252+
class TrackpadSpeedWidget extends StatefulWidget {
253+
final SimpleWrapper<int> value;
254+
// If null, no debouncer will be applied.
255+
final Function(int)? onDebouncer;
256+
257+
TrackpadSpeedWidget({Key? key, required this.value, this.onDebouncer});
258+
259+
@override
260+
TrackpadSpeedWidgetState createState() => TrackpadSpeedWidgetState();
261+
}
262+
263+
class TrackpadSpeedWidgetState extends State<TrackpadSpeedWidget> {
264+
final TextEditingController _controller = TextEditingController();
265+
late final Debouncer<int> debouncerSpeed;
266+
267+
set value(int v) => widget.value.value = v;
268+
int get value => widget.value.value;
269+
270+
void updateValue(int newValue) {
271+
setState(() {
272+
value = newValue.clamp(kMinTrackpadSpeed, kMaxTrackpadSpeed);
273+
// Scale the trackpad speed value to a percentage for display purposes.
274+
_controller.text = value.toString();
275+
if (widget.onDebouncer != null) {
276+
debouncerSpeed.setValue(value);
277+
}
278+
});
279+
}
280+
281+
@override
282+
void initState() {
283+
super.initState();
284+
debouncerSpeed = Debouncer<int>(
285+
Duration(milliseconds: 1000),
286+
onChanged: widget.onDebouncer,
287+
initialValue: widget.value.value,
288+
);
289+
}
290+
291+
@override
292+
Widget build(BuildContext context) {
293+
if (_controller.text.isEmpty) {
294+
_controller.text = value.toString();
295+
}
296+
return Row(
297+
children: [
298+
Expanded(
299+
flex: 3,
300+
child: Slider(
301+
value: value.toDouble(),
302+
min: kMinTrackpadSpeed.toDouble(),
303+
max: kMaxTrackpadSpeed.toDouble(),
304+
divisions: ((kMaxTrackpadSpeed - kMinTrackpadSpeed) / 10).round(),
305+
onChanged: (double v) => updateValue(v.round()),
306+
),
307+
),
308+
Expanded(
309+
flex: 1,
310+
child: Row(
311+
children: [
312+
SizedBox(
313+
width: 56,
314+
child: TextField(
315+
controller: _controller,
316+
keyboardType: TextInputType.number,
317+
textAlign: TextAlign.center,
318+
onSubmitted: (text) {
319+
int? v = int.tryParse(text);
320+
if (v != null) {
321+
updateValue(v);
322+
}
323+
},
324+
style: const TextStyle(fontSize: 13),
325+
decoration: InputDecoration(
326+
contentPadding:
327+
EdgeInsets.symmetric(vertical: 8.0, horizontal: 12.0),
328+
),
329+
),
330+
).marginOnly(right: 8.0),
331+
Text(
332+
'%',
333+
style: const TextStyle(fontSize: 15),
334+
)
335+
],
336+
)),
337+
],
338+
);
339+
}
340+
}

flutter/lib/consts.dart

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,12 @@ const double kDefaultQuality = 50;
226226
const double kMaxQuality = 100;
227227
const double kMaxMoreQuality = 2000;
228228

229+
// trackpad speed
230+
const String kKeyTrackpadSpeed = 'trackpad-speed';
231+
const int kMinTrackpadSpeed = 10;
232+
const int kDefaultTrackpadSpeed = 100;
233+
const int kMaxTrackpadSpeed = 1000;
234+
229235
// incomming (should be incoming) is kept, because change it will break the previous setting.
230236
const String kKeyPrinterIncomingJobAction = 'printer-incomming-job-action';
231237
const String kValuePrinterIncomingJobDismiss = 'dismiss';

flutter/lib/desktop/pages/desktop_setting_page.dart

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,9 @@ class DesktopSettingPage extends StatefulWidget {
7676
if (!isWeb && !bind.isIncomingOnly() && bind.pluginFeatureIsEnabled())
7777
SettingsTabKey.plugin,
7878
if (!bind.isDisableAccount()) SettingsTabKey.account,
79-
if (isWindows && bind.mainGetBuildinOption(key: kOptionHideRemotePrinterSetting) != 'Y') SettingsTabKey.printer,
79+
if (isWindows &&
80+
bind.mainGetBuildinOption(key: kOptionHideRemotePrinterSetting) != 'Y')
81+
SettingsTabKey.printer,
8082
SettingsTabKey.about,
8183
];
8284

@@ -1602,6 +1604,7 @@ class _DisplayState extends State<_Display> {
16021604
scrollStyle(context),
16031605
imageQuality(context),
16041606
codec(context),
1607+
if (isDesktop) trackpadSpeed(context),
16051608
if (!isWeb) privacyModeImpl(context),
16061609
other(context),
16071610
]).marginOnly(bottom: _kListViewBottomMargin);
@@ -1689,6 +1692,26 @@ class _DisplayState extends State<_Display> {
16891692
]);
16901693
}
16911694

1695+
Widget trackpadSpeed(BuildContext context) {
1696+
final initSpeed = (int.tryParse(
1697+
bind.mainGetUserDefaultOption(key: kKeyTrackpadSpeed)) ??
1698+
kDefaultTrackpadSpeed);
1699+
final curSpeed = SimpleWrapper(initSpeed);
1700+
void onDebouncer(int v) {
1701+
bind.mainSetUserDefaultOption(
1702+
key: kKeyTrackpadSpeed, value: v.toString());
1703+
// It's better to notify all sessions that the default speed is changed.
1704+
// But it may also be ok to take effect in the next connection.
1705+
}
1706+
1707+
return _Card(title: 'Default trackpad speed', children: [
1708+
TrackpadSpeedWidget(
1709+
value: curSpeed,
1710+
onDebouncer: onDebouncer,
1711+
),
1712+
]);
1713+
}
1714+
16921715
Widget codec(BuildContext context) {
16931716
onChanged(String value) async {
16941717
await bind.mainSetUserDefaultOption(

flutter/lib/desktop/widgets/remote_toolbar.dart

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import 'dart:async';
44
import 'package:flutter/material.dart';
55
import 'package:flutter/services.dart';
66
import 'package:flutter_hbb/common/widgets/audio_input.dart';
7+
import 'package:flutter_hbb/common/widgets/dialog.dart';
78
import 'package:flutter_hbb/common/widgets/toolbar.dart';
89
import 'package:flutter_hbb/models/chat_model.dart';
910
import 'package:flutter_hbb/models/state_model.dart';
@@ -1594,10 +1595,28 @@ class _KeyboardMenu extends StatelessWidget {
15941595
viewMode(),
15951596
Divider(),
15961597
...toolbarToggles(),
1598+
...mouseSpeed(),
15971599
...mobileActions(),
15981600
]);
15991601
}
16001602

1603+
mouseSpeed() {
1604+
final speedWidgets = [];
1605+
final sessionId = ffi.sessionId;
1606+
if (isDesktop) {
1607+
if (ffi.ffiModel.keyboard) {
1608+
final enabled = !ffi.ffiModel.viewOnly;
1609+
final trackpad = MenuButton(
1610+
child: Text(translate('Trackpad speed')).paddingOnly(left: 26.0),
1611+
onPressed: enabled ? () => trackpadSpeedDialog(sessionId, ffi) : null,
1612+
ffi: ffi,
1613+
);
1614+
speedWidgets.add(trackpad);
1615+
}
1616+
}
1617+
return speedWidgets;
1618+
}
1619+
16011620
keyboardMode() {
16021621
return futureBuilder(future: () async {
16031622
return await bind.sessionGetKeyboardMode(sessionId: ffi.sessionId) ??

flutter/lib/models/input_model.dart

Lines changed: 39 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -345,8 +345,11 @@ class InputModel {
345345
var _fling = false;
346346
Timer? _flingTimer;
347347
final _flingBaseDelay = 30;
348-
// trackpad, peer linux
349-
final _trackpadSpeed = 0.06;
348+
final _trackpadAdjustPeerLinux = 0.06;
349+
// This is an experience value.
350+
final _trackpadAdjustMacToWin = 2.50;
351+
int _trackpadSpeed = kDefaultTrackpadSpeed;
352+
double _trackpadSpeedInner = kDefaultTrackpadSpeed / 100.0;
350353
var _trackpadScrollUnsent = Offset.zero;
351354

352355
var _lastScale = 1.0;
@@ -370,6 +373,7 @@ class InputModel {
370373
bool get isViewOnly => parent.target!.ffiModel.viewOnly;
371374
double get devicePixelRatio => parent.target!.canvasModel.devicePixelRatio;
372375
bool get isViewCamera => parent.target!.connType == ConnType.viewCamera;
376+
int get trackpadSpeed => _trackpadSpeed;
373377

374378
InputModel(this.parent) {
375379
sessionId = parent.target!.sessionId;
@@ -385,6 +389,28 @@ class InputModel {
385389
}
386390
}
387391

392+
/// Updates the trackpad speed based on the session value.
393+
///
394+
/// The expected format of the retrieved value is a string that can be parsed into a double.
395+
/// If parsing fails or the value is out of bounds (less than `kMinTrackpadSpeed` or greater
396+
/// than `kMaxTrackpadSpeed`), the trackpad speed is reset to the default
397+
/// value (`kDefaultTrackpadSpeed`).
398+
///
399+
/// Bounds:
400+
/// - Minimum: `kMinTrackpadSpeed`
401+
/// - Maximum: `kMaxTrackpadSpeed`
402+
/// - Default: `kDefaultTrackpadSpeed`
403+
Future<void> updateTrackpadSpeed() async {
404+
_trackpadSpeed =
405+
(await bind.sessionGetTrackpadSpeed(sessionId: sessionId) ??
406+
kDefaultTrackpadSpeed);
407+
if (_trackpadSpeed < kMinTrackpadSpeed ||
408+
_trackpadSpeed > kMaxTrackpadSpeed) {
409+
_trackpadSpeed = kDefaultTrackpadSpeed;
410+
}
411+
_trackpadSpeedInner = _trackpadSpeed / 100.0;
412+
}
413+
388414
void handleKeyDownEventModifiers(KeyEvent e) {
389415
KeyUpEvent upEvent(e) => KeyUpEvent(
390416
physicalKey: e.physicalKey,
@@ -888,13 +914,16 @@ class InputModel {
888914
}
889915
}
890916

891-
final delta = e.panDelta;
917+
var delta = e.panDelta * _trackpadSpeedInner;
918+
if (isMacOS && peerPlatform == kPeerPlatformWindows) {
919+
delta *= _trackpadAdjustMacToWin;
920+
}
892921
_trackpadLastDelta = delta;
893922

894923
var x = delta.dx.toInt();
895924
var y = delta.dy.toInt();
896925
if (peerPlatform == kPeerPlatformLinux) {
897-
_trackpadScrollUnsent += (delta * _trackpadSpeed);
926+
_trackpadScrollUnsent += (delta * _trackpadAdjustPeerLinux);
898927
x = _trackpadScrollUnsent.dx.truncate();
899928
y = _trackpadScrollUnsent.dy.truncate();
900929
_trackpadScrollUnsent -= Offset(x.toDouble(), y.toDouble());
@@ -942,8 +971,8 @@ class InputModel {
942971
var dx = x.toInt();
943972
var dy = y.toInt();
944973
if (parent.target?.ffiModel.pi.platform == kPeerPlatformLinux) {
945-
dx = (x * _trackpadSpeed).toInt();
946-
dy = (y * _trackpadSpeed).toInt();
974+
dx = (x * _trackpadAdjustPeerLinux).toInt();
975+
dy = (y * _trackpadAdjustPeerLinux).toInt();
947976
}
948977

949978
var delay = _flingBaseDelay;
@@ -989,7 +1018,10 @@ class InputModel {
9891018
_stopFling = false;
9901019

9911020
// 2.0 is an experience value
992-
double minFlingValue = 2.0;
1021+
double minFlingValue = 2.0 * _trackpadSpeedInner;
1022+
if (isMacOS && peerPlatform == kPeerPlatformWindows) {
1023+
minFlingValue *= _trackpadAdjustMacToWin;
1024+
}
9931025
if (_trackpadLastDelta.dx.abs() > minFlingValue ||
9941026
_trackpadLastDelta.dy.abs() > minFlingValue) {
9951027
_fling = true;

flutter/lib/models/model.dart

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2991,6 +2991,10 @@ class FFI {
29912991
textureModel.updateCurrentDisplay(display ?? 0);
29922992
}
29932993

2994+
if (isDesktop) {
2995+
inputModel.updateTrackpadSpeed();
2996+
}
2997+
29942998
// CAUTION: `sessionStart()` and `sessionStartWithDisplays()` are an async functions.
29952999
// Though the stream is returned immediately, the stream may not be ready.
29963000
// Any operations that depend on the stream should be carefully handled.

libs/hbb_common

src/client.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2105,6 +2105,12 @@ impl LoginConfigHandler {
21052105
res
21062106
}
21072107

2108+
pub fn save_trackpad_speed(&mut self, speed: i32) {
2109+
let mut config = self.load_config();
2110+
config.trackpad_speed = speed;
2111+
self.save_config(config);
2112+
}
2113+
21082114
/// Create a [`Message`] for saving custom fps.
21092115
///
21102116
/// # Arguments

src/flutter_ffi.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -492,6 +492,20 @@ pub fn session_set_custom_fps(session_id: SessionID, fps: i32) {
492492
}
493493
}
494494

495+
pub fn session_get_trackpad_speed(session_id: SessionID) -> Option<i32> {
496+
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
497+
Some(session.get_trackpad_speed())
498+
} else {
499+
None
500+
}
501+
}
502+
503+
pub fn session_set_trackpad_speed(session_id: SessionID, value: i32) {
504+
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
505+
session.save_trackpad_speed(value);
506+
}
507+
}
508+
495509
pub fn session_lock_screen(session_id: SessionID) {
496510
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
497511
session.lock_screen();

0 commit comments

Comments
 (0)