Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Physical keyboard to android support #6097

Merged
merged 27 commits into from Nov 7, 2023
Merged

Conversation

mcfans
Copy link
Contributor

@mcfans mcfans commented Oct 19, 2023

Description

Support KeyEvent input in android side. Fix issue(#3556)
/claim #3556

Detailed Design

Android doesn't provide an API for send key event to the whole system, since most scenario that needs keyboard input are text input. I used android accessibility service to find current focused text input widget and set text to it. I create an invisible text input widget in InputService, and set current focusing accessibility node info's text and selection to it, send the key event to it then retrieve it's new state and set the new text back to the current focusing node.In this way, we don't need to handle what different key will do to text on our side, just use system default implementation.
Use getInputMethod() API, then send key event to it.

Other Approach

Register an IME to system then use that to send key stroke to system. WIFIKeyboard use this approach. But Android system can only have one IME at a time, receive side will not able to use their on screen IME while using RustDesk, that would make features like chatting not work.

Changes

  • Modified build.gradle to generate java code from message.proto for usage of KeyEvent in Kotlin code. KeyEvent is a struct with many field, pass its' fields through ffi is complicated, so I pass serialized data through ffi.
  • Updated rdev, add android key code, make keyboard mode(map) work.
  • Added canRetrieveWindowContent to accessibility_service_config.xml

Test

Tested on:
iOS -> Android,
macOS -> Android(map mode),
windows -> Android(legacy mode)

Compatibility

Android receive side needs update to this version to work.
iOS control side needs update to this version to show a soft keyboard.
Desktop version doesn't need update for legacy keyboard mode, need update for map keyboard mode.

A Video

IMG_1634.MOV

@mcfans
Copy link
Contributor Author

mcfans commented Oct 19, 2023

/claim #3556

Cargo.toml Outdated
@@ -64,7 +64,7 @@ default-net = "0.14"
wol-rs = "1.0"
flutter_rust_bridge = { version = "1.75", features = ["uuid"], optional = true}
errno = "0.3"
rdev = { git = "https://github.com/fufesou/rdev" }
rdev = { git = "https://github.com/mcfans/rdev" }
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please merge to https://github.com/fufesou/rdev
@fufesou please review.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will open a PR for that.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Finish this first

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@fufesou Could you take a look at this?

@RequiresApi(Build.VERSION_CODES.N)
fun onKeyEvent(data: ByteArray) {
val keyEvent = KeyEvent.parseFrom(data);
val handler = Handler(Looper.getMainLooper())
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can not parse it in rust?

Copy link
Contributor Author

@mcfans mcfans Oct 19, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's serialized on rust side by purpose, because I want to use generated KeyEvent class on Kotlin side instead of pass the fields of it on rust side through ffi that would be a lot of parameters and less convenient.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree

@rustdesk
Copy link
Owner

@fufesou please have more review.

@rustdesk
Copy link
Owner

rustdesk commented Oct 19, 2023

Have you tested different languages? e.g. German, French. And combination key, e.g. alt + key, will generate special character for different language.

@mcfans
Copy link
Contributor Author

mcfans commented Oct 19, 2023

Have you tested different languages? e.g. German, French. And combination key, e.g. alt + key, will generate special character for different language.

Not yet. I will try it later.

@fufesou
Copy link
Collaborator

fufesou commented Oct 19, 2023

👍 👍 👋

Win -> Android , en->en, the charaters are all correct for both "Map mode" and "Translate mode".

android.input.mp4

Some possible improvements

  1. Input events will have a side effect that will set the cursor to the end. So the arrow control doesn't work, the cursor always moves to the end.
  2. The input does not work on the input box on the web page. Tested chrome browser.

image

  1. Mouse click event issue. The specified position of the text cannot be clicked and range selection cannot be made. This has nothing to do with keyboard input, but I always want to click the mouse to a specified location.

avc permission issues

I'm not familiar with avc issues in android.

They're not related to the keyboard issue. But if you're familiar with it, it's good.

image

======================= update =============================

I see the secure context is scontext=u:r:untrusted_app:s0:c120,c257,c512,c768.

I'm trying to find out:

  1. What are affected
  2. How to set a domain as a trusted app.

image

@mcfans
Copy link
Contributor Author

mcfans commented Oct 19, 2023

I found a new way to do this. Accessibility service has a method called getInputMethod, we could then send key event directly to it.This could solve previous problem.

Input events will have a side effect that will set the cursor to the end. So the arrow control doesn't work, the cursor always moves to the end.

Old way can only set text and text selection separately, if application doesn't do some special text handling, this is fine.But if app would change text after text set, then our text selection data would be wrong. So I ignored text selection data for correctness. In this new way I leave this to system to handle, arrow key works fine.

The input does not work on the input box on the web page. Tested chrome browser.

This is because findFocus gave a wrong input widget, send event to IME also fix this.

@mcfans
Copy link
Contributor Author

mcfans commented Oct 19, 2023

Have you tested different languages? e.g. German, French. And combination key, e.g. alt + key, will generate special character for different language.

I tested combination key, I could enter exclamation mark(shift + 1). I couldn't test different languages for now because I found out my android device doesn't support other languages. Need some help on that.

@RequiresApi(Build.VERSION_CODES.N)
fun onKeyEvent(data: ByteArray) {
val keyEvent = KeyEvent.parseFrom(data);
getInputMethod()?.let { inputMethod ->

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getInputMethod added in API level 33

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point

Copy link
Owner

@rustdesk rustdesk Oct 20, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You may have some modification to make it compatible with lower level target device. @mcfans

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I checked the wrong field, I thought we were using API 33. My bad. So I just keep this for higher version and add the old code back for lower API?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not familiar with kotlin about how to call higher API on high target device but still can run on low target device.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My android device may be the lower version. The input does not work now, and it reports:

D/ffi     ( 3011): librustdesk::server::connection: call_main_service_key_event fail:Java exception was thrown

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you try it again? I added old code back with a version check.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The keyboard input works now.

@fufesou
Copy link
Collaborator

fufesou commented Oct 20, 2023

Have you tested different languages? e.g. German, French. And combination key, e.g. alt + key, will generate special character for different language.

The "Translate mode" works fine.

For the "Map mode" (fr -> fr), the the characters are different. It seems the default en-use keyboard layout is used.

image

Maybe it's better hide the "Legacy mode" and "Map mode" if local is desktop and remote is Android.

======================== update =============================

Maybe you need to use the scancode in the KeyEvent when using the "Map mode".

https://developer.android.com/reference/android/view/KeyEvent#KeyEvent

"Map mode" means using the "position code" (scancode) to control the key inputs.

With "Map mode", people can control the peer as the physical keyboard is connected directly to the peer.

@rustdesk
Copy link
Owner

"Map mode" means using the "position code" (scancode) to control the key inputs.

👍

@fufesou
Copy link
Collaborator

fufesou commented Oct 24, 2023

@mcfans 👍

Do you have a plan to implement the "Map mode"?
It's an important keyboard mode.

@mcfans
Copy link
Contributor Author

mcfans commented Oct 24, 2023

I am trying to implement Map mode these days.But there are two problems. First we can only send a keycode that matches US keyboard layout, Android keycode only has those keys. Set scancode of a keyevent is ineffective base on my experiments. Second I only found ways to send key events to application, not send to input method. On Android a hardware keyboard event is sent to input method, then the input method would figure out correct text to insert into application. I failed to find a way to do the same thing. However I have not tested what text would be inserted if the OS language matches input method, because my android device couldn't do that. If that could work, we could add map mode back.

@mcfans
Copy link
Contributor Author

mcfans commented Oct 24, 2023

I fixed translated mode and legacy mode in those commits.

@fufesou
Copy link
Collaborator

fufesou commented Oct 24, 2023

what text would be inserted if the OS language matches input method

The scancode should be handled by the input method. It should have nothing to do with the OS language.

I only found ways to send key events to application, not send to input method.

I'm not familiar with Android. But it seems you are right, the event should be sent to the system or input method.

@mcfans
Copy link
Contributor Author

mcfans commented Oct 24, 2023

If we can send key event to the system, everything would be perfect.

@mcfans
Copy link
Contributor Author

mcfans commented Oct 24, 2023

So should we merge this?

@rustdesk
Copy link
Owner

AnyDesk's map mode work?

@mcfans
Copy link
Contributor Author

mcfans commented Oct 24, 2023

I tested it. It doesn't work correctly with 1:1 mode on and local input method is fr, its output is US layout keys. Same behavior with my implementation.

@fufesou
Copy link
Collaborator

fufesou commented Oct 29, 2023

I've tried the ID edit in RustDesk.
The key input also does not work.

@@ -216,6 +216,11 @@ impl<T: InvokeUiSession> Session<T> {

pub fn get_keyboard_mode(&self) -> String {
let mode = self.lc.read().unwrap().keyboard_mode.clone();
if self.peer_platform() == crate::PLATFORM_ANDROID {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

This commit seems better than
#6168

It's independent of the UI.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But the logic here is the same as in flutter_ffi.rs. Maybe it's better to merge the code.

if session.peer_platform() == "Android" && mode == KeyboardMode::Map {
    return SyncReturn(false);
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The logic in flutter_ffi.rs is used to hide map mode in UI, ui_session_interface.rs is used to override keyboard mode saved in local storage. It’s two part of the same problem.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It’s two part of the same problem.

Yes, but if something changed, you have to change to two places, which is error prone.

They are doing almost the same filtering, maybe you can extract the filter code, or some func like should_replace_mode_to(conds, mode) -> Option<mode>

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree, that's better. Working on it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

@mcfans
Copy link
Contributor Author

mcfans commented Oct 30, 2023

I made the cursor works for API level lower than 33 just like AnyDesk.

@fufesou
Copy link
Collaborator

fufesou commented Oct 30, 2023

@mcfans Hi, what architecture did you use for testing? It's strange that I can't run x86_64 on my emulator.

@fufesou
Copy link
Collaborator

fufesou commented Oct 30, 2023

With the latest commit, I can't enter "Backspace" "Enter" "Left/Up/Right/Down".

@mcfans
Copy link
Contributor Author

mcfans commented Oct 30, 2023

@mcfans Hi, what architecture did you use for testing? It's strange that I can't run x86_64 on my emulator.

For API level 34, I am using arm64 apk on x86 emulator. It’s okay for Android 11 and above. For API level 28, I am using arm64 apk on arm64 emulator. I have a M2 MacBook, it’s arm64, so it’s fine. You probably need —split-per-abi when running flutter build. That will build an apk that contains arm64 only. Without this flag, output apk seems have 3 architecture, dyld trying to open .so in x86 directory, but nothing in there.

@mcfans
Copy link
Contributor Author

mcfans commented Oct 30, 2023

With the latest commit, I can't enter "Backspace" "Enter" "Left/Up/Right/Down".

Which API level are you using?

@rustdesk
Copy link
Owner

c2703d2
flutter and bridge updated to new versions.
If you can not run on mac, please rm -rf macos/rustdesk.xcodeproj, pub get/upgrade,

@rustdesk
Copy link
Owner

rustdesk commented Nov 7, 2023

@mcfans please fix the conflicts. I will merge then.

# Conflicts:
#	Cargo.lock
#	src/server/connection.rs
log::debug!("call_main_service_key_event fail:{}", e);
}
} else {
log::debug!("encode key event fail:{}", encode_result.err().unwrap());
Copy link
Owner

@rustdesk rustdesk Nov 7, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we remove this unwrap? BTW, space between : and {.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's safe here, but I changed it to a match block anyway.

@rustdesk rustdesk merged commit 5577731 into rustdesk:master Nov 7, 2023
1 of 8 checks passed
@basilgello
Copy link
Contributor

Thanks for so awaited feature! :)

@rustdesk
Copy link
Owner

#6417 big regression failure.

@Magustructor
Copy link

I don't know if here's the right place for reporting it but I have a problem with using keyboard in Windows 10 (R. ver.1.2.4)-> Android 11 (R. ver. 1.2.4) scenario.
The keyboard generally works properly except Enter key.
The Enter moves the cursor to the next line instead of confirming/accepting text when used in any search field like google.
I tried both legacy and translation modes.

Am I doing something incorrectly or it's a bug?

@rustdesk
Copy link
Owner

rustdesk commented Apr 15, 2024

This feature is ready in 1.2.4, current night build, https://github.com/rustdesk/rustdesk/releases/tag/nightly

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

7 participants