mirror of
https://github.com/rustdesk/rustdesk.git
synced 2026-03-07 20:30:08 +03:00
* - UI display: display_name first
- Fallback: name
- Technical identity: still name
### What changed
- Added account display helpers and display_name state in user model:
- flutter/lib/models/user_model.dart:16
- Account/logout label now uses display_name (@name) when both exist:
- flutter/lib/mobile/pages/settings_page.dart:689
- flutter/lib/desktop/pages/desktop_setting_page.dart:2016
- flutter/lib/desktop/pages/desktop_setting_page.dart:2135
- Desktop Account info now shows both when applicable:
- Display Name: ...
- Username: ...
- flutter/lib/desktop/pages/desktop_setting_page.dart:2039
- Previously done group-list behavior remains:
- group user list displays display_name with name fallback
- flutter/lib/common/widgets/my_group.dart:187
- Persistence path for display_name remains enabled (including group cache/submodule field):
- libs/hbb_common/src/config.rs:2347
- src/client.rs:2630
- LoginRequest.my_name now resolves as:
1. OPTION_DISPLAY_NAME (manual override)
2. user_info.display_name
3. user_info.name
4. OS username fallback
* 1. GUID key (...Uninstall\{GUID}) is MSI-native metadata generated by Windows Installer.
2. Non-GUID key (...Uninstall\RustDesk) is explicitly written by RustDesk’s MSI compatibility component in res/msi/Package/Components/Regs.wxs:44, populated by preprocess.py --arp from .github/workflows/
flutter-build.yml:262.
So they were not using the same EstimatedSize logic:
- MSI GUID key: MSI-calculated size (KB).
- RustDesk key: custom script value from res/msi/preprocess.py:339 (previously bytes, now fixed to KB).
That mismatch is exactly why you saw different sizes.
* improve display name handling
- Append (@username) when multiple users share the same display name
- Trim whitespace from display_name before comparison and display
- Add missing translate() for Logout button on desktop
Signed-off-by: 21pages <sunboeasy@gmail.com>
* group peer filter match both user's display name and user's name
Signed-off-by: 21pages <sunboeasy@gmail.com>
* case-insensitive search in group peer filter
Signed-off-by: 21pages <sunboeasy@gmail.com>
---------
Signed-off-by: 21pages <sunboeasy@gmail.com>
Co-authored-by: 21pages <sunboeasy@gmail.com>
300 lines
7.3 KiB
Dart
300 lines
7.3 KiB
Dart
import 'dart:convert';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter_hbb/common.dart';
|
|
import 'package:flutter_hbb/consts.dart';
|
|
|
|
import 'package:flutter_hbb/models/peer_model.dart';
|
|
|
|
import '../../models/platform_model.dart';
|
|
|
|
class HttpType {
|
|
static const kAuthReqTypeAccount = "account";
|
|
static const kAuthReqTypeMobile = "mobile";
|
|
static const kAuthReqTypeSMSCode = "sms_code";
|
|
static const kAuthReqTypeEmailCode = "email_code";
|
|
static const kAuthReqTypeTfaCode = "tfa_code";
|
|
|
|
static const kAuthResTypeToken = "access_token";
|
|
static const kAuthResTypeEmailCheck = "email_check";
|
|
static const kAuthResTypeTfaCheck = "tfa_check";
|
|
}
|
|
|
|
enum UserStatus { kDisabled, kNormal, kUnverified }
|
|
|
|
// to-do: The UserPayload does not contain all the fields of the user.
|
|
// Is all the fields of the user needed?
|
|
class UserPayload {
|
|
String name = '';
|
|
String displayName = '';
|
|
String email = '';
|
|
String note = '';
|
|
String? verifier;
|
|
UserStatus status;
|
|
bool isAdmin = false;
|
|
|
|
UserPayload.fromJson(Map<String, dynamic> json)
|
|
: name = json['name'] ?? '',
|
|
displayName = json['display_name'] ?? '',
|
|
email = json['email'] ?? '',
|
|
note = json['note'] ?? '',
|
|
verifier = json['verifier'],
|
|
status = json['status'] == 0
|
|
? UserStatus.kDisabled
|
|
: json['status'] == -1
|
|
? UserStatus.kUnverified
|
|
: UserStatus.kNormal,
|
|
isAdmin = json['is_admin'] == true;
|
|
|
|
Map<String, dynamic> toJson() {
|
|
final Map<String, dynamic> map = {
|
|
'name': name,
|
|
'display_name': displayName,
|
|
'status': status == UserStatus.kDisabled
|
|
? 0
|
|
: status == UserStatus.kUnverified
|
|
? -1
|
|
: 1,
|
|
};
|
|
return map;
|
|
}
|
|
|
|
Map<String, dynamic> toGroupCacheJson() {
|
|
final Map<String, dynamic> map = {
|
|
'name': name,
|
|
'display_name': displayName,
|
|
};
|
|
return map;
|
|
}
|
|
|
|
String get displayNameOrName {
|
|
return displayName.trim().isEmpty ? name : displayName;
|
|
}
|
|
}
|
|
|
|
class PeerPayload {
|
|
String id = '';
|
|
Map<String, dynamic> info = {};
|
|
int? status;
|
|
String user = '';
|
|
String user_name = '';
|
|
String? device_group_name;
|
|
String note = '';
|
|
|
|
PeerPayload.fromJson(Map<String, dynamic> json)
|
|
: id = json['id'] ?? '',
|
|
info = (json['info'] is Map<String, dynamic>) ? json['info'] : {},
|
|
status = json['status'],
|
|
user = json['user'] ?? '',
|
|
user_name = json['user_name'] ?? '',
|
|
device_group_name = json['device_group_name'] ?? '',
|
|
note = json['note'] ?? '';
|
|
|
|
static Peer toPeer(PeerPayload p) {
|
|
return Peer.fromJson({
|
|
"id": p.id,
|
|
'loginName': p.user_name,
|
|
"username": p.info['username'] ?? '',
|
|
"platform": _platform(p.info['os']),
|
|
"hostname": p.info['device_name'],
|
|
"device_group_name": p.device_group_name,
|
|
"note": p.note,
|
|
});
|
|
}
|
|
|
|
static String? _platform(dynamic field) {
|
|
if (field == null) {
|
|
return null;
|
|
}
|
|
final fieldStr = field.toString();
|
|
List<String> list = fieldStr.split(' / ');
|
|
if (list.isEmpty) return null;
|
|
final os = list[0];
|
|
switch (os.toLowerCase()) {
|
|
case 'windows':
|
|
return kPeerPlatformWindows;
|
|
case 'linux':
|
|
return kPeerPlatformLinux;
|
|
case 'macos':
|
|
return kPeerPlatformMacOS;
|
|
case 'android':
|
|
return kPeerPlatformAndroid;
|
|
default:
|
|
if (fieldStr.toLowerCase().contains('linux')) {
|
|
return kPeerPlatformLinux;
|
|
}
|
|
return null;
|
|
}
|
|
}
|
|
}
|
|
|
|
class LoginRequest {
|
|
String? username;
|
|
String? password;
|
|
String? id;
|
|
String? uuid;
|
|
bool? autoLogin;
|
|
String? type;
|
|
String? verificationCode;
|
|
String? tfaCode;
|
|
String? secret;
|
|
|
|
LoginRequest(
|
|
{this.username,
|
|
this.password,
|
|
this.id,
|
|
this.uuid,
|
|
this.autoLogin,
|
|
this.type,
|
|
this.verificationCode,
|
|
this.tfaCode,
|
|
this.secret});
|
|
|
|
Map<String, dynamic> toJson() {
|
|
final Map<String, dynamic> data = <String, dynamic>{};
|
|
if (username != null) data['username'] = username;
|
|
if (password != null) data['password'] = password;
|
|
if (id != null) data['id'] = id;
|
|
if (uuid != null) data['uuid'] = uuid;
|
|
if (autoLogin != null) data['autoLogin'] = autoLogin;
|
|
if (type != null) data['type'] = type;
|
|
if (verificationCode != null) {
|
|
data['verificationCode'] = verificationCode;
|
|
}
|
|
if (tfaCode != null) data['tfaCode'] = tfaCode;
|
|
if (secret != null) data['secret'] = secret;
|
|
|
|
Map<String, dynamic> deviceInfo = {};
|
|
try {
|
|
deviceInfo = jsonDecode(bind.mainGetLoginDeviceInfo());
|
|
} catch (e) {
|
|
debugPrint('Failed to decode get device info: $e');
|
|
}
|
|
data['deviceInfo'] = deviceInfo;
|
|
return data;
|
|
}
|
|
}
|
|
|
|
class LoginResponse {
|
|
String? access_token;
|
|
String? type;
|
|
String? tfa_type;
|
|
String? secret;
|
|
UserPayload? user;
|
|
|
|
LoginResponse(
|
|
{this.access_token, this.type, this.tfa_type, this.secret, this.user});
|
|
|
|
LoginResponse.fromJson(Map<String, dynamic> json) {
|
|
access_token = json['access_token'];
|
|
type = json['type'];
|
|
tfa_type = json['tfa_type'];
|
|
secret = json['secret'];
|
|
user = json['user'] != null ? UserPayload.fromJson(json['user']) : null;
|
|
}
|
|
}
|
|
|
|
class RequestException implements Exception {
|
|
int statusCode;
|
|
String cause;
|
|
RequestException(this.statusCode, this.cause);
|
|
|
|
@override
|
|
String toString() {
|
|
return "RequestException, statusCode: $statusCode, error: $cause";
|
|
}
|
|
}
|
|
|
|
enum ShareRule {
|
|
read(1),
|
|
readWrite(2),
|
|
fullControl(3);
|
|
|
|
const ShareRule(this.value);
|
|
final int value;
|
|
|
|
static String desc(int v) {
|
|
if (v == ShareRule.read.value) {
|
|
return translate('Read-only');
|
|
}
|
|
if (v == ShareRule.readWrite.value) {
|
|
return translate('Read/Write');
|
|
}
|
|
if (v == ShareRule.fullControl.value) {
|
|
return translate('Full Control');
|
|
}
|
|
return v.toString();
|
|
}
|
|
|
|
static String shortDesc(int v) {
|
|
if (v == ShareRule.read.value) {
|
|
return 'R';
|
|
}
|
|
if (v == ShareRule.readWrite.value) {
|
|
return 'RW';
|
|
}
|
|
if (v == ShareRule.fullControl.value) {
|
|
return 'F';
|
|
}
|
|
return v.toString();
|
|
}
|
|
|
|
static ShareRule? fromValue(int v) {
|
|
if (v == ShareRule.read.value) {
|
|
return ShareRule.read;
|
|
}
|
|
if (v == ShareRule.readWrite.value) {
|
|
return ShareRule.readWrite;
|
|
}
|
|
if (v == ShareRule.fullControl.value) {
|
|
return ShareRule.fullControl;
|
|
}
|
|
return null;
|
|
}
|
|
}
|
|
|
|
class AbProfile {
|
|
String guid;
|
|
String name;
|
|
String owner;
|
|
String? note;
|
|
dynamic info;
|
|
int rule;
|
|
|
|
AbProfile(this.guid, this.name, this.owner, this.note, this.rule, this.info);
|
|
|
|
AbProfile.fromJson(Map<String, dynamic> json)
|
|
: guid = json['guid'] ?? '',
|
|
name = json['name'] ?? '',
|
|
owner = json['owner'] ?? '',
|
|
note = json['note'] ?? '',
|
|
info = json['info'],
|
|
rule = json['rule'] ?? 0;
|
|
}
|
|
|
|
class AbTag {
|
|
String name;
|
|
int color;
|
|
|
|
AbTag(this.name, this.color);
|
|
|
|
AbTag.fromJson(Map<String, dynamic> json)
|
|
: name = json['name'] ?? '',
|
|
color = json['color'] ?? '';
|
|
}
|
|
|
|
class DeviceGroupPayload {
|
|
String name;
|
|
|
|
DeviceGroupPayload(this.name);
|
|
|
|
DeviceGroupPayload.fromJson(Map<String, dynamic> json)
|
|
: name = json['name'] ?? '';
|
|
|
|
Map<String, dynamic> toGroupCacheJson() {
|
|
final Map<String, dynamic> map = {
|
|
'name': name,
|
|
};
|
|
return map;
|
|
}
|
|
}
|