Merge pull request #964 from asur4s/master

Feat: Support new keyboard mode
This commit is contained in:
RustDesk 2022-09-07 21:34:29 +08:00 committed by GitHub
commit 47cefc57e6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
74 changed files with 1864 additions and 1493 deletions

892
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -75,7 +75,7 @@ sciter-rs = { git = "https://github.com/open-trade/rust-sciter", branch = "dyn"
sys-locale = "0.2"
enigo = { path = "libs/enigo", features = [ "with_serde" ] }
clipboard = { path = "libs/clipboard" }
rdev = { git = "https://github.com/open-trade/rdev" }
rdev = { git = "https://github.com/asur4s/rdev" }
ctrlc = "3.2"
arboard = "2.0"
#minreq = { version = "2.4", features = ["punycode", "https-native"] }
@ -108,6 +108,7 @@ async-process = "1.3"
mouce = { git="https://github.com/fufesou/mouce.git" }
evdev = { git="https://github.com/fufesou/evdev" }
[target.'cfg(target_os = "android")'.dependencies]
android_logger = "0.11"
jni = "0.19"

View File

@ -12,9 +12,6 @@ if [ "$1" = configure ]; then
fi
version=$(python3 -V 2>&1 | grep -Po '(?<=Python )(.+)')
parsedVersion=$(echo "${version//./}")
if [[ "$parsedVersion" -gt "360" ]]; then
sudo -H pip3 install pynput
fi
cp /usr/share/rustdesk/files/systemd/rustdesk.service /usr/lib/systemd/system/rustdesk.service
systemctl daemon-reload
systemctl enable rustdesk

View File

@ -26,6 +26,5 @@ package() {
install -Dm 644 ${HBB}/libsciter-gtk.so -t "${pkgdir}/usr/lib/rustdesk"
install -Dm 644 $HBB/rustdesk.service -t "${pkgdir}/usr/share/rustdesk/files"
install -Dm 644 $HBB/rustdesk.desktop -t "${pkgdir}/usr/share/rustdesk/files"
install -Dm 644 $HBB/pynput_service.py -t "${pkgdir}/usr/share/rustdesk/files"
install -Dm 644 $HBB/128x128@2x.png "${pkgdir}/usr/share/rustdesk/files/rustdesk.png"
}

View File

@ -80,11 +80,6 @@ sudo yum -y install gcc-c++ git curl wget nasm yasm gcc gtk3-devel clang libxcb-
sudo pacman -Syu --needed unzip git cmake gcc curl wget yasm nasm zip make pkg-config clang gtk3 xdotool libxcb libxfixes alsa-lib pulseaudio
```
### pynput package تثبيت
```sh
pip3 install pynput
```
### vcpkg تثبيت

View File

@ -75,12 +75,6 @@ sudo yum -y install gcc-c++ git curl wget nasm yasm gcc gtk3-devel clang libxcb-
sudo pacman -Syu --needed unzip git cmake gcc curl wget yasm nasm zip make pkg-config clang gtk3 xdotool libxcb libxfixes alsa-lib pulseaudio
```
### Instalace balíčku pynput
```sh
pip3 install pynput
```
### Instalace vcpkg
```sh

View File

@ -78,12 +78,6 @@ sudo yum -y install gcc-c++ git curl wget nasm yasm gcc gtk3-devel clang libxcb-
sudo pacman -Syu --needed unzip git cmake gcc curl wget yasm nasm zip make pkg-config clang gtk3 xdotool libxcb libxfixes alsa-lib pulseaudio
```
### بسته pynput را نصب کنید
```sh
pip3 install pynput
```
### نرم افزار vcpkg را نصب کنید
```sh

View File

@ -81,12 +81,6 @@ sudo yum -y install gcc-c++ git curl wget nasm yasm gcc gtk3-devel clang libxcb-
sudo pacman -Syu --needed unzip git cmake gcc curl wget yasm nasm zip make pkg-config clang gtk3 xdotool libxcb libxfixes alsa-lib pulseaudio
```
### Telepítsd a pynput csomagot
```sh
pip3 install pynput
```
### Telepítsd a vcpkg-t
```sh

View File

@ -80,12 +80,6 @@ sudo yum -y install gcc-c++ git curl wget nasm yasm gcc gtk3-devel clang libxcb-
sudo pacman -Syu --needed unzip git cmake gcc curl wget yasm nasm zip make pkg-config clang gtk3 xdotool libxcb libxfixes alsa-lib pulseaudio
```
### Install pynput package
```sh
pip3 install pynput
```
### Install vcpkg
```sh

View File

@ -78,12 +78,6 @@ sudo yum -y install gcc-c++ git curl wget nasm yasm gcc gtk3-devel clang libxcb-
sudo pacman -Syu --needed unzip git cmake gcc curl wget yasm nasm zip make pkg-config clang gtk3 xdotool libxcb libxfixes alsa-lib pulseaudio
```
### Install pynput package
```sh
pip3 install pynput
```
### Install vcpkg
```sh

View File

@ -82,12 +82,6 @@ sudo yum -y install gcc-c++ git curl wget nasm yasm gcc gtk3-devel clang libxcb-
sudo pacman -Syu --needed unzip git cmake gcc curl wget yasm nasm zip make pkg-config clang gtk3 xdotool libxcb libxfixes alsa-lib pulseaudio
```
### Cách tải về gói hàng pynput
```sh
pip3 install pynput
```
### Cách cài vcpkg
```sh

View File

@ -65,7 +65,9 @@ Please download sciter dynamic library yourself.
### Ubuntu 18 (Debian 10)
```sh
sudo apt install -y g++ gcc git curl wget nasm yasm libgtk-3-dev clang libxcb-randr0-dev libxdo-dev libxfixes-dev libxcb-shape0-dev libxcb-xfixes0-dev libasound2-dev libpulse-dev cmake
sudo apt install -y g++ gcc git curl wget nasm yasm libgtk-3-dev clang libxcb-randr0-dev libxdo-dev \
libxfixes-dev libxcb-shape0-dev libxcb-xfixes0-dev libasound2-dev libpulse-dev cmake \
libclang-dev ninja-build libayatana-appindicator3-1 libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev libayatana-appindicator3-dev
```
### Fedora 28 (CentOS 8)
@ -80,12 +82,6 @@ sudo yum -y install gcc-c++ git curl wget nasm yasm gcc gtk3-devel clang libxcb-
sudo pacman -Syu --needed unzip git cmake gcc curl wget yasm nasm zip make pkg-config clang gtk3 xdotool libxcb libxfixes alsa-lib pulseaudio
```
### Install pynput package
```sh
pip3 install pynput
```
### Install vcpkg
```sh
@ -128,6 +124,30 @@ VCPKG_ROOT=$HOME/vcpkg cargo run
RustDesk does not support Wayland. Check [this](https://docs.fedoraproject.org/en-US/quick-docs/configuring-xorg-as-default-gnome-session/) to configuring Xorg as the default GNOME session.
## Wayland support
Wayland does not seem to provide any API for sending keypresses to other windows. Therefore, the rustdesk uses an API from a lower level, namely the `/dev/uinput` device (Linux kernel level).
When wayland is the controlled side, you have to start in the following way:
```bash
# Start uinput service
$ sudo rustdesk --service
$ rustdesk
```
**Notice**: Wayland screen recording uses different interfaces, currently currently only supports org.freedesktop.portal.ScreenCast.
```bash
$ dbus-send --session --print-reply \
--dest=org.freedesktop.portal.Desktop \
/org/freedesktop/portal/desktop \
org.freedesktop.DBus.Properties.Get \
string:org.freedesktop.portal.ScreenCast string:version
# Not support
Error org.freedesktop.DBus.Error.InvalidArgs: No such interface “org.freedesktop.portal.ScreenCast”
# Support
method return time=1662544486.931020 sender=:1.54 -> destination=:1.139 serial=257 reply_serial=2
variant uint32 4
```
## How to build with Docker
Begin by cloning the repository and building the docker container:

View File

@ -9,8 +9,6 @@ script:
# Download sciter.so
- mkdir -p AppDir/usr/lib/rustdesk/
- pushd AppDir/usr/lib/rustdesk && wget https://github.com/c-smile/sciter-sdk/raw/29a598b6d20220b93848b5e8abab704619296857/bin.lnx/x64/libsciter-gtk.so && popd
# pynput_service.py
- cp ../pynput_service.py ./AppDir/usr/lib/rustdesk
# Build rustdesk
- pushd .. && python3 inline-sciter.py && cargo build --features inline,appimage --release && popd
- mkdir -p AppDir/usr/bin

View File

@ -1 +0,0 @@
pynput

View File

@ -140,8 +140,6 @@ def build_flutter_deb(version):
'cp rustdesk.service tmpdeb/usr/share/rustdesk/files/systemd/')
os.system(
'cp rustdesk.service.user tmpdeb/usr/share/rustdesk/files/systemd/')
os.system(
'cp ../pynput_service.py tmpdeb/usr/share/rustdesk/files/')
os.system(
'cp ../128x128@2x.png tmpdeb/usr/share/rustdesk/files/rustdesk.png')
os.system(
@ -150,7 +148,6 @@ def build_flutter_deb(version):
os.system('cp -a ../DEBIAN/* tmpdeb/DEBIAN/')
md5_file('usr/share/rustdesk/files/systemd/rustdesk.service')
md5_file('usr/share/rustdesk/files/systemd/rustdesk.service.user')
md5_file('usr/share/rustdesk/files/pynput_service.py')
os.system('dpkg-deb -b tmpdeb rustdesk.deb; /bin/rm -rf tmpdeb/')
os.rename('rustdesk.deb', '../rustdesk-%s.deb' % version)
os.chdir("..")
@ -285,15 +282,12 @@ def main():
'cp rustdesk.service tmpdeb/usr/share/rustdesk/files/systemd/')
os.system(
'cp rustdesk.service.user tmpdeb/usr/share/rustdesk/files/systemd/')
os.system(
'cp pynput_service.py tmpdeb/usr/share/rustdesk/files/')
os.system('cp -a DEBIAN/* tmpdeb/DEBIAN/')
os.system('strip tmpdeb/usr/bin/rustdesk')
os.system('mkdir -p tmpdeb/usr/lib/rustdesk')
os.system('cp libsciter-gtk.so tmpdeb/usr/lib/rustdesk/')
md5_file('usr/share/rustdesk/files/systemd/rustdesk.service')
md5_file('usr/share/rustdesk/files/systemd/rustdesk.service.user')
md5_file('usr/share/rustdesk/files/pynput_service.py')
md5_file('usr/lib/rustdesk/libsciter-gtk.so')
os.system('dpkg-deb -b tmpdeb rustdesk.deb; /bin/rm -rf tmpdeb/')
os.rename('rustdesk.deb', 'rustdesk-%s.deb' % version)

View File

@ -116,4 +116,4 @@ fn main() {
build_windows();
#[cfg(target_os = "macos")]
println!("cargo:rustc-link-lib=framework=ApplicationServices");
}
}

2
flutter/.gitignore vendored
View File

@ -36,11 +36,9 @@ lib/generated_plugin_registrant.dart
# Symbolication related
app.*.symbols
# Obfuscation related
app.*.map.json
jniLibs
.vscode
# flutter rust bridge

View File

@ -41,6 +41,7 @@ class _RemotePageState extends State<RemotePage>
with AutomaticKeepAliveClientMixin {
Timer? _timer;
String _value = '';
String keyboardMode = "legacy";
final _cursorOverImage = false.obs;
late RxBool _showRemoteCursor;
late RxBool _remoteCursorMoved;
@ -246,6 +247,92 @@ class _RemotePageState extends State<RemotePage>
], child: buildBody(context)));
}
KeyEventResult handleRawKeyEvent(FocusNode data, RawKeyEvent e) {
bind.sessionGetKeyboardName(id: widget.id).then((result) {
setState(() {
keyboardMode = result.toString();
});
});
if (keyboardMode == 'map') {
mapKeyboardMode(e);
} else if (keyboardMode == 'translate') {
legacyKeyboardMode(e);
} else {
legacyKeyboardMode(e);
}
return KeyEventResult.handled;
}
void mapKeyboardMode(RawKeyEvent e) {
int scanCode;
int keyCode;
bool down;
if (e.data is RawKeyEventDataMacOs) {
RawKeyEventDataMacOs newData = e.data as RawKeyEventDataMacOs;
scanCode = newData.keyCode;
keyCode = newData.keyCode;
} else if (e.data is RawKeyEventDataWindows) {
RawKeyEventDataWindows newData = e.data as RawKeyEventDataWindows;
scanCode = newData.scanCode;
keyCode = newData.keyCode;
} else if (e.data is RawKeyEventDataLinux) {
RawKeyEventDataLinux newData = e.data as RawKeyEventDataLinux;
scanCode = newData.scanCode;
keyCode = newData.keyCode;
} else {
scanCode = -1;
keyCode = -1;
}
if (e is RawKeyDownEvent) {
down = true;
} else {
down = false;
}
_ffi.inputRawKey(e.character ?? "", keyCode, scanCode, down);
}
void legacyKeyboardMode(RawKeyEvent e) {
final key = e.logicalKey;
if (e is RawKeyDownEvent) {
if (e.repeat) {
sendRawKey(e, press: true);
} else {
if (e.isAltPressed && !_ffi.alt) {
_ffi.alt = true;
} else if (e.isControlPressed && !_ffi.ctrl) {
_ffi.ctrl = true;
} else if (e.isShiftPressed && !_ffi.shift) {
_ffi.shift = true;
} else if (e.isMetaPressed && !_ffi.command) {
_ffi.command = true;
}
sendRawKey(e, down: true);
}
}
if (e is RawKeyUpEvent) {
if (key == LogicalKeyboardKey.altLeft ||
key == LogicalKeyboardKey.altRight) {
_ffi.alt = false;
} else if (key == LogicalKeyboardKey.controlLeft ||
key == LogicalKeyboardKey.controlRight) {
_ffi.ctrl = false;
} else if (key == LogicalKeyboardKey.shiftRight ||
key == LogicalKeyboardKey.shiftLeft) {
_ffi.shift = false;
} else if (key == LogicalKeyboardKey.metaLeft ||
key == LogicalKeyboardKey.metaRight ||
key == LogicalKeyboardKey.superKey) {
_ffi.command = false;
}
sendRawKey(e);
}
}
Widget getRawPointerAndKeyBody(Widget child) {
return FocusScope(
autofocus: true,
@ -256,42 +343,7 @@ class _RemotePageState extends State<RemotePage>
onFocusChange: (bool v) {
_imageFocused = v;
},
onKey: (data, e) {
final key = e.logicalKey;
if (e is RawKeyDownEvent) {
if (e.repeat) {
sendRawKey(e, press: true);
} else {
if (e.isAltPressed && !_ffi.alt) {
_ffi.alt = true;
} else if (e.isControlPressed && !_ffi.ctrl) {
_ffi.ctrl = true;
} else if (e.isShiftPressed && !_ffi.shift) {
_ffi.shift = true;
} else if (e.isMetaPressed && !_ffi.command) {
_ffi.command = true;
}
sendRawKey(e, down: true);
}
}
if (e is RawKeyUpEvent) {
if (key == LogicalKeyboardKey.altLeft ||
key == LogicalKeyboardKey.altRight) {
_ffi.alt = false;
} else if (key == LogicalKeyboardKey.controlLeft ||
key == LogicalKeyboardKey.controlRight) {
_ffi.ctrl = false;
} else if (key == LogicalKeyboardKey.shiftRight ||
key == LogicalKeyboardKey.shiftLeft) {
_ffi.shift = false;
} else if (key == LogicalKeyboardKey.metaLeft ||
key == LogicalKeyboardKey.metaRight) {
_ffi.command = false;
}
sendRawKey(e);
}
return KeyEventResult.handled;
},
onKey: handleRawKeyEvent,
child: child));
}
@ -304,7 +356,6 @@ class _RemotePageState extends State<RemotePage>
/// mouseMode only:
/// DoubleFiner -> right click
/// HoldDrag -> left drag
void _onPointHoverImage(PointerHoverEvent e) {
if (e.kind != ui.PointerDeviceKind.mouse) return;
if (!_isPhysicalMouse) {
@ -367,6 +418,19 @@ class _RemotePageState extends State<RemotePage>
}
}
void enterView(PointerEnterEvent evt) {
if (!_imageFocused) {
_physicalFocusNode.requestFocus();
}
_cursorOverImage.value = true;
_ffi.enterOrLeave(true);
}
void leaveView(PointerExitEvent evt) {
_cursorOverImage.value = false;
_ffi.enterOrLeave(false);
}
Widget _buildImageListener(Widget child) {
return Listener(
onPointerHover: _onPointHoverImage,
@ -374,17 +438,8 @@ class _RemotePageState extends State<RemotePage>
onPointerUp: _onPointUpImage,
onPointerMove: _onPointMoveImage,
onPointerSignal: _onPointerSignalImage,
child: MouseRegion(
onEnter: (evt) {
if (!_imageFocused) {
_physicalFocusNode.requestFocus();
}
_cursorOverImage.value = true;
},
onExit: (evt) {
_cursorOverImage.value = false;
},
child: child));
child:
MouseRegion(onEnter: enterView, onExit: leaveView, child: child));
}
Widget getBodyForDesktop(BuildContext context) {

View File

@ -93,6 +93,7 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
menubarItems.add(_buildMonitor(context));
menubarItems.add(_buildControl(context));
menubarItems.add(_buildDisplay(context));
menubarItems.add(_buildKeyboard(context));
if (!isWeb) {
menubarItems.add(_buildChat(context));
}
@ -264,6 +265,29 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
);
}
Widget _buildKeyboard(BuildContext context) {
return mod_menu.PopupMenuButton(
padding: EdgeInsets.zero,
icon: const Icon(
Icons.keyboard,
color: _MenubarTheme.commonColor,
),
tooltip: translate('Keyboard Settings'),
position: mod_menu.PopupMenuPosition.under,
onSelected: (String item) {},
itemBuilder: (BuildContext context) => _getKeyboardMenu()
.map((entry) => entry.build(
context,
const MenuConfig(
commonColor: _MenubarTheme.commonColor,
height: _MenubarTheme.height,
dividerHeight: _MenubarTheme.dividerHeight,
)))
.expand((i) => i)
.toList(),
);
}
Widget _buildClose(BuildContext context) {
return IconButton(
tooltip: translate('Close'),
@ -577,6 +601,28 @@ class _RemoteMenubarState extends State<RemoteMenubar> {
return displayMenu;
}
List<MenuEntryBase<String>> _getKeyboardMenu() {
final keyboardMenu = [
MenuEntryRadios<String>(
text: translate('Ratio'),
optionsGetter: () => [
MenuEntryRadioOption(
text: translate('Legacy mode'), value: 'legacy'),
MenuEntryRadioOption(text: translate('Map mode'), value: 'map'),
],
curOptionGetter: () async {
return await bind.sessionGetKeyboardName(id: widget.id) ?? 'legacy';
},
optionSetter: (String oldValue, String newValue) async {
await bind.sessionSetKeyboardMode(
id: widget.id, keyboardMode: newValue);
widget.ffi.canvasModel.updateViewStyle();
})
];
return keyboardMenu;
}
MenuEntrySwitch<String> _createSwitchMenuEntry(String text, String option) {
return MenuEntrySwitch<String>(
text: translate(text),

View File

@ -972,6 +972,28 @@ class FFI {
msg: json.encode(modify({'type': type, 'buttons': button.value})));
}
// Raw Key
void inputRawKey(String name, int keyCode, int scanCode, bool down) {
bind.sessionHandleFlutterKeyEvent(
id: id,
name: name,
keycode: keyCode,
scancode: scanCode,
downOrUp: down);
}
Future<String> getKeyboardMode() {
return bind.sessionGetKeyboardName(id: id);
}
void enterOrLeave(bool enter) {
// Fix status
if (!enter) {
resetModifiers();
}
bind.sessionEnterOrLeave(id: id, enter: enter);
}
/// Send key stroke event.
/// [down] indicates the key's state(down or up).
/// [press] indicates a click event(down and up).

View File

@ -97,7 +97,7 @@ class PlatformFFI {
final dylib = Platform.isAndroid
? DynamicLibrary.open('librustdesk.so')
: Platform.isLinux
? DynamicLibrary.open("/usr/lib/rustdesk/librustdesk.so")
? DynamicLibrary.open("librustdesk.so")
: Platform.isWindows
? DynamicLibrary.open("librustdesk.dll")
: Platform.isMacOS

View File

@ -74,6 +74,8 @@ corrosion_import_crate(MANIFEST_PATH ../../Cargo.toml
# [FEATURES <feature1> ... <featureN>]
)
set(BASE_RUSTDESK "librustdesk")
# Define the application target. To change its name, change BINARY_NAME above,
# not the value here, or `flutter run` will no longer work.
#
@ -91,8 +93,8 @@ apply_standard_settings(${BINARY_NAME})
# Add dependency libraries. Add any application-specific dependencies here.
target_link_libraries(${BINARY_NAME} PRIVATE flutter)
target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK)
target_link_libraries(${BINARY_NAME} PRIVATE librustdesk)
target_link_libraries(${BINARY_NAME} PRIVATE ${BASE_RUSTDESK})
# target_link_libraries(${BINARY_NAME} PRIVATE librustdesk)
# Run the Flutter tool portions of the build. This must not be removed.
add_dependencies(${BINARY_NAME} flutter_assemble)
@ -142,6 +144,8 @@ foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES})
COMPONENT Runtime)
endforeach(bundled_library)
install(FILES $<TARGET_FILE:${BASE_RUSTDESK}-shared> DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" COMPONENT Runtime RENAME librustdesk.so)
# Fully re-copy the assets directory on each build to avoid having stale files
# from a previous install.
set(FLUTTER_ASSET_DIR_NAME "flutter_assets")

View File

@ -1,7 +1,8 @@
#include <dlfcn.h>
#include "my_application.h"
#define RUSTDESK_LIB_PATH "/usr/lib/rustdesk/librustdesk.so"
#define RUSTDESK_LIB_PATH "ibrustdesk.so"
// #define RUSTDESK_LIB_PATH "/usr/lib/rustdesk/librustdesk.so"
typedef bool (*RustDeskCoreMain)();
bool flutter_rustdesk_core_main() {

View File

@ -1,78 +1,84 @@
PODS:
- bitsdojo_window_macos (0.0.1):
- desktop_drop (0.0.1):
- FlutterMacOS
- desktop_multi_window (0.0.1):
- FlutterMacOS
- device_info_plus_macos (0.0.1):
- FlutterMacOS
- Firebase/Analytics (8.15.0):
- Firebase/Analytics (9.4.0):
- Firebase/Core
- Firebase/Core (8.15.0):
- Firebase/Core (9.4.0):
- Firebase/CoreOnly
- FirebaseAnalytics (~> 8.15.0)
- Firebase/CoreOnly (8.15.0):
- FirebaseCore (= 8.15.0)
- firebase_analytics (9.1.9):
- Firebase/Analytics (= 8.15.0)
- FirebaseAnalytics (~> 9.4.0)
- Firebase/CoreOnly (9.4.0):
- FirebaseCore (= 9.4.0)
- firebase_analytics (9.3.3):
- Firebase/Analytics (= 9.4.0)
- firebase_core
- FlutterMacOS
- firebase_core (1.17.1):
- Firebase/CoreOnly (~> 8.15.0)
- firebase_core (1.21.1):
- Firebase/CoreOnly (~> 9.4.0)
- FlutterMacOS
- FirebaseAnalytics (8.15.0):
- FirebaseAnalytics/AdIdSupport (= 8.15.0)
- FirebaseCore (~> 8.0)
- FirebaseInstallations (~> 8.0)
- FirebaseAnalytics (9.4.0):
- FirebaseAnalytics/AdIdSupport (= 9.4.0)
- FirebaseCore (~> 9.0)
- FirebaseInstallations (~> 9.0)
- GoogleUtilities/AppDelegateSwizzler (~> 7.7)
- GoogleUtilities/MethodSwizzler (~> 7.7)
- GoogleUtilities/Network (~> 7.7)
- "GoogleUtilities/NSData+zlib (~> 7.7)"
- nanopb (~> 2.30908.0)
- FirebaseAnalytics/AdIdSupport (8.15.0):
- FirebaseCore (~> 8.0)
- FirebaseInstallations (~> 8.0)
- GoogleAppMeasurement (= 8.15.0)
- nanopb (< 2.30910.0, >= 2.30908.0)
- FirebaseAnalytics/AdIdSupport (9.4.0):
- FirebaseCore (~> 9.0)
- FirebaseInstallations (~> 9.0)
- GoogleAppMeasurement (= 9.4.0)
- GoogleUtilities/AppDelegateSwizzler (~> 7.7)
- GoogleUtilities/MethodSwizzler (~> 7.7)
- GoogleUtilities/Network (~> 7.7)
- "GoogleUtilities/NSData+zlib (~> 7.7)"
- nanopb (~> 2.30908.0)
- FirebaseCore (8.15.0):
- FirebaseCoreDiagnostics (~> 8.0)
- nanopb (< 2.30910.0, >= 2.30908.0)
- FirebaseCore (9.4.0):
- FirebaseCoreDiagnostics (~> 9.0)
- FirebaseCoreInternal (~> 9.0)
- GoogleUtilities/Environment (~> 7.7)
- GoogleUtilities/Logger (~> 7.7)
- FirebaseCoreDiagnostics (8.15.0):
- GoogleDataTransport (~> 9.1)
- FirebaseCoreDiagnostics (9.5.0):
- GoogleDataTransport (< 10.0.0, >= 9.1.4)
- GoogleUtilities/Environment (~> 7.7)
- GoogleUtilities/Logger (~> 7.7)
- nanopb (~> 2.30908.0)
- FirebaseInstallations (8.15.0):
- FirebaseCore (~> 8.0)
- nanopb (< 2.30910.0, >= 2.30908.0)
- FirebaseCoreInternal (9.5.0):
- "GoogleUtilities/NSData+zlib (~> 7.7)"
- FirebaseInstallations (9.5.0):
- FirebaseCore (~> 9.0)
- GoogleUtilities/Environment (~> 7.7)
- GoogleUtilities/UserDefaults (~> 7.7)
- PromisesObjC (< 3.0, >= 1.2)
- PromisesObjC (~> 2.1)
- FlutterMacOS (1.0.0)
- GoogleAppMeasurement (8.15.0):
- GoogleAppMeasurement/AdIdSupport (= 8.15.0)
- FMDB (2.7.5):
- FMDB/standard (= 2.7.5)
- FMDB/standard (2.7.5)
- GoogleAppMeasurement (9.4.0):
- GoogleAppMeasurement/AdIdSupport (= 9.4.0)
- GoogleUtilities/AppDelegateSwizzler (~> 7.7)
- GoogleUtilities/MethodSwizzler (~> 7.7)
- GoogleUtilities/Network (~> 7.7)
- "GoogleUtilities/NSData+zlib (~> 7.7)"
- nanopb (~> 2.30908.0)
- GoogleAppMeasurement/AdIdSupport (8.15.0):
- GoogleAppMeasurement/WithoutAdIdSupport (= 8.15.0)
- nanopb (< 2.30910.0, >= 2.30908.0)
- GoogleAppMeasurement/AdIdSupport (9.4.0):
- GoogleAppMeasurement/WithoutAdIdSupport (= 9.4.0)
- GoogleUtilities/AppDelegateSwizzler (~> 7.7)
- GoogleUtilities/MethodSwizzler (~> 7.7)
- GoogleUtilities/Network (~> 7.7)
- "GoogleUtilities/NSData+zlib (~> 7.7)"
- nanopb (~> 2.30908.0)
- GoogleAppMeasurement/WithoutAdIdSupport (8.15.0):
- nanopb (< 2.30910.0, >= 2.30908.0)
- GoogleAppMeasurement/WithoutAdIdSupport (9.4.0):
- GoogleUtilities/AppDelegateSwizzler (~> 7.7)
- GoogleUtilities/MethodSwizzler (~> 7.7)
- GoogleUtilities/Network (~> 7.7)
- "GoogleUtilities/NSData+zlib (~> 7.7)"
- nanopb (~> 2.30908.0)
- GoogleDataTransport (9.1.4):
- nanopb (< 2.30910.0, >= 2.30908.0)
- GoogleDataTransport (9.2.0):
- GoogleUtilities/Environment (~> 7.7)
- nanopb (< 2.30910.0, >= 2.30908.0)
- PromisesObjC (< 3.0, >= 1.2)
@ -95,18 +101,25 @@ PODS:
- GoogleUtilities/Logger
- GoogleUtilities/UserDefaults (7.7.0):
- GoogleUtilities/Logger
- nanopb (2.30908.0):
- nanopb/decode (= 2.30908.0)
- nanopb/encode (= 2.30908.0)
- nanopb/decode (2.30908.0)
- nanopb/encode (2.30908.0)
- nanopb (2.30909.0):
- nanopb/decode (= 2.30909.0)
- nanopb/encode (= 2.30909.0)
- nanopb/decode (2.30909.0)
- nanopb/encode (2.30909.0)
- package_info_plus_macos (0.0.1):
- FlutterMacOS
- path_provider_macos (0.0.1):
- FlutterMacOS
- PromisesObjC (2.1.0)
- PromisesObjC (2.1.1)
- screen_retriever (0.0.1):
- FlutterMacOS
- shared_preferences_macos (0.0.1):
- FlutterMacOS
- sqflite (0.0.2):
- FlutterMacOS
- FMDB (>= 2.7.5)
- tray_manager (0.0.1):
- FlutterMacOS
- url_launcher_macos (0.0.1):
- FlutterMacOS
- wakelock_macos (0.0.1):
@ -115,7 +128,7 @@ PODS:
- FlutterMacOS
DEPENDENCIES:
- bitsdojo_window_macos (from `Flutter/ephemeral/.symlinks/plugins/bitsdojo_window_macos/macos`)
- desktop_drop (from `Flutter/ephemeral/.symlinks/plugins/desktop_drop/macos`)
- desktop_multi_window (from `Flutter/ephemeral/.symlinks/plugins/desktop_multi_window/macos`)
- device_info_plus_macos (from `Flutter/ephemeral/.symlinks/plugins/device_info_plus_macos/macos`)
- firebase_analytics (from `Flutter/ephemeral/.symlinks/plugins/firebase_analytics/macos`)
@ -123,7 +136,10 @@ DEPENDENCIES:
- FlutterMacOS (from `Flutter/ephemeral`)
- package_info_plus_macos (from `Flutter/ephemeral/.symlinks/plugins/package_info_plus_macos/macos`)
- path_provider_macos (from `Flutter/ephemeral/.symlinks/plugins/path_provider_macos/macos`)
- screen_retriever (from `Flutter/ephemeral/.symlinks/plugins/screen_retriever/macos`)
- shared_preferences_macos (from `Flutter/ephemeral/.symlinks/plugins/shared_preferences_macos/macos`)
- sqflite (from `Flutter/ephemeral/.symlinks/plugins/sqflite/macos`)
- tray_manager (from `Flutter/ephemeral/.symlinks/plugins/tray_manager/macos`)
- url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`)
- wakelock_macos (from `Flutter/ephemeral/.symlinks/plugins/wakelock_macos/macos`)
- window_manager (from `Flutter/ephemeral/.symlinks/plugins/window_manager/macos`)
@ -134,7 +150,9 @@ SPEC REPOS:
- FirebaseAnalytics
- FirebaseCore
- FirebaseCoreDiagnostics
- FirebaseCoreInternal
- FirebaseInstallations
- FMDB
- GoogleAppMeasurement
- GoogleDataTransport
- GoogleUtilities
@ -142,8 +160,8 @@ SPEC REPOS:
- PromisesObjC
EXTERNAL SOURCES:
bitsdojo_window_macos:
:path: Flutter/ephemeral/.symlinks/plugins/bitsdojo_window_macos/macos
desktop_drop:
:path: Flutter/ephemeral/.symlinks/plugins/desktop_drop/macos
desktop_multi_window:
:path: Flutter/ephemeral/.symlinks/plugins/desktop_multi_window/macos
device_info_plus_macos:
@ -158,8 +176,14 @@ EXTERNAL SOURCES:
:path: Flutter/ephemeral/.symlinks/plugins/package_info_plus_macos/macos
path_provider_macos:
:path: Flutter/ephemeral/.symlinks/plugins/path_provider_macos/macos
screen_retriever:
:path: Flutter/ephemeral/.symlinks/plugins/screen_retriever/macos
shared_preferences_macos:
:path: Flutter/ephemeral/.symlinks/plugins/shared_preferences_macos/macos
sqflite:
:path: Flutter/ephemeral/.symlinks/plugins/sqflite/macos
tray_manager:
:path: Flutter/ephemeral/.symlinks/plugins/tray_manager/macos
url_launcher_macos:
:path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos
wakelock_macos:
@ -168,25 +192,30 @@ EXTERNAL SOURCES:
:path: Flutter/ephemeral/.symlinks/plugins/window_manager/macos
SPEC CHECKSUMS:
bitsdojo_window_macos: 44e3b8fe3dd463820e0321f6256c5b1c16bb6a00
desktop_drop: 69eeff437544aa619c8db7f4481b3a65f7696898
desktop_multi_window: 566489c048b501134f9d7fb6a2354c60a9126486
device_info_plus_macos: 1ad388a1ef433505c4038e7dd9605aadd1e2e9c7
Firebase: 5f8193dff4b5b7c5d5ef72ae54bb76c08e2b841d
firebase_analytics: d448483150504ed84f25c5437a34af2591a7929e
firebase_core: 7b87364e2d1eae70018a60698e89e7d6f5320bad
FirebaseAnalytics: 7761cbadb00a717d8d0939363eb46041526474fa
FirebaseCore: 5743c5785c074a794d35f2fff7ecc254a91e08b1
FirebaseCoreDiagnostics: 92e07a649aeb66352b319d43bdd2ee3942af84cb
FirebaseInstallations: 40bd9054049b2eae9a2c38ef1c3dd213df3605cd
Firebase: 7703fc4022824b6d6db1bf7bea58d13b8e17ec46
firebase_analytics: 57144bae6cd39d3be367a8767a1b8857a037cee5
firebase_core: 822a1076483bf9764284322c9310daa98e1e6817
FirebaseAnalytics: a1a24e72b7ba7f47045a4633f1abb545c07bd29c
FirebaseCore: 9a2b10270a854731c4d4d8a97d0aa8380ec3458d
FirebaseCoreDiagnostics: 17cbf4e72b1dbd64bfdc33d4b1f07bce4f16f1d8
FirebaseCoreInternal: 50a8e39cae8abf72d5145d07ea34c3244f70862b
FirebaseInstallations: 41f811b530c41dd90973d0174381cdb3fcb5e839
FlutterMacOS: 57701585bf7de1b3fc2bb61f6378d73bbdea8424
GoogleAppMeasurement: 4c19f031220c72464d460c9daa1fb5d1acce958e
GoogleDataTransport: 5fffe35792f8b96ec8d6775f5eccd83c998d5a3b
FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a
GoogleAppMeasurement: 5d69e04287fc2c10cc43724bfa4bf31fc12c3dff
GoogleDataTransport: 1c8145da7117bd68bbbed00cf304edb6a24de00f
GoogleUtilities: e0913149f6b0625b553d70dae12b49fc62914fd1
nanopb: a0ba3315591a9ae0a16a309ee504766e90db0c96
nanopb: b552cce312b6c8484180ef47159bc0f65a1f0431
package_info_plus_macos: f010621b07802a241d96d01876d6705f15e77c1c
path_provider_macos: 3c0c3b4b0d4a76d2bf989a913c2de869c5641a19
PromisesObjC: 99b6f43f9e1044bd87a95a60beff28c2c44ddb72
PromisesObjC: ab77feca74fa2823e7af4249b8326368e61014cb
screen_retriever: 59634572a57080243dd1bf715e55b6c54f241a38
shared_preferences_macos: a64dc611287ed6cbe28fd1297898db1336975727
sqflite: a5789cceda41d54d23f31d6de539d65bb14100ea
tray_manager: 9064e219c56d75c476e46b9a21182087930baf90
url_launcher_macos: 597e05b8e514239626bcf4a850fcf9ef5c856ec3
wakelock_macos: bc3f2a9bd8d2e6c89fee1e1822e7ddac3bd004a9
window_manager: 3a1844359a6295ab1e47659b1a777e36773cd6e8

