Merge branch 'master' into hwcodec
This commit is contained in:
		
						commit
						68204e0c56
					
				| @ -202,7 +202,7 @@ target/release/rustdesk | ||||
| - **[libs/scrap](https://github.com/rustdesk/rustdesk/tree/master/libs/scrap)**: 截屏 | ||||
| - **[libs/enigo](https://github.com/rustdesk/rustdesk/tree/master/libs/enigo)**: 平台相关的鼠标键盘输入 | ||||
| - **[src/ui](https://github.com/rustdesk/rustdesk/tree/master/src/ui)**: GUI | ||||
| - **[src/server](https://github.com/rustdesk/rustdesk/tree/master/src/server)**: 被控端服务,audio/clipboard/input/video 服务, 已经连接实现 | ||||
| - **[src/server](https://github.com/rustdesk/rustdesk/tree/master/src/server)**: 被控端服务,audio/clipboard/input/video 服务, 以及连接的实现 | ||||
| - **[src/client.rs](https://github.com/rustdesk/rustdesk/tree/master/src/client.rs)**: 控制端 | ||||
| - **[src/rendezvous_mediator.rs](https://github.com/rustdesk/rustdesk/tree/master/src/rendezvous_mediator.rs)**: 与[rustdesk-server](https://github.com/rustdesk/rustdesk-server)保持 UDP 通讯, 等待远程连接(通过打洞直连或者中继) | ||||
| - **[src/platform](https://github.com/rustdesk/rustdesk/tree/master/src/platform)**: 平台服务相关代码 | ||||
|  | ||||
| @ -43,7 +43,7 @@ | ||||
| 
 | ||||
|         <activity | ||||
|             android:name=".MainActivity" | ||||
|             android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode" | ||||
|             android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode|navigation" | ||||
|             android:exported="true" | ||||
|             android:hardwareAccelerated="true" | ||||
|             android:launchMode="singleTop" | ||||
|  | ||||
| @ -310,19 +310,19 @@ class ImageModel with ChangeNotifier { | ||||
|   } | ||||
| 
 | ||||
|   double get maxScale { | ||||
|     if (_image == null) return 1.0; | ||||
|     if (_image == null) return 1.5; | ||||
|     final size = MediaQueryData.fromWindow(ui.window).size; | ||||
|     final xscale = size.width / _image!.width; | ||||
|     final yscale = size.height / _image!.height; | ||||
|     return max(1.0, max(xscale, yscale)); | ||||
|     return max(1.5, max(xscale, yscale)); | ||||
|   } | ||||
| 
 | ||||
|   double get minScale { | ||||
|     if (_image == null) return 1.0; | ||||
|     if (_image == null) return 1.5; | ||||
|     final size = MediaQueryData.fromWindow(ui.window).size; | ||||
|     final xscale = size.width / _image!.width; | ||||
|     final yscale = size.height / _image!.height; | ||||
|     return min(xscale, yscale); | ||||
|     return min(xscale, yscale) / 1.5; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| @ -724,13 +724,17 @@ class FFI { | ||||
| 
 | ||||
|   static void inputKey(String name, {bool? down, bool? press}) { | ||||
|     if (!ffiModel.keyboard()) return; | ||||
|     setByName( | ||||
|         'input_key', | ||||
|         json.encode(modify({ | ||||
|           'name': name, | ||||
|           'down': (down ?? false).toString(), | ||||
|           'press': (press ?? true).toString() | ||||
|         }))); | ||||
|     final Map<String, String> out = Map(); | ||||
|     out['name'] = name; | ||||
|     // default: down = false | ||||
|     if (down == true) { | ||||
|       out['down'] = "true"; | ||||
|     } | ||||
|     // default: press = true | ||||
|     if (press != false) { | ||||
|       out['press'] = "true"; | ||||
|     } | ||||
|     setByName('input_key', json.encode(modify(out))); | ||||
|   } | ||||
| 
 | ||||
|   static void moveMouse(double x, double y) { | ||||
|  | ||||
| @ -343,9 +343,14 @@ class _RemotePageState extends State<RemotePage> { | ||||
|                     onKey: (data, e) { | ||||
|                       final key = e.logicalKey; | ||||
|                       if (e is RawKeyDownEvent) { | ||||
|                         if (e.repeat) { | ||||
|                         if (e.repeat && | ||||
|                             !e.isAltPressed && | ||||
|                             !e.isControlPressed && | ||||
|                             !e.isShiftPressed && | ||||
|                             !e.isMetaPressed) { | ||||
|                           sendRawKey(e, press: true); | ||||
|                         } else { | ||||
|                           sendRawKey(e, down: true); | ||||
|                           if (e.isAltPressed && !FFI.alt) { | ||||
|                             FFI.alt = true; | ||||
|                           } else if (e.isControlPressed && !FFI.ctrl) { | ||||
| @ -355,7 +360,6 @@ class _RemotePageState extends State<RemotePage> { | ||||
|                           } else if (e.isMetaPressed && !FFI.command) { | ||||
|                             FFI.command = true; | ||||
|                           } | ||||
|                           sendRawKey(e, down: true); | ||||
|                         } | ||||
|                       } | ||||
|                       // [!_showEdit] workaround for soft-keyboard's control_key like Backspace / Enter | ||||
| @ -481,6 +485,7 @@ class _RemotePageState extends State<RemotePage> { | ||||
|   ///   DoubleFiner -> right click | ||||
|   ///   HoldDrag -> left drag | ||||
| 
 | ||||
|   Offset _cacheLongPressPosition = Offset(0, 0); | ||||
|   Widget getBodyForMobileWithGesture() { | ||||
|     final touchMode = FFI.ffiModel.touchMode; | ||||
|     return getMixinGestureDetector( | ||||
| @ -504,10 +509,14 @@ class _RemotePageState extends State<RemotePage> { | ||||
|         }, | ||||
|         onLongPressDown: (d) { | ||||
|           if (touchMode) { | ||||
|             FFI.cursorModel.move(d.localPosition.dx, d.localPosition.dy); | ||||
|             _cacheLongPressPosition = d.localPosition; | ||||
|           } | ||||
|         }, | ||||
|         onLongPress: () { | ||||
|           if (touchMode) { | ||||
|             FFI.cursorModel | ||||
|                 .move(_cacheLongPressPosition.dx, _cacheLongPressPosition.dy); | ||||
|           } | ||||
|           FFI.tap(MouseButtons.right); | ||||
|         }, | ||||
|         onDoubleFinerTap: (d) { | ||||
| @ -534,6 +543,15 @@ class _RemotePageState extends State<RemotePage> { | ||||
|           if (touchMode) { | ||||
|             FFI.cursorModel.move(d.localPosition.dx, d.localPosition.dy); | ||||
|             FFI.sendMouse('down', MouseButtons.left); | ||||
|           } else { | ||||
|             final cursorX = FFI.cursorModel.x; | ||||
|             final cursorY = FFI.cursorModel.y; | ||||
|             final visible = | ||||
|                 FFI.cursorModel.getVisibleRect().inflate(1); // extend edges | ||||
|             final size = MediaQueryData.fromWindow(ui.window).size; | ||||
|             if (!visible.contains(Offset(cursorX, cursorY))) { | ||||
|               FFI.cursorModel.move(size.width / 2, size.height / 2); | ||||
|             } | ||||
|           } | ||||
|         }, | ||||
|         onOneFingerPanUpdate: (d) { | ||||
|  | ||||
| @ -2,7 +2,7 @@ import 'dart:async'; | ||||
| import 'package:flutter/gestures.dart'; | ||||
| import 'package:flutter/widgets.dart'; | ||||
| 
 | ||||
| enum CustomTouchGestureState { | ||||
| enum GestureState { | ||||
|   none, | ||||
|   oneFingerPan, | ||||
|   twoFingerScale, | ||||
| @ -35,64 +35,41 @@ class CustomTouchGestureRecognizer extends ScaleGestureRecognizer { | ||||
|   GestureDragUpdateCallback? onThreeFingerVerticalDragUpdate; | ||||
|   GestureDragEndCallback? onThreeFingerVerticalDragEnd; | ||||
| 
 | ||||
|   var _currentState = CustomTouchGestureState.none; | ||||
|   Timer? _startEventDebounceTimer; | ||||
|   var _currentState = GestureState.none; | ||||
|   Timer? _debounceTimer; | ||||
| 
 | ||||
|   void _init() { | ||||
|     debugPrint("CustomTouchGestureRecognizer init"); | ||||
|     onStart = (d) { | ||||
|       _startEventDebounceTimer?.cancel(); | ||||
|       if (d.pointerCount == 1) { | ||||
|         _currentState = CustomTouchGestureState.oneFingerPan; | ||||
|         if (onOneFingerPanStart != null) { | ||||
|           onOneFingerPanStart!(DragStartDetails( | ||||
|               localPosition: d.localFocalPoint, globalPosition: d.focalPoint)); | ||||
|         } | ||||
|         debugPrint("start oneFingerPan"); | ||||
|       } else if (d.pointerCount == 2) { | ||||
|         if (_currentState == CustomTouchGestureState.threeFingerVerticalDrag) { | ||||
|           // 3 -> 2 debounce | ||||
|           _startEventDebounceTimer = Timer(Duration(milliseconds: 200), () { | ||||
|             _currentState = CustomTouchGestureState.twoFingerScale; | ||||
|             if (onTwoFingerScaleStart != null) { | ||||
|               onTwoFingerScaleStart!(ScaleStartDetails( | ||||
|                   localFocalPoint: d.localFocalPoint, | ||||
|                   focalPoint: d.focalPoint)); | ||||
|             } | ||||
|             debugPrint("debounce start twoFingerScale success"); | ||||
|           }); | ||||
|         } | ||||
|         _currentState = CustomTouchGestureState.twoFingerScale; | ||||
|         // startWatchTimer(); | ||||
|         if (onTwoFingerScaleStart != null) { | ||||
|           onTwoFingerScaleStart!(ScaleStartDetails( | ||||
|               localFocalPoint: d.localFocalPoint, focalPoint: d.focalPoint)); | ||||
|         } | ||||
|         debugPrint("start twoFingerScale"); | ||||
|       } else if (d.pointerCount == 3) { | ||||
|         _currentState = CustomTouchGestureState.threeFingerVerticalDrag; | ||||
|     // onStart = (d) {}; | ||||
|     onUpdate = (d) { | ||||
|       _debounceTimer?.cancel(); | ||||
|       if (d.pointerCount == 1 && _currentState != GestureState.oneFingerPan) { | ||||
|         onOneFingerStartDebounce(d); | ||||
|       } else if (d.pointerCount == 2 && | ||||
|           _currentState != GestureState.twoFingerScale) { | ||||
|         onTwoFingerStartDebounce(d); | ||||
|       } else if (d.pointerCount == 3 && | ||||
|           _currentState != GestureState.threeFingerVerticalDrag) { | ||||
|         _currentState = GestureState.threeFingerVerticalDrag; | ||||
|         if (onThreeFingerVerticalDragStart != null) { | ||||
|           onThreeFingerVerticalDragStart!( | ||||
|               DragStartDetails(globalPosition: d.localFocalPoint)); | ||||
|         } | ||||
|         debugPrint("start threeFingerScale"); | ||||
|         // _reset(); | ||||
|       } | ||||
|     }; | ||||
|     onUpdate = (d) { | ||||
|       if (_currentState != CustomTouchGestureState.none) { | ||||
|       if (_currentState != GestureState.none) { | ||||
|         switch (_currentState) { | ||||
|           case CustomTouchGestureState.oneFingerPan: | ||||
|           case GestureState.oneFingerPan: | ||||
|             if (onOneFingerPanUpdate != null) { | ||||
|               onOneFingerPanUpdate!(_getDragUpdateDetails(d)); | ||||
|             } | ||||
|             break; | ||||
|           case CustomTouchGestureState.twoFingerScale: | ||||
|           case GestureState.twoFingerScale: | ||||
|             if (onTwoFingerScaleUpdate != null) { | ||||
|               onTwoFingerScaleUpdate!(d); | ||||
|             } | ||||
|             break; | ||||
|           case CustomTouchGestureState.threeFingerVerticalDrag: | ||||
|           case GestureState.threeFingerVerticalDrag: | ||||
|             if (onThreeFingerVerticalDragUpdate != null) { | ||||
|               onThreeFingerVerticalDragUpdate!(_getDragUpdateDetails(d)); | ||||
|             } | ||||
| @ -105,21 +82,22 @@ class CustomTouchGestureRecognizer extends ScaleGestureRecognizer { | ||||
|     }; | ||||
|     onEnd = (d) { | ||||
|       debugPrint("ScaleGestureRecognizer onEnd"); | ||||
|       _debounceTimer?.cancel(); | ||||
|       // end | ||||
|       switch (_currentState) { | ||||
|         case CustomTouchGestureState.oneFingerPan: | ||||
|         case GestureState.oneFingerPan: | ||||
|           debugPrint("TwoFingerState.pan onEnd"); | ||||
|           if (onOneFingerPanEnd != null) { | ||||
|             onOneFingerPanEnd!(_getDragEndDetails(d)); | ||||
|           } | ||||
|           break; | ||||
|         case CustomTouchGestureState.twoFingerScale: | ||||
|         case GestureState.twoFingerScale: | ||||
|           debugPrint("TwoFingerState.scale onEnd"); | ||||
|           if (onTwoFingerScaleEnd != null) { | ||||
|             onTwoFingerScaleEnd!(d); | ||||
|           } | ||||
|           break; | ||||
|         case CustomTouchGestureState.threeFingerVerticalDrag: | ||||
|         case GestureState.threeFingerVerticalDrag: | ||||
|           debugPrint("ThreeFingerState.vertical onEnd"); | ||||
|           if (onThreeFingerVerticalDragEnd != null) { | ||||
|             onThreeFingerVerticalDragEnd!(_getDragEndDetails(d)); | ||||
| @ -128,10 +106,50 @@ class CustomTouchGestureRecognizer extends ScaleGestureRecognizer { | ||||
|         default: | ||||
|           break; | ||||
|       } | ||||
|       _currentState = CustomTouchGestureState.none; | ||||
|       _debounceTimer = Timer(Duration(milliseconds: 200), () { | ||||
|         _currentState = GestureState.none; | ||||
|       }); | ||||
|     }; | ||||
|   } | ||||
| 
 | ||||
|   void onOneFingerStartDebounce(ScaleUpdateDetails d) { | ||||
|     final start = (ScaleUpdateDetails d) { | ||||
|       _currentState = GestureState.oneFingerPan; | ||||
|       if (onOneFingerPanStart != null) { | ||||
|         onOneFingerPanStart!(DragStartDetails( | ||||
|             localPosition: d.localFocalPoint, globalPosition: d.focalPoint)); | ||||
|       } | ||||
|     }; | ||||
|     if (_currentState != GestureState.none) { | ||||
|       _debounceTimer = Timer(Duration(milliseconds: 200), () { | ||||
|         start(d); | ||||
|         debugPrint("debounce start oneFingerPan"); | ||||
|       }); | ||||
|     } else { | ||||
|       start(d); | ||||
|       debugPrint("start oneFingerPan"); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   void onTwoFingerStartDebounce(ScaleUpdateDetails d) { | ||||
|     final start = (ScaleUpdateDetails d) { | ||||
|       _currentState = GestureState.twoFingerScale; | ||||
|       if (onTwoFingerScaleStart != null) { | ||||
|         onTwoFingerScaleStart!(ScaleStartDetails( | ||||
|             localFocalPoint: d.localFocalPoint, focalPoint: d.focalPoint)); | ||||
|       } | ||||
|     }; | ||||
|     if (_currentState == GestureState.threeFingerVerticalDrag) { | ||||
|       _debounceTimer = Timer(Duration(milliseconds: 200), () { | ||||
|         start(d); | ||||
|         debugPrint("debounce start twoFingerScale"); | ||||
|       }); | ||||
|     } else { | ||||
|       start(d); | ||||
|       debugPrint("start twoFingerScale"); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   DragUpdateDetails _getDragUpdateDetails(ScaleUpdateDetails d) => | ||||
|       DragUpdateDetails( | ||||
|           globalPosition: d.focalPoint, | ||||
|  | ||||
| @ -1,31 +1,177 @@ | ||||
| from pynput.keyboard import Key, Controller | ||||
| from pynput.keyboard._xorg import KeyCode | ||||
| from pynput._util.xorg import display_manager | ||||
| import Xlib | ||||
| from pynput._util.xorg import * | ||||
| import Xlib | ||||
| import os | ||||
| import sys | ||||
| import socket | ||||
| from Xlib.ext.xtest import fake_input | ||||
| from Xlib import X | ||||
| import Xlib | ||||
| 
 | ||||
| KeyCode._from_symbol("\0")  # test | ||||
| 
 | ||||
| DEAD_KEYS = { | ||||
|     '`': 65104, | ||||
|     '´': 65105, | ||||
|     '^': 65106, | ||||
|     '~': 65107, | ||||
|     '¯': 65108, | ||||
|     '˘': 65109, | ||||
|     '˙': 65110, | ||||
|     '¨': 65111, | ||||
|     '˚': 65112, | ||||
|     '˝': 65113, | ||||
|     'ˇ': 65114, | ||||
|     '¸': 65115, | ||||
|     '˛': 65116, | ||||
|     '℩': 65117,  # ? | ||||
|     '゛': 65118,  # ? | ||||
|     '゚ ': 65119, | ||||
|     'ٜ': 65120, | ||||
|     '↪': 65121, | ||||
|     ' ̛': 65122, | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| def my_keyboard_mapping(display): | ||||
|     """Generates a mapping from *keysyms* to *key codes* and required | ||||
|     modifier shift states. | ||||
| 
 | ||||
|     :param Xlib.display.Display display: The display for which to retrieve the | ||||
|         keyboard mapping. | ||||
| 
 | ||||
|     :return: the keyboard mapping | ||||
|     """ | ||||
|     mapping = {} | ||||
| 
 | ||||
|     shift_mask = 1 << 0 | ||||
|     group_mask = alt_gr_mask(display) | ||||
| 
 | ||||
|     # Iterate over all keysym lists in the keyboard mapping | ||||
|     min_keycode = display.display.info.min_keycode | ||||
|     keycode_count = display.display.info.max_keycode - min_keycode + 1 | ||||
|     for index, keysyms in enumerate(display.get_keyboard_mapping( | ||||
|             min_keycode, keycode_count)): | ||||
|         key_code = index + min_keycode | ||||
| 
 | ||||
|         # Normalise the keysym list to yield a tuple containing the two groups | ||||
|         normalized = keysym_normalize(keysyms) | ||||
|         if not normalized: | ||||
|             continue | ||||
| 
 | ||||
|         # Iterate over the groups to extract the shift and modifier state | ||||
|         for groups, group in zip(normalized, (False, True)): | ||||
|             for keysym, shift in zip(groups, (False, True)): | ||||
| 
 | ||||
|                 if not keysym: | ||||
|                     continue | ||||
|                 shift_state = 0 \ | ||||
|                     | (shift_mask if shift else 0) \ | ||||
|                     | (group_mask if group else 0) | ||||
| 
 | ||||
|                 # !!!: Save all keycode combinations of keysym | ||||
|                 if keysym in mapping: | ||||
|                     mapping[keysym].append((key_code, shift_state)) | ||||
|                 else: | ||||
|                     mapping[keysym] = [(key_code, shift_state)] | ||||
|     return mapping | ||||
| 
 | ||||
| 
 | ||||
| class MyController(Controller): | ||||
|     def _handle(self, key, is_press): | ||||
|         """Resolves a key identifier and sends a keyboard event. | ||||
|         :param event: The *X* keyboard event. | ||||
|         :param int keysym: The keysym to handle. | ||||
|     def _update_keyboard_mapping(self): | ||||
|         """Updates the keyboard mapping. | ||||
|         """ | ||||
|         keysym = self._keysym(key) | ||||
|         keycode = self._display.keysym_to_keycode(keysym) | ||||
|         with display_manager(self._display) as dm: | ||||
|             self._keyboard_mapping = my_keyboard_mapping(dm) | ||||
| 
 | ||||
|     def send_event(self, event, keycode, shift_state): | ||||
|         with display_manager(self._display) as dm, self.modifiers as modifiers: | ||||
|             # Under certain cimcumstances, such as when running under Xephyr, | ||||
|             # the value returned by dm.get_input_focus is an int | ||||
|             window = dm.get_input_focus().focus | ||||
|             send_event = getattr( | ||||
|                 window, | ||||
|                 'send_event', | ||||
|                 lambda event: dm.send_event(window, event)) | ||||
|             send_event(event( | ||||
|                 detail=keycode, | ||||
|                 state=shift_state | self._shift_mask(modifiers), | ||||
|                 time=0, | ||||
|                 root=dm.screen().root, | ||||
|                 window=window, | ||||
|                 same_screen=0, | ||||
|                 child=Xlib.X.NONE, | ||||
|                 root_x=0, root_y=0, event_x=0, event_y=0)) | ||||
| 
 | ||||
|     def fake_input(self, keycode, is_press): | ||||
|         with display_manager(self._display) as dm: | ||||
|             Xlib.ext.xtest.fake_input( | ||||
|                 dm, | ||||
|                 Xlib.X.KeyPress if is_press else Xlib.X.KeyRelease, | ||||
|                 keycode) | ||||
| 
 | ||||
|     def _handle(self, key, is_press): | ||||
|         """Resolves a key identifier and sends a keyboard event. | ||||
|         :param event: The *X* keyboard event. | ||||
|         :param int keysym: The keysym to handle. | ||||
|         """ | ||||
|         event = Xlib.display.event.KeyPress if is_press \ | ||||
|             else Xlib.display.event.KeyRelease | ||||
|         keysym = self._keysym(key) | ||||
| 
 | ||||
|         if key.vk is not None: | ||||
|             keycode = self._display.keysym_to_keycode(key.vk) | ||||
|             self.fake_input(keycode, is_press) | ||||
|             # Otherwise use XSendEvent; we need to use this in the general case to | ||||
|             # work around problems with keyboard layouts | ||||
|             self._emit('_on_fake_event', key, is_press) | ||||
|             return | ||||
| 
 | ||||
|         # Make sure to verify that the key was resolved | ||||
|         if keysym is None: | ||||
|             raise self.InvalidKeyException(key) | ||||
| 
 | ||||
|         # There may be multiple keycodes for keysym in keyboard_mapping | ||||
|         keycode_flag = len(self.keyboard_mapping[keysym]) == 1 | ||||
|         if keycode_flag: | ||||
|             keycode, shift_state = self.keyboard_mapping[keysym][0] | ||||
|         else: | ||||
|             keycode, shift_state = self._display.keysym_to_keycode(keysym), 0 | ||||
| 
 | ||||
|         keycode_set = set(map(lambda x: x[0], self.keyboard_mapping[keysym])) | ||||
|         # The keycode of the dead key is inconsistent, The keysym has multiple combinations of a keycode. | ||||
|         if keycode != self._display.keysym_to_keycode(keysym) \ | ||||
|             or (keycode_flag == False and keycode == list(keycode_set)[0] and len(keycode_set) == 1): | ||||
|             deakkey_chr = str(key).replace("'", '') | ||||
|             keysym = DEAD_KEYS[deakkey_chr] | ||||
|             keycode, shift_state = self.keyboard_mapping[keysym][0] | ||||
| 
 | ||||
|         # If the key has a virtual key code, use that immediately with | ||||
|         # fake_input; fake input,being an X server extension, has access to | ||||
|         # more internal state that we do | ||||
| 
 | ||||
|         try: | ||||
|             with self.modifiers as modifiers: | ||||
|                 alt_gr = Key.alt_gr in modifiers | ||||
|             # !!!: Send_event can't support lock screen, this condition cann't be modified | ||||
|             if alt_gr: | ||||
|                 self.send_event( | ||||
|                     event, keycode, shift_state) | ||||
|             else: | ||||
|                 self.fake_input(keycode, is_press) | ||||
|         except KeyError: | ||||
|             with self._borrow_lock: | ||||
|                 keycode, index, count = self._borrows[keysym] | ||||
|                 self._send_key( | ||||
|                     event, | ||||
|                     keycode, | ||||
|                     index_to_shift(self._display, index)) | ||||
|                 count += 1 if is_press else -1 | ||||
|                 self._borrows[keysym] = (keycode, index, count) | ||||
| 
 | ||||
|         # Notify any running listeners | ||||
|         self._emit('_on_fake_event', key, is_press) | ||||
| 
 | ||||
| 
 | ||||
| keyboard = MyController() | ||||
| @ -82,7 +228,7 @@ def loop(): | ||||
|                 else: | ||||
|                     keyboard.release(name) | ||||
|             except Exception as e: | ||||
|                 print(e) | ||||
|                 print('[x] error key',e) | ||||
| 
 | ||||
| 
 | ||||
| loop() | ||||
|  | ||||
| @ -47,7 +47,7 @@ case "$1" in | ||||
|   ;; | ||||
|   2) | ||||
|     # for upgrade | ||||
|     service rustdesk stop || true | ||||
|     systemctl stop rustdesk || true | ||||
|   ;; | ||||
| esac | ||||
| 
 | ||||
| @ -61,10 +61,26 @@ systemctl start rustdesk | ||||
| update-desktop-database | ||||
| 
 | ||||
| %preun | ||||
| systemctl stop rustdesk || true | ||||
| systemctl disable rustdesk || true | ||||
| rm /etc/systemd/system/rustdesk.service || true | ||||
| case "$1" in | ||||
|   0) | ||||
|     # for uninstall | ||||
|     systemctl stop rustdesk || true | ||||
|     systemctl disable rustdesk || true | ||||
|     rm /etc/systemd/system/rustdesk.service || true | ||||
|   ;; | ||||
|   1) | ||||
|     # for upgrade | ||||
|   ;; | ||||
| esac | ||||
| 
 | ||||
| %postun | ||||
| rm /usr/share/applications/rustdesk.desktop || true | ||||
| update-desktop-database | ||||
| case "$1" in | ||||
|   0) | ||||
|     # for uninstall | ||||
|     rm /usr/share/applications/rustdesk.desktop || true | ||||
|     update-desktop-database | ||||
|   ;; | ||||
|   1) | ||||
|     # for upgrade | ||||
|   ;; | ||||
| esac | ||||
|  | ||||
							
								
								
									
										28
									
								
								rpm.spec
									
									
									
									
									
								
							
							
						
						
									
										28
									
								
								rpm.spec
									
									
									
									
									
								
							| @ -48,7 +48,7 @@ case "$1" in | ||||
|   ;; | ||||
|   2) | ||||
|     # for upgrade | ||||
|     service rustdesk stop || true | ||||
|     systemctl stop rustdesk || true | ||||
|   ;; | ||||
| esac | ||||
| 
 | ||||
| @ -62,10 +62,26 @@ systemctl start rustdesk | ||||
| update-desktop-database | ||||
| 
 | ||||
| %preun | ||||
| systemctl stop rustdesk || true | ||||
| systemctl disable rustdesk || true | ||||
| rm /etc/systemd/system/rustdesk.service || true | ||||
| case "$1" in | ||||
|   0) | ||||
|     # for uninstall | ||||
|     systemctl stop rustdesk || true | ||||
|     systemctl disable rustdesk || true | ||||
|     rm /etc/systemd/system/rustdesk.service || true | ||||
|   ;; | ||||
|   1) | ||||
|     # for upgrade | ||||
|   ;; | ||||
| esac | ||||
| 
 | ||||
| %postun | ||||
| rm /usr/share/applications/rustdesk.desktop || true | ||||
| update-desktop-database | ||||
| case "$1" in | ||||
|   0) | ||||
|     # for uninstall | ||||
|     rm /usr/share/applications/rustdesk.desktop || true | ||||
|     update-desktop-database | ||||
|   ;; | ||||
|   1) | ||||
|     # for upgrade | ||||
|   ;; | ||||
| esac | ||||
|  | ||||
							
								
								
									
										26
									
								
								src/lang.rs
									
									
									
									
									
								
							
							
						
						
									
										26
									
								
								src/lang.rs
									
									
									
									
									
								
							| @ -1,21 +1,43 @@ | ||||
| use serde_json::{json, value::Value}; | ||||
| use std::ops::Deref; | ||||
| 
 | ||||
| mod cn; | ||||
| mod cs; | ||||
| mod da; | ||||
| mod sk; | ||||
| mod de; | ||||
| mod en; | ||||
| mod es; | ||||
| mod eo; | ||||
| mod es; | ||||
| mod fr; | ||||
| mod id; | ||||
| mod it; | ||||
| mod ptbr; | ||||
| mod ru; | ||||
| mod sk; | ||||
| mod tr; | ||||
| mod tw; | ||||
| 
 | ||||
| lazy_static::lazy_static! { | ||||
|     pub static ref LANGS: Value = | ||||
|         json!(vec![ | ||||
|             ("en", "English"), | ||||
|             ("it", "Italiano"), | ||||
|             ("fr", "Français"), | ||||
|             ("de", "Deutsch"), | ||||
|             ("cn", "简体中文"), | ||||
|             ("tw", "繁體中文"), | ||||
|             ("pt", "Português"), | ||||
|             ("es", "Español"), | ||||
|             ("ru", "Русский"), | ||||
|             ("sk", "Slovenčina"), | ||||
|             ("id", "Indonesia"), | ||||
|             ("cs", "Čeština"), | ||||
|             ("da", "Dansk"), | ||||
|             ("eo", "Esperanto"), | ||||
|             ("tr", "Türkçe"), | ||||
|         ]); | ||||
| } | ||||
| 
 | ||||
| #[cfg(not(any(target_os = "android", target_os = "ios")))] | ||||
| pub fn translate(name: String) -> String { | ||||
|     let locale = sys_locale::get_locale().unwrap_or_default().to_lowercase(); | ||||
|  | ||||
| @ -283,5 +283,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = | ||||
|         ("Turned off", "退出"), | ||||
|         ("In privacy mode", "进入隐私模式"), | ||||
|         ("Out privacy mode", "退出隐私模式"), | ||||
|         ("Language", "语言"), | ||||
|     ].iter().cloned().collect(); | ||||
| } | ||||
|  | ||||
| @ -283,5 +283,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = | ||||
|         ("Turned off", "Vypnutý"), | ||||
|         ("In privacy mode", "v režimu soukromí"), | ||||
|         ("Out privacy mode", "mimo režim soukromí"), | ||||
|         ("Language", ""), | ||||
|     ].iter().cloned().collect(); | ||||
| } | ||||
|  | ||||
| @ -283,5 +283,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = | ||||
|         ("Turned off", "Slukket"), | ||||
|         ("In privacy mode", "I databeskyttelsestilstand"), | ||||
|         ("Out privacy mode", "Databeskyttelsestilstand fra"), | ||||
|         ("Language", ""), | ||||
|     ].iter().cloned().collect(); | ||||
| } | ||||
|  | ||||
| @ -283,5 +283,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = | ||||
|         ("Turned off", "Ausgeschaltet"), | ||||
|         ("In privacy mode", "im Datenschutzmodus"), | ||||
|         ("Out privacy mode", "Datenschutzmodus aus"), | ||||
|         ("Language", "Sprache"), | ||||
|     ].iter().cloned().collect(); | ||||
| } | ||||
|  | ||||
| @ -283,5 +283,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = | ||||
|         ("Turned off", ""), | ||||
|         ("In privacy mode", ""), | ||||
|         ("Out privacy mode", ""), | ||||
|         ("Language", ""), | ||||
|     ].iter().cloned().collect(); | ||||
| } | ||||
|  | ||||
| @ -283,5 +283,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = | ||||
|         ("Turned off", "Apagado"), | ||||
|         ("In privacy mode", "En modo de privacidad"), | ||||
|         ("Out privacy mode", "Fuera del modo de privacidad"), | ||||
|         ("Language", ""), | ||||
|     ].iter().cloned().collect(); | ||||
| } | ||||
|  | ||||
| @ -283,5 +283,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = | ||||
|         ("Turned off", "Éteindre"), | ||||
|         ("In privacy mode", "en mode privé"), | ||||
|         ("Out privacy mode", "hors mode de confidentialité"), | ||||
|         ("Language", "Langue"), | ||||
|     ].iter().cloned().collect(); | ||||
| } | ||||
|  | ||||
| @ -283,5 +283,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = | ||||
|         ("Turned off", "Matikan"), | ||||
|         ("In privacy mode", "Dalam mode privasi"), | ||||
|         ("Out privacy mode", "Keluar dari mode privasi"), | ||||
|         ("Language", ""), | ||||
|     ].iter().cloned().collect(); | ||||
| } | ||||
|  | ||||
| @ -283,5 +283,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = | ||||
|         ("Turned off", "Spegni"), | ||||
|         ("In privacy mode", "In modalità privacy"), | ||||
|         ("Out privacy mode", "Fuori modalità privacy"), | ||||
|         ("Language", "Linguaggio"), | ||||
|     ].iter().cloned().collect(); | ||||
| } | ||||
|  | ||||
| @ -283,5 +283,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = | ||||
|         ("Turned off", "Desligado"), | ||||
|         ("In privacy mode", "No modo de privacidade"), | ||||
|         ("Out privacy mode", "Fora do modo de privacidade"), | ||||
|         ("Language", ""), | ||||
|     ].iter().cloned().collect(); | ||||
| } | ||||
|  | ||||
| @ -283,5 +283,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = | ||||
|         ("Turned off", "Выключен"), | ||||
|         ("In privacy mode", "В режиме конфиденциальности"), | ||||
|         ("Out privacy mode", "Выход из режима конфиденциальности"), | ||||
|         ("Language", ""), | ||||
|     ].iter().cloned().collect(); | ||||
| } | ||||
|  | ||||
| @ -283,5 +283,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = | ||||
|         ("Turned off", "Vypnutý"), | ||||
|         ("In privacy mode", "V režime súkromia"), | ||||
|         ("Out privacy mode", "Mimo režimu súkromia"), | ||||
|         ("Language", ""), | ||||
|     ].iter().cloned().collect(); | ||||
| } | ||||
|  | ||||
| @ -283,5 +283,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = | ||||
|         ("Turned off", ""), | ||||
|         ("In privacy mode", ""), | ||||
|         ("Out privacy mode", ""), | ||||
|         ("Language", ""), | ||||
|     ].iter().cloned().collect(); | ||||
| } | ||||
|  | ||||
| @ -283,5 +283,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = | ||||
|         ("Turned off", "Kapalı"), | ||||
|         ("In privacy mode", "Gizlilik modunda"), | ||||
|         ("Out privacy mode", "Gizlilik modu dışında"), | ||||
|         ("Language", ""), | ||||
|     ].iter().cloned().collect(); | ||||
| } | ||||
|  | ||||
| @ -283,5 +283,6 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = | ||||
|         ("Turned off", "退出"), | ||||
|         ("In privacy mode", "開啟隱私模式"), | ||||
|         ("Out privacy mode", "退出隱私模式"), | ||||
|         ("Language", "語言"), | ||||
|     ].iter().cloned().collect(); | ||||
| } | ||||
|  | ||||
| @ -1020,6 +1020,22 @@ copy /Y \"{tmp_path}\\Uninstall {app_name}.lnk\" \"{start_menu}\\\" | ||||
|     // https://docs.microsoft.com/zh-cn/windows/win32/msi/uninstall-registry-key?redirectedfrom=MSDNa
 | ||||
|     // https://www.windowscentral.com/how-edit-registry-using-command-prompt-windows-10
 | ||||
|     // https://www.tenforums.com/tutorials/70903-add-remove-allowed-apps-through-windows-firewall-windows-10-a.html
 | ||||
|     // Note: without if exist, the bat may exit in advance on some Windows7 https://github.com/rustdesk/rustdesk/issues/895
 | ||||
|     let dels = format!( | ||||
|         " | ||||
| if exist \"{mk_shortcut}\" del /f /q \"{mk_shortcut}\" | ||||
| if exist \"{uninstall_shortcut}\" del /f /q \"{uninstall_shortcut}\" | ||||
| if exist \"{tray_shortcut}\" del /f /q \"{tray_shortcut}\" | ||||
| if exist \"{tmp_path}\\{app_name}.lnk\" del /f /q \"{tmp_path}\\{app_name}.lnk\" | ||||
| if exist \"{tmp_path}\\Uninstall {app_name}.lnk\" del /f /q \"{tmp_path}\\Uninstall {app_name}.lnk\" | ||||
| if exist \"{tmp_path}\\{app_name} Tray.lnk\" del /f /q \"{tmp_path}\\{app_name} Tray.lnk\" | ||||
|         ",
 | ||||
|         mk_shortcut = mk_shortcut, | ||||
|         uninstall_shortcut = uninstall_shortcut, | ||||
|         tray_shortcut = tray_shortcut, | ||||
|         tmp_path = tmp_path, | ||||
|         app_name = crate::get_app_name(), | ||||
|     ); | ||||
|     let cmds = format!( | ||||
|         " | ||||
| {uninstall_str} | ||||
| @ -1048,17 +1064,13 @@ cscript \"{tray_shortcut}\" | ||||
| copy /Y \"{tmp_path}\\{app_name} Tray.lnk\" \"%PROGRAMDATA%\\Microsoft\\Windows\\Start Menu\\Programs\\Startup\\\" | ||||
| {shortcuts} | ||||
| copy /Y \"{tmp_path}\\Uninstall {app_name}.lnk\" \"{path}\\\" | ||||
| del /f \"{mk_shortcut}\" | ||||
| del /f \"{uninstall_shortcut}\" | ||||
| del /f \"{tray_shortcut}\" | ||||
| del /f \"{tmp_path}\\{app_name}.lnk\" | ||||
| del /f \"{tmp_path}\\Uninstall {app_name}.lnk\" | ||||
| del /f \"{tmp_path}\\{app_name} Tray.lnk\" | ||||
| {dels} | ||||
| sc create {app_name} binpath= \"\\\"{exe}\\\" --import-config \\\"{config_path}\\\"\" start= auto DisplayName= \"{app_name} Service\" | ||||
| sc start {app_name} | ||||
| sc stop {app_name} | ||||
| sc delete {app_name} | ||||
| {after_install} | ||||
| {sleep} | ||||
|     ",
 | ||||
|         uninstall_str=uninstall_str, | ||||
|         path=path, | ||||
| @ -1081,6 +1093,16 @@ sc delete {app_name} | ||||
|         config_path=Config::file().to_str().unwrap_or(""), | ||||
|         lic=register_licence(), | ||||
|         after_install=get_after_install(&exe), | ||||
|         sleep=if debug { | ||||
|             "timeout 300" | ||||
|         } else { | ||||
|             "" | ||||
|         }, | ||||
|         dels=if debug { | ||||
|             "" | ||||
|         } else { | ||||
|             &dels | ||||
|         }, | ||||
|     ); | ||||
|     run_cmds(cmds, debug, "install")?; | ||||
|     std::thread::sleep(std::time::Duration::from_millis(2000)); | ||||
| @ -1126,10 +1148,10 @@ fn get_uninstall() -> String { | ||||
|         " | ||||
|     {before_uninstall} | ||||
|     reg delete {subkey} /f | ||||
|     rd /s /q \"{path}\" | ||||
|     rd /s /q \"{start_menu}\" | ||||
|     del /f /q \"%PUBLIC%\\Desktop\\{app_name}*\" | ||||
|     del /f /q \"%PROGRAMDATA%\\Microsoft\\Windows\\Start Menu\\Programs\\Startup\\{app_name} Tray.lnk\" | ||||
|     if exist \"{path}\" rd /s /q \"{path}\" | ||||
|     if exist \"{start_menu}\" rd /s /q \"{start_menu}\" | ||||
|     if exist \"%PUBLIC%\\Desktop\\{app_name}.lnk\" del /f /q \"%PUBLIC%\\Desktop\\{app_name}.lnk\" | ||||
|     if exist \"%PROGRAMDATA%\\Microsoft\\Windows\\Start Menu\\Programs\\Startup\\{app_name} Tray.lnk\" del /f /q \"%PROGRAMDATA%\\Microsoft\\Windows\\Start Menu\\Programs\\Startup\\{app_name} Tray.lnk\" | ||||
|     ",
 | ||||
|         before_uninstall=get_before_uninstall(), | ||||
|         subkey=subkey, | ||||
| @ -1176,11 +1198,8 @@ fn run_cmds(cmds: String, show: bool, tip: &str) -> ResultType<()> { | ||||
|         .show(show) | ||||
|         .force_prompt(true) | ||||
|         .status(); | ||||
|     // leave the file for debug if execution failed
 | ||||
|     if let Ok(res) = res { | ||||
|         if res.success() { | ||||
|             allow_err!(std::fs::remove_file(tmp)); | ||||
|         } | ||||
|     if !show { | ||||
|         allow_err!(std::fs::remove_file(tmp)); | ||||
|     } | ||||
|     let _ = res?; | ||||
|     Ok(()) | ||||
|  | ||||
| @ -3,9 +3,9 @@ mod cm; | ||||
| mod inline; | ||||
| #[cfg(target_os = "macos")] | ||||
| mod macos; | ||||
| pub mod remote; | ||||
| #[cfg(target_os = "windows")] | ||||
| pub mod win_privacy; | ||||
| pub mod remote; | ||||
| use crate::common::SOFTWARE_UPDATE_URL; | ||||
| use crate::ipc; | ||||
| use hbb_common::{ | ||||
| @ -760,6 +760,10 @@ impl UI { | ||||
|         #[cfg(feature = "hwcodec")] | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
|     fn get_langs(&self) -> String { | ||||
|         crate::lang::LANGS.to_string() | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl sciter::EventHandler for UI { | ||||
| @ -837,6 +841,7 @@ impl sciter::EventHandler for UI { | ||||
|         fn get_lan_peers(); | ||||
|         fn get_uuid(); | ||||
|         fn has_hwcodec(); | ||||
|         fn get_langs(); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -163,6 +163,40 @@ class AudioInputs: Reactor.Component { | ||||
|         } | ||||
|         this.toggleMenuState(); | ||||
|     } | ||||
| }; | ||||
| 
 | ||||
| class Languages: Reactor.Component { | ||||
|     function render() { | ||||
|         var langs = JSON.parse(handler.get_langs()); | ||||
|         var me = this; | ||||
|         self.timer(1ms, function() { me.toggleMenuState() }); | ||||
|         return <li>{translate('Language')} | ||||
|             <menu #languages key={langs.length}> | ||||
|                 <li id="default"><span>{svg_checkmark}</span>Default</li> | ||||
|                 <div .separator /> | ||||
|                 {langs.map(function(lang) { | ||||
|                 return <li id={lang[0]}><span>{svg_checkmark}</span>{lang[1]}</li>; | ||||
|                 })} | ||||
|             </menu> | ||||
|         </li>; | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     function toggleMenuState() { | ||||
|         var cur = handler.get_local_option("lang") || "default"; | ||||
|         for (var el in this.$$(menu#languages>li)) { | ||||
|             var selected = cur == el.id; | ||||
|             el.attributes.toggleClass("selected", selected); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     event click $(menu#languages>li) (_, me) { | ||||
|         var v = me.id; | ||||
|         if (v == "default") v = ""; | ||||
|         handler.set_local_option("lang", v); | ||||
|         app.update(); | ||||
|         this.toggleMenuState(); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| var enhancementsMenu; | ||||
| @ -202,7 +236,6 @@ class Enhancements: Reactor.Component { | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| function getUserName() { | ||||
|     try { | ||||
|         return JSON.parse(handler.get_local_option("user_info")).name; | ||||
| @ -261,7 +294,7 @@ class MyIdMenu: Reactor.Component { | ||||
|                 {handler.is_ok_change_id() && key_confirmed ? <li #change-id>{translate('Change ID')}</li> : ""} | ||||
|                 <div .separator /> | ||||
|                 <li #allow-darktheme><span>{svg_checkmark}</span>{translate('Dark Theme')}</li> | ||||
|                 <div .separator /> | ||||
|                 <Languages /> | ||||
|                 <li #about>{translate('About')} {" "}{handler.get_app_name()}</li> | ||||
|             </menu> | ||||
|         </popup>; | ||||
|  | ||||
| @ -2001,11 +2001,11 @@ impl Remote { | ||||
|         let mut config: PeerConfig = self.handler.load_config(); | ||||
|         let mut transfer_metas = TransferSerde::default(); | ||||
|         for job in self.read_jobs.iter() { | ||||
|             let json_str = serde_json::to_string(&job.gen_meta()).unwrap(); | ||||
|             let json_str = serde_json::to_string(&job.gen_meta()).unwrap_or_default(); | ||||
|             transfer_metas.read_jobs.push(json_str); | ||||
|         } | ||||
|         for job in self.write_jobs.iter() { | ||||
|             let json_str = serde_json::to_string(&job.gen_meta()).unwrap(); | ||||
|             let json_str = serde_json::to_string(&job.gen_meta()).unwrap_or_default(); | ||||
|             transfer_metas.write_jobs.push(json_str); | ||||
|         } | ||||
|         log::info!("meta: {:?}", transfer_metas); | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user