mirror of
https://github.com/rustdesk/rustdesk.git
synced 2025-01-19 00:13:01 +08:00
Merge pull request #964 from asur4s/master
Feat: Support new keyboard mode
This commit is contained in:
commit
47cefc57e6
892
Cargo.lock
generated
892
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -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"
|
||||
|
@ -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
|
||||
|
1
PKGBUILD
1
PKGBUILD
@ -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"
|
||||
}
|
||||
|
@ -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 تثبيت
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
34
README.md
34
README.md
@ -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:
|
||||
|
@ -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
|
||||
|
@ -1 +0,0 @@
|
||||
pynput
|
6
build.py
6
build.py
@ -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)
|
||||
|
2
build.rs
2
build.rs
@ -116,4 +116,4 @@ fn main() {
|
||||
build_windows();
|
||||
#[cfg(target_os = "macos")]
|
||||
println!("cargo:rustc-link-lib=framework=ApplicationServices");
|
||||
}
|
||||
}
|
2
flutter/.gitignore
vendored
2
flutter/.gitignore
vendored
@ -36,11 +36,9 @@ lib/generated_plugin_registrant.dart
|
||||
|
||||
# Symbolication related
|
||||
app.*.symbols
|
||||
|
||||
# Obfuscation related
|
||||
app.*.map.json
|
||||
jniLibs
|
||||
|
||||
.vscode
|
||||
|
||||
# flutter rust bridge
|
||||
|
@ -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) {
|
||||
|
@ -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),
|
||||
|
@ -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).
|
||||
|
@ -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
|
||||
|
@ -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")
|
||||
|
@ -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() {
|
||||
|
@ -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
|
||||
|
@ -4,5 +4,7 @@
|
||||
<dict>
|
||||
<key>com.apple.security.app-sandbox</key>
|
||||
<true/>
|
||||
<key>com.apple.security.network.client</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
|
@ -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; };
|
||||
|
@ -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:
|
||||
|
@ -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]
|
||||
|
@ -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,
|
||||
///
|
||||
|
@ -1,5 +1,4 @@
|
||||
mod nix_impl;
|
||||
mod pynput;
|
||||
mod xdo;
|
||||
|
||||
pub use self::nix_impl::Enigo;
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
});
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,4 @@
|
||||
use core_graphics;
|
||||
|
||||
// TODO(dustin): use only the things i need
|
||||
|
||||
use self::core_graphics::display::*;
|
||||
|
@ -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
|
||||
///
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
@ -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()
|
@ -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
|
||||
|
3
rpm.spec
3
rpm.spec
@ -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
|
||||
|
@ -1,2 +1,2 @@
|
||||
[toolchain]
|
||||
channel = "1.62.0"
|
||||
channel = "1.62.0"
|
@ -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:
|
||||
|
@ -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),
|
||||
|
@ -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());
|
||||
}
|
@ -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())
|
||||
}
|
||||
}
|
@ -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);
|
||||
|
@ -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", "1:1传输"),
|
||||
("Translate mode", "翻译模式"),
|
||||
("Use temporary password", "使用临时密码"),
|
||||
("Use permanent password", "使用固定密码"),
|
||||
("Use both passwords", "同时使用两种密码"),
|
||||
|
@ -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", ""),
|
||||
|
@ -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", ""),
|
||||
|
@ -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"),
|
||||
|
@ -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", ""),
|
||||
|
@ -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"),
|
||||
|
@ -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", ""),
|
||||
|
@ -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", ""),
|
||||
|
@ -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"),
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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", "どちらのパスワードも使用"),
|
||||
|
@ -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", ""),
|
||||
|
@ -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", "Использовать оба пароля"),
|
||||
|
@ -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", ""),
|
||||
|
@ -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", ""),
|
||||
|
@ -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"),
|
||||
|
@ -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", "1:1傳輸"),
|
||||
("Translate mode", "翻譯模式"),
|
||||
("Use temporary password", "使用臨時密碼"),
|
||||
("Use permanent password", "使用固定密碼"),
|
||||
("Use both passwords", "同時使用兩種密碼"),
|
||||
|
@ -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"),
|
||||
|
@ -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);
|
||||
|
@ -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()
|
||||
};
|
||||
|
||||
|
@ -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)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
28
src/ui.rs
28
src/ui.rs
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
@ -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);
|
||||
|
Loading…
Reference in New Issue
Block a user