View File

@ -4,5 +4,7 @@
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.network.client</key>
<true/>
</dict>
</plist>

View File

@ -33,7 +33,7 @@
/* Begin PBXFileReference section */
ADDEDBA66A6E1 /* libresolv.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libresolv.tbd; path = usr/lib/libresolv.tbd; sourceTree = SDKROOT; };
CA603C4309E13EF4668187A5 /* Cargo.toml */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = Cargo.toml; path = /Users/ruizruiz/Work/Code/Projects/RustDesk/rustdesk/Cargo.toml; sourceTree = "<group>"; };
CA603C4309E13EF4668187A5 /* Cargo.toml */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = Cargo.toml; path = /Users/mac/Documents/project/rustdesk/Cargo.toml; sourceTree = "<group>"; };
CA604C7415FB2A3731F5016A /* liblibrustdesk_static.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = liblibrustdesk_static.a; sourceTree = BUILT_PRODUCTS_DIR; };
CA6071B5A0F5A7A3EF2297AA /* librustdesk.dylib */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.dylib"; includeInIndex = 0; path = librustdesk.dylib; sourceTree = BUILT_PRODUCTS_DIR; };
CA60D3BC5386B357B2AB834F /* rustdesk */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = rustdesk; sourceTree = BUILT_PRODUCTS_DIR; };

