Skip to content

Commit

Permalink
* add IME 'ScrcpyIME'
Browse files Browse the repository at this point in the history
* add switch cmd 'ctrl+e' to enable and use ime, 'ctrl+shift+e' to disable ime
* change original handler behavior:
	* change text_input to send all types char
        * when enable IME, injectKeyEvent skip Letter or Digit or Space char, InjectText to handler it
        * when disable IME, InjectText skip all text, injectKeyEvent to handler it
  • Loading branch information
wei.liang committed Nov 3, 2019
1 parent 17d53be commit f9ce19b
Show file tree
Hide file tree
Showing 22 changed files with 420 additions and 28 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,6 @@ build/
/dist/
.idea/
.gradle/
x
*.iml
local.properties
3 changes: 3 additions & 0 deletions app/src/control_msg.c
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,9 @@ control_msg_serialize(const struct control_msg *msg, unsigned char *buf) {
case CONTROL_MSG_TYPE_GET_CLIPBOARD:
// no additional data
return 1;
case CONTROL_MSG_TYPE_SET_INJECT_TEXT_MODE:
buf[1] = msg->set_inject_text_mode.mode;
return 2;
default:
LOGW("Unknown message type: %u", (unsigned) msg->type);
return 0;
Expand Down
9 changes: 9 additions & 0 deletions app/src/control_msg.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ enum control_msg_type {
CONTROL_MSG_TYPE_GET_CLIPBOARD,
CONTROL_MSG_TYPE_SET_CLIPBOARD,
CONTROL_MSG_TYPE_SET_SCREEN_POWER_MODE,
CONTROL_MSG_TYPE_SET_INJECT_TEXT_MODE,
};

enum screen_power_mode {
Expand All @@ -34,6 +35,11 @@ enum screen_power_mode {
SCREEN_POWER_MODE_NORMAL = 2,
};

enum inject_text_mode {
USE_INPUT_MANAGER = 0,
USE_SCRCPY_IME = 1,
};

struct control_msg {
enum control_msg_type type;
union {
Expand Down Expand Up @@ -61,6 +67,9 @@ struct control_msg {
struct {
enum screen_power_mode mode;
} set_screen_power_mode;
struct {
enum inject_text_mode mode;
} set_inject_text_mode;
};
};

Expand Down
27 changes: 21 additions & 6 deletions app/src/input_manager.c
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,18 @@ set_screen_power_mode(struct controller *controller,
}
}

static void
set_inject_text_mode(struct controller *controller,
enum inject_text_mode mode) {
struct control_msg msg;
msg.type = CONTROL_MSG_TYPE_SET_INJECT_TEXT_MODE;
msg.set_inject_text_mode.mode = mode;

if (!controller_push_msg(controller, &msg)) {
LOGW("Could not request 'set screen power mode'");
}
}

static void
switch_fps_counter_state(struct fps_counter *fps_counter) {
// the started state can only be written from the current thread, so there
Expand Down Expand Up @@ -214,12 +226,6 @@ clipboard_paste(struct controller *controller) {
void
input_manager_process_text_input(struct input_manager *input_manager,
const SDL_TextInputEvent *event) {
char c = event->text[0];
if (isalpha(c) || c == ' ') {
SDL_assert(event->text[1] == '\0');
// letters and space are handled as raw key event
return;
}
struct control_msg msg;
msg.type = CONTROL_MSG_TYPE_INJECT_TEXT;
msg.inject_text.text = SDL_strdup(event->text);
Expand Down Expand Up @@ -365,6 +371,15 @@ input_manager_process_key(struct input_manager *input_manager,
}
}
return;
case SDLK_e:
if (control && cmd && !repeat && down) {
if (shift) {
set_inject_text_mode(controller, USE_INPUT_MANAGER);
} else {
set_inject_text_mode(controller, USE_SCRCPY_IME);
}
}
return;
}

return;
Expand Down
6 changes: 6 additions & 0 deletions app/src/scrcpy.c
Original file line number Diff line number Diff line change
Expand Up @@ -150,12 +150,16 @@ handle_event(SDL_Event *event, bool control) {
break;
}
break;
case SDL_TEXTEDITING:
break;
case SDL_TEXTINPUT:
if (!control) {
break;
}
input_manager_process_text_input(&input_manager, &event->text);
break;
case SDL_KEYMAPCHANGED:
break;
case SDL_KEYDOWN:
case SDL_KEYUP:
// some key events do not interact with the device, so process the
Expand Down Expand Up @@ -194,6 +198,8 @@ handle_event(SDL_Event *event, bool control) {
file_handler_request(&file_handler, action, event->drop.file);
break;
}
default:
LOGD("Unknow event type:%d", event->type);
}
return EVENT_RESULT_CONTINUE;
}
Expand Down
28 changes: 28 additions & 0 deletions ime/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
apply plugin: 'com.android.application'

android {
compileSdkVersion 29
defaultConfig {
applicationId "com.genymobile.scrcpy.ime"
minSdkVersion 21
targetSdkVersion 29
versionCode 1
versionName "1.0"

testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"

}

buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}

}

dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
testImplementation 'junit:junit:4.12'
}
21 changes: 21 additions & 0 deletions ime/proguard-rules.pro
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html

# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}

# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable

# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
10 changes: 10 additions & 0 deletions ime/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.genymobile.scrcpy.ime">
<application android:label="@string/app_name">
<service android:label="@string/app_name" android:name="com.genymobile.scrcpy.ime.ScrcpyIME" android:permission="android.permission.BIND_INPUT_METHOD">
<intent-filter>
<action android:name="android.view.InputMethod"/>
</intent-filter>
<meta-data android:name="android.view.im" android:resource="@xml/method" />
</service>
</application>
</manifest>
81 changes: 81 additions & 0 deletions ime/src/main/java/com/genymobile/scrcpy/ime/ScrcpyIME.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package com.genymobile.scrcpy.ime;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.inputmethodservice.InputMethodService;
import android.util.Log;
import android.view.KeyEvent;
import android.view.inputmethod.InputConnection;

public class ScrcpyIME extends InputMethodService {
private static String TAG = "ScrcpyIME";
private BroadcastReceiver receiver;
private static final String COMMIT_TEXT_ACTION = "com.genymobile.scrcpy.ime.COMMIT_TEXT_ACTION";
private static final String STATE_CHANGE_ACTION = "com.genymobile.scrcpy.ime.STATE_CHANGE_ACTION";

@Override
public void onCreate() {
super.onCreate();
this.receiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
InputConnection inputConnection = getCurrentInputConnection();
if(inputConnection == null){
return;
}
String text = null;
KeyEvent keyEvent = null;
if((text = intent.getStringExtra("text")) != null && text.length() > 0) {
inputConnection.commitText(text, 0);
}else if((keyEvent = intent.getParcelableExtra("keyEvent")) != null) {
inputConnection.sendKeyEvent(keyEvent);
}
}
};
IntentFilter localIntentFilter = new IntentFilter(COMMIT_TEXT_ACTION);
registerReceiver(this.receiver, localIntentFilter);
}

@Override
public void onDestroy() {
unregisterReceiver(this.receiver);
Log.i(TAG, "disabling self due to destroy");
super.onDestroy();
}

@Override
public void onBindInput() {
super.onBindInput();
Log.i(TAG, "BindInput");
sendStateBroadcast("BindInput");
}

@Override
public void onUnbindInput() {
super.onUnbindInput();
Log.i(TAG, "UnbindInput");
sendStateBroadcast("UnbindInput");
}

@Override
public void onWindowShown() {
super.onWindowShown();
Log.i(TAG, "WindowShown");
sendStateBroadcast("WindowShown");
}

@Override
public void onWindowHidden() {
super.onWindowHidden();
Log.i(TAG, "WindowHidden");
sendStateBroadcast("WindowHidden");
}

