Merge branch 'master' of github.com-rustdesk:rustdesk/rustdesk
This commit is contained in:
commit
a316411f76
@ -26,6 +26,13 @@ const val WHEEL_BUTTON_UP = 34
|
|||||||
const val WHEEL_DOWN = 523331
|
const val WHEEL_DOWN = 523331
|
||||||
const val WHEEL_UP = 963
|
const val WHEEL_UP = 963
|
||||||
|
|
||||||
|
const val TOUCH_SCALE_START = 1
|
||||||
|
const val TOUCH_SCALE = 2
|
||||||
|
const val TOUCH_SCALE_END = 3
|
||||||
|
const val TOUCH_PAN_START = 4
|
||||||
|
const val TOUCH_PAN_UPDATE = 5
|
||||||
|
const val TOUCH_PAN_END = 6
|
||||||
|
|
||||||
const val WHEEL_STEP = 120
|
const val WHEEL_STEP = 120
|
||||||
const val WHEEL_DURATION = 50L
|
const val WHEEL_DURATION = 50L
|
||||||
const val LONG_TAP_DELAY = 200L
|
const val LONG_TAP_DELAY = 200L
|
||||||
@ -167,6 +174,30 @@ class InputService : AccessibilityService() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@RequiresApi(Build.VERSION_CODES.N)
|
||||||
|
fun onTouchInput(mask: Int, _x: Int, _y: Int) {
|
||||||
|
when (mask) {
|
||||||
|
TOUCH_PAN_UPDATE -> {
|
||||||
|
mouseX -= _x * SCREEN_INFO.scale
|
||||||
|
mouseY -= _y * SCREEN_INFO.scale
|
||||||
|
mouseX = max(0, mouseX);
|
||||||
|
mouseY = max(0, mouseY);
|
||||||
|
continueGesture(mouseX, mouseY)
|
||||||
|
}
|
||||||
|
TOUCH_PAN_START -> {
|
||||||
|
mouseX = max(0, _x) * SCREEN_INFO.scale
|
||||||
|
mouseY = max(0, _y) * SCREEN_INFO.scale
|
||||||
|
startGesture(mouseX, mouseY)
|
||||||
|
}
|
||||||
|
TOUCH_PAN_END -> {
|
||||||
|
endGesture(mouseX, mouseY)
|
||||||
|
mouseX = max(0, _x) * SCREEN_INFO.scale
|
||||||
|
mouseY = max(0, _y) * SCREEN_INFO.scale
|
||||||
|
}
|
||||||
|
else -> {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@RequiresApi(Build.VERSION_CODES.N)
|
@RequiresApi(Build.VERSION_CODES.N)
|
||||||
private fun consumeWheelActions() {
|
private fun consumeWheelActions() {
|
||||||
if (isWheelActionsPolling) {
|
if (isWheelActionsPolling) {
|
||||||
|
@ -71,17 +71,26 @@ class MainService : Service() {
|
|||||||
|
|
||||||
@Keep
|
@Keep
|
||||||
@RequiresApi(Build.VERSION_CODES.N)
|
@RequiresApi(Build.VERSION_CODES.N)
|
||||||
fun rustMouseInput(mask: Int, x: Int, y: Int) {
|
fun rustPointerInput(kind: String, mask: Int, x: Int, y: Int) {
|
||||||
// turn on screen with LIFT_DOWN when screen off
|
// turn on screen with LIFT_DOWN when screen off
|
||||||
if (!powerManager.isInteractive && mask == LIFT_DOWN) {
|
if (!powerManager.isInteractive && (kind == "touch" || mask == LIFT_DOWN)) {
|
||||||
if (wakeLock.isHeld) {
|
if (wakeLock.isHeld) {
|
||||||
Log.d(logTag,"Turn on Screen, WakeLock release")
|
Log.d(logTag, "Turn on Screen, WakeLock release")
|
||||||
wakeLock.release()
|
wakeLock.release()
|
||||||
}
|
}
|
||||||
Log.d(logTag,"Turn on Screen")
|
Log.d(logTag,"Turn on Screen")
|
||||||
wakeLock.acquire(5000)
|
wakeLock.acquire(5000)
|
||||||
} else {
|
} else {
|
||||||
InputService.ctx?.onMouseInput(mask,x,y)
|
when (kind) {
|
||||||
|
"touch" -> {
|
||||||
|
InputService.ctx?.onTouchInput(mask, x, y)
|
||||||
|
}
|
||||||
|
"mouse" -> {
|
||||||
|
InputService.ctx?.onMouseInput(mask, x, y)
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,6 +6,7 @@ import 'package:flutter/gestures.dart';
|
|||||||
|
|
||||||
import 'package:flutter_hbb/models/platform_model.dart';
|
import 'package:flutter_hbb/models/platform_model.dart';
|
||||||
import 'package:flutter_hbb/common.dart';
|
import 'package:flutter_hbb/common.dart';
|
||||||
|
import 'package:flutter_hbb/consts.dart';
|
||||||
import 'package:flutter_hbb/models/model.dart';
|
import 'package:flutter_hbb/models/model.dart';
|
||||||
import 'package:flutter_hbb/models/input_model.dart';
|
import 'package:flutter_hbb/models/input_model.dart';
|
||||||
|
|
||||||
@ -263,9 +264,9 @@ class _RawTouchGestureDetectorRegionState
|
|||||||
if (scale != 0) {
|
if (scale != 0) {
|
||||||
bind.sessionSendPointer(
|
bind.sessionSendPointer(
|
||||||
sessionId: sessionId,
|
sessionId: sessionId,
|
||||||
msg: json.encode({
|
msg: json.encode(
|
||||||
'touch': {'scale': scale}
|
PointerEventToRust(kPointerEventKindTouch, 'scale', scale)
|
||||||
}));
|
.toJson()));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// mobile
|
// mobile
|
||||||
@ -283,9 +284,8 @@ class _RawTouchGestureDetectorRegionState
|
|||||||
if (isDesktop) {
|
if (isDesktop) {
|
||||||
bind.sessionSendPointer(
|
bind.sessionSendPointer(
|
||||||
sessionId: sessionId,
|
sessionId: sessionId,
|
||||||
msg: json.encode({
|
msg: json.encode(
|
||||||
'touch': {'scale': 0}
|
PointerEventToRust(kPointerEventKindTouch, 'scale', 0).toJson()));
|
||||||
}));
|
|
||||||
} else {
|
} else {
|
||||||
// mobile
|
// mobile
|
||||||
_scale = 1;
|
_scale = 1;
|
||||||
|
@ -5,6 +5,7 @@ import 'package:flutter_hbb/common.dart';
|
|||||||
import 'package:flutter_hbb/models/state_model.dart';
|
import 'package:flutter_hbb/models/state_model.dart';
|
||||||
|
|
||||||
const double kDesktopRemoteTabBarHeight = 28.0;
|
const double kDesktopRemoteTabBarHeight = 28.0;
|
||||||
|
const int kInvalidWindowId = -1;
|
||||||
const int kMainWindowId = 0;
|
const int kMainWindowId = 0;
|
||||||
|
|
||||||
const String kPeerPlatformWindows = "Windows";
|
const String kPeerPlatformWindows = "Windows";
|
||||||
@ -38,7 +39,7 @@ const String kWindowEventGetRemoteList = "get_remote_list";
|
|||||||
const String kWindowEventGetSessionIdList = "get_session_id_list";
|
const String kWindowEventGetSessionIdList = "get_session_id_list";
|
||||||
|
|
||||||
const String kWindowEventMoveTabToNewWindow = "move_tab_to_new_window";
|
const String kWindowEventMoveTabToNewWindow = "move_tab_to_new_window";
|
||||||
const String kWindowEventCloseForSeparateWindow = "close_for_separate_window";
|
const String kWindowEventGetCachedSessionData = "get_cached_session_data";
|
||||||
|
|
||||||
const String kOptionOpenNewConnInTabs = "enable-open-new-connections-in-tabs";
|
const String kOptionOpenNewConnInTabs = "enable-open-new-connections-in-tabs";
|
||||||
const String kOptionOpenInTabs = "allow-open-in-tabs";
|
const String kOptionOpenInTabs = "allow-open-in-tabs";
|
||||||
@ -54,6 +55,9 @@ const String kTabLabelSettingPage = "Settings";
|
|||||||
const String kWindowPrefix = "wm_";
|
const String kWindowPrefix = "wm_";
|
||||||
const int kWindowMainId = 0;
|
const int kWindowMainId = 0;
|
||||||
|
|
||||||
|
const String kPointerEventKindTouch = "touch";
|
||||||
|
const String kPointerEventKindMouse = "mouse";
|
||||||
|
|
||||||
// the executable name of the portable version
|
// the executable name of the portable version
|
||||||
const String kEnvPortableExecutable = "RUSTDESK_APPNAME";
|
const String kEnvPortableExecutable = "RUSTDESK_APPNAME";
|
||||||
|
|
||||||
|
@ -17,7 +17,6 @@ import 'package:provider/provider.dart';
|
|||||||
import 'package:url_launcher/url_launcher.dart';
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
import 'package:url_launcher/url_launcher_string.dart';
|
import 'package:url_launcher/url_launcher_string.dart';
|
||||||
import 'package:flutter_hbb/desktop/widgets/scroll_wrapper.dart';
|
import 'package:flutter_hbb/desktop/widgets/scroll_wrapper.dart';
|
||||||
import 'package:window_manager/window_manager.dart';
|
|
||||||
|
|
||||||
import '../../common/widgets/dialog.dart';
|
import '../../common/widgets/dialog.dart';
|
||||||
import '../../common/widgets/login.dart';
|
import '../../common/widgets/login.dart';
|
||||||
|
@ -35,6 +35,7 @@ class RemotePage extends StatefulWidget {
|
|||||||
Key? key,
|
Key? key,
|
||||||
required this.id,
|
required this.id,
|
||||||
required this.sessionId,
|
required this.sessionId,
|
||||||
|
required this.tabWindowId,
|
||||||
required this.password,
|
required this.password,
|
||||||
required this.toolbarState,
|
required this.toolbarState,
|
||||||
required this.tabController,
|
required this.tabController,
|
||||||
@ -44,6 +45,7 @@ class RemotePage extends StatefulWidget {
|
|||||||
|
|
||||||
final String id;
|
final String id;
|
||||||
final SessionID? sessionId;
|
final SessionID? sessionId;
|
||||||
|
final int? tabWindowId;
|
||||||
final String? password;
|
final String? password;
|
||||||
final ToolbarState toolbarState;
|
final ToolbarState toolbarState;
|
||||||
final String? switchUuid;
|
final String? switchUuid;
|
||||||
@ -106,6 +108,7 @@ class _RemotePageState extends State<RemotePage>
|
|||||||
password: widget.password,
|
password: widget.password,
|
||||||
switchUuid: widget.switchUuid,
|
switchUuid: widget.switchUuid,
|
||||||
forceRelay: widget.forceRelay,
|
forceRelay: widget.forceRelay,
|
||||||
|
tabWindowId: widget.tabWindowId,
|
||||||
);
|
);
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, overlays: []);
|
SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, overlays: []);
|
||||||
|
@ -55,6 +55,7 @@ class _ConnectionTabPageState extends State<ConnectionTabPage> {
|
|||||||
RemoteCountState.init();
|
RemoteCountState.init();
|
||||||
peerId = params['id'];
|
peerId = params['id'];
|
||||||
final sessionId = params['session_id'];
|
final sessionId = params['session_id'];
|
||||||
|
final tabWindowId = params['tab_window_id'];
|
||||||
if (peerId != null) {
|
if (peerId != null) {
|
||||||
ConnectionTypeState.init(peerId!);
|
ConnectionTypeState.init(peerId!);
|
||||||
tabController.onSelected = (id) {
|
tabController.onSelected = (id) {
|
||||||
@ -77,6 +78,7 @@ class _ConnectionTabPageState extends State<ConnectionTabPage> {
|
|||||||
key: ValueKey(peerId),
|
key: ValueKey(peerId),
|
||||||
id: peerId!,
|
id: peerId!,
|
||||||
sessionId: sessionId == null ? null : SessionID(sessionId),
|
sessionId: sessionId == null ? null : SessionID(sessionId),
|
||||||
|
tabWindowId: tabWindowId,
|
||||||
password: params['password'],
|
password: params['password'],
|
||||||
toolbarState: _toolbarState,
|
toolbarState: _toolbarState,
|
||||||
tabController: tabController,
|
tabController: tabController,
|
||||||
@ -98,12 +100,14 @@ class _ConnectionTabPageState extends State<ConnectionTabPage> {
|
|||||||
print(
|
print(
|
||||||
"[Remote Page] call ${call.method} with args ${call.arguments} from window $fromWindowId");
|
"[Remote Page] call ${call.method} with args ${call.arguments} from window $fromWindowId");
|
||||||
|
|
||||||
|
dynamic returnValue;
|
||||||
// for simplify, just replace connectionId
|
// for simplify, just replace connectionId
|
||||||
if (call.method == kWindowEventNewRemoteDesktop) {
|
if (call.method == kWindowEventNewRemoteDesktop) {
|
||||||
final args = jsonDecode(call.arguments);
|
final args = jsonDecode(call.arguments);
|
||||||
final id = args['id'];
|
final id = args['id'];
|
||||||
final switchUuid = args['switch_uuid'];
|
final switchUuid = args['switch_uuid'];
|
||||||
final sessionId = args['session_id'];
|
final sessionId = args['session_id'];
|
||||||
|
final tabWindowId = args['tab_window_id'];
|
||||||
windowOnTop(windowId());
|
windowOnTop(windowId());
|
||||||
ConnectionTypeState.init(id);
|
ConnectionTypeState.init(id);
|
||||||
_toolbarState.setShow(
|
_toolbarState.setShow(
|
||||||
@ -118,6 +122,7 @@ class _ConnectionTabPageState extends State<ConnectionTabPage> {
|
|||||||
key: ValueKey(id),
|
key: ValueKey(id),
|
||||||
id: id,
|
id: id,
|
||||||
sessionId: sessionId == null ? null : SessionID(sessionId),
|
sessionId: sessionId == null ? null : SessionID(sessionId),
|
||||||
|
tabWindowId: tabWindowId,
|
||||||
password: args['password'],
|
password: args['password'],
|
||||||
toolbarState: _toolbarState,
|
toolbarState: _toolbarState,
|
||||||
tabController: tabController,
|
tabController: tabController,
|
||||||
@ -147,12 +152,24 @@ class _ConnectionTabPageState extends State<ConnectionTabPage> {
|
|||||||
.map((e) => '${e.key},${(e.page as RemotePage).ffi.sessionId}')
|
.map((e) => '${e.key},${(e.page as RemotePage).ffi.sessionId}')
|
||||||
.toList()
|
.toList()
|
||||||
.join(';');
|
.join(';');
|
||||||
} else if (call.method == kWindowEventCloseForSeparateWindow) {
|
} else if (call.method == kWindowEventGetCachedSessionData) {
|
||||||
|
// Ready to show new window and close old tab.
|
||||||
final peerId = call.arguments;
|
final peerId = call.arguments;
|
||||||
|
try {
|
||||||
|
final remotePage = tabController.state.value.tabs
|
||||||
|
.firstWhere((tab) => tab.key == peerId)
|
||||||
|
.page as RemotePage;
|
||||||
|
returnValue = remotePage.ffi.ffiModel.cachedPeerData.toString();
|
||||||
|
} catch (e) {
|
||||||
|
debugPrint('Failed to get cached session data: $e');
|
||||||
|
}
|
||||||
|
if (returnValue != null) {
|
||||||
closeSessionOnDispose[peerId] = false;
|
closeSessionOnDispose[peerId] = false;
|
||||||
tabController.closeBy(peerId);
|
tabController.closeBy(peerId);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
_update_remote_count();
|
_update_remote_count();
|
||||||
|
return returnValue;
|
||||||
});
|
});
|
||||||
Future.delayed(Duration.zero, () {
|
Future.delayed(Duration.zero, () {
|
||||||
restoreWindowPosition(
|
restoreWindowPosition(
|
||||||
|
@ -771,7 +771,7 @@ class ScreenAdjustor {
|
|||||||
updateScreen() async {
|
updateScreen() async {
|
||||||
final v = await rustDeskWinManager.call(
|
final v = await rustDeskWinManager.call(
|
||||||
WindowType.Main, kWindowGetWindowInfo, '');
|
WindowType.Main, kWindowGetWindowInfo, '');
|
||||||
final String valueStr = v;
|
final String valueStr = v.result;
|
||||||
if (valueStr.isEmpty) {
|
if (valueStr.isEmpty) {
|
||||||
_screen = null;
|
_screen = null;
|
||||||
} else {
|
} else {
|
||||||
|
@ -225,7 +225,7 @@ class AbModel {
|
|||||||
final api = "${await bind.mainGetApiServer()}/api/ab";
|
final api = "${await bind.mainGetApiServer()}/api/ab";
|
||||||
var authHeaders = getHttpHeaders();
|
var authHeaders = getHttpHeaders();
|
||||||
authHeaders['Content-Type'] = "application/json";
|
authHeaders['Content-Type'] = "application/json";
|
||||||
final body = jsonEncode(_serialize());
|
final body = jsonEncode({"data": jsonEncode(_serialize())});
|
||||||
http.Response resp;
|
http.Response resp;
|
||||||
// support compression
|
// support compression
|
||||||
if (licensedDevices > 0 && body.length > 1024) {
|
if (licensedDevices > 0 && body.length > 1024) {
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:texture_rgba_renderer/texture_rgba_renderer.dart';
|
import 'package:texture_rgba_renderer/texture_rgba_renderer.dart';
|
||||||
|
|
||||||
@ -21,7 +20,6 @@ class RenderTexture {
|
|||||||
_sessionId = sessionId;
|
_sessionId = sessionId;
|
||||||
|
|
||||||
textureRenderer.createTexture(_textureKey).then((id) async {
|
textureRenderer.createTexture(_textureKey).then((id) async {
|
||||||
debugPrint("id: $id, texture_key: $_textureKey");
|
|
||||||
if (id != -1) {
|
if (id != -1) {
|
||||||
final ptr = await textureRenderer.getTexturePtr(_textureKey);
|
final ptr = await textureRenderer.getTexturePtr(_textureKey);
|
||||||
platformFFI.registerTexture(sessionId, ptr);
|
platformFFI.registerTexture(sessionId, ptr);
|
||||||
|
@ -35,6 +35,24 @@ extension ToString on MouseButtons {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class PointerEventToRust {
|
||||||
|
final String kind;
|
||||||
|
final String type;
|
||||||
|
final dynamic value;
|
||||||
|
|
||||||
|
PointerEventToRust(this.kind, this.type, this.value);
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return {
|
||||||
|
'k': kind,
|
||||||
|
'v': {
|
||||||
|
't': type,
|
||||||
|
'v': value,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class InputModel {
|
class InputModel {
|
||||||
final WeakReference<FFI> parent;
|
final WeakReference<FFI> parent;
|
||||||
String keyboardMode = "legacy";
|
String keyboardMode = "legacy";
|
||||||
@ -62,11 +80,11 @@ class InputModel {
|
|||||||
int _lastButtons = 0;
|
int _lastButtons = 0;
|
||||||
Offset lastMousePos = Offset.zero;
|
Offset lastMousePos = Offset.zero;
|
||||||
|
|
||||||
get id => parent.target?.id ?? "";
|
|
||||||
|
|
||||||
late final SessionID sessionId;
|
late final SessionID sessionId;
|
||||||
|
|
||||||
bool get keyboardPerm => parent.target!.ffiModel.keyboard;
|
bool get keyboardPerm => parent.target!.ffiModel.keyboard;
|
||||||
|
String get id => parent.target?.id ?? '';
|
||||||
|
String? get peerPlatform => parent.target?.ffiModel.pi.platform;
|
||||||
|
|
||||||
InputModel(this.parent) {
|
InputModel(this.parent) {
|
||||||
sessionId = parent.target!.sessionId;
|
sessionId = parent.target!.sessionId;
|
||||||
@ -223,14 +241,8 @@ class InputModel {
|
|||||||
command: command);
|
command: command);
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<String, dynamic> getEvent(PointerEvent evt, String type) {
|
Map<String, dynamic> _getMouseEvent(PointerEvent evt, String type) {
|
||||||
final Map<String, dynamic> out = {};
|
final Map<String, dynamic> out = {};
|
||||||
out['x'] = evt.position.dx;
|
|
||||||
out['y'] = evt.position.dy;
|
|
||||||
if (alt) out['alt'] = 'true';
|
|
||||||
if (shift) out['shift'] = 'true';
|
|
||||||
if (ctrl) out['ctrl'] = 'true';
|
|
||||||
if (command) out['command'] = 'true';
|
|
||||||
|
|
||||||
// Check update event type and set buttons to be sent.
|
// Check update event type and set buttons to be sent.
|
||||||
int buttons = _lastButtons;
|
int buttons = _lastButtons;
|
||||||
@ -260,7 +272,6 @@ class InputModel {
|
|||||||
|
|
||||||
out['buttons'] = buttons;
|
out['buttons'] = buttons;
|
||||||
out['type'] = type;
|
out['type'] = type;
|
||||||
|
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -292,7 +303,7 @@ class InputModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Modify the given modifier map [evt] based on current modifier key status.
|
/// Modify the given modifier map [evt] based on current modifier key status.
|
||||||
Map<String, String> modify(Map<String, String> evt) {
|
Map<String, dynamic> modify(Map<String, dynamic> evt) {
|
||||||
if (ctrl) evt['ctrl'] = 'true';
|
if (ctrl) evt['ctrl'] = 'true';
|
||||||
if (shift) evt['shift'] = 'true';
|
if (shift) evt['shift'] = 'true';
|
||||||
if (alt) evt['alt'] = 'true';
|
if (alt) evt['alt'] = 'true';
|
||||||
@ -334,35 +345,41 @@ class InputModel {
|
|||||||
isPhysicalMouse.value = true;
|
isPhysicalMouse.value = true;
|
||||||
}
|
}
|
||||||
if (isPhysicalMouse.value) {
|
if (isPhysicalMouse.value) {
|
||||||
handleMouse(getEvent(e, _kMouseEventMove));
|
handleMouse(_getMouseEvent(e, _kMouseEventMove), e.position);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void onPointerPanZoomStart(PointerPanZoomStartEvent e) {
|
void onPointerPanZoomStart(PointerPanZoomStartEvent e) {
|
||||||
_lastScale = 1.0;
|
_lastScale = 1.0;
|
||||||
_stopFling = true;
|
_stopFling = true;
|
||||||
|
|
||||||
|
if (peerPlatform == kPeerPlatformAndroid) {
|
||||||
|
handlePointerEvent('touch', 'pan_start', e.position);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://docs.flutter.dev/release/breaking-changes/trackpad-gestures
|
// https://docs.flutter.dev/release/breaking-changes/trackpad-gestures
|
||||||
void onPointerPanZoomUpdate(PointerPanZoomUpdateEvent e) {
|
void onPointerPanZoomUpdate(PointerPanZoomUpdateEvent e) {
|
||||||
|
if (peerPlatform != kPeerPlatformAndroid) {
|
||||||
final scale = ((e.scale - _lastScale) * 1000).toInt();
|
final scale = ((e.scale - _lastScale) * 1000).toInt();
|
||||||
_lastScale = e.scale;
|
_lastScale = e.scale;
|
||||||
|
|
||||||
if (scale != 0) {
|
if (scale != 0) {
|
||||||
bind.sessionSendPointer(
|
bind.sessionSendPointer(
|
||||||
sessionId: sessionId,
|
sessionId: sessionId,
|
||||||
msg: json.encode({
|
msg: json.encode(
|
||||||
'touch': {'scale': scale}
|
PointerEventToRust(kPointerEventKindTouch, 'scale', scale)
|
||||||
}));
|
.toJson()));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
final delta = e.panDelta;
|
final delta = e.panDelta;
|
||||||
_trackpadLastDelta = delta;
|
_trackpadLastDelta = delta;
|
||||||
|
|
||||||
var x = delta.dx.toInt();
|
var x = delta.dx.toInt();
|
||||||
var y = delta.dy.toInt();
|
var y = delta.dy.toInt();
|
||||||
if (parent.target?.ffiModel.pi.platform == kPeerPlatformLinux) {
|
if (peerPlatform == kPeerPlatformLinux) {
|
||||||
_trackpadScrollUnsent += (delta * _trackpadSpeed);
|
_trackpadScrollUnsent += (delta * _trackpadSpeed);
|
||||||
x = _trackpadScrollUnsent.dx.truncate();
|
x = _trackpadScrollUnsent.dx.truncate();
|
||||||
y = _trackpadScrollUnsent.dy.truncate();
|
y = _trackpadScrollUnsent.dy.truncate();
|
||||||
@ -378,11 +395,15 @@ class InputModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (x != 0 || y != 0) {
|
if (x != 0 || y != 0) {
|
||||||
|
if (peerPlatform == kPeerPlatformAndroid) {
|
||||||
|
handlePointerEvent('touch', 'pan_update', Offset(x.toDouble(), y.toDouble()));
|
||||||
|
} else {
|
||||||
bind.sessionSendMouse(
|
bind.sessionSendMouse(
|
||||||
sessionId: sessionId,
|
sessionId: sessionId,
|
||||||
msg: '{"type": "trackpad", "x": "$x", "y": "$y"}');
|
msg: '{"type": "trackpad", "x": "$x", "y": "$y"}');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void _scheduleFling(double x, double y, int delay) {
|
void _scheduleFling(double x, double y, int delay) {
|
||||||
if ((x == 0 && y == 0) || _stopFling) {
|
if ((x == 0 && y == 0) || _stopFling) {
|
||||||
@ -436,11 +457,15 @@ class InputModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void onPointerPanZoomEnd(PointerPanZoomEndEvent e) {
|
void onPointerPanZoomEnd(PointerPanZoomEndEvent e) {
|
||||||
|
if (peerPlatform == kPeerPlatformAndroid) {
|
||||||
|
handlePointerEvent('touch', 'pan_end', e.position);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
bind.sessionSendPointer(
|
bind.sessionSendPointer(
|
||||||
sessionId: sessionId,
|
sessionId: sessionId,
|
||||||
msg: json.encode({
|
msg: json.encode(
|
||||||
'touch': {'scale': 0}
|
PointerEventToRust(kPointerEventKindTouch, 'scale', 0).toJson()));
|
||||||
}));
|
|
||||||
|
|
||||||
waitLastFlingDone();
|
waitLastFlingDone();
|
||||||
_stopFling = false;
|
_stopFling = false;
|
||||||
@ -465,21 +490,21 @@ class InputModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (isPhysicalMouse.value) {
|
if (isPhysicalMouse.value) {
|
||||||
handleMouse(getEvent(e, _kMouseEventDown));
|
handleMouse(_getMouseEvent(e, _kMouseEventDown), e.position);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void onPointUpImage(PointerUpEvent e) {
|
void onPointUpImage(PointerUpEvent e) {
|
||||||
if (e.kind != ui.PointerDeviceKind.mouse) return;
|
if (e.kind != ui.PointerDeviceKind.mouse) return;
|
||||||
if (isPhysicalMouse.value) {
|
if (isPhysicalMouse.value) {
|
||||||
handleMouse(getEvent(e, _kMouseEventUp));
|
handleMouse(_getMouseEvent(e, _kMouseEventUp), e.position);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void onPointMoveImage(PointerMoveEvent e) {
|
void onPointMoveImage(PointerMoveEvent e) {
|
||||||
if (e.kind != ui.PointerDeviceKind.mouse) return;
|
if (e.kind != ui.PointerDeviceKind.mouse) return;
|
||||||
if (isPhysicalMouse.value) {
|
if (isPhysicalMouse.value) {
|
||||||
handleMouse(getEvent(e, _kMouseEventMove));
|
handleMouse(_getMouseEvent(e, _kMouseEventMove), e.position);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -504,19 +529,16 @@ class InputModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void refreshMousePos() => handleMouse({
|
void refreshMousePos() => handleMouse({
|
||||||
'x': lastMousePos.dx,
|
|
||||||
'y': lastMousePos.dy,
|
|
||||||
'buttons': 0,
|
'buttons': 0,
|
||||||
'type': _kMouseEventMove,
|
'type': _kMouseEventMove,
|
||||||
});
|
}, lastMousePos);
|
||||||
|
|
||||||
void tryMoveEdgeOnExit(Offset pos) => handleMouse(
|
void tryMoveEdgeOnExit(Offset pos) => handleMouse(
|
||||||
{
|
{
|
||||||
'x': pos.dx,
|
|
||||||
'y': pos.dy,
|
|
||||||
'buttons': 0,
|
'buttons': 0,
|
||||||
'type': _kMouseEventMove,
|
'type': _kMouseEventMove,
|
||||||
},
|
},
|
||||||
|
pos,
|
||||||
onExit: true,
|
onExit: true,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -550,17 +572,49 @@ class InputModel {
|
|||||||
return Offset(x, y);
|
return Offset(x, y);
|
||||||
}
|
}
|
||||||
|
|
||||||
void handleMouse(
|
void handlePointerEvent(String kind, String type, Offset offset) {
|
||||||
Map<String, dynamic> evt, {
|
double x = offset.dx;
|
||||||
bool onExit = false,
|
double y = offset.dy;
|
||||||
}) {
|
if (_checkPeerControlProtected(x, y)) {
|
||||||
double x = evt['x'];
|
return;
|
||||||
double y = max(0.0, evt['y']);
|
}
|
||||||
final cursorModel = parent.target!.cursorModel;
|
// Only touch events are handled for now. So we can just ignore buttons.
|
||||||
|
// to-do: handle mouse events
|
||||||
|
|
||||||
|
late final dynamic evtValue;
|
||||||
|
if (type == 'pan_update') {
|
||||||
|
evtValue = {
|
||||||
|
'x': x.toInt(),
|
||||||
|
'y': y.toInt(),
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
final isMoveTypes = ['pan_start', 'pan_end'];
|
||||||
|
final pos = handlePointerDevicePos(
|
||||||
|
kPointerEventKindTouch,
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
isMoveTypes.contains(type),
|
||||||
|
type,
|
||||||
|
);
|
||||||
|
if (pos == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
evtValue = {
|
||||||
|
'x': pos.x,
|
||||||
|
'y': pos.y,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
final evt = PointerEventToRust(kind, type, evtValue).toJson();
|
||||||
|
bind.sessionSendPointer(
|
||||||
|
sessionId: sessionId, msg: json.encode(modify(evt)));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool _checkPeerControlProtected(double x, double y) {
|
||||||
|
final cursorModel = parent.target!.cursorModel;
|
||||||
if (cursorModel.isPeerControlProtected) {
|
if (cursorModel.isPeerControlProtected) {
|
||||||
lastMousePos = ui.Offset(x, y);
|
lastMousePos = ui.Offset(x, y);
|
||||||
return;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!cursorModel.gotMouseControl) {
|
if (!cursorModel.gotMouseControl) {
|
||||||
@ -571,10 +625,23 @@ class InputModel {
|
|||||||
cursorModel.gotMouseControl = true;
|
cursorModel.gotMouseControl = true;
|
||||||
} else {
|
} else {
|
||||||
lastMousePos = ui.Offset(x, y);
|
lastMousePos = ui.Offset(x, y);
|
||||||
return;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
lastMousePos = ui.Offset(x, y);
|
lastMousePos = ui.Offset(x, y);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void handleMouse(
|
||||||
|
Map<String, dynamic> evt,
|
||||||
|
Offset offset, {
|
||||||
|
bool onExit = false,
|
||||||
|
}) {
|
||||||
|
double x = offset.dx;
|
||||||
|
double y = max(0.0, offset.dy);
|
||||||
|
if (_checkPeerControlProtected(x, y)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var type = '';
|
var type = '';
|
||||||
var isMove = false;
|
var isMove = false;
|
||||||
@ -592,17 +659,58 @@ class InputModel {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
evt['type'] = type;
|
evt['type'] = type;
|
||||||
|
|
||||||
|
final pos = handlePointerDevicePos(
|
||||||
|
kPointerEventKindMouse,
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
isMove,
|
||||||
|
type,
|
||||||
|
onExit: onExit,
|
||||||
|
buttons: evt['buttons'],
|
||||||
|
);
|
||||||
|
if (pos == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (type != '') {
|
||||||
|
evt['x'] = '0';
|
||||||
|
evt['y'] = '0';
|
||||||
|
} else {
|
||||||
|
evt['x'] = '${pos.x}';
|
||||||
|
evt['y'] = '${pos.y}';
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<int, String> mapButtons = {
|
||||||
|
kPrimaryMouseButton: 'left',
|
||||||
|
kSecondaryMouseButton: 'right',
|
||||||
|
kMiddleMouseButton: 'wheel',
|
||||||
|
kBackMouseButton: 'back',
|
||||||
|
kForwardMouseButton: 'forward'
|
||||||
|
};
|
||||||
|
evt['buttons'] = mapButtons[evt['buttons']] ?? '';
|
||||||
|
bind.sessionSendMouse(sessionId: sessionId, msg: json.encode(modify(evt)));
|
||||||
|
}
|
||||||
|
|
||||||
|
Point? handlePointerDevicePos(
|
||||||
|
String kind,
|
||||||
|
double x,
|
||||||
|
double y,
|
||||||
|
bool isMove,
|
||||||
|
String evtType, {
|
||||||
|
bool onExit = false,
|
||||||
|
int buttons = kPrimaryMouseButton,
|
||||||
|
}) {
|
||||||
y -= CanvasModel.topToEdge;
|
y -= CanvasModel.topToEdge;
|
||||||
x -= CanvasModel.leftToEdge;
|
x -= CanvasModel.leftToEdge;
|
||||||
final canvasModel = parent.target!.canvasModel;
|
final canvasModel = parent.target!.canvasModel;
|
||||||
final nearThr = 3;
|
|
||||||
var nearRight = (canvasModel.size.width - x) < nearThr;
|
|
||||||
var nearBottom = (canvasModel.size.height - y) < nearThr;
|
|
||||||
|
|
||||||
final ffiModel = parent.target!.ffiModel;
|
final ffiModel = parent.target!.ffiModel;
|
||||||
if (isMove) {
|
if (isMove) {
|
||||||
canvasModel.moveDesktopMouse(x, y);
|
canvasModel.moveDesktopMouse(x, y);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final nearThr = 3;
|
||||||
|
var nearRight = (canvasModel.size.width - x) < nearThr;
|
||||||
|
var nearBottom = (canvasModel.size.height - y) < nearThr;
|
||||||
final d = ffiModel.display;
|
final d = ffiModel.display;
|
||||||
final imageWidth = d.width * canvasModel.scale;
|
final imageWidth = d.width * canvasModel.scale;
|
||||||
final imageHeight = d.height * canvasModel.scale;
|
final imageHeight = d.height * canvasModel.scale;
|
||||||
@ -650,7 +758,7 @@ class InputModel {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
debugPrintStack(
|
debugPrintStack(
|
||||||
label: 'canvasModel.scale value ${canvasModel.scale}, $e');
|
label: 'canvasModel.scale value ${canvasModel.scale}, $e');
|
||||||
return;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
int minX = d.x.toInt();
|
int minX = d.x.toInt();
|
||||||
@ -659,40 +767,16 @@ class InputModel {
|
|||||||
int maxY = (d.y + d.height).toInt() - 1;
|
int maxY = (d.y + d.height).toInt() - 1;
|
||||||
evtX = trySetNearestRange(evtX, minX, maxX, 5);
|
evtX = trySetNearestRange(evtX, minX, maxX, 5);
|
||||||
evtY = trySetNearestRange(evtY, minY, maxY, 5);
|
evtY = trySetNearestRange(evtY, minY, maxY, 5);
|
||||||
|
if (kind == kPointerEventKindMouse) {
|
||||||
if (evtX < minX || evtY < minY || evtX > maxX || evtY > maxY) {
|
if (evtX < minX || evtY < minY || evtX > maxX || evtY > maxY) {
|
||||||
// If left mouse up, no early return.
|
// If left mouse up, no early return.
|
||||||
if (evt['buttons'] != kPrimaryMouseButton || type != 'up') {
|
if (!(buttons == kPrimaryMouseButton && evtType == 'up')) {
|
||||||
return;
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (type != '') {
|
return Point(evtX, evtY);
|
||||||
evtX = 0;
|
|
||||||
evtY = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
evt['x'] = '$evtX';
|
|
||||||
evt['y'] = '$evtY';
|
|
||||||
var buttons = '';
|
|
||||||
switch (evt['buttons']) {
|
|
||||||
case kPrimaryMouseButton:
|
|
||||||
buttons = 'left';
|
|
||||||
break;
|
|
||||||
case kSecondaryMouseButton:
|
|
||||||
buttons = 'right';
|
|
||||||
break;
|
|
||||||
case kMiddleMouseButton:
|
|
||||||
buttons = 'wheel';
|
|
||||||
break;
|
|
||||||
case kBackMouseButton:
|
|
||||||
buttons = 'back';
|
|
||||||
break;
|
|
||||||
case kForwardMouseButton:
|
|
||||||
buttons = 'forward';
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
evt['buttons'] = buttons;
|
|
||||||
bind.sessionSendMouse(sessionId: sessionId, msg: json.encode(evt));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Web only
|
/// Web only
|
||||||
|
@ -4,6 +4,7 @@ import 'dart:io';
|
|||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
import 'dart:ui' as ui;
|
import 'dart:ui' as ui;
|
||||||
|
|
||||||
|
import 'package:desktop_multi_window/desktop_multi_window.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_hbb/consts.dart';
|
import 'package:flutter_hbb/consts.dart';
|
||||||
@ -41,7 +42,50 @@ final _waitForImageDialogShow = <UuidValue, bool>{};
|
|||||||
final _waitForFirstImage = <UuidValue, bool>{};
|
final _waitForFirstImage = <UuidValue, bool>{};
|
||||||
final _constSessionId = Uuid().v4obj();
|
final _constSessionId = Uuid().v4obj();
|
||||||
|
|
||||||
|
class CachedPeerData {
|
||||||
|
Map<String, dynamic> updatePrivacyMode = {};
|
||||||
|
Map<String, dynamic> peerInfo = {};
|
||||||
|
List<Map<String, dynamic>> cursorDataList = [];
|
||||||
|
Map<String, dynamic> lastCursorId = {};
|
||||||
|
bool secure = false;
|
||||||
|
bool direct = false;
|
||||||
|
|
||||||
|
CachedPeerData();
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return jsonEncode({
|
||||||
|
'updatePrivacyMode': updatePrivacyMode,
|
||||||
|
'peerInfo': peerInfo,
|
||||||
|
'cursorDataList': cursorDataList,
|
||||||
|
'lastCursorId': lastCursorId,
|
||||||
|
'secure': secure,
|
||||||
|
'direct': direct,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static CachedPeerData? fromString(String s) {
|
||||||
|
try {
|
||||||
|
final map = jsonDecode(s);
|
||||||
|
final data = CachedPeerData();
|
||||||
|
data.updatePrivacyMode = map['updatePrivacyMode'];
|
||||||
|
data.peerInfo = map['peerInfo'];
|
||||||
|
for (final cursorData in map['cursorDataList']) {
|
||||||
|
data.cursorDataList.add(cursorData);
|
||||||
|
}
|
||||||
|
data.lastCursorId = map['lastCursorId'];
|
||||||
|
data.secure = map['secure'];
|
||||||
|
data.direct = map['direct'];
|
||||||
|
return data;
|
||||||
|
} catch (e) {
|
||||||
|
debugPrint('Failed to parse CachedPeerData: $e');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class FfiModel with ChangeNotifier {
|
class FfiModel with ChangeNotifier {
|
||||||
|
CachedPeerData cachedPeerData = CachedPeerData();
|
||||||
PeerInfo _pi = PeerInfo();
|
PeerInfo _pi = PeerInfo();
|
||||||
Display _display = Display();
|
Display _display = Display();
|
||||||
|
|
||||||
@ -117,6 +161,8 @@ class FfiModel with ChangeNotifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setConnectionType(String peerId, bool secure, bool direct) {
|
setConnectionType(String peerId, bool secure, bool direct) {
|
||||||
|
cachedPeerData.secure = secure;
|
||||||
|
cachedPeerData.direct = direct;
|
||||||
_secure = secure;
|
_secure = secure;
|
||||||
_direct = direct;
|
_direct = direct;
|
||||||
try {
|
try {
|
||||||
@ -143,6 +189,22 @@ class FfiModel with ChangeNotifier {
|
|||||||
_permissions.clear();
|
_permissions.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleCachedPeerData(CachedPeerData data, String peerId) async {
|
||||||
|
handleMsgBox({
|
||||||
|
'type': 'success',
|
||||||
|
'title': 'Successful',
|
||||||
|
'text': 'Connected, waiting for image...',
|
||||||
|
'link': '',
|
||||||
|
}, sessionId, peerId);
|
||||||
|
updatePrivacyMode(data.updatePrivacyMode, sessionId, peerId);
|
||||||
|
setConnectionType(peerId, data.secure, data.direct);
|
||||||
|
handlePeerInfo(data.peerInfo, peerId);
|
||||||
|
for (var element in data.cursorDataList) {
|
||||||
|
handleCursorData(element);
|
||||||
|
}
|
||||||
|
handleCursorId(data.lastCursorId);
|
||||||
|
}
|
||||||
|
|
||||||
// todo: why called by two position
|
// todo: why called by two position
|
||||||
StreamEventHandler startEventListener(SessionID sessionId, String peerId) {
|
StreamEventHandler startEventListener(SessionID sessionId, String peerId) {
|
||||||
return (evt) async {
|
return (evt) async {
|
||||||
@ -159,9 +221,9 @@ class FfiModel with ChangeNotifier {
|
|||||||
} else if (name == 'switch_display') {
|
} else if (name == 'switch_display') {
|
||||||
handleSwitchDisplay(evt, sessionId, peerId);
|
handleSwitchDisplay(evt, sessionId, peerId);
|
||||||
} else if (name == 'cursor_data') {
|
} else if (name == 'cursor_data') {
|
||||||
await parent.target?.cursorModel.updateCursorData(evt);
|
await handleCursorData(evt);
|
||||||
} else if (name == 'cursor_id') {
|
} else if (name == 'cursor_id') {
|
||||||
await parent.target?.cursorModel.updateCursorId(evt);
|
await handleCursorId(evt);
|
||||||
} else if (name == 'cursor_position') {
|
} else if (name == 'cursor_position') {
|
||||||
await parent.target?.cursorModel.updateCursorPosition(evt, peerId);
|
await parent.target?.cursorModel.updateCursorPosition(evt, peerId);
|
||||||
} else if (name == 'clipboard') {
|
} else if (name == 'clipboard') {
|
||||||
@ -464,6 +526,8 @@ class FfiModel with ChangeNotifier {
|
|||||||
|
|
||||||
/// Handle the peer info event based on [evt].
|
/// Handle the peer info event based on [evt].
|
||||||
handlePeerInfo(Map<String, dynamic> evt, String peerId) async {
|
handlePeerInfo(Map<String, dynamic> evt, String peerId) async {
|
||||||
|
cachedPeerData.peerInfo = evt;
|
||||||
|
|
||||||
// recent peer updated by handle_peer_info(ui_session_interface.rs) --> handle_peer_info(client.rs) --> save_config(client.rs)
|
// recent peer updated by handle_peer_info(ui_session_interface.rs) --> handle_peer_info(client.rs) --> save_config(client.rs)
|
||||||
bind.mainLoadRecentPeers();
|
bind.mainLoadRecentPeers();
|
||||||
|
|
||||||
@ -579,9 +643,20 @@ class FfiModel with ChangeNotifier {
|
|||||||
return d;
|
return d;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleCursorId(Map<String, dynamic> evt) async {
|
||||||
|
cachedPeerData.lastCursorId = evt;
|
||||||
|
await parent.target?.cursorModel.updateCursorId(evt);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleCursorData(Map<String, dynamic> evt) async {
|
||||||
|
cachedPeerData.cursorDataList.add(evt);
|
||||||
|
await parent.target?.cursorModel.updateCursorData(evt);
|
||||||
|
}
|
||||||
|
|
||||||
/// Handle the peer info synchronization event based on [evt].
|
/// Handle the peer info synchronization event based on [evt].
|
||||||
handleSyncPeerInfo(Map<String, dynamic> evt, SessionID sessionId) async {
|
handleSyncPeerInfo(Map<String, dynamic> evt, SessionID sessionId) async {
|
||||||
if (evt['displays'] != null) {
|
if (evt['displays'] != null) {
|
||||||
|
cachedPeerData.peerInfo['displays'] = evt['displays'];
|
||||||
List<dynamic> displays = json.decode(evt['displays']);
|
List<dynamic> displays = json.decode(evt['displays']);
|
||||||
List<Display> newDisplays = [];
|
List<Display> newDisplays = [];
|
||||||
for (int i = 0; i < displays.length; ++i) {
|
for (int i = 0; i < displays.length; ++i) {
|
||||||
@ -1596,7 +1671,6 @@ class FFI {
|
|||||||
/// dialogManager use late to ensure init after main page binding [globalKey]
|
/// dialogManager use late to ensure init after main page binding [globalKey]
|
||||||
late final dialogManager = OverlayDialogManager();
|
late final dialogManager = OverlayDialogManager();
|
||||||
|
|
||||||
late final bool isSessionAdded;
|
|
||||||
late final SessionID sessionId;
|
late final SessionID sessionId;
|
||||||
late final ImageModel imageModel; // session
|
late final ImageModel imageModel; // session
|
||||||
late final FfiModel ffiModel; // session
|
late final FfiModel ffiModel; // session
|
||||||
@ -1615,7 +1689,6 @@ class FFI {
|
|||||||
late final ElevationModel elevationModel; // session
|
late final ElevationModel elevationModel; // session
|
||||||
|
|
||||||
FFI(SessionID? sId) {
|
FFI(SessionID? sId) {
|
||||||
isSessionAdded = sId != null;
|
|
||||||
sessionId = sId ?? (isDesktop ? Uuid().v4obj() : _constSessionId);
|
sessionId = sId ?? (isDesktop ? Uuid().v4obj() : _constSessionId);
|
||||||
imageModel = ImageModel(WeakReference(this));
|
imageModel = ImageModel(WeakReference(this));
|
||||||
ffiModel = FfiModel(WeakReference(this));
|
ffiModel = FfiModel(WeakReference(this));
|
||||||
@ -1641,7 +1714,8 @@ class FFI {
|
|||||||
bool isRdp = false,
|
bool isRdp = false,
|
||||||
String? switchUuid,
|
String? switchUuid,
|
||||||
String? password,
|
String? password,
|
||||||
bool? forceRelay}) {
|
bool? forceRelay,
|
||||||
|
int? tabWindowId}) {
|
||||||
closed = false;
|
closed = false;
|
||||||
auditNote = '';
|
auditNote = '';
|
||||||
assert(!(isFileTransfer && isPortForward), 'more than one connect type');
|
assert(!(isFileTransfer && isPortForward), 'more than one connect type');
|
||||||
@ -1656,7 +1730,9 @@ class FFI {
|
|||||||
imageModel.id = id;
|
imageModel.id = id;
|
||||||
cursorModel.id = id;
|
cursorModel.id = id;
|
||||||
}
|
}
|
||||||
if (!isSessionAdded) {
|
// If tabWindowId != null, this session is a "tab -> window" one.
|
||||||
|
// Else this session is a new one.
|
||||||
|
if (tabWindowId == null) {
|
||||||
// ignore: unused_local_variable
|
// ignore: unused_local_variable
|
||||||
final addRes = bind.sessionAddSync(
|
final addRes = bind.sessionAddSync(
|
||||||
sessionId: sessionId,
|
sessionId: sessionId,
|
||||||
@ -1677,8 +1753,25 @@ class FFI {
|
|||||||
// Preserved for the rgba data.
|
// Preserved for the rgba data.
|
||||||
stream.listen((message) {
|
stream.listen((message) {
|
||||||
if (closed) return;
|
if (closed) return;
|
||||||
if (isSessionAdded && !isToNewWindowNotified.value) {
|
if (tabWindowId != null && !isToNewWindowNotified.value) {
|
||||||
bind.sessionReadyToNewWindow(sessionId: sessionId);
|
// Session is read to be moved to a new window.
|
||||||
|
// Get the cached data and handle the cached data.
|
||||||
|
Future.delayed(Duration.zero, () async {
|
||||||
|
final cachedData = await DesktopMultiWindow.invokeMethod(
|
||||||
|
tabWindowId, kWindowEventGetCachedSessionData, id);
|
||||||
|
if (cachedData == null) {
|
||||||
|
// unreachable
|
||||||
|
debugPrint('Unreachable, the cached data is empty.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final data = CachedPeerData.fromString(cachedData);
|
||||||
|
if (data == null) {
|
||||||
|
debugPrint('Unreachable, the cached data cannot be decoded.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ffiModel.handleCachedPeerData(data, id);
|
||||||
|
await bind.sessionRefresh(sessionId: sessionId);
|
||||||
|
});
|
||||||
isToNewWindowNotified.value = true;
|
isToNewWindowNotified.value = true;
|
||||||
}
|
}
|
||||||
() async {
|
() async {
|
||||||
|
@ -28,6 +28,13 @@ extension Index on int {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class MultiWindowCallResult {
|
||||||
|
int windowId;
|
||||||
|
dynamic result;
|
||||||
|
|
||||||
|
MultiWindowCallResult(this.windowId, this.result);
|
||||||
|
}
|
||||||
|
|
||||||
/// Window Manager
|
/// Window Manager
|
||||||
/// mainly use it in `Main Window`
|
/// mainly use it in `Main Window`
|
||||||
/// use it in sub window is not recommended
|
/// use it in sub window is not recommended
|
||||||
@ -47,6 +54,7 @@ class RustDeskMultiWindowManager {
|
|||||||
var params = {
|
var params = {
|
||||||
'type': WindowType.RemoteDesktop.index,
|
'type': WindowType.RemoteDesktop.index,
|
||||||
'id': peerId,
|
'id': peerId,
|
||||||
|
'tab_window_id': windowId,
|
||||||
'session_id': sessionId,
|
'session_id': sessionId,
|
||||||
};
|
};
|
||||||
await _newSession(
|
await _newSession(
|
||||||
@ -57,17 +65,15 @@ class RustDeskMultiWindowManager {
|
|||||||
_remoteDesktopWindows,
|
_remoteDesktopWindows,
|
||||||
jsonEncode(params),
|
jsonEncode(params),
|
||||||
);
|
);
|
||||||
await DesktopMultiWindow.invokeMethod(
|
|
||||||
windowId, kWindowEventCloseForSeparateWindow, peerId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
newSessionWindow(
|
Future<int> newSessionWindow(
|
||||||
WindowType type, String remoteId, String msg, List<int> windows) async {
|
WindowType type, String remoteId, String msg, List<int> windows) async {
|
||||||
final windowController = await DesktopMultiWindow.createWindow(msg);
|
final windowController = await DesktopMultiWindow.createWindow(msg);
|
||||||
|
final windowId = windowController.windowId;
|
||||||
windowController
|
windowController
|
||||||
..setFrame(const Offset(0, 0) &
|
..setFrame(
|
||||||
Size(1280 + windowController.windowId * 20,
|
const Offset(0, 0) & Size(1280 + windowId * 20, 720 + windowId * 20))
|
||||||
720 + windowController.windowId * 20))
|
|
||||||
..center()
|
..center()
|
||||||
..setTitle(getWindowNameWithId(
|
..setTitle(getWindowNameWithId(
|
||||||
remoteId,
|
remoteId,
|
||||||
@ -76,11 +82,12 @@ class RustDeskMultiWindowManager {
|
|||||||
if (Platform.isMacOS) {
|
if (Platform.isMacOS) {
|
||||||
Future.microtask(() => windowController.show());
|
Future.microtask(() => windowController.show());
|
||||||
}
|
}
|
||||||
registerActiveWindow(windowController.windowId);
|
registerActiveWindow(windowId);
|
||||||
windows.add(windowController.windowId);
|
windows.add(windowId);
|
||||||
|
return windowId;
|
||||||
}
|
}
|
||||||
|
|
||||||
_newSession(
|
Future<MultiWindowCallResult> _newSession(
|
||||||
bool openInTabs,
|
bool openInTabs,
|
||||||
WindowType type,
|
WindowType type,
|
||||||
String methodName,
|
String methodName,
|
||||||
@ -90,9 +97,10 @@ class RustDeskMultiWindowManager {
|
|||||||
) async {
|
) async {
|
||||||
if (openInTabs) {
|
if (openInTabs) {
|
||||||
if (windows.isEmpty) {
|
if (windows.isEmpty) {
|
||||||
await newSessionWindow(type, remoteId, msg, windows);
|
final windowId = await newSessionWindow(type, remoteId, msg, windows);
|
||||||
|
return MultiWindowCallResult(windowId, null);
|
||||||
} else {
|
} else {
|
||||||
call(type, methodName, msg);
|
return call(type, methodName, msg);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (_inactiveWindows.isNotEmpty) {
|
if (_inactiveWindows.isNotEmpty) {
|
||||||
@ -103,15 +111,16 @@ class RustDeskMultiWindowManager {
|
|||||||
await DesktopMultiWindow.invokeMethod(windowId, methodName, msg);
|
await DesktopMultiWindow.invokeMethod(windowId, methodName, msg);
|
||||||
WindowController.fromWindowId(windowId).show();
|
WindowController.fromWindowId(windowId).show();
|
||||||
registerActiveWindow(windowId);
|
registerActiveWindow(windowId);
|
||||||
return;
|
return MultiWindowCallResult(windowId, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
await newSessionWindow(type, remoteId, msg, windows);
|
final windowId = await newSessionWindow(type, remoteId, msg, windows);
|
||||||
|
return MultiWindowCallResult(windowId, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<dynamic> newSession(
|
Future<MultiWindowCallResult> newSession(
|
||||||
WindowType type,
|
WindowType type,
|
||||||
String methodName,
|
String methodName,
|
||||||
String remoteId,
|
String remoteId,
|
||||||
@ -143,15 +152,15 @@ class RustDeskMultiWindowManager {
|
|||||||
for (final windowId in windows) {
|
for (final windowId in windows) {
|
||||||
if (await DesktopMultiWindow.invokeMethod(
|
if (await DesktopMultiWindow.invokeMethod(
|
||||||
windowId, kWindowEventActiveSession, remoteId)) {
|
windowId, kWindowEventActiveSession, remoteId)) {
|
||||||
return;
|
return MultiWindowCallResult(windowId, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await _newSession(openInTabs, type, methodName, remoteId, windows, msg);
|
return _newSession(openInTabs, type, methodName, remoteId, windows, msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<dynamic> newRemoteDesktop(
|
Future<MultiWindowCallResult> newRemoteDesktop(
|
||||||
String remoteId, {
|
String remoteId, {
|
||||||
String? password,
|
String? password,
|
||||||
String? switchUuid,
|
String? switchUuid,
|
||||||
@ -168,7 +177,7 @@ class RustDeskMultiWindowManager {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<dynamic> newFileTransfer(String remoteId,
|
Future<MultiWindowCallResult> newFileTransfer(String remoteId,
|
||||||
{String? password, bool? forceRelay}) async {
|
{String? password, bool? forceRelay}) async {
|
||||||
return await newSession(
|
return await newSession(
|
||||||
WindowType.FileTransfer,
|
WindowType.FileTransfer,
|
||||||
@ -180,7 +189,7 @@ class RustDeskMultiWindowManager {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<dynamic> newPortForward(String remoteId, bool isRDP,
|
Future<MultiWindowCallResult> newPortForward(String remoteId, bool isRDP,
|
||||||
{String? password, bool? forceRelay}) async {
|
{String? password, bool? forceRelay}) async {
|
||||||
return await newSession(
|
return await newSession(
|
||||||
WindowType.PortForward,
|
WindowType.PortForward,
|
||||||
@ -193,18 +202,22 @@ class RustDeskMultiWindowManager {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<dynamic> call(WindowType type, String methodName, dynamic args) async {
|
Future<MultiWindowCallResult> call(
|
||||||
|
WindowType type, String methodName, dynamic args) async {
|
||||||
final wnds = _findWindowsByType(type);
|
final wnds = _findWindowsByType(type);
|
||||||
if (wnds.isEmpty) {
|
if (wnds.isEmpty) {
|
||||||
return;
|
return MultiWindowCallResult(kInvalidWindowId, null);
|
||||||
}
|
}
|
||||||
for (final windowId in wnds) {
|
for (final windowId in wnds) {
|
||||||
if (_activeWindows.contains(windowId)) {
|
if (_activeWindows.contains(windowId)) {
|
||||||
return await DesktopMultiWindow.invokeMethod(
|
final res =
|
||||||
windowId, methodName, args);
|
await DesktopMultiWindow.invokeMethod(windowId, methodName, args);
|
||||||
|
return MultiWindowCallResult(windowId, res);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return await DesktopMultiWindow.invokeMethod(wnds[0], methodName, args);
|
final res =
|
||||||
|
await DesktopMultiWindow.invokeMethod(wnds[0], methodName, args);
|
||||||
|
return MultiWindowCallResult(wnds[0], res);
|
||||||
}
|
}
|
||||||
|
|
||||||
List<int> _findWindowsByType(WindowType type) {
|
List<int> _findWindowsByType(WindowType type) {
|
||||||
|
@ -118,9 +118,29 @@ message TouchScaleUpdate {
|
|||||||
int32 scale = 1;
|
int32 scale = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message TouchPanStart {
|
||||||
|
int32 x = 1;
|
||||||
|
int32 y = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message TouchPanUpdate {
|
||||||
|
// The delta x position relative to the previous position.
|
||||||
|
int32 x = 1;
|
||||||
|
// The delta y position relative to the previous position.
|
||||||
|
int32 y = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message TouchPanEnd {
|
||||||
|
int32 x = 1;
|
||||||
|
int32 y = 2;
|
||||||
|
}
|
||||||
|
|
||||||
message TouchEvent {
|
message TouchEvent {
|
||||||
oneof union {
|
oneof union {
|
||||||
TouchScaleUpdate scale_update = 1;
|
TouchScaleUpdate scale_update = 1;
|
||||||
|
TouchPanStart pan_start = 2;
|
||||||
|
TouchPanUpdate pan_update = 3;
|
||||||
|
TouchPanEnd pan_end = 4;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -154,17 +154,18 @@ pub extern "system" fn Java_com_carriez_flutter_1hbb_MainService_init(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn call_main_service_mouse_input(mask: i32, x: i32, y: i32) -> JniResult<()> {
|
pub fn call_main_service_pointer_input(kind: &str, mask: i32, x: i32, y: i32) -> JniResult<()> {
|
||||||
if let (Some(jvm), Some(ctx)) = (
|
if let (Some(jvm), Some(ctx)) = (
|
||||||
JVM.read().unwrap().as_ref(),
|
JVM.read().unwrap().as_ref(),
|
||||||
MAIN_SERVICE_CTX.read().unwrap().as_ref(),
|
MAIN_SERVICE_CTX.read().unwrap().as_ref(),
|
||||||
) {
|
) {
|
||||||
let mut env = jvm.attach_current_thread_as_daemon()?;
|
let mut env = jvm.attach_current_thread_as_daemon()?;
|
||||||
|
let kind = env.new_string(kind)?;
|
||||||
env.call_method(
|
env.call_method(
|
||||||
ctx,
|
ctx,
|
||||||
"rustMouseInput",
|
"rustPointerInput",
|
||||||
"(III)V",
|
"(Ljava/lang/String;III)V",
|
||||||
&[JValue::Int(mask), JValue::Int(x), JValue::Int(y)],
|
&[JValue::Object(&JObject::from(kind)), JValue::Int(mask), JValue::Int(x), JValue::Int(y)],
|
||||||
)?;
|
)?;
|
||||||
return Ok(());
|
return Ok(());
|
||||||
} else {
|
} else {
|
||||||
|
@ -2387,7 +2387,7 @@ pub trait Interface: Send + Clone + 'static + Sized {
|
|||||||
fn send(&self, data: Data);
|
fn send(&self, data: Data);
|
||||||
fn msgbox(&self, msgtype: &str, title: &str, text: &str, link: &str);
|
fn msgbox(&self, msgtype: &str, title: &str, text: &str, link: &str);
|
||||||
fn handle_login_error(&mut self, err: &str) -> bool;
|
fn handle_login_error(&mut self, err: &str) -> bool;
|
||||||
fn handle_peer_info(&mut self, pi: PeerInfo, is_cached_pi: bool);
|
fn handle_peer_info(&mut self, pi: PeerInfo);
|
||||||
fn on_error(&self, err: &str) {
|
fn on_error(&self, err: &str) {
|
||||||
self.msgbox("error", "Error", err, "");
|
self.msgbox("error", "Error", err, "");
|
||||||
}
|
}
|
||||||
|
@ -125,18 +125,7 @@ impl<T: InvokeUiSession> Remote<T> {
|
|||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok((mut peer, direct, pk)) => {
|
Ok((mut peer, direct, pk)) => {
|
||||||
let is_secured = peer.is_secured();
|
self.handler.set_connection_type(peer.is_secured(), direct); // flutter -> connection_ready
|
||||||
#[cfg(feature = "flutter")]
|
|
||||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
|
||||||
{
|
|
||||||
self.handler
|
|
||||||
.cache_flutter
|
|
||||||
.write()
|
|
||||||
.unwrap()
|
|
||||||
.is_secured_direct
|
|
||||||
.replace((is_secured, direct));
|
|
||||||
}
|
|
||||||
self.handler.set_connection_type(is_secured, direct); // flutter -> connection_ready
|
|
||||||
self.handler.update_direct(Some(direct));
|
self.handler.update_direct(Some(direct));
|
||||||
if conn_type == ConnType::DEFAULT_CONN {
|
if conn_type == ConnType::DEFAULT_CONN {
|
||||||
self.handler
|
self.handler
|
||||||
@ -1021,12 +1010,7 @@ impl<T: InvokeUiSession> Remote<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some(login_response::Union::PeerInfo(pi)) => {
|
Some(login_response::Union::PeerInfo(pi)) => {
|
||||||
#[cfg(feature = "flutter")]
|
self.handler.handle_peer_info(pi);
|
||||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
|
||||||
{
|
|
||||||
self.handler.cache_flutter.write().unwrap().pi = pi.clone();
|
|
||||||
}
|
|
||||||
self.handler.handle_peer_info(pi, false);
|
|
||||||
#[cfg(not(feature = "flutter"))]
|
#[cfg(not(feature = "flutter"))]
|
||||||
self.check_clipboard_file_context();
|
self.check_clipboard_file_context();
|
||||||
if !(self.handler.is_file_transfer() || self.handler.is_port_forward()) {
|
if !(self.handler.is_file_transfer() || self.handler.is_port_forward()) {
|
||||||
@ -1073,22 +1057,9 @@ impl<T: InvokeUiSession> Remote<T> {
|
|||||||
_ => {}
|
_ => {}
|
||||||
},
|
},
|
||||||
Some(message::Union::CursorData(cd)) => {
|
Some(message::Union::CursorData(cd)) => {
|
||||||
#[cfg(feature = "flutter")]
|
|
||||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
|
||||||
{
|
|
||||||
let mut lock = self.handler.cache_flutter.write().unwrap();
|
|
||||||
if !lock.cursor_data.contains_key(&cd.id) {
|
|
||||||
lock.cursor_data.insert(cd.id, cd.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self.handler.set_cursor_data(cd);
|
self.handler.set_cursor_data(cd);
|
||||||
}
|
}
|
||||||
Some(message::Union::CursorId(id)) => {
|
Some(message::Union::CursorId(id)) => {
|
||||||
#[cfg(feature = "flutter")]
|
|
||||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
|
||||||
{
|
|
||||||
self.handler.cache_flutter.write().unwrap().cursor_id = id;
|
|
||||||
}
|
|
||||||
self.handler.set_cursor_id(id.to_string());
|
self.handler.set_cursor_id(id.to_string());
|
||||||
}
|
}
|
||||||
Some(message::Union::CursorPosition(cp)) => {
|
Some(message::Union::CursorPosition(cp)) => {
|
||||||
@ -1305,16 +1276,6 @@ impl<T: InvokeUiSession> Remote<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some(misc::Union::SwitchDisplay(s)) => {
|
Some(misc::Union::SwitchDisplay(s)) => {
|
||||||
#[cfg(feature = "flutter")]
|
|
||||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
|
||||||
{
|
|
||||||
self.handler
|
|
||||||
.cache_flutter
|
|
||||||
.write()
|
|
||||||
.unwrap()
|
|
||||||
.sp
|
|
||||||
.replace(s.clone());
|
|
||||||
}
|
|
||||||
self.handler.handle_peer_switch_display(&s);
|
self.handler.handle_peer_switch_display(&s);
|
||||||
self.video_sender.send(MediaData::Reset).ok();
|
self.video_sender.send(MediaData::Reset).ok();
|
||||||
if s.width > 0 && s.height > 0 {
|
if s.width > 0 && s.height > 0 {
|
||||||
@ -1506,12 +1467,6 @@ impl<T: InvokeUiSession> Remote<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some(message::Union::PeerInfo(pi)) => {
|
Some(message::Union::PeerInfo(pi)) => {
|
||||||
#[cfg(feature = "flutter")]
|
|
||||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
|
||||||
{
|
|
||||||
self.handler.cache_flutter.write().unwrap().pi.displays =
|
|
||||||
pi.displays.clone();
|
|
||||||
}
|
|
||||||
self.handler.set_displays(&pi.displays);
|
self.handler.set_displays(&pi.displays);
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
|
@ -36,9 +36,11 @@ pub(crate) const APP_TYPE_CM: &str = "cm";
|
|||||||
#[cfg(any(target_os = "android", target_os = "ios"))]
|
#[cfg(any(target_os = "android", target_os = "ios"))]
|
||||||
pub(crate) const APP_TYPE_CM: &str = "main";
|
pub(crate) const APP_TYPE_CM: &str = "main";
|
||||||
|
|
||||||
pub(crate) const APP_TYPE_DESKTOP_REMOTE: &str = "remote";
|
// Do not remove the following constants.
|
||||||
pub(crate) const APP_TYPE_DESKTOP_FILE_TRANSFER: &str = "file transfer";
|
// Uncomment them when they are used.
|
||||||
pub(crate) const APP_TYPE_DESKTOP_PORT_FORWARD: &str = "port forward";
|
// pub(crate) const APP_TYPE_DESKTOP_REMOTE: &str = "remote";
|
||||||
|
// pub(crate) const APP_TYPE_DESKTOP_FILE_TRANSFER: &str = "file transfer";
|
||||||
|
// pub(crate) const APP_TYPE_DESKTOP_PORT_FORWARD: &str = "port forward";
|
||||||
|
|
||||||
lazy_static::lazy_static! {
|
lazy_static::lazy_static! {
|
||||||
pub(crate) static ref CUR_SESSION_ID: RwLock<SessionID> = Default::default();
|
pub(crate) static ref CUR_SESSION_ID: RwLock<SessionID> = Default::default();
|
||||||
@ -1130,6 +1132,85 @@ pub fn stop_global_event_stream(app_type: String) {
|
|||||||
let _ = GLOBAL_EVENT_STREAM.write().unwrap().remove(&app_type);
|
let _ = GLOBAL_EVENT_STREAM.write().unwrap().remove(&app_type);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn session_send_touch_scale(
|
||||||
|
session_id: SessionID,
|
||||||
|
v: &serde_json::Value,
|
||||||
|
alt: bool,
|
||||||
|
ctrl: bool,
|
||||||
|
shift: bool,
|
||||||
|
command: bool,
|
||||||
|
) {
|
||||||
|
match v.get("v").and_then(|s| s.as_i64()) {
|
||||||
|
Some(scale) => {
|
||||||
|
if let Some(session) = SESSIONS.read().unwrap().get(&session_id) {
|
||||||
|
session.send_touch_scale(scale as _, alt, ctrl, shift, command);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn session_send_touch_pan(
|
||||||
|
session_id: SessionID,
|
||||||
|
v: &serde_json::Value,
|
||||||
|
pan_event: &str,
|
||||||
|
alt: bool,
|
||||||
|
ctrl: bool,
|
||||||
|
shift: bool,
|
||||||
|
command: bool,
|
||||||
|
) {
|
||||||
|
match v.get("v") {
|
||||||
|
Some(v) => match (
|
||||||
|
v.get("x").and_then(|x| x.as_i64()),
|
||||||
|
v.get("y").and_then(|y| y.as_i64()),
|
||||||
|
) {
|
||||||
|
(Some(x), Some(y)) => {
|
||||||
|
if let Some(session) = SESSIONS.read().unwrap().get(&session_id) {
|
||||||
|
session
|
||||||
|
.send_touch_pan_event(pan_event, x as _, y as _, alt, ctrl, shift, command);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
},
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn session_send_touch_event(
|
||||||
|
session_id: SessionID,
|
||||||
|
v: &serde_json::Value,
|
||||||
|
alt: bool,
|
||||||
|
ctrl: bool,
|
||||||
|
shift: bool,
|
||||||
|
command: bool,
|
||||||
|
) {
|
||||||
|
match v.get("t").and_then(|t| t.as_str()) {
|
||||||
|
Some("scale") => session_send_touch_scale(session_id, v, alt, ctrl, shift, command),
|
||||||
|
Some(pan_event) => {
|
||||||
|
session_send_touch_pan(session_id, v, pan_event, alt, ctrl, shift, command)
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn session_send_pointer(session_id: SessionID, msg: String) {
|
||||||
|
if let Ok(m) = serde_json::from_str::<HashMap<String, serde_json::Value>>(&msg) {
|
||||||
|
let alt = m.get("alt").is_some();
|
||||||
|
let ctrl = m.get("ctrl").is_some();
|
||||||
|
let shift = m.get("shift").is_some();
|
||||||
|
let command = m.get("command").is_some();
|
||||||
|
match (m.get("k"), m.get("v")) {
|
||||||
|
(Some(k), Some(v)) => match k.as_str() {
|
||||||
|
Some("touch") => session_send_touch_event(session_id, v, alt, ctrl, shift, command),
|
||||||
|
_ => {}
|
||||||
|
},
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
unsafe extern "C" fn get_rgba() {}
|
unsafe extern "C" fn get_rgba() {}
|
||||||
|
|
||||||
|
@ -597,14 +597,6 @@ pub fn session_change_resolution(session_id: SessionID, display: i32, width: i32
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn session_ready_to_new_window(session_id: SessionID) {
|
|
||||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
|
||||||
if let Some(session) = SESSIONS.write().unwrap().get_mut(&session_id) {
|
|
||||||
session.restore_flutter_cache();
|
|
||||||
session.refresh_video();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn session_set_size(_session_id: SessionID, _width: usize, _height: usize) {
|
pub fn session_set_size(_session_id: SessionID, _width: usize, _height: usize) {
|
||||||
#[cfg(feature = "flutter_texture_render")]
|
#[cfg(feature = "flutter_texture_render")]
|
||||||
if let Some(session) = SESSIONS.write().unwrap().get_mut(&_session_id) {
|
if let Some(session) = SESSIONS.write().unwrap().get_mut(&_session_id) {
|
||||||
@ -1179,21 +1171,7 @@ pub fn main_load_ab() -> String {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn session_send_pointer(session_id: SessionID, msg: String) {
|
pub fn session_send_pointer(session_id: SessionID, msg: String) {
|
||||||
if let Ok(m) = serde_json::from_str::<HashMap<String, serde_json::Value>>(&msg) {
|
super::flutter::session_send_pointer(session_id, msg);
|
||||||
let alt = m.get("alt").is_some();
|
|
||||||
let ctrl = m.get("ctrl").is_some();
|
|
||||||
let shift = m.get("shift").is_some();
|
|
||||||
let command = m.get("command").is_some();
|
|
||||||
if let Some(touch_event) = m.get("touch") {
|
|
||||||
if let Some(scale) = touch_event.get("scale") {
|
|
||||||
if let Some(session) = SESSIONS.read().unwrap().get(&session_id) {
|
|
||||||
if let Some(scale) = scale.as_i64() {
|
|
||||||
session.send_touch_scale(scale as _, alt, ctrl, shift, command);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn session_send_mouse(session_id: SessionID, msg: String) {
|
pub fn session_send_mouse(session_id: SessionID, msg: String) {
|
||||||
|
@ -146,7 +146,7 @@ async fn connect_and_login(
|
|||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
Some(login_response::Union::PeerInfo(pi)) => {
|
Some(login_response::Union::PeerInfo(pi)) => {
|
||||||
interface.handle_peer_info(pi, false);
|
interface.handle_peer_info(pi);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
|
@ -39,7 +39,7 @@ use hbb_common::{
|
|||||||
tokio_util::codec::{BytesCodec, Framed},
|
tokio_util::codec::{BytesCodec, Framed},
|
||||||
};
|
};
|
||||||
#[cfg(any(target_os = "android", target_os = "ios"))]
|
#[cfg(any(target_os = "android", target_os = "ios"))]
|
||||||
use scrap::android::call_main_service_mouse_input;
|
use scrap::android::call_main_service_pointer_input;
|
||||||
use serde_json::{json, value::Value};
|
use serde_json::{json, value::Value};
|
||||||
use sha2::{Digest, Sha256};
|
use sha2::{Digest, Sha256};
|
||||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||||
@ -1547,8 +1547,8 @@ impl Connection {
|
|||||||
match msg.union {
|
match msg.union {
|
||||||
Some(message::Union::MouseEvent(me)) => {
|
Some(message::Union::MouseEvent(me)) => {
|
||||||
#[cfg(any(target_os = "android", target_os = "ios"))]
|
#[cfg(any(target_os = "android", target_os = "ios"))]
|
||||||
if let Err(e) = call_main_service_mouse_input(me.mask, me.x, me.y) {
|
if let Err(e) = call_main_service_pointer_input("mouse", me.mask, me.x, me.y) {
|
||||||
log::debug!("call_main_service_mouse_input fail:{}", e);
|
log::debug!("call_main_service_pointer_input fail:{}", e);
|
||||||
}
|
}
|
||||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||||
if self.peer_keyboard_enabled() {
|
if self.peer_keyboard_enabled() {
|
||||||
@ -1560,8 +1560,35 @@ impl Connection {
|
|||||||
self.input_mouse(me, self.inner.id());
|
self.input_mouse(me, self.inner.id());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some(message::Union::PointerDeviceEvent(pde)) =>
|
Some(message::Union::PointerDeviceEvent(pde)) => {
|
||||||
{
|
#[cfg(any(target_os = "android", target_os = "ios"))]
|
||||||
|
if let Err(e) = match pde.union {
|
||||||
|
Some(pointer_device_event::Union::TouchEvent(touch)) => match touch.union {
|
||||||
|
Some(touch_event::Union::PanStart(pan_start)) => {
|
||||||
|
call_main_service_pointer_input(
|
||||||
|
"touch",
|
||||||
|
4,
|
||||||
|
pan_start.x,
|
||||||
|
pan_start.y,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Some(touch_event::Union::PanUpdate(pan_update)) => {
|
||||||
|
call_main_service_pointer_input(
|
||||||
|
"touch",
|
||||||
|
5,
|
||||||
|
pan_update.x,
|
||||||
|
pan_update.y,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Some(touch_event::Union::PanEnd(pan_end)) => {
|
||||||
|
call_main_service_pointer_input("touch", 6, pan_end.x, pan_end.y)
|
||||||
|
}
|
||||||
|
_ => Ok(()),
|
||||||
|
},
|
||||||
|
_ => Ok(()),
|
||||||
|
} {
|
||||||
|
log::debug!("call_main_service_pointer_input fail:{}", e);
|
||||||
|
}
|
||||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||||
if self.peer_keyboard_enabled() {
|
if self.peer_keyboard_enabled() {
|
||||||
MOUSE_MOVE_TIME.store(get_time(), Ordering::SeqCst);
|
MOUSE_MOVE_TIME.store(get_time(), Ordering::SeqCst);
|
||||||
|
@ -325,6 +325,7 @@ impl<T: InvokeUiCM> IpcTaskRunner<T> {
|
|||||||
|
|
||||||
// for tmp use, without real conn id
|
// for tmp use, without real conn id
|
||||||
let mut write_jobs: Vec<fs::TransferJob> = Vec::new();
|
let mut write_jobs: Vec<fs::TransferJob> = Vec::new();
|
||||||
|
#[cfg(windows)]
|
||||||
let is_authorized = self.cm.is_authorized(self.conn_id);
|
let is_authorized = self.cm.is_authorized(self.conn_id);
|
||||||
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
|
@ -48,17 +48,6 @@ pub static IS_IN: AtomicBool = AtomicBool::new(false);
|
|||||||
|
|
||||||
const CHANGE_RESOLUTION_VALID_TIMEOUT_SECS: u64 = 15;
|
const CHANGE_RESOLUTION_VALID_TIMEOUT_SECS: u64 = 15;
|
||||||
|
|
||||||
#[cfg(feature = "flutter")]
|
|
||||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
|
||||||
#[derive(Default)]
|
|
||||||
pub struct CacheFlutter {
|
|
||||||
pub pi: PeerInfo,
|
|
||||||
pub sp: Option<SwitchDisplay>,
|
|
||||||
pub cursor_data: HashMap<u64, CursorData>,
|
|
||||||
pub cursor_id: u64,
|
|
||||||
pub is_secured_direct: Option<(bool, bool)>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Default)]
|
#[derive(Clone, Default)]
|
||||||
pub struct Session<T: InvokeUiSession> {
|
pub struct Session<T: InvokeUiSession> {
|
||||||
pub session_id: SessionID, // different from the one in LoginConfigHandler, used for flutter UI message pass
|
pub session_id: SessionID, // different from the one in LoginConfigHandler, used for flutter UI message pass
|
||||||
@ -73,9 +62,6 @@ pub struct Session<T: InvokeUiSession> {
|
|||||||
pub server_file_transfer_enabled: Arc<RwLock<bool>>,
|
pub server_file_transfer_enabled: Arc<RwLock<bool>>,
|
||||||
pub server_clipboard_enabled: Arc<RwLock<bool>>,
|
pub server_clipboard_enabled: Arc<RwLock<bool>>,
|
||||||
pub last_change_display: Arc<Mutex<ChangeDisplayRecord>>,
|
pub last_change_display: Arc<Mutex<ChangeDisplayRecord>>,
|
||||||
#[cfg(feature = "flutter")]
|
|
||||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
|
||||||
pub cache_flutter: Arc<RwLock<CacheFlutter>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@ -724,6 +710,49 @@ impl<T: InvokeUiSession> Session<T> {
|
|||||||
send_pointer_device_event(evt, alt, ctrl, shift, command, self);
|
send_pointer_device_event(evt, alt, ctrl, shift, command, self);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn send_touch_pan_event(
|
||||||
|
&self,
|
||||||
|
event: &str,
|
||||||
|
x: i32,
|
||||||
|
y: i32,
|
||||||
|
alt: bool,
|
||||||
|
ctrl: bool,
|
||||||
|
shift: bool,
|
||||||
|
command: bool,
|
||||||
|
) {
|
||||||
|
let mut touch_evt = TouchEvent::new();
|
||||||
|
match event {
|
||||||
|
"pan_start" => {
|
||||||
|
touch_evt.set_pan_start(TouchPanStart {
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
"pan_update" => {
|
||||||
|
touch_evt.set_pan_update(TouchPanUpdate {
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
"pan_end" => {
|
||||||
|
touch_evt.set_pan_end(TouchPanEnd {
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
log::warn!("unknown touch pan event: {}", event);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let mut evt = PointerDeviceEvent::new();
|
||||||
|
evt.set_touch_event(touch_evt);
|
||||||
|
send_pointer_device_event(evt, alt, ctrl, shift, command, self);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn send_mouse(
|
pub fn send_mouse(
|
||||||
&self,
|
&self,
|
||||||
mask: i32,
|
mask: i32,
|
||||||
@ -1095,7 +1124,7 @@ impl<T: InvokeUiSession> Interface for Session<T> {
|
|||||||
handle_login_error(self.lc.clone(), err, self)
|
handle_login_error(self.lc.clone(), err, self)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_peer_info(&mut self, mut pi: PeerInfo, is_cached_pi: bool) {
|
fn handle_peer_info(&mut self, mut pi: PeerInfo) {
|
||||||
log::debug!("handle_peer_info :{:?}", pi);
|
log::debug!("handle_peer_info :{:?}", pi);
|
||||||
pi.username = self.lc.read().unwrap().get_username(&pi);
|
pi.username = self.lc.read().unwrap().get_username(&pi);
|
||||||
if pi.current_display as usize >= pi.displays.len() {
|
if pi.current_display as usize >= pi.displays.len() {
|
||||||
@ -1116,13 +1145,11 @@ impl<T: InvokeUiSession> Interface for Session<T> {
|
|||||||
self.msgbox("error", "Remote Error", "No Display", "");
|
self.msgbox("error", "Remote Error", "No Display", "");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if !is_cached_pi {
|
|
||||||
self.try_change_init_resolution(pi.current_display);
|
self.try_change_init_resolution(pi.current_display);
|
||||||
let p = self.lc.read().unwrap().should_auto_login();
|
let p = self.lc.read().unwrap().should_auto_login();
|
||||||
if !p.is_empty() {
|
if !p.is_empty() {
|
||||||
input_os_password(p, true, self.clone());
|
input_os_password(p, true, self.clone());
|
||||||
}
|
}
|
||||||
}
|
|
||||||
let current = &pi.displays[pi.current_display as usize];
|
let current = &pi.displays[pi.current_display as usize];
|
||||||
self.set_display(
|
self.set_display(
|
||||||
current.x,
|
current.x,
|
||||||
@ -1222,23 +1249,6 @@ impl<T: InvokeUiSession> Session<T> {
|
|||||||
pub fn ctrl_alt_del(&self) {
|
pub fn ctrl_alt_del(&self) {
|
||||||
self.send_key_event(&crate::keyboard::client::event_ctrl_alt_del());
|
self.send_key_event(&crate::keyboard::client::event_ctrl_alt_del());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "flutter")]
|
|
||||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
|
||||||
pub fn restore_flutter_cache(&mut self) {
|
|
||||||
if let Some((is_secured, direct)) = self.cache_flutter.read().unwrap().is_secured_direct {
|
|
||||||
self.set_connection_type(is_secured, direct);
|
|
||||||
}
|
|
||||||
let pi = self.cache_flutter.read().unwrap().pi.clone();
|
|
||||||
self.handle_peer_info(pi, true);
|
|
||||||
if let Some(sp) = self.cache_flutter.read().unwrap().sp.as_ref() {
|
|
||||||
self.handle_peer_switch_display(sp);
|
|
||||||
}
|
|
||||||
for (_, cd) in self.cache_flutter.read().unwrap().cursor_data.iter() {
|
|
||||||
self.set_cursor_data(cd.clone());
|
|
||||||
}
|
|
||||||
self.set_cursor_id(self.cache_flutter.read().unwrap().cursor_id.to_string());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::main(flavor = "current_thread")]
|
#[tokio::main(flavor = "current_thread")]
|
||||||
|
Loading…
x
Reference in New Issue
Block a user