View File

@ -28,7 +28,7 @@ packages:
name: animations
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.0.3"
version: "2.0.4"
archive:
dependency: transitive
description:
@ -235,12 +235,10 @@ packages:
dash_chat_2:
dependency: "direct main"
description:
path: "."
ref: feat_maxWidth
resolved-ref: "3946ecf86d3600b54632fd80d0eb0ef0e74f2d6a"
url: "https://github.com/fufesou/Dash-Chat-2"
source: git
version: "0.0.12"
name: dash_chat_2
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.0.14"
desktop_drop:
dependency: "direct main"
description:
@ -347,42 +345,42 @@ packages:
name: firebase_analytics
url: "https://pub.flutter-io.cn"
source: hosted
version: "9.3.2"
version: "9.3.3"
firebase_analytics_platform_interface:
dependency: transitive
description:
name: firebase_analytics_platform_interface
url: "https://pub.flutter-io.cn"
source: hosted
version: "3.3.2"
version: "3.3.3"
firebase_analytics_web:
dependency: transitive
description:
name: firebase_analytics_web
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.4.2+2"
version: "0.4.2+3"
firebase_core:
dependency: transitive
description:
name: firebase_core
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.21.0"
version: "1.21.1"
firebase_core_platform_interface:
dependency: transitive
description:
name: firebase_core_platform_interface
url: "https://pub.flutter-io.cn"
source: hosted
version: "4.5.0"
version: "4.5.1"
firebase_core_web:
dependency: transitive
description:
name: firebase_core_web
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.7.1"
version: "1.7.2"
fixnum:
dependency: transitive
description:
@ -735,7 +733,7 @@ packages:
name: path_provider_android
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.0.19"
version: "2.0.20"
path_provider_ios:
dependency: transitive
description:
@ -849,7 +847,7 @@ packages:
source: hosted
version: "3.1.0"
rxdart:
dependency: transitive
dependency: "direct main"
description:
name: rxdart
url: "https://pub.flutter-io.cn"
@ -1257,7 +1255,7 @@ packages:
name: xdg_directories
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.2.0+1"
version: "0.2.0+2"
xml:
dependency: transitive
description:

View File

@ -22,6 +22,8 @@ appveyor = { repository = "pythoneer/enigo-85xiy" }
serde = { version = "1.0", optional = true }
serde_derive = { version = "1.0", optional = true }
log = "0.4"
rdev = { git = "https://github.com/asur4s/rdev" }
tfc = { git = "https://github.com/asur4s/The-Fat-Controller" }
hbb_common = { path = "../hbb_common" }
[features]

View File