private void sendStateBroadcast(String state) {
Intent intent = new Intent(STATE_CHANGE_ACTION);
intent.putExtra("state", state);
sendBroadcast(intent);
}
}
3 changes: 3 additions & 0 deletions ime/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<resources>
<string name="app_name">Scrcpy</string>
</resources>
4 changes: 4 additions & 0 deletions ime/src/main/res/xml/method.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<input-method xmlns:android="http://schemas.android.com/apk/res/android">
<subtype android:label="@string/app_name" android:imeSubtypeLocale="en_US" android:imeSubtypeMode="keyboard" />
</input-method>
2 changes: 1 addition & 1 deletion server/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
prebuilt_server = get_option('prebuilt_server')
if prebuilt_server == ''
custom_target('scrcpy-server',
build_always: true, # gradle is responsible for tracking source changes
build_by_default: true, # gradle is responsible for tracking source changes
output: 'scrcpy-server.jar',
command: [find_program('./scripts/build-wrapper.sh'), meson.current_source_dir(), '@OUTPUT@', get_option('buildtype')],
console: true,
Expand Down
29 changes: 29 additions & 0 deletions server/src/main/aidl/android/content/IIntentReceiver.aidl
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* Copyright (C) 2006 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.content;
import android.content.Intent;
import android.os.Bundle;
/**
* System private API for dispatching intent broadcasts. This is given to the
* activity manager as part of registering for an intent broadcasts, and is
* called when it receives intents.
*
* {@hide}
*/
oneway interface IIntentReceiver {
void performReceive(in Intent intent, int resultCode, String data,
in Bundle extras, boolean ordered, boolean sticky, int sendingUser);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
* Copyright (C) 2008 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.internal.view;

/**
* Interface a client of the IInputMethodManager implements, to identify
* itself and receive information about changes to the global manager state.
*/
interface IInputMethodClient {
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ public final class ControlMessage {
public static final int TYPE_GET_CLIPBOARD = 7;
public static final int TYPE_SET_CLIPBOARD = 8;
public static final int TYPE_SET_SCREEN_POWER_MODE = 9;
public static final int TYPE_SET_INJECT_TEXT_MODE = 10;

private int type;
private String text;
Expand Down Expand Up @@ -80,6 +81,13 @@ public static ControlMessage createSetScreenPowerMode(int mode) {
return msg;
}

public static ControlMessage createSetInjectTextMode(int mode) {
ControlMessage msg = new ControlMessage();
msg.type = TYPE_SET_INJECT_TEXT_MODE;
msg.action = mode;
return msg;
}

public static ControlMessage createEmpty(int type) {
ControlMessage msg = new ControlMessage();
msg.type = type;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ public class ControlMessageReader {
private static final int INJECT_MOUSE_EVENT_PAYLOAD_LENGTH = 17;
private static final int INJECT_SCROLL_EVENT_PAYLOAD_LENGTH = 20;
private static final int SET_SCREEN_POWER_MODE_PAYLOAD_LENGTH = 1;
private static final int SET_INJECT_TEXT_MODE_PAYLOAD_LENGTH = 1;

public static final int TEXT_MAX_LENGTH = 300;
public static final int CLIPBOARD_TEXT_MAX_LENGTH = 4093;
Expand Down Expand Up @@ -77,6 +78,9 @@ public ControlMessage next() {
case ControlMessage.TYPE_GET_CLIPBOARD:
msg = ControlMessage.createEmpty(type);
break;
case ControlMessage.TYPE_SET_INJECT_TEXT_MODE:
msg = parseSetInjectTextMode();
break;
default:
Ln.w("Unknown event type: " + type);
msg = null;
Expand Down Expand Up @@ -156,6 +160,14 @@ private ControlMessage parseSetScreenPowerMode() {
return ControlMessage.createSetScreenPowerMode(mode);
}

private ControlMessage parseSetInjectTextMode() {
if (buffer.remaining() < SET_INJECT_TEXT_MODE_PAYLOAD_LENGTH) {
return null;
}
int mode = buffer.get();
return ControlMessage.createSetInjectTextMode(mode);
}

private static Position readPosition(ByteBuffer buffer) {
int x = buffer.getInt();
int y = buffer.getInt();
Expand Down
Loading

0 comments on commit f9ce19b

Please sign in to comment.