@ -257,7 +257,7 @@ pub enum Key {
Backspace,
/// caps lock key
CapsLock,
#[deprecated(since = "0.0.12", note = "now renamed to Meta")]
// #[deprecated(since = "0.0.12", note = "now renamed to Meta")]
/// command key on macOS (super key on Linux, windows key on Windows)
Command,
/// control key
@ -314,14 +314,14 @@ pub enum Key {
Shift,
/// space key
Space,
#[deprecated(since = "0.0.12", note = "now renamed to Meta")]
// #[deprecated(since = "0.0.12", note = "now renamed to Meta")]
/// super key on linux (command key on macOS, windows key on Windows)
Super,
/// tab key (tabulator)
Tab,
/// up arrow key
UpArrow,
#[deprecated(since = "0.0.12", note = "now renamed to Meta")]
// #[deprecated(since = "0.0.12", note = "now renamed to Meta")]
/// windows key on Windows (super key on Linux, command key on macOS)
Windows,
///

View File

@ -1,5 +1,4 @@
mod nix_impl;
mod pynput;
mod xdo;
pub use self::nix_impl::Enigo;

View File

@ -1,12 +1,14 @@
use super::{pynput::EnigoPynput, xdo::EnigoXdo};
use super::xdo::EnigoXdo;
use crate::{Key, KeyboardControllable, MouseButton, MouseControllable};
use std::io::Read;
use tfc::{traits::*, Context as TFC_Context, Key as TFC_Key};
/// The main struct for handling the event emitting
// #[derive(Default)]
pub struct Enigo {
xdo: EnigoXdo,
pynput: EnigoPynput,
is_x11: bool,
tfc: TFC_Context,
uinput_keyboard: Option<Box<dyn KeyboardControllable + Send>>,
uinput_mouse: Option<Box<dyn MouseControllable + Send>>,
}
@ -20,10 +22,6 @@ impl Enigo {
pub fn set_delay(&mut self, delay: u64) {
self.xdo.set_delay(delay)
}
/// Reset pynput.
pub fn reset(&mut self) {
self.pynput.reset();
}
/// Set uinput keyboard.
pub fn set_uinput_keyboard(
&mut self,
@ -35,16 +33,51 @@ impl Enigo {
pub fn set_uinput_mouse(&mut self, uinput_mouse: Option<Box<dyn MouseControllable + Send>>) {
self.uinput_mouse = uinput_mouse
}
fn tfc_key_down_or_up(&mut self, key: Key, down: bool, up: bool) -> bool {
if let Key::Layout(chr) = key {
if down {
if let Err(_) = self.tfc.unicode_char_down(chr) {
return false;
}
}
if up {
if let Err(_) = self.tfc.unicode_char_up(chr) {
return false;
}
}
return true;
}
let key = match convert_to_tfc_key(key) {
Some(key) => key,
None => {
return false;
}
};
if down {
if let Err(_) = self.tfc.key_down(key) {
return false;
}
};
if up {
if let Err(_) = self.tfc.key_up(key) {
return false;
}
};
return true;
}
}
impl Default for Enigo {
fn default() -> Self {
Self {
is_x11: "x11" == hbb_common::platform::linux::get_display_server(),
tfc: TFC_Context::new().expect("kbd context error"),
uinput_keyboard: None,
uinput_mouse: None,
xdo: EnigoXdo::default(),
pynput: EnigoPynput::default(),
}
}
}
@ -117,6 +150,26 @@ impl MouseControllable for Enigo {
}
}
fn get_led_state(key: Key) -> bool {
let led_file = match key {
Key::CapsLock => "/sys/class/leds/input1::capslock/brightness",
Key::NumLock => "/sys/class/leds/input1::numlock/brightness",
_ => {
return false;
}
};
let status = if let Ok(mut file) = std::fs::File::open(&led_file) {
let mut content = String::new();
file.read_to_string(&mut content).ok();
let status = content.trim_end().to_string().parse::<i32>().unwrap_or(0);
status
} else {
0
};
status == 1
}
impl KeyboardControllable for Enigo {
fn get_key_state(&mut self, key: Key) -> bool {
if self.is_x11 {
@ -125,7 +178,7 @@ impl KeyboardControllable for Enigo {
if let Some(keyboard) = &mut self.uinput_keyboard {
keyboard.get_key_state(key)
} else {
false
get_led_state(key)
}
}
}
@ -142,10 +195,12 @@ impl KeyboardControllable for Enigo {
fn key_down(&mut self, key: Key) -> crate::ResultType {
if self.is_x11 {
if self.pynput.send_pynput(&key, true) {
return Ok(());
let has_down = self.tfc_key_down_or_up(key, true, false);
if !has_down {
self.xdo.key_down(key)
} else {
Ok(())
}
self.xdo.key_down(key)
} else {
if let Some(keyboard) = &mut self.uinput_keyboard {
keyboard.key_down(key)
@ -156,10 +211,10 @@ impl KeyboardControllable for Enigo {
}
fn key_up(&mut self, key: Key) {
if self.is_x11 {
if self.pynput.send_pynput(&key, false) {
return;
let has_down = self.tfc_key_down_or_up(key, false, true);
if !has_down {
self.xdo.key_up(key)
}
self.xdo.key_up(key)
} else {
if let Some(keyboard) = &mut self.uinput_keyboard {
keyboard.key_up(key)
@ -167,12 +222,76 @@ impl KeyboardControllable for Enigo {
}
}
fn key_click(&mut self, key: Key) {
if self.is_x11 {
self.xdo.key_click(key)
} else {
if let Some(keyboard) = &mut self.uinput_keyboard {
keyboard.key_click(key)
}
}
self.key_down(key).ok();
self.key_up(key);
}
}
fn convert_to_tfc_key(key: Key) -> Option<TFC_Key> {
let key = match key {
Key::Alt => TFC_Key::Alt,
Key::Backspace => TFC_Key::DeleteOrBackspace,
Key::CapsLock => TFC_Key::CapsLock,
Key::Control => TFC_Key::Control,
Key::Delete => TFC_Key::ForwardDelete,
Key::DownArrow => TFC_Key::DownArrow,
Key::End => TFC_Key::End,
Key::Escape => TFC_Key::Escape,
Key::F1 => TFC_Key::F1,
Key::F10 => TFC_Key::F10,
Key::F11 => TFC_Key::F11,
Key::F12 => TFC_Key::F12,
Key::F2 => TFC_Key::F2,
Key::F3 => TFC_Key::F3,
Key::F4 => TFC_Key::F4,
Key::F5 => TFC_Key::F5,
Key::F6 => TFC_Key::F6,
Key::F7 => TFC_Key::F7,
Key::F8 => TFC_Key::F8,
Key::F9 => TFC_Key::F9,
Key::Home => TFC_Key::Home,
Key::LeftArrow => TFC_Key::LeftArrow,
Key::PageDown => TFC_Key::PageDown,
Key::PageUp => TFC_Key::PageUp,
Key::Return => TFC_Key::ReturnOrEnter,
Key::RightArrow => TFC_Key::RightArrow,
Key::Shift => TFC_Key::Shift,
Key::Space => TFC_Key::Space,
Key::Tab => TFC_Key::Tab,
Key::UpArrow => TFC_Key::UpArrow,
Key::Numpad0 => TFC_Key::N0,
Key::Numpad1 => TFC_Key::N1,
Key::Numpad2 => TFC_Key::N2,
Key::Numpad3 => TFC_Key::N3,
Key::Numpad4 => TFC_Key::N4,
Key::Numpad5 => TFC_Key::N5,
Key::Numpad6 => TFC_Key::N6,
Key::Numpad7 => TFC_Key::N7,
Key::Numpad8 => TFC_Key::N8,
Key::Numpad9 => TFC_Key::N9,
Key::Decimal => TFC_Key::NumpadDecimal,
Key::Clear => TFC_Key::NumpadClear,
Key::Pause => TFC_Key::PlayPause,
Key::Print => TFC_Key::Print,
Key::Snapshot => TFC_Key::PrintScreen,
Key::Insert => TFC_Key::Insert,
Key::Scroll => TFC_Key::ScrollLock,
Key::NumLock => TFC_Key::NumLock,
Key::RWin => TFC_Key::Meta,
Key::Apps => TFC_Key::Apps,
Key::Multiply => TFC_Key::NumpadMultiply,
Key::Add => TFC_Key::NumpadPlus,
Key::Subtract => TFC_Key::NumpadMinus,
Key::Divide => TFC_Key::NumpadDivide,
Key::Equals => TFC_Key::NumpadEquals,
Key::NumpadEnter => TFC_Key::NumpadEnter,
Key::RightShift => TFC_Key::RightShift,
Key::RightControl => TFC_Key::RightControl,
Key::RightAlt => TFC_Key::RightAlt,
Key::Command | Key::Super | Key::Windows | Key::Meta => TFC_Key::Meta,
_ => {
return None;
}
};
Some(key)
}

View File

@ -1,280 +0,0 @@
use crate::Key;
use std::{io::prelude::*, sync::mpsc};
enum PyMsg {
Char(char),
Str(&'static str),
}
/// The main struct for handling the event emitting
pub(super) struct EnigoPynput {
tx: mpsc::Sender<(PyMsg, bool)>,
}
impl Default for EnigoPynput {
fn default() -> Self {
let (tx, rx) = mpsc::channel();
start_pynput_service(rx);
Self { tx }
}
}
impl EnigoPynput {
pub(super) fn reset(&mut self) {
self.tx.send((PyMsg::Char('\0'), true)).ok();
}
#[inline]
pub(super) fn send_pynput(&mut self, key: &Key, is_press: bool) -> bool {
if unsafe { PYNPUT_EXIT || !PYNPUT_REDAY } {
return false;
}
if let Key::Layout(c) = key {
return self.tx.send((PyMsg::Char(*c), is_press)).is_ok();
}
if let Key::Raw(_) = key {
return false;
}
#[allow(deprecated)]
let s = match key {
Key::Alt => "Alt_L",
Key::Backspace => "BackSpace",
Key::CapsLock => "Caps_Lock",
Key::Control => "Control_L",
Key::Delete => "Delete",
Key::DownArrow => "Down",
Key::End => "End",
Key::Escape => "Escape",
Key::F1 => "F1",
Key::F10 => "F10",
Key::F11 => "F11",
Key::F12 => "F12",
Key::F2 => "F2",
Key::F3 => "F3",
Key::F4 => "F4",
Key::F5 => "F5",
Key::F6 => "F6",
Key::F7 => "F7",
Key::F8 => "F8",
Key::F9 => "F9",
Key::Home => "Home",
Key::LeftArrow => "Left",
Key::Option => "Option",
Key::PageDown => "Page_Down",
Key::PageUp => "Page_Up",
Key::Return => "Return",
Key::RightArrow => "Right",
Key::Shift => "Shift_L",
Key::Space => "space",
Key::Tab => "Tab",
Key::UpArrow => "Up",
Key::Numpad0 => "0",
Key::Numpad1 => "1",
Key::Numpad2 => "2",
Key::Numpad3 => "3",
Key::Numpad4 => "4",
Key::Numpad5 => "5",
Key::Numpad6 => "6",
Key::Numpad7 => "7",
Key::Numpad8 => "8",
Key::Numpad9 => "9",
Key::Decimal => "KP_Decimal",
Key::Cancel => "Cancel",
Key::Clear => "Clear",
Key::Pause => "Pause",
Key::Kana => "Kana",
Key::Hangul => "Hangul",
Key::Hanja => "Hanja",
Key::Kanji => "Kanji",
Key::Select => "Select",
Key::Print => "Print",
Key::Execute => "Execute",
Key::Snapshot => "3270_PrintScreen",
Key::Insert => "Insert",
Key::Help => "Help",
Key::Separator => "KP_Separator",
Key::Scroll => "Scroll_Lock",
Key::NumLock => "Num_Lock",
Key::RWin => "Super_R",
Key::Apps => "Menu",
Key::Multiply => "KP_Multiply",
Key::Add => "KP_Add",
Key::Subtract => "KP_Subtract",
Key::Divide => "KP_Divide",
Key::Equals => "KP_Equal",
Key::NumpadEnter => "KP_Enter",
Key::RightShift => "Shift_R",
Key::RightControl => "Control_R",
Key::RightAlt => "Mode_switch",
Key::Command | Key::Super | Key::Windows | Key::Meta => "Super_L",
_ => {
return true;
}
};
log::info!("send pynput: {:?}", &s);
return self.tx.send((PyMsg::Str(s), is_press)).is_ok();
}
}
// impl MouseControllable for EnigoPynput {
// fn mouse_move_to(&mut self, _x: i32, _y: i32) {
// unimplemented!()
// }
// fn mouse_move_relative(&mut self, _x: i32, _y: i32) {
// unimplemented!()
// }
// fn mouse_down(&mut self, _button: MouseButton) -> crate::ResultType {
// unimplemented!()
// }
// fn mouse_up(&mut self, _button: MouseButton) {
// unimplemented!()
// }
// fn mouse_click(&mut self, _button: MouseButton) {
// unimplemented!()
// }
// fn mouse_scroll_x(&mut self, _length: i32) {
// unimplemented!()
// }
// fn mouse_scroll_y(&mut self, _length: i32) {
// unimplemented!()
// }
// }
// impl KeyboardControllable for EnigoPynput {
// fn get_key_state(&mut self, _key: Key) -> bool {
// unimplemented!()
// }
// fn key_sequence(&mut self, _sequence: &str) {
// unimplemented!()
// }
// fn key_down(&mut self, key: Key) -> crate::ResultType {
// let _ = self.send_pynput(&key, true);
// Ok(())
// }
// fn key_up(&mut self, key: Key) {
// let _ = self.send_pynput(&key, false);
// }
// fn key_click(&mut self, _key: Key) {
// unimplemented!()
// }
// }
static mut PYNPUT_EXIT: bool = false;
static mut PYNPUT_REDAY: bool = false;
static IPC_FILE: &'static str = "/tmp/RustDesk/pynput_service";
fn start_pynput_service(rx: mpsc::Receiver<(PyMsg, bool)>) {
let mut py = "./pynput_service.py".to_owned();
if !std::path::Path::new(&py).exists() {
py = "/usr/share/rustdesk/files/pynput_service.py".to_owned();
if !std::path::Path::new(&py).exists() {
py = "/usr/lib/rustdesk/pynput_service.py".to_owned();
if !std::path::Path::new(&py).exists() {
log::error!("{} not exits", py);
}
}
}
log::info!("pynput service: {}", py);
std::thread::spawn(move || {
let username = std::env::var("PYNPUT_USERNAME").unwrap_or("".to_owned());
let userid = std::env::var("PYNPUT_USERID").unwrap_or("".to_owned());
let status = if username.is_empty() {
std::process::Command::new("python3")
.arg(&py)
.arg(IPC_FILE)
.status()
.map(|x| x.success())
} else {
let mut status = Ok(true);
for i in 0..100 {
if i % 10 == 0 {
log::info!("#{} try to start pynput server", i);
}
status = std::process::Command::new("sudo")
.args(vec![
"-E",
&format!("XDG_RUNTIME_DIR=/run/user/{}", userid) as &str,
"-u",
&username,
"python3",
&py,
IPC_FILE,
])
.status()
.map(|x| x.success());
match status {
Ok(true) => break,
_ => {}
}
std::thread::sleep(std::time::Duration::from_millis(100));
}
status
};
log::info!(
"pynput server exit with username/id {}/{}: {:?}",
username,
userid,
status
);
unsafe {
PYNPUT_EXIT = true;
}
});
std::thread::spawn(move || {
for i in 0..300 {
std::thread::sleep(std::time::Duration::from_millis(100));
let mut conn = match std::os::unix::net::UnixStream::connect(IPC_FILE) {
Ok(conn) => conn,
Err(err) => {
if i % 15 == 0 {
log::warn!("Failed to connect to {}: {}", IPC_FILE, err);
}
continue;
}
};
if let Err(err) = conn.set_nonblocking(true) {
log::error!("Failed to set ipc nonblocking: {}", err);
return;
}
log::info!("Conntected to pynput server");
let d = std::time::Duration::from_millis(30);
unsafe {
PYNPUT_REDAY = true;
}
let mut buf = [0u8; 1024];
loop {
if unsafe { PYNPUT_EXIT } {
break;
}
match rx.recv_timeout(d) {
Ok((msg, is_press)) => {
let msg = match msg {
PyMsg::Char(chr) => {
format!("{}{}", if is_press { 'p' } else { 'r' }, chr)
}
PyMsg::Str(s) => format!("{}{}", if is_press { 'p' } else { 'r' }, s),
};
let n = msg.len();
buf[0] = n as _;
buf[1..(n + 1)].copy_from_slice(msg.as_bytes());
if let Err(err) = conn.write_all(&buf[..n + 1]) {
log::error!("Failed to write to ipc: {}", err);
break;
}
}
Err(err) => match err {
mpsc::RecvTimeoutError::Disconnected => {
log::error!("pynput sender disconnecte");
break;
}
_ => {}
},
}
}
unsafe {
PYNPUT_REDAY = false;
}
break;
}
});
}

View File

@ -370,4 +370,19 @@ impl KeyboardControllable for EnigoXdo {
}
}
}
fn key_sequence_parse(&mut self, sequence: &str)
where
Self: Sized,
{
self.key_sequence_parse_try(sequence)
.expect("Could not parse sequence");
}
fn key_sequence_parse_try(&mut self, sequence: &str) -> Result<(), crate::dsl::ParseError>
where
Self: Sized,
{
crate::dsl::eval(self, sequence)
}
}

View File

@ -1,5 +1,4 @@
use core_graphics;
// TODO(dustin): use only the things i need
use self::core_graphics::display::*;

View File

@ -1,9 +1,8 @@
use winapi;
use self::winapi::ctypes::c_int;
use self::winapi::shared::{basetsd::ULONG_PTR, minwindef::*, windef::*};
use self::winapi::um::winbase::*;
use self::winapi::um::winuser::*;
use winapi;
use crate::win::keycodes::*;
use crate::{Key, KeyboardControllable, MouseButton, MouseControllable};
@ -200,7 +199,7 @@ impl KeyboardControllable for Enigo {
fn key_down(&mut self, key: Key) -> crate::ResultType {
let code = self.key_to_keycode(key);
if code == 0 || code == 65535 {
return Err("".into());
return Err("".into());
}
let res = keybd_event(0, code, 0);
if res == 0 {
@ -227,7 +226,8 @@ impl KeyboardControllable for Enigo {
}
impl Enigo {
/// Gets the (width, height) of the main display in screen coordinates (pixels).
/// Gets the (width, height) of the main display in screen coordinates
/// (pixels).
///
/// # Example
///

View File

@ -105,6 +105,13 @@ message MouseEvent {
repeated ControlKey modifiers = 4;
}
enum KeyboardMode{
Legacy = 0;
Map = 1;
Translate = 2;
Auto = 3;
}
enum ControlKey {
Unknown = 0;
Alt = 1;
@ -198,6 +205,7 @@ message KeyEvent {
string seq = 6;
}
repeated ControlKey modifiers = 8;
KeyboardMode mode = 9;
}
message CursorData {

View File

@ -7,7 +7,7 @@ post_install() {
# do something here
cp /usr/share/rustdesk/files/rustdesk.service /etc/systemd/system/rustdesk.service
cp /usr/share/rustdesk/files/rustdesk.desktop /usr/share/applications/
sudo -H pip3 install pynput
sudo -H pip3 install
systemctl daemon-reload
systemctl enable rustdesk
systemctl start rustdesk

View File

@ -1,236 +0,0 @@
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
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 _update_keyboard_mapping(self):
"""Updates the keyboard mapping.
"""
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()
server_address = sys.argv[1]
if not os.path.exists(os.path.dirname(server_address)):
os.makedirs(os.path.dirname(server_address))
try:
os.unlink(server_address)
except OSError:
if os.path.exists(server_address):
raise
server = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
server.bind(server_address)
server.listen(1)
clientsocket, address = server.accept()
os.system('chmod a+rw %s' % server_address)
print("Got pynput connection")
def loop():
global keyboard
buf = []
while True:
data = clientsocket.recv(1024)
if not data:
print("Connection broken")
break
buf.extend(data)
while buf:
n = buf[0]
n = n + 1
if len(buf) < n:
break
msg = bytearray(buf[1:n]).decode("utf-8")
buf = buf[n:]
if len(msg) < 2:
continue
if msg[1] == "\0":
keyboard = MyController()
print("Keyboard reset")
continue
if len(msg) == 2:
name = msg[1]
else:
name = KeyCode._from_symbol(msg[1:])
if str(name) == "<0>":
continue
try:
if msg[0] == "p":
keyboard.press(name)
else:
keyboard.release(name)
except Exception as e:
print('[x] error key',e)
loop()
clientsocket.close()
server.close()

View File

@ -25,7 +25,6 @@ install $HBB/libsciter-gtk.so %{buildroot}/usr/lib/rustdesk/libsciter-gtk.so
install $HBB/rustdesk.service %{buildroot}/usr/share/rustdesk/files/
install $HBB/128x128@2x.png %{buildroot}/usr/share/rustdesk/files/rustdesk.png
install $HBB/rustdesk.desktop %{buildroot}/usr/share/rustdesk/files/
install $HBB/pynput_service.py %{buildroot}/usr/share/rustdesk/files/
%files
/usr/bin/rustdesk
@ -33,7 +32,6 @@ install $HBB/pynput_service.py %{buildroot}/usr/share/rustdesk/files/
/usr/share/rustdesk/files/rustdesk.service
/usr/share/rustdesk/files/rustdesk.png
/usr/share/rustdesk/files/rustdesk.desktop
/usr/share/rustdesk/files/pynput_service.py
%changelog
# let's skip this for now
@ -54,7 +52,6 @@ esac
%post
cp /usr/share/rustdesk/files/rustdesk.service /etc/systemd/system/rustdesk.service
cp /usr/share/rustdesk/files/rustdesk.desktop /usr/share/applications/
sudo -H pip3 install pynput
systemctl daemon-reload
systemctl enable rustdesk
systemctl start rustdesk

View File

@ -25,7 +25,6 @@ install $HBB/libsciter-gtk.so %{buildroot}/usr/lib/rustdesk/libsciter-gtk.so
install $HBB/rustdesk.service %{buildroot}/usr/share/rustdesk/files/
install $HBB/128x128@2x.png %{buildroot}/usr/share/rustdesk/files/rustdesk.png
install $HBB/rustdesk.desktop %{buildroot}/usr/share/rustdesk/files/
install $HBB/pynput_service.py %{buildroot}/usr/share/rustdesk/files/
%files
/usr/bin/rustdesk
@ -33,7 +32,6 @@ install $HBB/pynput_service.py %{buildroot}/usr/share/rustdesk/files/
/usr/share/rustdesk/files/rustdesk.service
/usr/share/rustdesk/files/rustdesk.png
/usr/share/rustdesk/files/rustdesk.desktop
/usr/share/rustdesk/files/pynput_service.py
/usr/share/rustdesk/files/__pycache__/*
%changelog
@ -55,7 +53,6 @@ esac
%post
cp /usr/share/rustdesk/files/rustdesk.service /etc/systemd/system/rustdesk.service
cp /usr/share/rustdesk/files/rustdesk.desktop /usr/share/applications/
sudo -H pip3 install pynput
systemctl daemon-reload
systemctl enable rustdesk
systemctl start rustdesk

View File

@ -1,2 +1,2 @@
[toolchain]
channel = "1.62.0"
channel = "1.62.0"

View File

@ -108,13 +108,7 @@ parts:
plugin: nil
override-pull: |
mkdir -p ${SNAPCRAFT_PART_INSTALL}/usr/share/rustdesk/files/systemd/
cp ${SNAPCRAFT_PART_SRC}/../../rustdesk/src/pynput_service.py ${SNAPCRAFT_PART_INSTALL}/usr/share/rustdesk/files/
cp ${SNAPCRAFT_PART_SRC}/../../rustdesk/src/rustdesk.service ${SNAPCRAFT_PART_INSTALL}/usr/share/rustdesk/files/systemd/
python3-deps:
plugin: python
python-packages:
- pynput == 1.7.6
layout:
/usr/share/rustdesk:

View File

@ -154,7 +154,8 @@ impl Client {
return Err(err);
}
}
Ok(x) => Ok(x),
Ok(x) => {
Ok(x)},
}
}
@ -1707,7 +1708,7 @@ pub enum Data {
}
/// Keycode for key events.
#[derive(Clone)]
#[derive(Clone, Debug)]
pub enum Key {
ControlKey(ControlKey),
Chr(u32),

View File

@ -666,3 +666,14 @@ pub fn make_privacy_mode_msg(state: back_notification::PrivacyModeState) -> Mess
msg_out.set_misc(misc);
msg_out
}
#[cfg(not(target_os = "linux"))]
lazy_static::lazy_static! {
pub static ref IS_X11: Mutex<bool> = Mutex::new(false);
}
#[cfg(target_os = "linux")]
lazy_static::lazy_static! {
pub static ref IS_X11: Mutex<bool> = Mutex::new("x11" == hbb_common::platform::linux::get_display_server());
}

View File

@ -475,4 +475,4 @@ pub fn make_fd_flutter(id: i32, entries: &Vec<FileEntry>, only_count: bool) -> S
}
m.insert("total_size".into(), json!(n as f64));
serde_json::to_string(&m).unwrap_or("".into())
}
}

View File

@ -17,7 +17,7 @@ use crate::flutter::{self, SESSIONS};
use crate::start_server;
use crate::ui_interface;
#[cfg(not(any(target_os = "android", target_os = "ios")))]
use crate::ui_interface::{change_id, check_connect_status, is_ok_change_id};
use crate::ui_interface::change_id;
use crate::ui_interface::{
check_mouse_time, check_super_user_permission, discover, forget_password, get_api_server,
get_app_name, get_async_job_status, get_connect_status, get_fav, get_id, get_lan_peers,
@ -104,9 +104,9 @@ pub fn stop_global_event_stream(app_type: String) {
.remove(&app_type);
}
pub fn host_stop_system_key_propagate(stopped: bool) {
pub fn host_stop_system_key_propagate(_stopped: bool) {
#[cfg(windows)]
crate::platform::windows::stop_system_key_propagate(stopped);
crate::platform::windows::stop_system_key_propagate(_stopped);
}
// FIXME: -> ResultType<()> cannot be parsed by frb_codegen
@ -233,6 +233,28 @@ pub fn session_switch_display(id: String, value: i32) {
}
}
pub fn session_handle_flutter_key_event(
id: String,
name: String,
keycode: i32,
scancode: i32,
down_or_up: bool,
) {
if let Some(session) = SESSIONS.read().unwrap().get(&id) {
session.handle_flutter_key_event(&name, keycode, scancode, down_or_up);
}
}
pub fn session_enter_or_leave(id: String, enter: bool) {
if let Some(session) = SESSIONS.read().unwrap().get(&id) {
if enter {
session.enter();
} else {
session.leave();
}
}
}
pub fn session_input_key(
id: String,
name: String,
@ -274,6 +296,19 @@ pub fn session_get_peer_option(id: String, name: String) -> String {
"".to_string()
}
pub fn session_get_keyboard_name(id: String) -> String {
if let Some(session) = SESSIONS.read().unwrap().get(&id) {
return session.get_keyboard_mode();
}
"legacy".to_string()
}
pub fn session_set_keyboard_mode(id: String, keyboard_mode: String) {
if let Some(session) = SESSIONS.read().unwrap().get(&id) {
session.save_keyboard_mode(keyboard_mode);
}
}
pub fn session_input_os_password(id: String, value: String) {
if let Some(session) = SESSIONS.read().unwrap().get(&id) {
session.input_os_password(value, true);

View File

@ -290,6 +290,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Ignore Battery Optimizations", "忽略电池优化"),
("android_open_battery_optimizations_tip", "如需关闭此功能请在接下来的RustDesk应用设置页面中找到并进入 [电源] 页面,取消勾选 [不受限制]"),
("Connection not allowed", "对方不允许连接"),
("Legacy mode", "传统模式"),
("Map mode", "11传输"),
("Translate mode", "翻译模式"),
("Use temporary password", "使用临时密码"),
("Use permanent password", "使用固定密码"),
("Use both passwords", "同时使用两种密码"),

View File

@ -290,6 +290,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Ignore Battery Optimizations", ""),
("android_open_battery_optimizations_tip", ""),
("Connection not allowed", ""),
("Legacy mode", ""),
("Map mode", ""),
("Translate mode", ""),
("Use temporary password", ""),
("Use permanent password", ""),
("Use both passwords", ""),

View File

@ -290,6 +290,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Ignore Battery Optimizations", ""),
("android_open_battery_optimizations_tip", ""),
("Connection not allowed", ""),
("Legacy mode", ""),
("Map mode", ""),
("Translate mode", ""),
("Use temporary password", ""),
("Use permanent password", ""),
("Use both passwords", ""),

View File

@ -290,6 +290,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Ignore Battery Optimizations", "Batterieoptimierung ignorieren"),
("android_open_battery_optimizations_tip", "Möchten Sie die Batterieopimierungs-Einstellungen öffnen?"),
("Connection not allowed", "Verbindung abgelehnt"),
("Legacy mode", ""),
("Map mode", ""),
("Translate mode", ""),
("Use temporary password", "Temporäres Passwort verwenden"),
("Use permanent password", "Dauerhaftes Passwort verwenden"),
("Use both passwords", "Beide Passwörter verwenden"),

View File

@ -290,6 +290,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Ignore Battery Optimizations", ""),
("android_open_battery_optimizations_tip", ""),
("Connection not allowed", ""),
("Legacy mode", ""),
("Map mode", ""),
("Translate mode", ""),
("Use temporary password", ""),
("Use permanent password", ""),
("Use both passwords", ""),

View File

@ -289,6 +289,22 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Keep RustDesk background service", "Dejar RustDesk como Servicio en 2do plano"),
("Ignore Battery Optimizations", "Ignorar optimizacioens de bateria"),
("android_open_battery_optimizations_tip", ""),
("Random Password After Session", ""),
("Keep", ""),
("Update", ""),
("Disable", ""),
("Onetime Password", ""),
("Verification Method", ""),
("Enable security password", ""),
("Enable random password", ""),
("Enable onetime password", ""),
("Disable onetime password", ""),
("Activate onetime password", ""),
("Set security password", ""),
("Connection not allowed", ""),
("Legacy mode", ""),
("Map mode", ""),
("Translate mode", ""),
("Connection not allowed", "Conexión no disponible"),
("Use temporary password", "Usar contraseña temporal"),
("Use permanent password", "Usar contraseña permamente"),

View File

@ -290,6 +290,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Ignore Battery Optimizations", ""),
("android_open_battery_optimizations_tip", ""),
("Connection not allowed", ""),
("Legacy mode", ""),
("Map mode", ""),
("Translate mode", ""),
("Use temporary password", ""),
("Use permanent password", ""),
("Use both passwords", ""),

View File

@ -290,6 +290,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Ignore Battery Optimizations", ""),
("android_open_battery_optimizations_tip", ""),
("Connection not allowed", ""),
("Legacy mode", ""),
("Map mode", ""),
("Translate mode", ""),
("Use temporary password", ""),
("Use permanent password", ""),
("Use both passwords", ""),

View File

@ -289,6 +289,22 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Keep RustDesk background service", "Pertahankan RustDesk berjalan pada background service"),
("Ignore Battery Optimizations", "Abaikan Pengoptimalan Baterai"),
("android_open_battery_optimizations_tip", ""),
("Random Password After Session", ""),
("Keep", ""),
("Update", ""),
("Disable", ""),
("Onetime Password", ""),
("Verification Method", ""),
("Enable security password", ""),
("Enable random password", ""),
("Enable onetime password", ""),
("Disable onetime password", ""),
("Activate onetime password", ""),
("Set security password", ""),
("Connection not allowed", ""),
("Legacy mode", ""),
("Map mode", ""),
("Translate mode", ""),
("Connection not allowed", "Koneksi tidak dijinkan"),
("Use temporary password", "Gunakan kata sandi sementara"),
("Use permanent password", "Gunakan kata sandi permanaen"),

View File

@ -318,5 +318,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Insecure Connection", "Connessione insicura"),
("Scale original", "Scala originale"),
("Scale adaptive", "Scala adattiva"),
("Legacy mode", ""),
("Map mode", ""),
("Translate mode", ""),
].iter().cloned().collect();
}

View File

@ -288,6 +288,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Ignore Battery Optimizations", "バッテリーの最適化を無効にする"),
("android_open_battery_optimizations_tip", "この機能を使わない場合は、次のRestDeskアプリ設定ページから「バッテリー」に進み、「制限なし」の選択を外してください"),
("Connection not allowed", "接続が許可されていません"),
("Legacy mode", ""),
("Map mode", ""),
("Translate mode", ""),
("Use temporary password", "使い捨てのパスワードを使用"),
("Use permanent password", "固定のパスワードを使用"),
("Use both passwords", "どちらのパスワードも使用"),

View File

@ -290,6 +290,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Ignore Battery Optimizations", ""),
("android_open_battery_optimizations_tip", ""),
("Connection not allowed", ""),
("Legacy mode", ""),
("Map mode", ""),
("Translate mode", ""),
("Use temporary password", ""),
("Use permanent password", ""),
("Use both passwords", ""),

View File

@ -290,6 +290,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Ignore Battery Optimizations", "Игнорировать оптимизацию батареи"),
("android_open_battery_optimizations_tip", "Перейдите на следующую страницу настроек "),
("Connection not allowed", "Подключение не разрешено"),
("Legacy mode", ""),
("Map mode", ""),
("Translate mode", ""),
("Use temporary password", "Использовать временный пароль"),
("Use permanent password", "Использовать постоянный пароль"),
("Use both passwords", "Использовать оба пароля"),

View File

@ -290,6 +290,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Ignore Battery Optimizations", ""),
("android_open_battery_optimizations_tip", ""),
("Connection not allowed", ""),
("Legacy mode", ""),
("Map mode", ""),
("Translate mode", ""),
("Use temporary password", ""),
("Use permanent password", ""),
("Use both passwords", ""),

View File

@ -290,6 +290,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Ignore Battery Optimizations", ""),
("android_open_battery_optimizations_tip", ""),
("Connection not allowed", ""),
("Legacy mode", ""),
("Map mode", ""),
("Translate mode", ""),
("Use temporary password", ""),
("Use permanent password", ""),
("Use both passwords", ""),

View File

@ -289,6 +289,22 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Keep RustDesk background service", ""),
("Ignore Battery Optimizations", ""),
("android_open_battery_optimizations_tip", ""),
("Random Password After Session", ""),
("Keep", ""),
("Update", ""),
("Disable", ""),
("Onetime Password", ""),
("Verification Method", ""),
("Enable security password", ""),
("Enable random password", ""),
("Enable onetime password", ""),
("Disable onetime password", ""),
("Activate onetime password", ""),
("Set security password", ""),
("Connection not allowed", ""),
("Legacy mode", ""),
("Map mode", ""),
("Translate mode", ""),
("Connection not allowed", "bağlantıya izin verilmedi"),
("Use temporary password", "Geçici şifre kullan"),
("Use permanent password", "Kalıcı şifre kullan"),

View File

@ -290,6 +290,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Ignore Battery Optimizations", "忽略電池優化"),
("android_open_battery_optimizations_tip", "如需關閉此功能請在接下來的RustDesk應用設置頁面中找到並進入 [電源] 頁面,取消勾選 [不受限制]"),
("Connection not allowed", "對方不允許連接"),
("Legacy mode", "傳統模式"),
("Map mode", "11傳輸"),
("Translate mode", "翻譯模式"),
("Use temporary password", "使用臨時密碼"),
("Use permanent password", "使用固定密碼"),
("Use both passwords", "同時使用兩種密碼"),

View File

@ -290,6 +290,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Ignore Battery Optimizations", "Bỏ qua các tối ưu pin"),
("android_open_battery_optimizations_tip", "Nếu bạn muốn tắt tính năng này, vui lòng chuyển đến trang cài đặt ứng dụng RustDesk tiếp theo, tìm và nhập [Pin], Bỏ chọn [Không hạn chế]"),
("Connection not allowed", "Kết nối không đuợc phép"),
("Legacy mode", ""),
("Map mode", ""),
("Translate mode", ""),
("Use temporary password", "Sử dụng mật khẩu tạm thời"),
("Use permanent password", "Sử dụng mật khẩu vĩnh viễn"),
("Use both passwords", "Sử dụng cả hai mật khẩu"),

View File

@ -1,4 +1,3 @@
use std::collections::HashMap;
use std::{
net::SocketAddr,
sync::{
@ -10,12 +9,10 @@ use std::{
use uuid::Uuid;
use hbb_common::config::DiscoveryPeer;
use hbb_common::tcp::FramedStream;
use hbb_common::{
allow_err,
anyhow::bail,
config,
config::{Config, REG_INTERVAL, RENDEZVOUS_PORT, RENDEZVOUS_TIMEOUT},
futures::future::join_all,
log,
@ -571,6 +568,7 @@ pub fn get_mac() -> String {
"".to_owned()
}
#[allow(dead_code)]
fn lan_discovery() -> ResultType<()> {
let addr = SocketAddr::from(([0, 0, 0, 0], get_broadcast_port()));
let socket = std::net::UdpSocket::bind(addr)?;
@ -640,7 +638,7 @@ pub async fn query_online_states<F: FnOnce(Vec<String>, Vec<String>)>(ids: Vec<S
}
async fn create_online_stream() -> ResultType<FramedStream> {
let (mut rendezvous_server, servers, contained) = crate::get_rendezvous_server(1_000).await;
let (rendezvous_server, _servers, _contained) = crate::get_rendezvous_server(1_000).await;
let tmp: Vec<&str> = rendezvous_server.split(":").collect();
if tmp.len() != 2 {
bail!("Invalid server address: {}", rendezvous_server);

View File

@ -448,11 +448,12 @@ impl Connection {
handle_mouse(&msg, id);
}
MessageInput::Key((mut msg, press)) => {
if press {
// todo: press and down have similar meanings.
if press && msg.mode.unwrap() == KeyboardMode::Legacy {
msg.down = true;
}
handle_key(&msg);
if press {
if press && msg.mode.unwrap() == KeyboardMode::Legacy {
msg.down = false;
handle_key(&msg);
}
@ -632,7 +633,7 @@ impl Connection {
let mut pi = PeerInfo {
username: username.clone(),
conn_id: self.inner.id,
version: crate::VERSION.to_owned(),
version: VERSION.to_owned(),
..Default::default()
};

View File

@ -1,8 +1,10 @@
use super::*;
use crate::common::IS_X11;
#[cfg(target_os = "macos")]
use dispatch::Queue;
use enigo::{Enigo, Key, KeyboardControllable, MouseButton, MouseControllable};
use hbb_common::{config::COMPRESS_LEVEL, protobuf::EnumOrUnknown};
use rdev::{simulate, EventType, Key as RdevKey};
use std::{
convert::TryFrom,
sync::atomic::{AtomicBool, Ordering},
@ -21,8 +23,6 @@ impl super::service::Reset for StateCursor {
*self = Default::default();
crate::platform::reset_input_cache();
fix_key_down_timeout(true);
#[cfg(target_os = "linux")]
ENIGO.lock().unwrap().reset();
}
}
@ -145,7 +145,8 @@ fn run_cursor(sp: MouseCursorService, state: &mut StateCursor) -> ResultType<()>
msg = cached.clone();
} else {
let mut data = crate::get_cursor_data(hcursor)?;
data.colors = hbb_common::compress::compress(&data.colors[..], COMPRESS_LEVEL).into();
data.colors =
hbb_common::compress::compress(&data.colors[..], COMPRESS_LEVEL).into();
let mut tmp = Message::new();
tmp.set_cursor_data(data);
msg = Arc::new(tmp);
@ -166,13 +167,6 @@ fn run_cursor(sp: MouseCursorService, state: &mut StateCursor) -> ResultType<()>
lazy_static::lazy_static! {
static ref ENIGO: Arc<Mutex<Enigo>> = {
#[cfg(target_os = "linux")]
{
if crate::platform::is_root() {
std::env::set_var("PYNPUT_USERNAME", crate::platform::linux::get_active_username());
std::env::set_var("PYNPUT_USERID", crate::platform::linux::get_active_userid());
}
}
Arc::new(Mutex::new(Enigo::new()))
};
static ref KEYS_DOWN: Arc<Mutex<HashMap<u64, Instant>>> = Default::default();
@ -466,10 +460,14 @@ pub async fn lock_screen() {
// loginctl lock-session also not work, they both work run rustdesk from cmd
std::thread::spawn(|| {
let mut key_event = KeyEvent::new();
key_event.down = true;
key_event.set_chr('l' as _);
key_event.modifiers.push(ControlKey::Meta.into());
key_event.mode = KeyboardMode::Legacy.into();
key_event.down = true;
handle_key(&key_event);
key_event.down = false;
handle_key(&key_event);
});
@ -477,10 +475,13 @@ pub async fn lock_screen() {
// CGSession -suspend not real lock screen, it is user switch
std::thread::spawn(|| {
let mut key_event = KeyEvent::new();
key_event.down = true;
key_event.set_chr('q' as _);
key_event.modifiers.push(ControlKey::Meta.into());
key_event.modifiers.push(ControlKey::Control.into());
key_event.mode = KeyboardMode::Legacy.into();
key_event.down = true;
handle_key(&key_event);
key_event.down = false;
handle_key(&key_event);
@ -597,25 +598,125 @@ pub fn handle_key(evt: &KeyEvent) {
handle_key_(evt);
}
fn handle_key_(evt: &KeyEvent) {
if EXITING.load(Ordering::SeqCst) {
fn rdev_key_down_or_up(key: RdevKey, down_or_up: bool) {
let event_type = match down_or_up {
true => EventType::KeyPress(key),
false => EventType::KeyRelease(key),
};
let delay = std::time::Duration::from_millis(20);
match simulate(&event_type) {
Ok(()) => (),
Err(_simulate_error) => {
log::error!("Could not send {:?}", &event_type);
}
}
// Let ths OS catchup (at least MacOS)
std::thread::sleep(delay);
}
fn rdev_key_click(key: RdevKey) {
rdev_key_down_or_up(key, true);
rdev_key_down_or_up(key, false);
}
fn sync_status(evt: &KeyEvent) -> (bool, bool) {
let mut en = ENIGO.lock().unwrap();
// remote caps status
let caps_locking = evt
.modifiers
.iter()
.position(|&r| r == ControlKey::CapsLock.into())
.is_some();
// remote numpad status
let num_locking = evt
.modifiers
.iter()
.position(|&r| r == ControlKey::NumLock.into())
.is_some();
let click_capslock = (caps_locking && !en.get_key_state(enigo::Key::CapsLock))
|| (!caps_locking && en.get_key_state(enigo::Key::CapsLock));
let click_numlock = (num_locking && !en.get_key_state(enigo::Key::NumLock))
|| (!num_locking && en.get_key_state(enigo::Key::NumLock));
return (click_capslock, click_numlock);
}
fn map_keyboard_mode(evt: &KeyEvent) {
// map mode(1): Send keycode according to the peer platform.
let (click_capslock, click_numlock) = sync_status(evt);
// Wayland
#[cfg(target_os = "linux")]
if !*IS_X11.lock().unwrap() {
let mut en = ENIGO.lock().unwrap();
let code = evt.chr() as u16;
#[cfg(not(target_os = "macos"))]
if click_capslock {
en.key_click(enigo::Key::CapsLock);
}
#[cfg(not(target_os = "macos"))]
if click_numlock {
en.key_click(enigo::Key::NumLock);
}
#[cfg(target_os = "macos")]
en.key_down(enigo::Key::CapsLock);
if evt.down {
en.key_down(enigo::Key::Raw(code)).ok();
} else {
en.key_up(enigo::Key::Raw(code));
}
return;
}
#[cfg(not(target_os = "macos"))]
if click_capslock {
rdev_key_click(RdevKey::CapsLock);
}
#[cfg(not(target_os = "macos"))]
if click_numlock {
rdev_key_click(RdevKey::NumLock);
}
#[cfg(target_os = "macos")]
if evt.down && click_capslock {
rdev_key_down_or_up(RdevKey::CapsLock, evt.down);
}
rdev_key_down_or_up(RdevKey::Unknown(evt.chr()), evt.down);
return;
}
fn legacy_keyboard_mode(evt: &KeyEvent) {
let (click_capslock, click_numlock) = sync_status(evt);
#[cfg(windows)]
crate::platform::windows::try_change_desktop();
let mut en = ENIGO.lock().unwrap();
if click_capslock {
en.key_click(Key::CapsLock);
}
if click_numlock {
en.key_click(Key::NumLock);
}
// disable numlock if press home etc when numlock is on,
// because we will get numpad value (7,8,9 etc) if not
#[cfg(windows)]
let mut disable_numlock = false;
#[cfg(target_os = "macos")]
en.reset_flag();
// When long-pressed the command key, then press and release
// the Tab key, there should be CGEventFlagCommand in the flag.
#[cfg(target_os = "macos")]
for ck in evt.modifiers.iter() {
if let Some(key) = KEY_MAP.get(&ck.value()) {
en.add_flag(key);
}
}
#[cfg(not(target_os = "macos"))]
let mut to_release = Vec::new();
#[cfg(not(target_os = "macos"))]
let mut has_cap = false;
#[cfg(windows)]
let mut has_numlock = false;
if evt.down {
let ck = if let Some(key_event::Union::ControlKey(ck)) = evt.union {
ck.value()
@ -637,40 +738,16 @@ fn handle_key_(evt: &KeyEvent) {
continue;
}
}
#[cfg(target_os = "macos")]
en.add_flag(key);
#[cfg(not(target_os = "macos"))]
{
if key == &Key::CapsLock {
has_cap = true;
} else if key == &Key::NumLock {
#[cfg(windows)]
{
has_numlock = true;
}
} else {
if !get_modifier_state(key.clone(), &mut en) {
en.key_down(key.clone()).ok();
modifier_sleep();
to_release.push(key);
}
}
if !get_modifier_state(key.clone(), &mut en) {
en.key_down(key.clone()).ok();
modifier_sleep();
to_release.push(key);
}
}
}
}
#[cfg(not(target_os = "macos"))]
if has_cap != en.get_key_state(Key::CapsLock) {
en.key_down(Key::CapsLock).ok();
en.key_up(Key::CapsLock);
}
#[cfg(windows)]
if crate::common::valid_for_numlock(evt) {
if has_numlock != en.get_key_state(Key::NumLock) {
en.key_down(Key::NumLock).ok();
en.key_up(Key::NumLock);
}
}
match evt.union {
Some(key_event::Union::ControlKey(ck)) => {
if let Some(key) = KEY_MAP.get(&ck.value()) {
@ -683,7 +760,7 @@ fn handle_key_(evt: &KeyEvent) {
}
}
if evt.down {
allow_err!(en.key_down(key.clone()));
en.key_down(key.clone()).ok();
KEYS_DOWN
.lock()
.unwrap()
@ -719,6 +796,10 @@ fn handle_key_(evt: &KeyEvent) {
en.key_sequence(&x);
}
}
KEYS_DOWN
.lock()
.unwrap()
.insert(chr as u64 + KEY_CHAR_START, Instant::now());
} else {
en.key_up(get_layout(chr));
KEYS_DOWN
@ -741,10 +822,26 @@ fn handle_key_(evt: &KeyEvent) {
for key in to_release {
en.key_up(key.clone());
}
#[cfg(windows)]
if disable_numlock {
en.key_down(Key::NumLock).ok();
en.key_up(Key::NumLock);
}
fn handle_key_(evt: &KeyEvent) {
if EXITING.load(Ordering::SeqCst) {
return;
}
match evt.mode.unwrap() {
KeyboardMode::Legacy => {
legacy_keyboard_mode(evt);
}
KeyboardMode::Map => {
map_keyboard_mode(evt);
}
KeyboardMode::Translate => {
legacy_keyboard_mode(evt);
}
_ => {
legacy_keyboard_mode(evt);
}
}
}
@ -759,3 +856,52 @@ async fn send_sas() -> ResultType<()> {
timeout(1000, stream.send(&crate::ipc::Data::SAS)).await??;
Ok(())
}
#[cfg(test)]
mod test {
use super::*;
use rdev::{listen, Event, EventType, Key};
use std::sync::mpsc;
#[test]
fn test_handle_key() {
// listen
let (tx, rx) = mpsc::channel();
std::thread::spawn(move || {
std::env::set_var("KEYBOARD_ONLY", "y");
let func = move |event: Event| {
tx.send(event).ok();
};
if let Err(error) = listen(func) {
println!("Error: {:?}", error);
}
});
// set key/char base on char
let mut evt = KeyEvent::new();
evt.set_chr(66);
evt.mode = KeyboardMode::Legacy.into();
evt.modifiers.push(ControlKey::CapsLock.into());
// press
evt.down = true;
handle_key(&evt);
if let Ok(listen_evt) = rx.recv() {
assert_eq!(listen_evt.event_type, EventType::KeyPress(Key::Num1))
}
// release
evt.down = false;
handle_key(&evt);
if let Ok(listen_evt) = rx.recv() {
assert_eq!(listen_evt.event_type, EventType::KeyRelease(Key::Num1))
}
}
#[test]
fn test_get_key_state() {
let mut en = ENIGO.lock().unwrap();
println!(
"[*] test_get_key_state: {:?}",
en.get_key_state(enigo::Key::NumLock)
);
}
}

View File

@ -350,6 +350,14 @@ pub mod service {
DataKeyboard::Sequence(_seq) => {
// ignore
}
DataKeyboard::KeyDown(enigo::Key::Raw(code)) => {
let down_event = InputEvent::new(EventType::KEY, *code - 8, 1);
allow_err!(keyboard.emit(&[down_event]));
}
DataKeyboard::KeyUp(enigo::Key::Raw(code)) => {
let down_event = InputEvent::new(EventType::KEY, *code - 8, 0);
allow_err!(keyboard.emit(&[down_event]));
}
DataKeyboard::KeyDown(key) => {
if let Ok(k) = map_key(key) {
let down_event = InputEvent::new(EventType::KEY, k.code(), 1);
@ -378,6 +386,14 @@ pub mod service {
false
}
}
} else if enigo::Key::NumLock == *key {
match keyboard.get_led_state() {
Ok(leds) => leds.contains(evdev::LedType::LED_NUML),
Err(_e) => {
// log::debug!("Failed to get led state {}", &_e);
false
}
}
} else {
match keyboard.get_key_state() {
Ok(keys) => match key {
@ -393,7 +409,6 @@ pub mod service {
keys.contains(evdev::Key::KEY_LEFTALT)
|| keys.contains(evdev::Key::KEY_RIGHTALT)
}
enigo::Key::NumLock => keys.contains(evdev::Key::KEY_NUMLOCK),
enigo::Key::Meta => {
keys.contains(evdev::Key::KEY_LEFTMETA)
|| keys.contains(evdev::Key::KEY_RIGHTMETA)

View File

@ -9,17 +9,16 @@ use sciter::Value;
use hbb_common::{
allow_err,
config::{self, Config, LocalConfig, PeerConfig, RENDEZVOUS_PORT, RENDEZVOUS_TIMEOUT},
config::{self, Config, PeerConfig, RENDEZVOUS_PORT, RENDEZVOUS_TIMEOUT},
futures::future::join_all,
log,
protobuf::Message as _,
rendezvous_proto::*,
sleep,
tcp::FramedStream,
tokio::{self, sync::mpsc, time},
};
use crate::common::{get_app_name, SOFTWARE_UPDATE_URL};
use crate::common::{get_app_name};
use crate::ipc;
use crate::ui_interface::{
check_mouse_time, closing, create_shortcut, current_is_wayland, fix_login_wayland,
@ -59,6 +58,27 @@ lazy_static::lazy_static! {
struct UIHostHandler;
fn check_connect_status(
reconnect: bool,
) -> (
Arc<Mutex<Status>>,
Arc<Mutex<HashMap<String, String>>>,
mpsc::UnboundedSender<ipc::Data>,
Arc<Mutex<String>>,
) {
let status = Arc::new(Mutex::new((0, false, 0, "".to_owned())));
let options = Arc::new(Mutex::new(Config::get_options()));
let cloned = status.clone();
let cloned_options = options.clone();
let (tx, rx) = mpsc::unbounded_channel::<ipc::Data>();
let password = Arc::new(Mutex::new(String::default()));
let cloned_password = password.clone();
std::thread::spawn(move || {
crate::ui_interface::check_connect_status_(reconnect, rx)
});
(status, options, tx, password)
}
pub fn start(args: &mut [String]) {
#[cfg(target_os = "macos")]
if args.len() == 1 && args[0] == "--server" {
@ -87,7 +107,7 @@ pub fn start(args: &mut [String]) {
}
#[cfg(windows)]
if args.len() > 0 && args[0] == "--tray" {
let options = crate::ui_interface::check_connect_status(false).1;
let options = check_connect_status(false).1;
crate::tray::start_tray(options);
return;
}

View File

@ -155,6 +155,7 @@ var svg_send = <svg viewBox="0 0 448 448">
var svg_chat = <svg viewBox="0 0 511.07 511.07">
<path d="m74.39 480.54h-36.213l25.607-25.607c13.807-13.807 22.429-31.765 24.747-51.246-36.029-23.644-62.375-54.751-76.478-90.425-14.093-35.647-15.864-74.888-5.121-113.48 12.89-46.309 43.123-88.518 85.128-118.85 45.646-32.963 102.47-50.387 164.33-50.387 77.927 0 143.61 22.389 189.95 64.745 41.744 38.159 64.734 89.63 64.734 144.93 0 26.868-5.471 53.011-16.26 77.703-11.165 25.551-27.514 48.302-48.593 67.619-46.399 42.523-112.04 65-189.83 65-28.877 0-59.01-3.855-85.913-10.929-25.465 26.123-59.972 40.929-96.086 40.929zm182-420c-124.04 0-200.15 73.973-220.56 147.28-19.284 69.28 9.143 134.74 76.043 175.12l7.475 4.511-0.23 8.727c-0.456 17.274-4.574 33.912-11.945 48.952 17.949-6.073 34.236-17.083 46.99-32.151l6.342-7.493 9.405 2.813c26.393 7.894 57.104 12.241 86.477 12.241 154.37 0 224.68-93.473 224.68-180.32 0-46.776-19.524-90.384-54.976-122.79-40.713-37.216-99.397-56.888-169.71-56.888z"/>
</svg>;
var svg_keyboard = <svg viewBox="0 0 511.07 511.07"><path d="M491.979 217.631H110.205a48.41 48.41 0 0 1 .637-4.061c4.408-21.755 23.676-38.152 46.71-38.152h149.314c39.306 0 71.282-32.246 71.282-71.552 0-11.28-9.145-20.56-20.426-20.56s-20.426 9.077-20.426 20.359c0 3.419-.575 6.941-1.619 10.01-4.082 11.998-15.451 20.893-28.812 20.893H157.553c-46.995 0-85.535 36.766-88.331 83.064H20.021C8.739 217.631 0 226.296 0 237.578v170.345c0 11.28 8.739 20.773 20.021 20.773H491.98c11.28 0 20.021-9.492 20.021-20.773V237.578c-.001-11.282-8.74-19.947-20.022-19.947zm-20.83 170.213H40.851V258.482h430.298v129.362z"/><path d="M113.021 273.461H89.872c-11.28 0-20.426 9.145-20.426 20.426s9.145 20.426 20.426 20.426h23.149c11.28 0 20.426-9.145 20.426-20.426s-9.145-20.426-20.426-20.426zM190.638 273.461h-23.149c-11.28 0-20.426 9.145-20.426 20.426s9.145 20.426 20.426 20.426h23.149c11.28 0 20.426-9.145 20.426-20.426s-9.145-20.426-20.426-20.426zM268.255 273.461h-23.149c-11.28 0-20.426 9.145-20.426 20.426s9.145 20.426 20.426 20.426h23.149c11.28 0 20.426-9.145 20.426-20.426s-9.145-20.426-20.426-20.426zM345.872 273.461h-23.149c-11.28 0-20.426 9.145-20.426 20.426s9.145 20.426 20.426 20.426h23.149c11.28 0 20.426-9.145 20.426-20.426s-9.145-20.426-20.426-20.426zM423.489 273.461H400.34c-11.28 0-20.426 9.145-20.426 20.426s9.145 20.426 20.426 20.426h23.149c11.28 0 20.426-9.145 20.426-20.426s-9.145-20.426-20.426-20.426zM113.021 325.206H89.872c-11.28 0-20.426 9.145-20.426 20.426s9.145 20.425 20.426 20.425h23.149c11.28 0 20.426-9.145 20.426-20.425s-9.145-20.426-20.426-20.426zM423.489 325.206H400.34c-11.28 0-20.426 9.145-20.426 20.426s9.145 20.425 20.426 20.425h23.149c11.28 0 20.426-9.145 20.426-20.425s-9.145-20.426-20.426-20.426zM345.872 329.291H167.489c-11.28 0-20.426 9.145-20.426 20.426s9.145 20.426 20.426 20.426h178.383c11.28 0 20.426-9.145 20.426-20.426s-9.145-20.426-20.426-20.426z"/></svg>;
function scrollToBottom(el) {
var y = el.box(#height, #content) - el.box(#height, #client);

View File

@ -139,11 +139,22 @@ class Header: Reactor.Component {
<span #chat>{svg_chat}</span>
<span #action>{svg_action}</span>
<span #display>{svg_display}</span>
<span #keyboard>{svg_keyboard}</span>
{this.renderKeyboardPop()}
{this.renderDisplayPop()}
{this.renderActionPop()}
</div>;
}
function renderKeyboardPop(){
return <popup>
<menu.context #keyboard-options>
<li #legacy><span>{svg_checkmark}</span>{translate('Legacy mode')}</li>
<li #map><span>{svg_checkmark}</span>{translate('Map mode')}</li>
</menu>
</popup>;
}
function renderDisplayPop() {
var codecs = handler.supported_hwcodec();
var show_codec = handler.has_hwcodec() && (codecs[0] || codecs[1]);
@ -263,6 +274,11 @@ class Header: Reactor.Component {
me.popup(menu);
}
event click $(#keyboard) (_, me) {
var menu = $(menu#keyboard-options);
me.popup(menu);
}
event click $(#screen) (_, me) {
if (pi.current_display == me.index) return;
handler.switch_display(me.index);
@ -352,6 +368,17 @@ class Header: Reactor.Component {
toggleMenuState();
}
}
event click $(menu#keyboard-options>li) (_, me) {
if (me.id == "legacy") {
handler.save_keyboard_mode("legacy");
} else if (me.id == "map") {
handler.save_keyboard_mode("map");
} else if (me.id == "translate") {
handler.save_keyboard_mode("translate");
}
toggleMenuState()
}
}
function handle_custom_image_quality() {
@ -375,12 +402,17 @@ function toggleMenuState() {
var s = handler.get_view_style();
if (!s) s = "original";
values.push(s);
var k = handler.get_keyboard_mode();
values.push(k);
var c = handler.get_option("codec-preference");
if (!c) c = "auto";
values.push(c);
for (var el in $$(menu#display-options li)) {
el.attributes.toggleClass("selected", values.indexOf(el.id) >= 0);
}
for (var el in $$(menu#keyboard-options>li)) {
el.attributes.toggleClass("selected", values.indexOf(el.id) >= 0);
}
for (var id in ["show-remote-cursor", "show-quality-monitor", "disable-audio", "enable-file-transfer", "disable-clipboard", "lock-after-session-end"]) {
var el = self.select('#' + id);
if (el) {

View File

@ -1,7 +1,7 @@
use std::{
collections::HashMap,
ops::{Deref, DerefMut},
sync::{atomic::Ordering, Arc, Mutex},
sync::{Arc, Mutex},
};
use sciter::{
@ -28,7 +28,7 @@ use hbb_common::{
use crate::clipboard_file::*;
use crate::{
client::*,
ui_session_interface::{InvokeUiSession, Session, IS_IN},
ui_session_interface::{InvokeUiSession, Session},
};
type Video = AssetPtr<video_destination>;
@ -399,6 +399,8 @@ impl sciter::EventHandler for SciterSession {
fn get_remember();
fn peer_platform();
fn set_write_override(i32, i32, bool, bool, bool);
fn get_keyboard_mode();
fn save_keyboard_mode(String);
fn has_hwcodec();
fn supported_hwcodec();
fn change_prefer_codec();
@ -560,18 +562,6 @@ impl SciterSession {
self.close_state.insert(k, v);
}
fn enter(&mut self) {
#[cfg(windows)]
crate::platform::windows::stop_system_key_propagate(true);
IS_IN.store(true, Ordering::SeqCst);
}
fn leave(&mut self) {
#[cfg(windows)]
crate::platform::windows::stop_system_key_propagate(false);
IS_IN.store(false, Ordering::SeqCst);
}
fn get_key_event(&self, down_or_up: i32, name: &str, code: i32) -> Option<KeyEvent> {
let mut key_event = KeyEvent::new();
if down_or_up == 2 {
@ -732,4 +722,4 @@ pub fn make_fd(id: i32, entries: &Vec<FileEntry>, only_count: bool) -> Value {
m.set_item("num_entries", entries.len() as i32);
m.set_item("total_size", n as f64);
m
}
}

View File

@ -683,6 +683,7 @@ pub fn check_super_user_permission() -> bool {
true
}
#[allow(dead_code)]
pub fn check_zombie(childs: Childs) {
let mut deads = Vec::new();
loop {
@ -714,7 +715,7 @@ pub(crate) fn check_connect_status(reconnect: bool) -> mpsc::UnboundedSender<ipc
// notice: avoiding create ipc connecton repeatly,
// because windows named pipe has serious memory leak issue.
#[tokio::main(flavor = "current_thread")]
async fn check_connect_status_(reconnect: bool, rx: mpsc::UnboundedReceiver<ipc::Data>) {
pub(crate) async fn check_connect_status_(reconnect: bool, rx: mpsc::UnboundedReceiver<ipc::Data>) {
let mut key_confirmed = false;
let mut rx = rx;
let mut mouse_time = 0;

View File

@ -6,18 +6,16 @@ use crate::client::{
load_config, send_mouse, start_video_audio_threads, FileManager, Key, LoginConfigHandler,
QualityStatus, KEY_MAP, SERVER_KEYBOARD_ENABLED,
};
use crate::common;
use crate::{client::Data, client::Interface};
use async_trait::async_trait;
use hbb_common::config::{Config, LocalConfig, PeerConfig};
use hbb_common::rendezvous_proto::ConnType;
use hbb_common::tokio::{self, sync::mpsc};
use rdev::{Event, EventType::*, Key as RdevKey, Keyboard as RdevKeyboard, KeyboardState};
use hbb_common::{allow_err, message_proto::*};
use hbb_common::{fs, get_version_number, log, Stream};
use std::collections::HashMap;
use std::collections::{HashMap, HashSet};
use std::ops::{Deref, DerefMut};
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use std::sync::{Arc, Mutex, RwLock};
@ -29,6 +27,12 @@ static KEYBOARD_HOOKED: AtomicBool = AtomicBool::new(false);
#[cfg(windows)]
static mut IS_ALT_GR: bool = false;
#[cfg(not(any(target_os = "android", target_os = "ios")))]
lazy_static::lazy_static! {
static ref TO_RELEASE: Arc<Mutex<HashSet<RdevKey>>> = Arc::new(Mutex::new(HashSet::<RdevKey>::new()));
static ref KEYBOARD: Arc<Mutex<RdevKeyboard>> = Arc::new(Mutex::new(RdevKeyboard::new().unwrap()));
}
#[derive(Clone, Default)]
pub struct Session<T: InvokeUiSession> {
pub cmd: String,
@ -49,12 +53,21 @@ impl<T: InvokeUiSession> Session<T> {
pub fn get_image_quality(&self) -> String {
self.lc.read().unwrap().image_quality.clone()
}
/// Get custom image quality.
pub fn get_custom_image_quality(&self) -> Vec<i32> {
self.lc.read().unwrap().custom_image_quality.clone()
}
pub fn get_keyboard_mode(&self) -> String {
return std::env::var("KEYBOARD_MODE")
.unwrap_or(String::from("legacy"))
.to_lowercase();
}
pub fn save_keyboard_mode(&self, value: String) {
std::env::set_var("KEYBOARD_MODE", value);
}
pub fn save_view_style(&mut self, value: String) {
self.lc.write().unwrap().save_view_style(value);
}
@ -233,25 +246,144 @@ impl<T: InvokeUiSession> Session<T> {
if self.peer_platform() == "Windows" {
let mut key_event = KeyEvent::new();
key_event.set_control_key(ControlKey::CtrlAltDel);
self.key_down_or_up(1, key_event, false, false, false, false);
// todo
key_event.down = true;
self.send_key_event(key_event, KeyboardMode::Legacy);
} else {
let mut key_event = KeyEvent::new();
key_event.set_control_key(ControlKey::Delete);
self.key_down_or_up(3, key_event, true, true, false, false);
self.legacy_modifiers(&mut key_event, true, true, false, false);
// todo
key_event.press = true;
self.send_key_event(key_event, KeyboardMode::Legacy);
}
}
pub fn key_down_or_up(
fn send_key_event(&self, mut evt: KeyEvent, keyboard_mode: KeyboardMode) {
// mode: legacy(0), map(1), translate(2), auto(3)
evt.mode = keyboard_mode.into();
let mut msg_out = Message::new();
msg_out.set_key_event(evt);
self.send(Data::Message(msg_out));
}
#[allow(dead_code)]
fn convert_numpad_keys(&self, key: RdevKey) -> RdevKey {
if get_key_state(enigo::Key::NumLock) {
return key;
}
match key {
RdevKey::Kp0 => RdevKey::Insert,
RdevKey::KpDecimal => RdevKey::Delete,
RdevKey::Kp1 => RdevKey::End,
RdevKey::Kp2 => RdevKey::DownArrow,
RdevKey::Kp3 => RdevKey::PageDown,
RdevKey::Kp4 => RdevKey::LeftArrow,
RdevKey::Kp5 => RdevKey::Clear,
RdevKey::Kp6 => RdevKey::RightArrow,
RdevKey::Kp7 => RdevKey::Home,
RdevKey::Kp8 => RdevKey::UpArrow,
RdevKey::Kp9 => RdevKey::PageUp,
_ => key,
}
}
fn map_keyboard_mode(&self, down_or_up: bool, key: RdevKey, _evt: Option<Event>) {
// map mode(1): Send keycode according to the peer platform.
#[cfg(target_os = "windows")]
let key = if let Some(e) = _evt {
rdev::get_win_key(e.code.into(), e.scan_code)
} else {
key
};
#[cfg(not(windows))]
let key = self.convert_numpad_keys(key);
let peer = self.peer_platform();
let mut key_event = KeyEvent::new();
// According to peer platform.
let keycode: u32 = if peer == "Linux" {
rdev::linux_keycode_from_key(key).unwrap_or_default().into()
} else if peer == "Windows" {
rdev::win_keycode_from_key(key).unwrap_or_default().into()
} else {
// Without Clear Key on Mac OS
if key == rdev::Key::Clear {
return;
}
rdev::macos_keycode_from_key(key).unwrap_or_default().into()
};
key_event.set_chr(keycode);
key_event.down = down_or_up;
if get_key_state(enigo::Key::CapsLock) {
key_event.modifiers.push(ControlKey::CapsLock.into());
}
if get_key_state(enigo::Key::NumLock) {
key_event.modifiers.push(ControlKey::NumLock.into());
}
self.send_key_event(key_event, KeyboardMode::Map);
}
fn translate_keyboard_mode(&self, down_or_up: bool, key: RdevKey, evt: Event) {
// translate mode(2): locally generated characters are send to the peer.
// get char
let string = match KEYBOARD.lock() {
Ok(mut keyboard) => {
let string = keyboard.add(&evt.event_type).unwrap_or_default();
if keyboard.is_dead() && string == "" && down_or_up == true {
return;
}
string
}
Err(_) => "".to_owned(),
};
// maybe two string
let chars = if string == "" {
None
} else {
let chars: Vec<char> = string.chars().collect();
Some(chars)
};
if let Some(chars) = chars {
for chr in chars {
let mut key_event = KeyEvent::new();
key_event.set_chr(chr as _);
key_event.down = true;
key_event.press = false;
self.send_key_event(key_event, KeyboardMode::Translate);
}
} else {
let success = if down_or_up == true {
TO_RELEASE.lock().unwrap().insert(key)
} else {
TO_RELEASE.lock().unwrap().remove(&key)
};
// AltGr && LeftControl(SpecialKey) without action
if key == RdevKey::AltGr || evt.scan_code == 541 {
return;
}
if success {
self.map_keyboard_mode(down_or_up, key, None);
}
}
}
fn legacy_modifiers(
&self,
down_or_up: i32,
evt: KeyEvent,
key_event: &mut KeyEvent,
alt: bool,
ctrl: bool,
shift: bool,
command: bool,
) {
let mut key_event = evt;
if alt
&& !crate::is_control_key(&key_event, &ControlKey::Alt)
&& !crate::is_control_key(&key_event, &ControlKey::RAlt)
@ -276,25 +408,253 @@ impl<T: InvokeUiSession> Session<T> {
{
key_event.modifiers.push(ControlKey::Meta.into());
}
#[cfg(not(any(target_os = "android", target_os = "ios")))]
if get_key_state(enigo::Key::CapsLock) {
key_event.modifiers.push(ControlKey::CapsLock.into());
}
#[cfg(not(any(target_os = "android", target_os = "ios")))]
if self.peer_platform() != "Mac OS" {
if get_key_state(enigo::Key::NumLock) && common::valid_for_numlock(&key_event) {
if get_key_state(enigo::Key::NumLock) {
key_event.modifiers.push(ControlKey::NumLock.into());
}
}
if down_or_up == 1 {
key_event.down = true;
} else if down_or_up == 3 {
key_event.press = true;
}
fn legacy_keyboard_mode(&self, down_or_up: bool, key: RdevKey, evt: Event) {
// legacy mode(0): Generate characters locally, look for keycode on other side.
let peer = self.peer_platform();
let is_win = peer == "Windows";
let alt = get_key_state(enigo::Key::Alt);
#[cfg(windows)]
let ctrl = {
let mut tmp =
get_key_state(enigo::Key::Control) || get_key_state(enigo::Key::RightControl);
unsafe {
if IS_ALT_GR {
if alt || key == RdevKey::AltGr {
if tmp {
tmp = false;
}
} else {
IS_ALT_GR = false;
}
}
}
tmp
};
#[cfg(not(windows))]
let ctrl = get_key_state(enigo::Key::Control) || get_key_state(enigo::Key::RightControl);
let shift = get_key_state(enigo::Key::Shift) || get_key_state(enigo::Key::RightShift);
#[cfg(windows)]
let command = crate::platform::windows::get_win_key_state();
#[cfg(not(windows))]
let command = get_key_state(enigo::Key::Meta);
let control_key = match key {
RdevKey::Alt => Some(ControlKey::Alt),
RdevKey::AltGr => Some(ControlKey::RAlt),
RdevKey::Backspace => Some(ControlKey::Backspace),
RdevKey::ControlLeft => {
// when pressing AltGr, an extra VK_LCONTROL with a special
// scancode with bit 9 set is sent, let's ignore this.
#[cfg(windows)]
if evt.scan_code & 0x200 != 0 {
unsafe {
IS_ALT_GR = true;
}
return;
}
Some(ControlKey::Control)
}
RdevKey::ControlRight => Some(ControlKey::RControl),
RdevKey::DownArrow => Some(ControlKey::DownArrow),
RdevKey::Escape => Some(ControlKey::Escape),
RdevKey::F1 => Some(ControlKey::F1),
RdevKey::F10 => Some(ControlKey::F10),
RdevKey::F11 => Some(ControlKey::F11),
RdevKey::F12 => Some(ControlKey::F12),
RdevKey::F2 => Some(ControlKey::F2),
RdevKey::F3 => Some(ControlKey::F3),
RdevKey::F4 => Some(ControlKey::F4),
RdevKey::F5 => Some(ControlKey::F5),
RdevKey::F6 => Some(ControlKey::F6),
RdevKey::F7 => Some(ControlKey::F7),
RdevKey::F8 => Some(ControlKey::F8),
RdevKey::F9 => Some(ControlKey::F9),
RdevKey::LeftArrow => Some(ControlKey::LeftArrow),
RdevKey::MetaLeft => Some(ControlKey::Meta),
RdevKey::MetaRight => Some(ControlKey::RWin),
RdevKey::Return => Some(ControlKey::Return),
RdevKey::RightArrow => Some(ControlKey::RightArrow),
RdevKey::ShiftLeft => Some(ControlKey::Shift),
RdevKey::ShiftRight => Some(ControlKey::RShift),
RdevKey::Space => Some(ControlKey::Space),
RdevKey::Tab => Some(ControlKey::Tab),
RdevKey::UpArrow => Some(ControlKey::UpArrow),
RdevKey::Delete => {
if is_win && ctrl && alt {
self.ctrl_alt_del();
return;
}
Some(ControlKey::Delete)
}
RdevKey::Apps => Some(ControlKey::Apps),
RdevKey::Cancel => Some(ControlKey::Cancel),
RdevKey::Clear => Some(ControlKey::Clear),
RdevKey::Kana => Some(ControlKey::Kana),
RdevKey::Hangul => Some(ControlKey::Hangul),
RdevKey::Junja => Some(ControlKey::Junja),
RdevKey::Final => Some(ControlKey::Final),
RdevKey::Hanja => Some(ControlKey::Hanja),
RdevKey::Hanji => Some(ControlKey::Hanja),
RdevKey::Convert => Some(ControlKey::Convert),
RdevKey::Print => Some(ControlKey::Print),
RdevKey::Select => Some(ControlKey::Select),
RdevKey::Execute => Some(ControlKey::Execute),
RdevKey::PrintScreen => Some(ControlKey::Snapshot),
RdevKey::Help => Some(ControlKey::Help),
RdevKey::Sleep => Some(ControlKey::Sleep),
RdevKey::Separator => Some(ControlKey::Separator),
RdevKey::KpReturn => Some(ControlKey::NumpadEnter),
RdevKey::Kp0 => Some(ControlKey::Numpad0),
RdevKey::Kp1 => Some(ControlKey::Numpad1),
RdevKey::Kp2 => Some(ControlKey::Numpad2),
RdevKey::Kp3 => Some(ControlKey::Numpad3),
RdevKey::Kp4 => Some(ControlKey::Numpad4),
RdevKey::Kp5 => Some(ControlKey::Numpad5),
RdevKey::Kp6 => Some(ControlKey::Numpad6),
RdevKey::Kp7 => Some(ControlKey::Numpad7),
RdevKey::Kp8 => Some(ControlKey::Numpad8),
RdevKey::Kp9 => Some(ControlKey::Numpad9),
RdevKey::KpDivide => Some(ControlKey::Divide),
RdevKey::KpMultiply => Some(ControlKey::Multiply),
RdevKey::KpDecimal => Some(ControlKey::Decimal),
RdevKey::KpMinus => Some(ControlKey::Subtract),
RdevKey::KpPlus => Some(ControlKey::Add),
RdevKey::CapsLock | RdevKey::NumLock | RdevKey::ScrollLock => {
return;
}
RdevKey::Home => Some(ControlKey::Home),
RdevKey::End => Some(ControlKey::End),
RdevKey::Insert => Some(ControlKey::Insert),
RdevKey::PageUp => Some(ControlKey::PageUp),
RdevKey::PageDown => Some(ControlKey::PageDown),
RdevKey::Pause => Some(ControlKey::Pause),
_ => None,
};
let mut key_event = KeyEvent::new();
if let Some(k) = control_key {
key_event.set_control_key(k);
} else {
let mut chr = match evt.name {
Some(ref s) => {
if s.len() <= 2 {
// exclude chinese characters
s.chars().next().unwrap_or('\0')
} else {
'\0'
}
}
_ => '\0',
};
if chr == '·' {
// special for Chinese
chr = '`';
}
if chr == '\0' {
chr = match key {
RdevKey::Num1 => '1',
RdevKey::Num2 => '2',
RdevKey::Num3 => '3',
RdevKey::Num4 => '4',
RdevKey::Num5 => '5',
RdevKey::Num6 => '6',
RdevKey::Num7 => '7',
RdevKey::Num8 => '8',
RdevKey::Num9 => '9',
RdevKey::Num0 => '0',
RdevKey::KeyA => 'a',
RdevKey::KeyB => 'b',
RdevKey::KeyC => 'c',
RdevKey::KeyD => 'd',
RdevKey::KeyE => 'e',
RdevKey::KeyF => 'f',
RdevKey::KeyG => 'g',
RdevKey::KeyH => 'h',
RdevKey::KeyI => 'i',
RdevKey::KeyJ => 'j',
RdevKey::KeyK => 'k',
RdevKey::KeyL => 'l',
RdevKey::KeyM => 'm',
RdevKey::KeyN => 'n',
RdevKey::KeyO => 'o',
RdevKey::KeyP => 'p',
RdevKey::KeyQ => 'q',
RdevKey::KeyR => 'r',
RdevKey::KeyS => 's',
RdevKey::KeyT => 't',
RdevKey::KeyU => 'u',
RdevKey::KeyV => 'v',
RdevKey::KeyW => 'w',
RdevKey::KeyX => 'x',
RdevKey::KeyY => 'y',
RdevKey::KeyZ => 'z',
RdevKey::Comma => ',',
RdevKey::Dot => '.',
RdevKey::SemiColon => ';',
RdevKey::Quote => '\'',
RdevKey::LeftBracket => '[',
RdevKey::RightBracket => ']',
RdevKey::BackSlash => '\\',
RdevKey::Minus => '-',
RdevKey::Equal => '=',
RdevKey::BackQuote => '`',
_ => '\0',
}
}
if chr != '\0' {
if chr == 'l' && is_win && command {
self.lock_screen();
return;
}
key_event.set_chr(chr as _);
} else {
log::error!("Unknown key {:?}", evt);
return;
}
}
self.legacy_modifiers(&mut key_event, alt, ctrl, shift, command);
if down_or_up == true {
key_event.down = true;
}
self.send_key_event(key_event, KeyboardMode::Legacy)
}
fn key_down_or_up(&self, down_or_up: bool, key: RdevKey, evt: Event) {
// Call different functions according to keyboard mode.
let mode = match self.get_keyboard_mode().as_str() {
"map" => KeyboardMode::Map,
"legacy" => KeyboardMode::Legacy,
"translate" => KeyboardMode::Translate,
_ => KeyboardMode::Legacy,
};
match mode {
KeyboardMode::Map => {
if down_or_up == true {
TO_RELEASE.lock().unwrap().insert(key);
} else {
TO_RELEASE.lock().unwrap().remove(&key);
}
self.map_keyboard_mode(down_or_up, key, Some(evt));
}
KeyboardMode::Legacy => self.legacy_keyboard_mode(down_or_up, key, evt),
KeyboardMode::Translate => {
self.translate_keyboard_mode(down_or_up, key, evt);
}
_ => self.legacy_keyboard_mode(down_or_up, key, evt),
}
let mut msg_out = Message::new();
msg_out.set_key_event(key_event);
log::debug!("{:?}", msg_out);
self.send(Data::Message(msg_out));
}
pub fn get_platform(&self, is_remote: bool) -> String {
@ -350,7 +710,59 @@ impl<T: InvokeUiSession> Session<T> {
pub fn lock_screen(&self) {
let mut key_event = KeyEvent::new();
key_event.set_control_key(ControlKey::LockScreen);
self.key_down_or_up(1, key_event, false, false, false, false);
// todo
key_event.down = true;
self.send_key_event(key_event, KeyboardMode::Legacy);
}
pub fn enter(&self) {
#[cfg(windows)]
crate::platform::windows::stop_system_key_propagate(true);
IS_IN.store(true, Ordering::SeqCst);
}
pub fn leave(&self) {
for key in TO_RELEASE.lock().unwrap().iter() {
self.map_keyboard_mode(false, *key, None)
}
#[cfg(windows)]
crate::platform::windows::stop_system_key_propagate(false);
IS_IN.store(false, Ordering::SeqCst);
}
pub fn handle_flutter_key_event(
&self,
name: &str,
keycode: i32,
scancode: i32,
down_or_up: bool,
) {
if scancode < 0 || keycode < 0 {
return;
}
let keycode: u32 = keycode as u32;
let scancode: u32 = scancode as u32;
#[cfg(not(target_os = "windows"))]
let key = rdev::key_from_scancode(scancode) as RdevKey;
// Windows requires special handling
#[cfg(target_os = "windows")]
let key = rdev::get_win_key(keycode, scancode);
let event_type = if down_or_up {
KeyPress(key)
} else {
KeyRelease(key)
};
let evt = Event {
time: std::time::SystemTime::now(),
name: Option::Some(name.to_owned()),
code: keycode as _,
scan_code: scancode as _,
event_type: event_type,
};
self.key_down_or_up(down_or_up, key, evt)
}
// flutter only TODO new input
@ -426,7 +838,14 @@ impl<T: InvokeUiSession> Session<T> {
}
}
self.key_down_or_up(v, key_event, alt, ctrl, shift, command);
self.legacy_modifiers(&mut key_event, alt, ctrl, shift, command);
if v == 1 {
key_event.down = true;
} else if v == 3 {
key_event.press = true;
}
self.send_key_event(key_event, KeyboardMode::Legacy);
}
pub fn send_mouse(
@ -752,222 +1171,52 @@ impl<T: InvokeUiSession> Session<T> {
}
log::info!("keyboard hooked");
let me = self.clone();
let peer = self.peer_platform();
let is_win = peer == "Windows";
#[cfg(windows)]
crate::platform::windows::enable_lowlevel_keyboard(std::ptr::null_mut() as _);
std::thread::spawn(move || {
// This will block.
std::env::set_var("KEYBOARD_ONLY", "y"); // pass to rdev
use rdev::{EventType::*, *};
std::env::set_var("KEYBOARD_ONLY", "y");
lazy_static::lazy_static! {
static ref MUTEX_SPECIAL_KEYS: Mutex<HashMap<RdevKey, bool>> = {
let mut m = HashMap::new();
m.insert(RdevKey::ShiftLeft, false);
m.insert(RdevKey::ShiftRight, false);
m.insert(RdevKey::ControlLeft, false);
m.insert(RdevKey::ControlRight, false);
m.insert(RdevKey::Alt, false);
m.insert(RdevKey::AltGr, false);
m.insert(RdevKey::MetaLeft, false);
m.insert(RdevKey::MetaRight, false);
Mutex::new(m)
};
}
let func = move |evt: Event| {
if !IS_IN.load(Ordering::SeqCst) || !SERVER_KEYBOARD_ENABLED.load(Ordering::SeqCst)
{
return;
}
let (key, down) = match evt.event_type {
KeyPress(k) => (k, 1),
KeyRelease(k) => (k, 0),
let (_key, down) = match evt.event_type {
KeyPress(k) => {
// keyboard long press
if MUTEX_SPECIAL_KEYS.lock().unwrap().contains_key(&k) {
if *MUTEX_SPECIAL_KEYS.lock().unwrap().get(&k).unwrap() {
return;
}
MUTEX_SPECIAL_KEYS.lock().unwrap().insert(k, true);
}
(k, true)
}
KeyRelease(k) => {
// keyboard long press
if MUTEX_SPECIAL_KEYS.lock().unwrap().contains_key(&k) {
MUTEX_SPECIAL_KEYS.lock().unwrap().insert(k, false);
}
(k, false)
}
_ => return,
};
let alt = get_key_state(enigo::Key::Alt);
#[cfg(windows)]
let ctrl = {
let mut tmp = get_key_state(enigo::Key::Control);
unsafe {
if IS_ALT_GR {
if alt || key == Key::AltGr {
if tmp {
tmp = false;
}
} else {
IS_ALT_GR = false;
}
}
}
tmp
};
#[cfg(not(windows))]
let ctrl = get_key_state(enigo::Key::Control);
let shift = get_key_state(enigo::Key::Shift);
#[cfg(windows)]
let command = crate::platform::windows::get_win_key_state();
#[cfg(not(windows))]
let command = get_key_state(enigo::Key::Meta);
let control_key = match key {
Key::Alt => Some(ControlKey::Alt),
Key::AltGr => Some(ControlKey::RAlt),
Key::Backspace => Some(ControlKey::Backspace),
Key::ControlLeft => {
// when pressing AltGr, an extra VK_LCONTROL with a special
// scancode with bit 9 set is sent, let's ignore this.
#[cfg(windows)]
if evt.scan_code & 0x200 != 0 {
unsafe {
IS_ALT_GR = true;
}
return;
}
Some(ControlKey::Control)
}
Key::ControlRight => Some(ControlKey::RControl),
Key::DownArrow => Some(ControlKey::DownArrow),
Key::Escape => Some(ControlKey::Escape),
Key::F1 => Some(ControlKey::F1),
Key::F10 => Some(ControlKey::F10),
Key::F11 => Some(ControlKey::F11),
Key::F12 => Some(ControlKey::F12),
Key::F2 => Some(ControlKey::F2),
Key::F3 => Some(ControlKey::F3),
Key::F4 => Some(ControlKey::F4),
Key::F5 => Some(ControlKey::F5),
Key::F6 => Some(ControlKey::F6),
Key::F7 => Some(ControlKey::F7),
Key::F8 => Some(ControlKey::F8),
Key::F9 => Some(ControlKey::F9),
Key::LeftArrow => Some(ControlKey::LeftArrow),
Key::MetaLeft => Some(ControlKey::Meta),
Key::MetaRight => Some(ControlKey::RWin),
Key::Return => Some(ControlKey::Return),
Key::RightArrow => Some(ControlKey::RightArrow),
Key::ShiftLeft => Some(ControlKey::Shift),
Key::ShiftRight => Some(ControlKey::RShift),
Key::Space => Some(ControlKey::Space),
Key::Tab => Some(ControlKey::Tab),
Key::UpArrow => Some(ControlKey::UpArrow),
Key::Delete => {
if is_win && ctrl && alt {
me.ctrl_alt_del();
return;
}
Some(ControlKey::Delete)
}
Key::Apps => Some(ControlKey::Apps),
Key::Cancel => Some(ControlKey::Cancel),
Key::Clear => Some(ControlKey::Clear),
Key::Kana => Some(ControlKey::Kana),
Key::Hangul => Some(ControlKey::Hangul),
Key::Junja => Some(ControlKey::Junja),
Key::Final => Some(ControlKey::Final),
Key::Hanja => Some(ControlKey::Hanja),
Key::Hanji => Some(ControlKey::Hanja),
Key::Convert => Some(ControlKey::Convert),
Key::Print => Some(ControlKey::Print),
Key::Select => Some(ControlKey::Select),
Key::Execute => Some(ControlKey::Execute),
Key::PrintScreen => Some(ControlKey::Snapshot),
Key::Help => Some(ControlKey::Help),
Key::Sleep => Some(ControlKey::Sleep),
Key::Separator => Some(ControlKey::Separator),
Key::KpReturn => Some(ControlKey::NumpadEnter),
Key::Kp0 => Some(ControlKey::Numpad0),
Key::Kp1 => Some(ControlKey::Numpad1),
Key::Kp2 => Some(ControlKey::Numpad2),
Key::Kp3 => Some(ControlKey::Numpad3),
Key::Kp4 => Some(ControlKey::Numpad4),
Key::Kp5 => Some(ControlKey::Numpad5),
Key::Kp6 => Some(ControlKey::Numpad6),
Key::Kp7 => Some(ControlKey::Numpad7),
Key::Kp8 => Some(ControlKey::Numpad8),
Key::Kp9 => Some(ControlKey::Numpad9),
Key::KpDivide => Some(ControlKey::Divide),
Key::KpMultiply => Some(ControlKey::Multiply),
Key::KpDecimal => Some(ControlKey::Decimal),
Key::KpMinus => Some(ControlKey::Subtract),
Key::KpPlus => Some(ControlKey::Add),
Key::CapsLock | Key::NumLock | Key::ScrollLock => {
return;
}
Key::Home => Some(ControlKey::Home),
Key::End => Some(ControlKey::End),
Key::Insert => Some(ControlKey::Insert),
Key::PageUp => Some(ControlKey::PageUp),
Key::PageDown => Some(ControlKey::PageDown),
Key::Pause => Some(ControlKey::Pause),
_ => None,
};
let mut key_event = KeyEvent::new();
if let Some(k) = control_key {
key_event.set_control_key(k);
} else {
let mut chr = match evt.name {
Some(ref s) => {
if s.len() <= 2 {
// exclude chinese characters
s.chars().next().unwrap_or('\0')
} else {
'\0'
}
}
_ => '\0',
};
if chr == '·' {
// special for Chinese
chr = '`';
}
if chr == '\0' {
chr = match key {
Key::Num1 => '1',
Key::Num2 => '2',
Key::Num3 => '3',
Key::Num4 => '4',
Key::Num5 => '5',
Key::Num6 => '6',
Key::Num7 => '7',
Key::Num8 => '8',
Key::Num9 => '9',
Key::Num0 => '0',
Key::KeyA => 'a',
Key::KeyB => 'b',
Key::KeyC => 'c',
Key::KeyD => 'd',
Key::KeyE => 'e',
Key::KeyF => 'f',
Key::KeyG => 'g',
Key::KeyH => 'h',
Key::KeyI => 'i',
Key::KeyJ => 'j',
Key::KeyK => 'k',
Key::KeyL => 'l',
Key::KeyM => 'm',
Key::KeyN => 'n',
Key::KeyO => 'o',
Key::KeyP => 'p',
Key::KeyQ => 'q',
Key::KeyR => 'r',
Key::KeyS => 's',
Key::KeyT => 't',
Key::KeyU => 'u',
Key::KeyV => 'v',
Key::KeyW => 'w',
Key::KeyX => 'x',
Key::KeyY => 'y',
Key::KeyZ => 'z',
Key::Comma => ',',
Key::Dot => '.',
Key::SemiColon => ';',
Key::Quote => '\'',
Key::LeftBracket => '[',
Key::RightBracket => ']',
Key::BackSlash => '\\',
Key::Minus => '-',
Key::Equal => '=',
Key::BackQuote => '`',
_ => '\0',
}
}
if chr != '\0' {
if chr == 'l' && is_win && command {
me.lock_screen();
return;
}
key_event.set_chr(chr as _);
} else {
log::error!("Unknown key {:?}", evt);
return;
}
}
me.key_down_or_up(down, key_event, alt, ctrl, shift, command); // TODO
me.key_down_or_up(down, _key, evt);
};
if let Err(error) = rdev::listen(func) {
log::error!("rdev: {:?}", error);