Code cleanup

Added LorieService
New touch parsing mechanism
Added Preferences activity with options: show or not additional keyboard, fullscreen mode toggle, touch input mode toggle, show or not software keyboard if external keyboard is connected
This commit is contained in:
Twaik Yont 2019-08-19 18:46:13 +03:00
parent 48fd564d21
commit 12b7b00710
31 changed files with 1433 additions and 369 deletions

View File

@ -5,6 +5,7 @@ Android NDK: current module
[arm64-v8a] Compile++ : lorie <= client.cpp [arm64-v8a] Compile++ : lorie <= client.cpp
[arm64-v8a] Compile++ : lorie <= clipboard.cpp [arm64-v8a] Compile++ : lorie <= clipboard.cpp
[arm64-v8a] Compile++ : lorie <= compositor.cpp [arm64-v8a] Compile++ : lorie <= compositor.cpp
[arm64-v8a] Compile++ : lorie <= egl-helper.cpp
[arm64-v8a] Compile++ : lorie <= message-queue.cpp [arm64-v8a] Compile++ : lorie <= message-queue.cpp
[arm64-v8a] Compile++ : lorie <= renderer.cpp [arm64-v8a] Compile++ : lorie <= renderer.cpp
[arm64-v8a] Compile++ : lorie <= seat.cpp [arm64-v8a] Compile++ : lorie <= seat.cpp
@ -14,5 +15,5 @@ Android NDK: current module
[arm64-v8a] Compile++ : lorie <= utils.cpp [arm64-v8a] Compile++ : lorie <= utils.cpp
[arm64-v8a] Compile++ : lorie <= wayland.cpp [arm64-v8a] Compile++ : lorie <= wayland.cpp
[arm64-v8a] Compile++ : lorie <= android-app.cpp [arm64-v8a] Compile++ : lorie <= android-app.cpp
[arm64-v8a] Compile++ : lorie <= GLContext.cpp [arm64-v8a] Compile : lorie <= utils.c
[arm64-v8a] SharedLibrary : liblorie.so [arm64-v8a] SharedLibrary : liblorie.so

View File

@ -1,52 +0,0 @@
/home/twaik/TermuxX11/app/src/main/jni/lorie/backend/android/android-app.cpp:274:6: error: use of undeclared identifier 'jcompositor'; did you mean 'compositor'?
if (jcompositor == 0) return;
^~~~~~~~~~~
compositor
/home/twaik/TermuxX11/app/src/main/jni/lorie/backend/android/android-app.cpp:271:50: note: 'compositor' declared here
jlong compositor, jint type,
^
/home/twaik/TermuxX11/app/src/main/jni/lorie/backend/android/android-app.cpp:275:39: error: use of undeclared identifier 'jcompositor'; did you mean 'compositor'?
LorieBackendAndroid *b = fromLong(jcompositor);
^~~~~~~~~~~
compositor
/home/twaik/TermuxX11/app/src/main/jni/lorie/backend/android/android-app.cpp:271:50: note: 'compositor' declared here
jlong compositor, jint type,
^
/home/twaik/TermuxX11/app/src/main/jni/lorie/backend/android/android-app.cpp:282:9: error: use of undeclared identifier 'key_code'; did you mean 'keyCode'?
if (key_code && !characters) {
^~~~~~~~
keyCode
/home/twaik/TermuxX11/app/src/main/jni/lorie/backend/android/android-app.cpp:272:49: note: 'keyCode' declared here
jint keyCode, jint jshift,
^
/home/twaik/TermuxX11/app/src/main/jni/lorie/backend/android/android-app.cpp:285:7: error: use of undeclared identifier 'xkb_names'
if (xkb_names.layout != "us") {
^
/home/twaik/TermuxX11/app/src/main/jni/lorie/backend/android/android-app.cpp:290:10: error: use of undeclared identifier 'key_code'; did you mean 'keyCode'?
if (!key_code && characters) {
^~~~~~~~
keyCode
/home/twaik/TermuxX11/app/src/main/jni/lorie/backend/android/android-app.cpp:272:49: note: 'keyCode' declared here
jint keyCode, jint jshift,
^
/home/twaik/TermuxX11/app/src/main/jni/lorie/backend/android/android-app.cpp:293:26: error: 'xkb_names' is a private member of 'LorieBackendAndroid'
if (layout && b->xkb_names.layout != layout) {
^
/home/twaik/TermuxX11/app/src/main/jni/lorie/backend/android/android-app.cpp:45:24: note: declared private here
struct xkb_rule_names xkb_names = {0};
^
/home/twaik/TermuxX11/app/src/main/jni/lorie/backend/android/android-app.cpp:298:90: error: use of undeclared identifier 'key_code'; did you mean 'keyCode'?
LOGE("Keyboard input: keyCode: %d; eventCode: %d; characters: %s; shift: %d, type: %d", key_code, event_code, characters, shift, type);
^~~~~~~~
keyCode
/home/twaik/TermuxX11/app/src/main/jni/lorie/include/log.h:26:34: note: expanded from macro 'LOGE'
#define LOGE(...) LOG(LOG_ERROR, __VA_ARGS__)
^
/home/twaik/TermuxX11/app/src/main/jni/lorie/include/log.h:20:41: note: expanded from macro 'LOG'
#define LOG(prio, ...) LogMessage(prio, __VA_ARGS__)
^
/home/twaik/TermuxX11/app/src/main/jni/lorie/backend/android/android-app.cpp:272:49: note: 'keyCode' declared here
jint keyCode, jint jshift,
^
7 errors generated.
make: *** [/home/twaik/TermuxX11/app/build/intermediates/ndkBuild/debug/obj/local/x86_64/objs-debug/lorie/backend/android/android-app.o] Error 1

View File

@ -2,4 +2,50 @@ Android NDK: WARNING:/home/twaik/TermuxX11/app/src/main/jni/lorie/Android.mk:lor
Android NDK: This is likely to result in incorrect builds. Try using LOCAL_STATIC_LIBRARIES Android NDK: This is likely to result in incorrect builds. Try using LOCAL_STATIC_LIBRARIES
Android NDK: or LOCAL_SHARED_LIBRARIES instead to list the library dependencies of the Android NDK: or LOCAL_SHARED_LIBRARIES instead to list the library dependencies of the
Android NDK: current module Android NDK: current module
[x86_64] Compile++ : lorie <= client.cpp
[x86_64] Compile++ : lorie <= clipboard.cpp
[x86_64] Compile++ : lorie <= compositor.cpp
[x86_64] Compile++ : lorie <= egl-helper.cpp
[x86_64] Compile++ : lorie <= message-queue.cpp
[x86_64] Compile++ : lorie <= renderer.cpp
[x86_64] Compile++ : lorie <= seat.cpp
[x86_64] Compile++ : lorie <= surface.cpp
[x86_64] Compile++ : lorie <= output.cpp
[x86_64] Compile++ : lorie <= log.cpp
[x86_64] Compile++ : lorie <= utils.cpp
[x86_64] Compile++ : lorie <= wayland.cpp
[x86_64] Compile++ : lorie <= android-app.cpp [x86_64] Compile++ : lorie <= android-app.cpp
[x86_64] Compile : lorie <= utils.c
[x86_64] Compile : xkbcommon <= parser.c
[x86_64] Compile : xkbcommon <= paths.c
[x86_64] Compile : xkbcommon <= state.c
[x86_64] Compile : xkbcommon <= table.c
[x86_64] Compile : xkbcommon <= action.c
[x86_64] Compile : xkbcommon <= ast-build.c
[x86_64] Compile : xkbcommon <= compat.c
[x86_64] Compile : xkbcommon <= expr.c
[x86_64] Compile : xkbcommon <= include.c
[x86_64] Compile : xkbcommon <= keycodes.c
[x86_64] Compile : xkbcommon <= keymap.c
[x86_64] Compile : xkbcommon <= keymap-dump.c
[x86_64] Compile : xkbcommon <= keywords.c
[x86_64] Compile : xkbcommon <= parser.c
[x86_64] Compile : xkbcommon <= rules.c
[x86_64] Compile : xkbcommon <= scanner.c
[x86_64] Compile : xkbcommon <= symbols.c
[x86_64] Compile : xkbcommon <= types.c
[x86_64] Compile : xkbcommon <= vmod.c
[x86_64] Compile : xkbcommon <= xkbcomp.c
[x86_64] Compile : xkbcommon <= atom.c
[x86_64] Compile : xkbcommon <= context.c
[x86_64] Compile : xkbcommon <= context-priv.c
[x86_64] Compile : xkbcommon <= keysym.c
[x86_64] Compile : xkbcommon <= keysym-utf.c
[x86_64] Compile : xkbcommon <= keymap.c
[x86_64] Compile : xkbcommon <= keymap-priv.c
[x86_64] Compile : xkbcommon <= state.c
[x86_64] Compile : xkbcommon <= text.c
[x86_64] Compile : xkbcommon <= utf8.c
[x86_64] Compile : xkbcommon <= utils.c
[x86_64] SharedLibrary : libxkbcommon.so
[x86_64] SharedLibrary : liblorie.so

2
app/.gitignore vendored
View File

@ -1 +1,3 @@
/build /build
/release
.externalNativeBuild

BIN
app/release/app-release.apk Normal file

Binary file not shown.

Binary file not shown.

1
app/release/output.json Normal file
View File

@ -0,0 +1 @@
[{"outputType":{"type":"APK"},"apkData":{"type":"MAIN","splits":[],"versionCode":1,"versionName":"1.0","enabled":true,"outputFile":"app-release.apk","fullName":"release","baseName":"release"},"path":"app-release.apk","properties":{}}]

View File

@ -1,17 +1,36 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.termux.x11" package="com.termux.x11"
android:sharedUserId="com.termux"> android:sharedUserId="com.termux">
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS"/>
<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS"
tools:ignore="ProtectedPermissions" />
<application <application
android:allowBackup="true" android:allowBackup="false"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
android:label="@string/app_name" android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round" android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true" android:supportsRtl="true"
android:theme="@style/AppTheme"> android:theme="@style/AppTheme"
tools:ignore="GoogleAppIndexingWarning" >
<service
android:name=".LorieService"
android:enabled="true"
android:exported="false"
android:persistent="true" />
<service
android:name=".LorieTestService"
android:enabled="true"
android:exported="true"
android:process="com.termux.x11.separated"
android:persistent="true" />
<activity android:name=".MainActivity" <activity android:name=".MainActivity"
android:theme="@style/NoActionBar" android:theme="@style/NoActionBar"
android:launchMode="singleInstance"
android:configChanges="fontScale|orientation|screenSize|keyboard|keyboardHidden|layoutDirection|locale|mcc|mnc|navigation|screenLayout|touchscreen|uiMode|smallestScreenSize|density"> android:configChanges="fontScale|orientation|screenSize|keyboard|keyboardHidden|layoutDirection|locale|mcc|mnc|navigation|screenLayout|touchscreen|uiMode|smallestScreenSize|density">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
@ -19,6 +38,12 @@
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter> </intent-filter>
</activity> </activity>
<activity
android:theme="@style/Theme.AppCompat.DayNight"
android:excludeFromRecents="true"
android:name=".LoriePreferences" >
</activity>
</application> </application>
</manifest> </manifest>

View File

@ -0,0 +1,12 @@
// ITermuxService.aidl
package com.termux.x11;
import android.view.Surface;
interface ITermuxService {
oneway void windowChanged(in Surface surface, int width, int height);
oneway void pointerMotion(int x, int y);
oneway void pointerScroll(int axis, float value);
oneway void pointerButton(int button, int type);
oneway void keyboardKey(int key, int type, int shift, String characters);
}

View File

@ -0,0 +1,118 @@
package com.termux.x11;
import android.content.SharedPreferences;
import android.inputmethodservice.Keyboard;
import android.os.Bundle;
import android.preference.Preference;
import android.preference.PreferenceFragment;
import android.preference.PreferenceScreen;
import android.provider.Settings;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.MenuItem;
public class LoriePreferences extends AppCompatActivity implements SharedPreferences.OnSharedPreferenceChangeListener {
static final String SHOW_IME_WITH_HARD_KEYBOARD = "show_ime_with_hard_keyboard";
LoriePreferenceFragment loriePreferenceFragment;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
loriePreferenceFragment = new LoriePreferenceFragment();
getFragmentManager().beginTransaction().replace(android.R.id.content, loriePreferenceFragment).commit();
ActionBar actionBar = getSupportActionBar();
if (actionBar != null) {
actionBar.setDisplayHomeAsUpEnabled(true);
actionBar.setHomeButtonEnabled(true);
actionBar.setTitle("Preferences");
}
}
@Override
public void onResume() {
super.onResume();
if (loriePreferenceFragment != null)
loriePreferenceFragment.getPreferenceManager().getSharedPreferences().registerOnSharedPreferenceChangeListener(this);
}
@Override
public void onPause() {
if (loriePreferenceFragment != null)
loriePreferenceFragment.getPreferenceManager().getSharedPreferences().unregisterOnSharedPreferenceChangeListener(this);
super.onPause();
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
if (id == android.R.id.home) {
finish();
return true;
}
return super.onOptionsItemSelected(item);
}
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
LorieService.start(LorieService.ACTION_PREFERENCES_CHAGED);
}
public static class LoriePreferenceFragment extends PreferenceFragment implements PreferenceScreen.OnPreferenceClickListener, Preference.OnPreferenceChangeListener {
@Override
public void onCreate(final Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.preferences);
String showImeEnabled = Settings.Secure.getString(getActivity().getContentResolver(), SHOW_IME_WITH_HARD_KEYBOARD);
SharedPreferences.Editor p = getPreferenceManager().getSharedPreferences().edit();
p.putBoolean("showIMEWhileExternalConnected", showImeEnabled.equals("1"));
p.apply();
PreferenceScreen s = getPreferenceScreen();
for (int i=0; i<s.getPreferenceCount(); i++) {
s.getPreference(i).setOnPreferenceClickListener(this);
s.getPreference(i).setOnPreferenceChangeListener(this);
}
}
@Override
public boolean onPreferenceClick(Preference preference) {
return false;
}
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
String key = preference.getKey();
Log.e("Preferences", "changed preference: " + key);
if (key.equals("showIMEWhileExternalConnected")) {
boolean enabled = newValue.toString().equals("true");
try {
Settings.Secure.putString(getActivity().getContentResolver(), SHOW_IME_WITH_HARD_KEYBOARD, enabled ? "1" : "0");
} catch (Exception e) {
if (e instanceof SecurityException) {
new AlertDialog.Builder(getActivity())
.setTitle("Permission denied")
.setMessage("Android requires WRITE_SECURE_SETTINGS permission to change this setting.\n" +
"Please, launch this command using ADB:\n" +
"adb shell pm grant com.termux.x11 android.permission.WRITE_SECURE_SETTINGS")
.setNegativeButton("OK", null)
.create()
.show();
} else e.printStackTrace();
return false;
}
}
return true;
}
}
}

View File

@ -1,340 +1,389 @@
package com.termux.x11; package com.termux.x11;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.app.Activity; import android.app.ActivityManager;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context; import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.res.Configuration; import android.content.res.Configuration;
import android.net.Uri;
import android.os.Build; import android.os.Build;
import android.os.IBinder;
import android.os.PowerManager;
import android.preference.PreferenceManager;
import android.provider.Settings;
import android.support.annotation.NonNull;
import android.support.annotation.RequiresApi;
import android.support.v4.app.NotificationCompat;
import android.util.DisplayMetrics; import android.util.DisplayMetrics;
import android.util.Log; import android.util.Log;
import android.view.InputDevice; import android.view.InputDevice;
import android.view.KeyEvent; import android.view.KeyEvent;
import android.view.MotionEvent; import android.view.MotionEvent;
import android.view.PointerIcon;
import android.view.Surface; import android.view.Surface;
import android.view.SurfaceHolder; import android.view.SurfaceHolder;
import android.view.SurfaceView; import android.view.SurfaceView;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.widget.Toast;
import android.view.WindowManager;
import android.widget.FrameLayout;
import android.widget.RelativeLayout;
import android.widget.ScrollView;
@SuppressWarnings("unused") @SuppressWarnings("ConstantConditions")
@SuppressLint("ClickableViewAccessibility") @SuppressLint({"ClickableViewAccessibility", "StaticFieldLeak"})
public class LorieService extends FrameLayout implements SurfaceHolder.Callback, View.OnTouchListener, View.OnKeyListener, KeyboardUtils.SoftKeyboardToggleListener, View.OnHoverListener, View.OnGenericMotionListener { public class LorieService extends Service {
private final static int SV2_ID = 0xACCEDED;
private static final int BTN_LEFT = 0x110;
private static final int BTN_RIGHT = 0x111;
private static final int BTN_MIDDLE = 0x112;
private static final int TOUCH_MODE_TOUCHSCREEN = 0x01; static final String ACTION_STOP_SERVICE = "com.termux.x11.service_stop";
private static final int TOUCH_MODE_TOUCHPAD = 0x02; static final String ACTION_START_FROM_ACTIVITY = "com.termux.x11.start_from_activity";
static final String ACTION_START_PREFERENCES_ACTIVITY = "com.termux.x11.start_preferences_activity";
static final String ACTION_PREFERENCES_CHAGED = "com.termux.x11.preferences_changed";
private static final int WL_STATE_PRESSED = 1; private static LorieService instance = null;
private static final int WL_STATE_RELEASED = 0; //private
private static final int WL_POINTER_MOTION = 2; //static
long compositor;
private static ServiceEventListener listener = new ServiceEventListener();
private static MainActivity act;
private static final int WL_POINTER_AXIS_VERTICAL_SCROLL = 0; private TouchParser mTP;
private static final int WL_POINTER_AXIS_HORIZONTAL_SCROLL = 1;
private static int[] keys = { public LorieService(){
KeyEvent.KEYCODE_ESCAPE, instance = this;
KeyEvent.KEYCODE_TAB, }
KeyEvent.KEYCODE_CTRL_LEFT,
KeyEvent.KEYCODE_ALT_LEFT,
KeyEvent.KEYCODE_DPAD_UP,
KeyEvent.KEYCODE_DPAD_DOWN,
KeyEvent.KEYCODE_DPAD_LEFT,
KeyEvent.KEYCODE_DPAD_RIGHT,
};
private long compositor;
private Activity act;
private AdditionalKeyboardView kbd;
private int touchscreenMode;
private boolean hardwareKeyboardConnected = false;
private touchPadListener tpListener; static void setMainActivity(MainActivity activity) {
private touchScreenListener tsListener;
private hardwareMouseListener hmListener;
LorieService(Context ctx) {super(ctx);}
LorieService(Activity activity) {
super(activity);
act = activity; act = activity;
}
static void start(String action) {
Intent intent = new Intent(act, LorieService.class);
intent.setAction(action);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
act.startForegroundService(intent);
} else {
act.startService(intent);
}
}
@Override
public void onCreate() {
if (isServiceRunningInForeground(this, LorieService.class)) return;
compositor = createLorieThread(); compositor = createLorieThread();
if (compositor == 0) { if (compositor == 0) {
Log.e("LorieService", "compositor thread was not created"); Log.e("LorieService", "compositor thread was not created");
return; return;
} }
int dp = (int) act.getResources().getDisplayMetrics().density; instance = this;
ViewGroup.LayoutParams match_parent = new WindowManager.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); Toast.makeText(this, "Service was Created", Toast.LENGTH_LONG).show();
setLayoutParams(match_parent); Log.e("LorieService", "created");
SurfaceView lorieView = new SurfaceView(act); Intent notificationIntent = new Intent(getApplicationContext(), MainActivity.class);
addView(lorieView, match_parent); notificationIntent.putExtra("foo_bar_extra_key", "foo_bar_extra_value");
notificationIntent.setAction(Long.toString(System.currentTimeMillis()));
RelativeLayout rl = new RelativeLayout(act); Intent exitIntent = new Intent(getApplicationContext(), LorieService.class);
ScrollView sv = new ScrollView(act); exitIntent.setAction(ACTION_STOP_SERVICE);
kbd = new AdditionalKeyboardView(act);
RelativeLayout.LayoutParams svlp = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.MATCH_PARENT);
RelativeLayout.LayoutParams kbdlp = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, 50*dp);
svlp.addRule(RelativeLayout.BELOW, SV2_ID); Intent preferencesIntent = new Intent(getApplicationContext(), LoriePreferences.class);
kbdlp.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM); preferencesIntent.setAction(ACTION_START_PREFERENCES_ACTIVITY);
kbd.setId(SV2_ID);
rl.addView(kbd, kbdlp);
rl.addView(sv, svlp);
addView(rl);
kbd.setVisibility(View.INVISIBLE); PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT);
PendingIntent pendingExitIntent = PendingIntent.getService(getApplicationContext(), 0, exitIntent, 0);
PendingIntent pendingPreferencesIntent = PendingIntent.getActivity(getApplicationContext(), 0, preferencesIntent, 0);
lorieView.getHolder().addCallback(this); //For creating the Foreground Service
lorieView.setOnTouchListener(this); int priority = Notification.PRIORITY_HIGH;
lorieView.setOnKeyListener(this); if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N)
priority = NotificationManager.IMPORTANCE_HIGH;
NotificationManager notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
String channelId = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O ? getNotificationChannel(notificationManager) : "";
Notification notification = new NotificationCompat.Builder(this, channelId)
.setContentTitle("Termux:X11")
.setSmallIcon(R.drawable.ic_x11_icon)
.setContentText("is running in foreground")
.setContentIntent(pendingIntent)
.setOngoing(true)
.setPriority(priority)
.setShowWhen(false)
.setColor(0xFF607D8B)
.addAction(0, "Preferences", pendingPreferencesIntent)
.addAction(0, "Exit", pendingExitIntent)
.build();
startForeground(1, notification);
lorieView.setFocusable(true); if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
lorieView.setFocusableInTouchMode(true); String packageName = getPackageName();
lorieView.requestFocus(); PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
if (!pm.isIgnoringBatteryOptimizations(packageName)) {
kbd.reload(keys, lorieView, this); Intent whitelist = new Intent();
whitelist.setAction(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
lorieView.setOnHoverListener(this); whitelist.setData(Uri.parse("package:" + packageName));
lorieView.setOnGenericMotionListener(this); whitelist.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(whitelist);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) }
setPointerIcon(PointerIcon.getSystemIcon(act, PointerIcon.TYPE_NULL)); }
// prevents SurfaceView from being resized but interrupts additional keyboard showing process
// act.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN);
touchscreenMode = TOUCH_MODE_TOUCHSCREEN;
tpListener = new touchPadListener(this);
tsListener = new touchScreenListener(this);
hmListener = new hardwareMouseListener(this);
} }
void finishInit() { @RequiresApi(Build.VERSION_CODES.O)
KeyboardUtils.addKeyboardToggleListener(act, this); private String getNotificationChannel(NotificationManager notificationManager){
String channelId = getResources().getString(R.string.app_name);
String channelName = getResources().getString(R.string.app_name);
NotificationChannel channel = new NotificationChannel(channelId, channelName, NotificationManager.IMPORTANCE_HIGH);
channel.setImportance(NotificationManager.IMPORTANCE_NONE);
channel.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE);
notificationManager.createNotificationChannel(channel);
return channelId;
}
public static boolean isServiceRunningInForeground(Context context, Class<?> serviceClass) {
ActivityManager manager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
for (ActivityManager.RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) {
if (serviceClass.getName().equals(service.service.getClassName())) {
return service.foreground;
}
}
return false;
}
private static void onPreferencesChanged() {
if (instance == null || act == null) return;
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(instance);
instance.mTP.setMode(
Integer.parseInt(
preferences.getString("touchMode", "1")
)
);
act.showAdditionalKbd = preferences.getBoolean("showAdditionalKbd", true);
Log.e("LorieService", "Preferences changed");
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.e("LorieService", "start");
String action = intent.getAction();
if (action.equals(ACTION_START_FROM_ACTIVITY)) {
act.onLorieServiceStart(this);
}
if (action.equals(ACTION_STOP_SERVICE)) {
Log.e("LorieService", action);
terminate();
sleep(500);
act.finish();
stopSelf();
}
onPreferencesChanged();
return START_STICKY;
}
@Override
public void onDestroy() {
Log.e("LorieService", "destroyed");
}
static LorieService getInstance() {
if (instance == null) {
Log.e("LorieService", "Instance was requested, but no instances available");
}
return instance;
}
void setListeners(@NonNull SurfaceView view) {
Context a = view.getRootView().findViewById(android.R.id.content).getContext();
if (!(a instanceof MainActivity)) {
Log.e("LorieService", "Context is not an activity!!!");
}
act = (MainActivity) a;
view.setFocusable(true);
view.setFocusableInTouchMode(true);
view.requestFocus();
listener.svc = this;
listener.setAsListenerTo(view);
mTP = new TouchParser(view, listener);
onPreferencesChanged();
}
static View.OnKeyListener getOnKeyListener() {
return listener;
} }
void terminate() { void terminate() {
terminate(compositor); terminate(compositor);
compositor = 0; compositor = 0;
Log.e("LorieService", "terminate");
} }
@Override @Override
public void surfaceCreated(SurfaceHolder holder) { public IBinder onBind(Intent intent) {
return null;
} }
@Override private static class ServiceEventListener implements SurfaceHolder.Callback, View.OnTouchListener, View.OnKeyListener, View.OnHoverListener, View.OnGenericMotionListener, TouchParser.OnTouchParseListener {
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { LorieService svc;
DisplayMetrics dm = new DisplayMetrics();
int mmWidth, mmHeight;
act.getWindowManager().getDefaultDisplay().getMetrics(dm);
if (act.getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) { private void setAsListenerTo(SurfaceView view) {
mmWidth = (int) Math.round((width * 25.4) / dm.xdpi); view.getHolder().addCallback(this);
mmHeight = (int) Math.round((height * 25.4) / dm.ydpi); view.setOnTouchListener(this);
} else { view.setOnHoverListener(this);
mmWidth = (int) Math.round((width * 25.4) / dm.ydpi); view.setOnGenericMotionListener(this);
mmHeight = (int) Math.round((height * 25.4) / dm.xdpi); view.setOnKeyListener(this);
surfaceChanged(view.getHolder(), 0, view.getWidth(), view.getHeight());
} }
windowChanged(compositor, holder.getSurface(), width, height, mmWidth, mmHeight); public void onPointerButton(int button, int state) {
} if (svc == null) return;
svc.pointerButton(button, state);
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
}
@Override
public boolean onTouch(View v, MotionEvent e) {
switch(e.getSource()) {
case InputDevice.SOURCE_TOUCHSCREEN:
switch(touchscreenMode) {
case TOUCH_MODE_TOUCHPAD: return tpListener.onTouch(v, e);
case TOUCH_MODE_TOUCHSCREEN: return tsListener.onTouch(v, e);
default: return tsListener.onTouch(v, e);
}
case InputDevice.SOURCE_MOUSE: return hmListener.onTouch(v, e);
default: return false;
}
}
private void toggleKeyboard() {
if (!hardwareKeyboardConnected)
KeyboardUtils.toggleKeyboardVisibility(act);
else {
boolean isKbdVisible = kbd.getVisibility() == View.VISIBLE;
kbd.setVisibility(isKbdVisible ? View.INVISIBLE : View.VISIBLE);
}
}
static boolean rightPressed = false; // Prevent right button press event from being repeated
@Override
public boolean onKey(View v, int keyCode, KeyEvent e) {
int action = 0;
if (keyCode == KeyEvent.KEYCODE_BACK) {
if (e.getSource() == InputDevice.SOURCE_MOUSE && rightPressed != (e.getAction() == KeyEvent.ACTION_DOWN)) {
pointerButton(compositor, BTN_RIGHT, (e.getAction() == KeyEvent.ACTION_DOWN) ? WL_STATE_PRESSED : WL_STATE_RELEASED);
rightPressed = (e.getAction() == KeyEvent.ACTION_DOWN);
} else if (e.getAction() == KeyEvent.ACTION_UP) {
toggleKeyboard();
}
return true;
} }
if (e.getAction() == KeyEvent.ACTION_DOWN) action = WL_STATE_PRESSED; public void onPointerMotion(int x, int y) {
if (e.getAction() == KeyEvent.ACTION_UP) action = WL_STATE_RELEASED; if (svc == null) return;
keyboardKey(compositor, action, keyCode, e.isShiftPressed() ? 1 : 0, e.getCharacters()); svc.pointerMotion(x, y);
//onKey(compositor, action, keyCode, e.isShiftPressed() ? 1 : 0, e.getCharacters());
return true;
}
@Override
public void onToggleSoftKeyboard(boolean isVisible) {
kbd.setVisibility((isVisible)?View.VISIBLE:View.INVISIBLE);
//Log.d("LorieActivity", "keyboard is " + (isVisible?"visible":"not visible"));
}
public void onConfigurationChanged(Configuration config) {
hardwareKeyboardConnected = config.hardKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_NO;
}
@Override
public boolean onGenericMotion(View v, MotionEvent e) {
onTouch(v, e);
return true;
}
@Override
public boolean onHover(View v, MotionEvent e) {
onTouch(v, e);
return true;
}
private class hardwareMouseListener implements View.OnTouchListener {
private int savedBS = 0;
private int currentBS = 0;
private LorieService svc;
hardwareMouseListener(LorieService service) { svc = service; }
boolean isMouseButtonChanged(int mask) {
return (savedBS & mask) != (currentBS & mask);
} }
int mouseButtonState(int mask) { public void onPointerScroll(int axis, float value) {
return ((currentBS & mask) != 0) ? WL_STATE_PRESSED : WL_STATE_RELEASED; if (svc == null) return;
svc.pointerScroll(axis, value);
} }
public boolean onTouch(View v, MotionEvent e) { public void onTouchDown(int id, float x, float y) {
if (e.getAction() == MotionEvent.ACTION_SCROLL) { if (svc == null) return;
float scrollY = -5 * e.getAxisValue(MotionEvent.AXIS_VSCROLL); svc.touchDown(id, x, y);
float scrollX = -5 * e.getAxisValue(MotionEvent.AXIS_HSCROLL);
if (scrollY != 0) pointerScroll(WL_POINTER_AXIS_VERTICAL_SCROLL, scrollY);
if (scrollX != 0) pointerScroll(WL_POINTER_AXIS_HORIZONTAL_SCROLL, scrollX);
return true;
}
svc.pointerMotion(e.getX(), e.getY());
currentBS = e.getButtonState();
if (isMouseButtonChanged(MotionEvent.BUTTON_PRIMARY)) {
svc.pointerButton(svc.compositor, BTN_LEFT, mouseButtonState(MotionEvent.BUTTON_PRIMARY));
}
if (isMouseButtonChanged(MotionEvent.BUTTON_TERTIARY)) {
svc.pointerButton(svc.compositor, BTN_MIDDLE, mouseButtonState(MotionEvent.BUTTON_TERTIARY));
}
if (isMouseButtonChanged(MotionEvent.BUTTON_SECONDARY)) {
svc.pointerButton(svc.compositor, BTN_RIGHT, mouseButtonState(MotionEvent.BUTTON_SECONDARY));
}
savedBS = currentBS;
return true;
}
}
private class touchScreenListener implements View.OnTouchListener {
private LorieService svc;
touchScreenListener(LorieService service) { svc = service; }
public boolean onTouch(View v, MotionEvent e) {
int type = WL_POINTER_MOTION;
switch (e.getAction()) {
case MotionEvent.ACTION_POINTER_DOWN:
case MotionEvent.ACTION_BUTTON_PRESS:
case MotionEvent.ACTION_DOWN:
type = WL_STATE_PRESSED;
break;
case MotionEvent.ACTION_POINTER_UP:
case MotionEvent.ACTION_BUTTON_RELEASE:
case MotionEvent.ACTION_UP:
type = WL_STATE_RELEASED;
break;
}
svc.pointerMotion(e.getX(), e.getY());
if (type != WL_POINTER_MOTION)
svc.pointerButton(svc.compositor, BTN_LEFT, type);
return true;
} }
} public void onTouchMotion(int id, float x, float y) {
if (svc == null) return;
svc.touchMotion(id, x, y);
}
private class touchPadListener implements View.OnTouchListener { public void onTouchUp(int id) {
private int startX, startY; if (svc == null) return;
private int curX, curY; svc.touchUp(id);
private int dX, dY; }
private long curTime;
private LorieService svc;
touchPadListener(LorieService service) { svc = service; } public void onTouchFrame() {
if (svc == null) return;
svc.touchFrame();
}
@Override @Override
public boolean onTouch(View v, MotionEvent e) { public boolean onTouch(View v, MotionEvent e) {
int X, Y; if (svc == null) return false;
switch (e.getActionMasked()) { return svc.mTP.onTouchEvent(e);
case MotionEvent.ACTION_DOWN: }
startX = (int) e.getX();
startY = (int) e.getY();
curTime= System.currentTimeMillis();
break;
case MotionEvent.ACTION_MOVE:
X = (int) e.getX(); @Override
Y = (int) e.getY(); public boolean onGenericMotion(View v, MotionEvent e) {
//ClientThread.update(Integer.toString(X - startX) + " " + Integer.toString(Y - startY)); if (svc == null) return false;
curX = X; return svc.mTP.onTouchEvent(e);
curY = Y; }
if (dX < curX - startX) dX = curX - startX;
if (dY < curY - startY) dY = curY - startY; @Override
break; public boolean onHover(View v, MotionEvent e) {
case MotionEvent.ACTION_UP: if (svc == null) return false;
long duration=System.currentTimeMillis()-curTime; return svc.mTP.onTouchEvent(e);
if(duration<100) { }
Log.d("Click",Long.toString(duration));
//ClientThread.update("3"); private boolean isSource(KeyEvent e, int source) {
} return (e.getSource() & source) == source;
break; }
private boolean rightPressed = false; // Prevent right button press event from being repeated
private boolean middlePressed = false; // Prevent middle button press event from being repeated
@Override
public boolean onKey(View v, int keyCode, KeyEvent e) {
if (svc == null) return false;
int action = 0;
if (keyCode == KeyEvent.KEYCODE_BACK) {
if (
isSource(e, InputDevice.SOURCE_MOUSE) &&
rightPressed != (e.getAction() == KeyEvent.ACTION_DOWN)
) {
svc.pointerButton(TouchParser.BTN_RIGHT, (e.getAction() == KeyEvent.ACTION_DOWN) ? TouchParser.ACTION_DOWN : TouchParser.ACTION_UP);
rightPressed = (e.getAction() == KeyEvent.ACTION_DOWN);
} else if (e.getAction() == KeyEvent.ACTION_UP) {
KeyboardUtils.toggleKeyboardVisibility(act);
}
return true;
} }
if (
keyCode == KeyEvent.KEYCODE_MENU &&
isSource(e, InputDevice.SOURCE_MOUSE) &&
middlePressed != (e.getAction() == KeyEvent.ACTION_DOWN)
) {
svc.pointerButton(TouchParser.BTN_MIDDLE, (e.getAction() == KeyEvent.ACTION_DOWN) ? TouchParser.ACTION_DOWN : TouchParser.ACTION_UP);
middlePressed = (e.getAction() == KeyEvent.ACTION_DOWN);
return true;
}
if (e.getAction() == KeyEvent.ACTION_DOWN) action = TouchParser.ACTION_DOWN;
if (e.getAction() == KeyEvent.ACTION_UP) action = TouchParser.ACTION_UP;
svc.keyboardKey(action, keyCode, e.isShiftPressed() ? 1 : 0, e.getCharacters());
return true; return true;
} }
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
DisplayMetrics dm = new DisplayMetrics();
int mmWidth, mmHeight;
act.getWindowManager().getDefaultDisplay().getMetrics(dm);
if (act.getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) {
mmWidth = (int) Math.round((width * 25.4) / dm.xdpi);
mmHeight = (int) Math.round((height * 25.4) / dm.ydpi);
} else {
mmWidth = (int) Math.round((width * 25.4) / dm.ydpi);
mmHeight = (int) Math.round((height * 25.4) / dm.xdpi);
}
svc.windowChanged(holder.getSurface(), width, height, mmWidth, mmHeight);
}
@Override public void surfaceCreated(SurfaceHolder holder) {}
@Override public void surfaceDestroyed(SurfaceHolder holder) {}
}
void sleep(long millis) {
try {
Thread.sleep(millis);
} catch (InterruptedException e) {
e.printStackTrace();
}
} }
private void windowChanged(Surface s, int w, int h, int pw, int ph) {windowChanged(compositor, s, w, h, pw, ph);}
private void touchDown(int id, float x, float y) { touchDown(compositor, id, (int) x, (int) y); }
private void touchMotion(int id, float x, float y) { touchMotion(compositor, id, (int) x, (int) y); }
private void touchUp(int id) { touchUp(compositor, id); }
private void touchFrame() { touchFrame(compositor); }
private void pointerMotion(float x, float y) { pointerMotion(compositor, (int) x, (int) y); } private void pointerMotion(float x, float y) { pointerMotion(compositor, (int) x, (int) y); }
private void pointerScroll(int axis, float value) { pointerScroll(compositor, axis, value); } private void pointerScroll(int axis, float value) { pointerScroll(compositor, axis, value); }
private void pointerButton(int button, int type) { pointerButton(compositor, button, type); } private void pointerButton(int button, int type) { pointerButton(compositor, button, type); }
private void keyboardKey(int key, int type, int shift, String characters) {keyboardKey(compositor, key, type, shift, characters);}
private native long createLorieThread(); private native long createLorieThread();
private native void windowChanged(long compositor, Surface surface, int width, int height, int mmWidth, int mmHeight); private native void windowChanged(long compositor, Surface surface, int width, int height, int mmWidth, int mmHeight);
private native void touchDown(long compositor, int id, int x, int y);
private native void touchMotion(long compositor, int id, int x, int y);
private native void touchUp(long compositor, int id);
private native void touchFrame(long compositor);
private native void pointerMotion(long compositor, int x, int y); private native void pointerMotion(long compositor, int x, int y);
private native void pointerScroll(long compositor, int axis, float value); private native void pointerScroll(long compositor, int axis, float value);
private native void pointerButton(long compositor, int button, int type); private native void pointerButton(long compositor, int button, int type);

View File

@ -0,0 +1,69 @@
package com.termux.x11;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.os.IBinder;
import android.support.annotation.RequiresApi;
import android.support.v4.app.NotificationCompat;
public class LorieTestService extends Service {
@Override
public IBinder onBind(Intent intent) {
return null;
}
static void start(Context context) {
Intent intent = new Intent(context, LorieTestService.class);
intent.setAction("start");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
context.startForegroundService(intent);
} else {
context.startService(intent);
}
}
@Override
public void onCreate() {
Intent notificationIntent = new Intent(getApplicationContext(), MainActivity.class);
notificationIntent.putExtra("foo_bar_extra_key", "foo_bar_extra_value");
notificationIntent.setAction(Long.toString(System.currentTimeMillis()));
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT);
//For creating the Foreground Service
int priority = Notification.PRIORITY_HIGH;
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N)
priority = NotificationManager.IMPORTANCE_HIGH;
NotificationManager notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
String channelId = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O ? getNotificationChannel(notificationManager) : "";
Notification notification = new NotificationCompat.Builder(this, channelId)
.setContentTitle("Termux:X11 Test service")
.setSmallIcon(R.drawable.ic_x11_icon)
.setContentText("foreground service")
.setContentIntent(pendingIntent)
.setOngoing(true)
.setPriority(priority)
.setShowWhen(false)
.setColor(0xFF607D8B)
.build();
startForeground(3, notification);
}
@RequiresApi(Build.VERSION_CODES.O)
private String getNotificationChannel(NotificationManager notificationManager){
String channelId = getResources().getString(R.string.app_name);
String channelName = getResources().getString(R.string.app_name);
NotificationChannel channel = new NotificationChannel(channelId, channelName, NotificationManager.IMPORTANCE_HIGH);
channel.setImportance(NotificationManager.IMPORTANCE_NONE);
channel.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE);
notificationManager.createNotificationChannel(channel);
return channelId;
}
}

View File

@ -1,36 +1,100 @@
package com.termux.x11; package com.termux.x11;
import android.content.res.Configuration; import android.content.SharedPreferences;
import android.inputmethodservice.InputMethodService;
import android.os.Build;
import android.preference.PreferenceManager;
import android.provider.Settings;
import android.support.v7.app.AppCompatActivity; import android.support.v7.app.AppCompatActivity;
import android.os.Bundle; import android.os.Bundle;
import android.view.KeyEvent;
import android.view.PointerIcon;
import android.view.SurfaceView;
import android.view.View;
import android.view.Window;
import android.view.WindowManager; import android.view.WindowManager;
import android.view.inputmethod.InputMethodManager;
public class MainActivity extends AppCompatActivity implements KeyboardUtils.SoftKeyboardToggleListener {
private static int[] keys = {
KeyEvent.KEYCODE_ESCAPE,
KeyEvent.KEYCODE_TAB,
KeyEvent.KEYCODE_CTRL_LEFT,
KeyEvent.KEYCODE_ALT_LEFT,
KeyEvent.KEYCODE_DPAD_UP,
KeyEvent.KEYCODE_DPAD_DOWN,
KeyEvent.KEYCODE_DPAD_LEFT,
KeyEvent.KEYCODE_DPAD_RIGHT,
};
AdditionalKeyboardView kbd;
boolean showAdditionalKbd = true;
public class MainActivity extends AppCompatActivity {
LorieService svc;
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
LorieService.setMainActivity(this);
LorieService.start(LorieService.ACTION_START_FROM_ACTIVITY);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
svc = new LorieService(this); requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(svc);
svc.finishInit(); setContentView(R.layout.main_activity);
svc.onConfigurationChanged(getResources().getConfiguration());
kbd = findViewById(R.id.additionalKbd);
kbd.setVisibility(View.INVISIBLE);
KeyboardUtils.addKeyboardToggleListener(this, this);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
getWindow().
getDecorView().
setPointerIcon(PointerIcon.getSystemIcon(this, PointerIcon.TYPE_NULL));
getResources().getConfiguration();
} }
public void onLorieServiceStart(LorieService instance) {
SurfaceView lorieView = findViewById(R.id.lorieView);
instance.setListeners(lorieView);
kbd.reload(keys, lorieView, LorieService.getOnKeyListener());
}
@Override @Override
public void onConfigurationChanged(Configuration newConfig) { public void onWindowFocusChanged(boolean hasFocus)
super.onConfigurationChanged(newConfig ); {
svc.onConfigurationChanged(newConfig); super.onWindowFocusChanged(hasFocus);
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
Window window = getWindow();
View decorView = window.getDecorView();
if (hasFocus && preferences.getBoolean("fullscreen", false))
{
window.setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);
decorView.setSystemUiVisibility(
View.SYSTEM_UI_FLAG_LAYOUT_STABLE
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_FULLSCREEN
| View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
} else {
window.clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
decorView.setSystemUiVisibility(0);
}
}
@Override
public void onToggleSoftKeyboard(boolean isVisible) {
if (kbd != null && showAdditionalKbd)
kbd.setVisibility((isVisible)?View.VISIBLE:View.INVISIBLE);
} }
@Override @Override
public void onBackPressed() {} public void onBackPressed() {}
@Override
public void onDestroy() {
svc.terminate();
super.onDestroy();
}
} }

View File

@ -0,0 +1,547 @@
/*
* 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.termux.x11;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Point;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.view.InputDevice;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
@SuppressWarnings("unused")
public class TouchParser {
private static final int WL_STATE_PRESSED = 1;
private static final int WL_STATE_RELEASED = 0;
private static final int WL_POINTER_AXIS_VERTICAL_SCROLL = 0;
private static final int WL_POINTER_AXIS_HORIZONTAL_SCROLL = 1;
static final int TOUCH_MODE_DIRECT = 1;
static final int TOUCH_MODE_MOUSE = 2;
static final int TOUCH_MODE_TOUCHPAD = 3;
static final int BTN_LEFT = 0x110;
static final int BTN_RIGHT = 0x111;
static final int BTN_MIDDLE = 0x112;
static final int ACTION_DOWN = WL_STATE_PRESSED;
static final int ACTION_UP = WL_STATE_RELEASED;
private static final int AXIS_X = WL_POINTER_AXIS_HORIZONTAL_SCROLL;
private static final int AXIS_Y = WL_POINTER_AXIS_VERTICAL_SCROLL;
public interface OnTouchParseListener {
void onPointerButton(int button, int state);
void onPointerMotion(int x, int y);
void onPointerScroll(int axis, float value);
void onTouchDown(int id, float x, float y);
void onTouchMotion(int id, float x, float y);
void onTouchUp(int id);
void onTouchFrame();
}
private int mTouchSlopSquare;
private int mDoubleTapTouchSlopSquare;
private int mDoubleTapSlopSquare;
private static final int LONGPRESS_TIMEOUT = ViewConfiguration.getLongPressTimeout();
private static final int TAP_TIMEOUT = ViewConfiguration.getTapTimeout();
private static final int DOUBLE_TAP_TIMEOUT = ViewConfiguration.getDoubleTapTimeout();
private static final int DOUBLE_TAP_MIN_TIME = 40;
private static final int FLAG_IS_GENERATED_GESTURE = 0x8;
private static final int TOUCH_SLOP = 8;
private static final int DOUBLE_TAP_SLOP = 100;
private static final int DOUBLE_TAP_TOUCH_SLOP = TOUCH_SLOP;
// constants for Message.what used by GestureHandler below
private static final int SHOW_PRESS = 1;
private static final int LONG_PRESS = 2;
private static final int TAP = 3;
private static final int RIGHT_TAP = 4;
private static final int TOUCH_FRAME = 5;
private final Handler mHandler;
private final OnTouchParseListener mListener;
private final HardwareMouseListener hmListener;
private boolean mStillDown;
private boolean mDeferConfirmSingleTap;
private boolean mInLongPress;
private boolean mInContextClick;
private boolean mAlwaysInTapRegion;
private boolean mAlwaysInBiggerTapRegion;
private MotionEvent mCurrentDownEvent;
private MotionEvent mPreviousUpEvent;
private boolean mIsDoubleTapping;
private float mLastFocusX;
private float mLastFocusY;
private float mDownFocusX;
private float mDownFocusY;
private boolean mIsLongpressEnabled;
private View target;
private Point cursor;
private int mMode = TOUCH_MODE_DIRECT;
private boolean mInDrag = false;
private boolean mWasInDrag = false;
@SuppressLint("HandlerLeak")
private class GestureHandler extends Handler {
GestureHandler() {
super();
}
GestureHandler(Handler handler) {
super(handler.getLooper());
}
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case SHOW_PRESS:
break;
case LONG_PRESS:
dispatchLongPress();
break;
case TAP:
// If the user's finger is still down, do not count it as a tap
if (!mStillDown) {
Log.e("tp", "tap");
mListener.onPointerButton(BTN_LEFT, ACTION_DOWN);
mListener.onPointerButton(BTN_LEFT, ACTION_UP);
} else {
mDeferConfirmSingleTap = true;
}
break;
case RIGHT_TAP:
break;
default:
throw new RuntimeException("Unknown message " + msg); //never
}
}
}
TouchParser(View view, OnTouchParseListener listener) {
mListener = listener;
hmListener = new HardwareMouseListener();
mHandler = new GestureHandler();
target = view;
cursor = new Point(target.getWidth()/2, target.getHeight()/2);
init();
}
private void init() {
Context context;
if (target == null) {
Log.e("TouchParser","TouchParser did not receive target view");
context = null;
}
else
context = target.getRootView().findViewById(android.R.id.content).getContext();
mIsLongpressEnabled = true;
// Fallback to support pre-donuts releases
int touchSlop, doubleTapSlop, doubleTapTouchSlop;
if (context == null) {
//noinspection deprecation
touchSlop = ViewConfiguration.getTouchSlop();
doubleTapTouchSlop = touchSlop; // Hack rather than adding a hiden method for this
doubleTapSlop = DOUBLE_TAP_SLOP;
//noinspection
} else {
final ViewConfiguration configuration = ViewConfiguration.get(context);
touchSlop = configuration.getScaledTouchSlop();
doubleTapTouchSlop = DOUBLE_TAP_TOUCH_SLOP;
doubleTapSlop = configuration.getScaledDoubleTapSlop();
}
mTouchSlopSquare = touchSlop * touchSlop;
mDoubleTapTouchSlopSquare = doubleTapTouchSlop * doubleTapTouchSlop;
mDoubleTapSlopSquare = doubleTapSlop * doubleTapSlop;
}
void setMode(int mode) {
mMode = mode;
}
public void setIsLongpressEnabled(boolean isLongpressEnabled) {
mIsLongpressEnabled = isLongpressEnabled;
}
public boolean isLongpressEnabled() {
return mIsLongpressEnabled;
}
boolean onTouchEvent(MotionEvent ev) {
if ((ev.getSource() & InputDevice.SOURCE_MOUSE) == InputDevice.SOURCE_MOUSE) {
return hmListener.onTouch(ev);
}
if (mMode == TOUCH_MODE_DIRECT) {
// get pointer index from the event object
int pointerIndex = ev.getActionIndex();
// get pointer ID
int pointerId = ev.getPointerId(pointerIndex);
switch(ev.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_POINTER_DOWN:
mListener.onTouchDown(pointerId, ev.getX(pointerIndex), ev.getY(pointerIndex));
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_POINTER_UP:
mListener.onTouchUp(pointerId);
break;
case MotionEvent.ACTION_MOVE:
for (int i=0; i< ev.getPointerCount(); i++)
mListener.onTouchMotion(ev.getPointerId(i), ev.getX(i), ev.getY(i));
break;
}
mListener.onTouchFrame();
return true;
}
final int action = ev.getAction();
final boolean pointerUp =
(action & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_POINTER_UP;
final int skipIndex = pointerUp ? ev.getActionIndex() : -1;
final boolean isGeneratedGesture =
(ev.getFlags() & FLAG_IS_GENERATED_GESTURE) != 0;
// Determine focal point
float sumX = 0, sumY = 0;
final int count = ev.getPointerCount();
for (int i = 0; i < count; i++) {
if (skipIndex == i) continue;
sumX += ev.getX(i);
sumY += ev.getY(i);
}
final int div = pointerUp ? count - 1 : count;
final float focusX = sumX / div;
final float focusY = sumY / div;
boolean handled = false;
switch (action & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_POINTER_DOWN:
mDownFocusX = mLastFocusX = focusX;
mDownFocusY = mLastFocusY = focusY;
// Cancel long press and taps
stopDrag();
cancelTaps();
mHandler.sendEmptyMessageDelayed(RIGHT_TAP, TAP_TIMEOUT);
break;
case MotionEvent.ACTION_POINTER_UP:
mDownFocusX = mLastFocusX = focusX;
mDownFocusY = mLastFocusY = focusY;
stopDrag();
break;
case MotionEvent.ACTION_DOWN:
boolean hadTapMessage = mHandler.hasMessages(TAP);
if (hadTapMessage) {
mHandler.removeMessages(TAP);
mHandler.removeMessages(RIGHT_TAP);
}
if ((mCurrentDownEvent != null) && (mPreviousUpEvent != null)
&& hadTapMessage
&& isConsideredDoubleTap(mCurrentDownEvent, mPreviousUpEvent, ev)) {
// This is a second tap
mIsDoubleTapping = true;
} else {
// This is a first tap
mHandler.sendEmptyMessageDelayed(TAP, DOUBLE_TAP_TIMEOUT);
}
mDownFocusX = mLastFocusX = focusX;
mDownFocusY = mLastFocusY = focusY;
if (mCurrentDownEvent != null) {
mCurrentDownEvent.recycle();
}
mCurrentDownEvent = MotionEvent.obtain(ev);
mAlwaysInTapRegion = true;
mAlwaysInBiggerTapRegion = true;
mStillDown = true;
mInLongPress = false;
mDeferConfirmSingleTap = false;
if (mIsLongpressEnabled) {
mHandler.removeMessages(LONG_PRESS);
if (!mIsDoubleTapping)
mHandler.sendEmptyMessageAtTime(LONG_PRESS,
mCurrentDownEvent.getDownTime() + LONGPRESS_TIMEOUT);
}
if (!mIsDoubleTapping)
mHandler.sendEmptyMessageAtTime(SHOW_PRESS,
mCurrentDownEvent.getDownTime() + TAP_TIMEOUT);
handled = true;
if (mMode == TOUCH_MODE_MOUSE)
mListener.onPointerMotion((int) ev.getX(), (int) ev.getY());
break;
case MotionEvent.ACTION_MOVE:
if (mInContextClick) {
break;
}
final float scrollX = mLastFocusX - focusX;
final float scrollY = mLastFocusY - focusY;
if (mAlwaysInTapRegion) {
final int deltaX = (int) (focusX - mDownFocusX);
final int deltaY = (int) (focusY - mDownFocusY);
int distance = (deltaX * deltaX) + (deltaY * deltaY);
int slopSquare = isGeneratedGesture ? 0 : mTouchSlopSquare;
if (distance > slopSquare) {
handled = true;
onScroll(ev, scrollX, scrollY);
mLastFocusX = focusX;
mLastFocusY = focusY;
mAlwaysInTapRegion = false;
mHandler.removeMessages(TAP);
mHandler.removeMessages(RIGHT_TAP);
mHandler.removeMessages(SHOW_PRESS);
mHandler.removeMessages(LONG_PRESS);
}
int doubleTapSlopSquare = isGeneratedGesture ? 0 : mDoubleTapTouchSlopSquare;
if (distance > doubleTapSlopSquare) {
mAlwaysInBiggerTapRegion = false;
}
} else if ((Math.abs(scrollX) >= 1) || (Math.abs(scrollY) >= 1)) {
handled = true;
onScroll(ev, scrollX, scrollY);
mLastFocusX = focusX;
mLastFocusY = focusY;
}
break;
case MotionEvent.ACTION_UP:
mStillDown = false;
MotionEvent currentUpEvent = MotionEvent.obtain(ev);
if (mMode == TOUCH_MODE_MOUSE) {
stopDrag();
}
if (mIsDoubleTapping) {
Log.e("1", "1");
mListener.onPointerButton(BTN_LEFT, ACTION_DOWN);
mListener.onPointerButton(BTN_LEFT, ACTION_UP);
mListener.onPointerButton(BTN_LEFT, ACTION_DOWN);
mListener.onPointerButton(BTN_LEFT, ACTION_UP);
handled = true;
} else if (mInLongPress) {
Log.e("2", "2");
mHandler.removeMessages(TAP);
mInLongPress = false;
mListener.onPointerButton(BTN_LEFT, ACTION_UP);
} else if (mAlwaysInTapRegion) {
Log.e("3", "3");
//if (mDeferConfirmSingleTap) {
//handled = mListener.onSingleTapConfirmed(ev);
//}
} else if (mHandler.hasMessages(RIGHT_TAP)) {
//Log.e("touch", "right tap confirmed");
mListener.onPointerButton(BTN_RIGHT, ACTION_DOWN);
mListener.onPointerButton(BTN_RIGHT, ACTION_UP);
}
if (mPreviousUpEvent != null) {
mPreviousUpEvent.recycle();
}
// Hold the event we obtained above - listeners may have changed the original.
mPreviousUpEvent = currentUpEvent;
mIsDoubleTapping = false;
mInLongPress = false;
mDeferConfirmSingleTap = false;
mInDrag = false;
mWasInDrag = false;
mHandler.removeMessages(SHOW_PRESS);
mHandler.removeMessages(LONG_PRESS);
mHandler.removeMessages(RIGHT_TAP);
break;
case MotionEvent.ACTION_CANCEL:
cancel();
break;
}
return handled;
}
private void onScroll(MotionEvent ev, float scrollX, float scrollY) {
if (ev.getPointerCount() == 1) {
if (mMode == TOUCH_MODE_MOUSE) {
if (!mWasInDrag) {
startDrag();
mListener.onPointerMotion((int) ev.getX(), (int) ev.getY());
}
return;
}
cursor.x -= (int) scrollX;
cursor.y -= (int) scrollY;
if (cursor.x < 0) cursor.x = 0;
if (cursor.y < 0) cursor.y = 0;
if (cursor.x > target.getWidth()) cursor.x = target.getWidth();
if (cursor.y > target.getHeight()) cursor.y = target.getHeight();
mListener.onPointerMotion(cursor.x, cursor.y);
} else if (ev.getPointerCount() == 2) {
if (scrollX != 0)
mListener.onPointerScroll(MotionEvent.AXIS_Y, (int)scrollX);
if (scrollY != 0)
mListener.onPointerScroll(MotionEvent.AXIS_X, (int)scrollY);
}
}
private void startDrag() {
if (mWasInDrag) return;
if (!mInDrag) {
Log.e("drag", "start");
mListener.onPointerButton(BTN_LEFT, ACTION_DOWN);
mInDrag = true;
}
}
private void stopDrag() {
if (mInDrag) {
Log.e("drag", "stop");
mListener.onPointerButton(BTN_LEFT, ACTION_UP);
mInDrag = false;
}
mWasInDrag = true;
}
private void cancel() {
mHandler.removeMessages(SHOW_PRESS);
mHandler.removeMessages(LONG_PRESS);
mHandler.removeMessages(TAP);
mHandler.removeMessages(RIGHT_TAP);
mIsDoubleTapping = false;
mStillDown = false;
mAlwaysInTapRegion = false;
mAlwaysInBiggerTapRegion = false;
mDeferConfirmSingleTap = false;
mInLongPress = false;
mInContextClick = false;
}
private void cancelTaps() {
mHandler.removeMessages(SHOW_PRESS);
mHandler.removeMessages(LONG_PRESS);
mHandler.removeMessages(TAP);
mIsDoubleTapping = false;
mAlwaysInTapRegion = false;
mAlwaysInBiggerTapRegion = false;
mDeferConfirmSingleTap = false;
mInLongPress = false;
mInContextClick = false;
}
private boolean isConsideredDoubleTap(MotionEvent firstDown, MotionEvent firstUp,
MotionEvent secondDown) {
if (!mAlwaysInBiggerTapRegion) {
return false;
}
final long deltaTime = secondDown.getEventTime() - firstUp.getEventTime();
if (deltaTime > DOUBLE_TAP_TIMEOUT || deltaTime < DOUBLE_TAP_MIN_TIME) {
return false;
}
int deltaX = (int) firstDown.getX() - (int) secondDown.getX();
int deltaY = (int) firstDown.getY() - (int) secondDown.getY();
final boolean isGeneratedGesture =
(firstDown.getFlags() & FLAG_IS_GENERATED_GESTURE) != 0;
int slopSquare = isGeneratedGesture ? 0 : mDoubleTapSlopSquare;
return (deltaX * deltaX + deltaY * deltaY < slopSquare);
}
private void dispatchLongPress() {
mHandler.removeMessages(TAP);
mDeferConfirmSingleTap = false;
mInLongPress = true;
Log.e("tp", "long press");
mListener.onPointerButton(BTN_LEFT, ACTION_DOWN);
}
private class HardwareMouseListener {
private int savedBS = 0;
private int currentBS = 0;
boolean isMouseButtonChanged(int mask) {
return (savedBS & mask) != (currentBS & mask);
}
int mouseButtonState(int mask) {
return ((currentBS & mask) != 0) ? ACTION_DOWN : ACTION_UP;
}
@SuppressLint("ClickableViewAccessibility")
boolean onTouch(MotionEvent e) {
if (e.getAction() == MotionEvent.ACTION_SCROLL) {
float scrollY = -5 * e.getAxisValue(MotionEvent.AXIS_VSCROLL);
float scrollX = -5 * e.getAxisValue(MotionEvent.AXIS_HSCROLL);
if (scrollY != 0) mListener.onPointerScroll(AXIS_Y, scrollY);
if (scrollX != 0) mListener.onPointerScroll(AXIS_X, scrollX);
return true;
}
mListener.onPointerMotion((int) e.getX(), (int) e.getY());
currentBS = e.getButtonState();
if (isMouseButtonChanged(MotionEvent.BUTTON_PRIMARY)) {
mListener.onPointerButton(BTN_LEFT, mouseButtonState(MotionEvent.BUTTON_PRIMARY));
}
if (isMouseButtonChanged(MotionEvent.BUTTON_TERTIARY)) {
mListener.onPointerButton(BTN_MIDDLE, mouseButtonState(MotionEvent.BUTTON_TERTIARY));
}
if (isMouseButtonChanged(MotionEvent.BUTTON_SECONDARY)) {
mListener.onPointerButton(BTN_RIGHT, mouseButtonState(MotionEvent.BUTTON_SECONDARY));
}
savedBS = currentBS;
return true;
}
}
}

View File

@ -122,7 +122,7 @@ void LorieBackendAndroid::get_keymap(int *fd, int *size) {
} }
void LorieBackendAndroid::window_change_callback(EGLNativeWindowType win, uint32_t width, uint32_t height, uint32_t physical_width, uint32_t physical_height) { void LorieBackendAndroid::window_change_callback(EGLNativeWindowType win, uint32_t width, uint32_t height, uint32_t physical_width, uint32_t physical_height) {
LOGE("WindowChangeCallback");
helper.setWindow(win); helper.setWindow(win);
output_resize(width, height, physical_width, physical_height); output_resize(width, height, physical_width, physical_height);
} }
@ -194,6 +194,42 @@ JNI_DECLARE(LorieService, windowChanged)(JNIEnv *env, jobject __unused instance,
b->queue.call(&LorieBackendAndroid::window_change_callback, b, win, width, height, mmWidth, mmHeight); b->queue.call(&LorieBackendAndroid::window_change_callback, b, win, width, height, mmWidth, mmHeight);
} }
extern "C" JNIEXPORT void JNICALL
JNI_DECLARE(LorieService, touchDown)(JNIEnv __unused *env, jobject __unused instance, jlong jcompositor, jint id, jint x, jint y) {
if (jcompositor == 0) return;
LorieBackendAndroid *b = fromLong(jcompositor);
LOGV("JNI: touch down");
b->touch_down(static_cast<uint32_t>(id), static_cast<uint32_t>(x), static_cast<uint32_t>(y));
}
extern "C" JNIEXPORT void JNICALL
JNI_DECLARE(LorieService, touchMotion)(JNIEnv __unused *env, jobject __unused instance, jlong jcompositor, jint id, jint x, jint y) {
if (jcompositor == 0) return;
LorieBackendAndroid *b = fromLong(jcompositor);
LOGV("JNI: touch motion");
b->touch_motion(static_cast<uint32_t>(id), static_cast<uint32_t>(x), static_cast<uint32_t>(y));
}
extern "C" JNIEXPORT void JNICALL
JNI_DECLARE(LorieService, touchUp)(JNIEnv __unused *env, jobject __unused instance, jlong jcompositor, jint id) {
if (jcompositor == 0) return;
LorieBackendAndroid *b = fromLong(jcompositor);
LOGV("JNI: touch up");
b->touch_up(static_cast<uint32_t>(id));
}
extern "C" JNIEXPORT void JNICALL
JNI_DECLARE(LorieService, touchFrame)(JNIEnv __unused *env, jobject __unused instance, jlong jcompositor) {
if (jcompositor == 0) return;
LorieBackendAndroid *b = fromLong(jcompositor);
LOGV("JNI: touch frame");
b->touch_frame();
}
extern "C" JNIEXPORT void JNICALL extern "C" JNIEXPORT void JNICALL
JNI_DECLARE(LorieService, pointerMotion)(JNIEnv __unused *env, jobject __unused instance, jlong jcompositor, jint x, jint y) { JNI_DECLARE(LorieService, pointerMotion)(JNIEnv __unused *env, jobject __unused instance, jlong jcompositor, jint x, jint y) {
if (jcompositor == 0) return; if (jcompositor == 0) return;
@ -208,8 +244,8 @@ JNI_DECLARE(LorieService, pointerScroll)(JNIEnv __unused *env, jobject __unused
if (jcompositor == 0) return; if (jcompositor == 0) return;
LorieBackendAndroid *b = fromLong(jcompositor); LorieBackendAndroid *b = fromLong(jcompositor);
LOGV("JNI: pointer scroll %d %d", axis, value); LOGV("JNI: pointer scroll %d %f", axis, value);
b->pointer_scroll(axis, value); b->pointer_scroll(static_cast<uint32_t>(axis), value);
} }
extern "C" JNIEXPORT void JNICALL extern "C" JNIEXPORT void JNICALL
@ -221,21 +257,6 @@ JNI_DECLARE(LorieService, pointerButton)(JNIEnv __unused *env, jobject __unused
b->pointer_button(static_cast<uint32_t>(button), static_cast<uint32_t>(type)); b->pointer_button(static_cast<uint32_t>(button), static_cast<uint32_t>(type));
} }
/*extern "C" JNIEXPORT void JNICALL
JNI_DECLARE(LorieService, onTouch)(JNIEnv __unused *env, jobject __unused instance, jlong __unused jcompositor, jint type, jint button, jint x, jint y) {
if (jcompositor == 0) return;
LorieBackendAndroid *b = fromLong(jcompositor);
if (type == WL_POINTER_MOTION) {
LOGV("JNI: pointer motion %dx%d", x, y);
b->pointer_motion(x, y);
}
else {
LOGV("JNI: pointer button %d type %d", button, type);
b->pointer_button(button, type);
}
}*/
extern "C" void get_character_data(char** layout, int *shift, int *ec, char *ch); extern "C" void get_character_data(char** layout, int *shift, int *ec, char *ch);
extern "C" void android_keycode_get_eventcode(int kc, int *ec, int *shift); extern "C" void android_keycode_get_eventcode(int kc, int *ec, int *shift);
@ -257,7 +278,6 @@ JNI_DECLARE(LorieService, keyboardKey)(JNIEnv *env, jobject __unused instance,
LOGE("kc: %d ec: %d", key_code, event_code); LOGE("kc: %d ec: %d", key_code, event_code);
if (strcmp(b->xkb_names.layout, "us") && event_code != 0) { if (strcmp(b->xkb_names.layout, "us") && event_code != 0) {
b->queue.call(&LorieBackendAndroid::layout_change_callback, b, (char*)"us"); b->queue.call(&LorieBackendAndroid::layout_change_callback, b, (char*)"us");
//lorie_layout_change_event(backend, "us");
} }
} }
if (!key_code && characters) { if (!key_code && characters) {
@ -265,7 +285,6 @@ JNI_DECLARE(LorieService, keyboardKey)(JNIEnv *env, jobject __unused instance,
get_character_data(&layout, &shift, &event_code, characters); get_character_data(&layout, &shift, &event_code, characters);
if (layout && b->xkb_names.layout != layout) { if (layout && b->xkb_names.layout != layout) {
b->queue.call(&LorieBackendAndroid::layout_change_callback, b, layout); b->queue.call(&LorieBackendAndroid::layout_change_callback, b, layout);
//lorie_layout_change_event(backend, layout);
} }
} }
LOGE("Keyboard input: keyCode: %d; eventCode: %d; characters: %s; shift: %d, type: %d", key_code, event_code, characters, shift, type); LOGE("Keyboard input: keyCode: %d; eventCode: %d; characters: %s; shift: %d, type: %d", key_code, event_code, characters, shift, type);

View File

@ -2,7 +2,7 @@
#include <android/log.h> #include <android/log.h>
#ifndef LOG_TAG #ifndef LOG_TAG
#define LOG_TAG "XLorie" #define LOG_TAG "LorieNative"
#endif #endif
#define LOG(prio, ...) __android_log_print(prio, LOG_TAG, __VA_ARGS__) #define LOG(prio, ...) __android_log_print(prio, LOG_TAG, __VA_ARGS__)

View File

@ -15,6 +15,10 @@ LorieCompositor::LorieCompositor() :
wrapper(terminate), wrapper(terminate),
wrapper(output_redraw), wrapper(output_redraw),
wrapper(output_resize), wrapper(output_resize),
wrapper(touch_down),
wrapper(touch_motion),
wrapper(touch_up),
wrapper(touch_frame),
wrapper(pointer_motion), wrapper(pointer_motion),
wrapper(pointer_scroll), wrapper(pointer_scroll),
wrapper(pointer_button), wrapper(pointer_button),
@ -97,21 +101,62 @@ void LorieCompositor::real_output_redraw() {
} }
void LorieCompositor::real_output_resize(uint32_t width, uint32_t height, uint32_t physical_width, uint32_t physical_height) { void LorieCompositor::real_output_resize(uint32_t width, uint32_t height, uint32_t physical_width, uint32_t physical_height) {
// Xwayland segfaults without that line
if (width == 0 || height == 0 || physical_width == 0 || physical_height == 0) return;
renderer.resize(width, height, physical_width, physical_height); renderer.resize(width, height, physical_width, physical_height);
output_redraw(); output_redraw();
} }
void LorieCompositor::real_touch_down(uint32_t id, uint32_t x, uint32_t y) {
LorieClient *client = get_toplevel_client();
if (toplevel == nullptr || client == nullptr) return;
wl_fixed_t surface_x = wl_fixed_from_int (x);
wl_fixed_t surface_y = wl_fixed_from_int (y);
client->touch.send_down(next_serial(), LorieUtils::timestamp(), toplevel->resource, id, surface_x, surface_y);
renderer.setCursorVisibility(false);
}
void LorieCompositor::real_touch_motion(uint32_t id, uint32_t x, uint32_t y) {
LorieClient *client = get_toplevel_client();
if (toplevel == nullptr || client == nullptr) return;
wl_fixed_t surface_x = wl_fixed_from_int (x);
wl_fixed_t surface_y = wl_fixed_from_int (y);
client->touch.send_motion(LorieUtils::timestamp(), id, surface_x, surface_y);
renderer.setCursorVisibility(false);
}
void LorieCompositor::real_touch_up(uint32_t id) {
LorieClient *client = get_toplevel_client();
if (toplevel == nullptr || client == nullptr) return;
client->touch.send_up(next_serial(), LorieUtils::timestamp(), id);
renderer.setCursorVisibility(false);
}
void LorieCompositor::real_touch_frame() {
LorieClient *client = get_toplevel_client();
if (toplevel == nullptr || client == nullptr) return;
client->touch.send_frame();
renderer.setCursorVisibility(false);
}
void LorieCompositor::real_pointer_motion(uint32_t x, uint32_t y) { void LorieCompositor::real_pointer_motion(uint32_t x, uint32_t y) {
LorieClient *client = get_toplevel_client(); LorieClient *client = get_toplevel_client();
if (client == nullptr) return; if (client == nullptr) return;
wl_fixed_t surface_x = wl_fixed_from_int (x); wl_fixed_t surface_x = wl_fixed_from_int (x);
wl_fixed_t surface_y = wl_fixed_from_int (y); wl_fixed_t surface_y = wl_fixed_from_int (y);
client->pointer.send_motion(LorieUtils::timestamp(), surface_x, surface_y); client->pointer.send_motion(LorieUtils::timestamp(), surface_x, surface_y);
client->pointer.send_frame(); client->pointer.send_frame();
renderer.cursorMove(x, y); renderer.setCursorVisibility(true);
renderer.cursorMove(x, y);
} }
void LorieCompositor::real_pointer_scroll(uint32_t axis, float value) { void LorieCompositor::real_pointer_scroll(uint32_t axis, float value) {
@ -123,6 +168,7 @@ void LorieCompositor::real_pointer_scroll(uint32_t axis, float value) {
client->pointer.send_axis_discrete(axis, (scroll>=0)?1:-1); client->pointer.send_axis_discrete(axis, (scroll>=0)?1:-1);
client->pointer.send_axis(LorieUtils::timestamp(), axis, scroll); client->pointer.send_axis(LorieUtils::timestamp(), axis, scroll);
client->pointer.send_frame(); client->pointer.send_frame();
renderer.setCursorVisibility(true);
} }
void LorieCompositor::real_pointer_button(uint32_t button, uint32_t state) { void LorieCompositor::real_pointer_button(uint32_t button, uint32_t state) {
@ -132,6 +178,7 @@ void LorieCompositor::real_pointer_button(uint32_t button, uint32_t state) {
LOGI("pointer button: %d %d", button, state); LOGI("pointer button: %d %d", button, state);
client->pointer.send_button (next_serial(), LorieUtils::timestamp(), button, state); client->pointer.send_button (next_serial(), LorieUtils::timestamp(), button, state);
client->pointer.send_frame(); client->pointer.send_frame();
renderer.setCursorVisibility(true);
} }
void LorieCompositor::real_keyboard_key(uint32_t key, uint32_t state) { void LorieCompositor::real_keyboard_key(uint32_t key, uint32_t state) {
@ -177,4 +224,4 @@ uint32_t LorieCompositor::next_serial() {
return wl_display_next_serial(display); return wl_display_next_serial(display);
} }
#pragma clang diagnostic pop #pragma clang diagnostic pop

View File

@ -25,6 +25,11 @@ public:
void real_terminate(); void real_terminate();
void real_output_redraw(); void real_output_redraw();
void real_output_resize(uint32_t width, uint32_t height, uint32_t physical_width, uint32_t physical_height); void real_output_resize(uint32_t width, uint32_t height, uint32_t physical_width, uint32_t physical_height);
void real_touch_down(uint32_t id, uint32_t x, uint32_t y);
void real_touch_motion(uint32_t id, uint32_t x, uint32_t y);
void real_touch_up(uint32_t id);
void real_touch_frame();
void real_pointer_motion(uint32_t x, uint32_t y); // absolute values void real_pointer_motion(uint32_t x, uint32_t y); // absolute values
void real_pointer_scroll(uint32_t axis, float value); void real_pointer_scroll(uint32_t axis, float value);
void real_pointer_button(uint32_t button, uint32_t state); void real_pointer_button(uint32_t button, uint32_t state);
@ -37,6 +42,10 @@ public:
wrapper(terminate); wrapper(terminate);
wrapper(output_redraw); wrapper(output_redraw);
wrapper(output_resize); wrapper(output_resize);
wrapper(touch_down);
wrapper(touch_motion);
wrapper(touch_up);
wrapper(touch_frame);
wrapper(pointer_motion); wrapper(pointer_motion);
wrapper(pointer_scroll); wrapper(pointer_scroll);
wrapper(pointer_button); wrapper(pointer_button);

View File

@ -14,10 +14,13 @@ public:
uint32_t physical_width = 270; uint32_t physical_width = 270;
uint32_t physical_height = 158; uint32_t physical_height = 158;
bool cursorVisible = false;
uint32_t hotspot_x, hotspot_y; uint32_t hotspot_x, hotspot_y;
void resize(uint32_t w, uint32_t h, uint32_t pw, uint32_t ph); void resize(uint32_t w, uint32_t h, uint32_t pw, uint32_t ph);
void cursorMove(uint32_t x, uint32_t y); void cursorMove(uint32_t x, uint32_t y);
void setCursorVisibility(bool visibility);
~LorieRenderer(); ~LorieRenderer();
private: private:
static void idleDraw(void *data); static void idleDraw(void *data);

View File

@ -171,6 +171,11 @@ void LorieRenderer::cursorMove(uint32_t x, uint32_t y) {
requestRedraw(); requestRedraw();
} }
void LorieRenderer::setCursorVisibility(bool visibility) {
if (cursorVisible != visibility)
cursorVisible = visibility;
}
void LorieRenderer::idleDraw(void *data) { void LorieRenderer::idleDraw(void *data) {
LorieRenderer* r = static_cast<LorieRenderer*> (data); LorieRenderer* r = static_cast<LorieRenderer*> (data);
@ -207,7 +212,8 @@ void LorieRenderer::redraw() {
compositor.toplevel->texture.draw(-1.0, -1.0, 1.0, 1.0); compositor.toplevel->texture.draw(-1.0, -1.0, 1.0, 1.0);
} }
drawCursor(); if (cursorVisible)
drawCursor();
compositor.swap_buffers(); compositor.swap_buffers();
} }

View File

@ -4,7 +4,8 @@
#include <unistd.h> #include <unistd.h>
void LorieSeat::on_create() { void LorieSeat::on_create() {
send_capabilities (WL_SEAT_CAPABILITY_POINTER|WL_SEAT_CAPABILITY_KEYBOARD); uint32_t capabilities = (*client)->compositor.input_capabilities();
send_capabilities (capabilities);
send_name("default"); send_name("default");
} }

View File

@ -23,6 +23,13 @@ extern "C" {
extern void *blacklist[]; extern void *blacklist[];
#define skip_blacklisted(f) for (int z=0; blacklist[z]!=NULL; z++) if (blacklist[z]==f) return; #define skip_blacklisted(f) for (int z=0; blacklist[z]!=NULL; z++) if (blacklist[z]==f) return;
static bool lock_needed =
#ifndef __ANDROID__
true;
#else
false;
#endif
static pthread_mutex_t lock; static pthread_mutex_t lock;
// see https://github.com/littlstar/asprintf.c/blob/master/asprintf.c // see https://github.com/littlstar/asprintf.c/blob/master/asprintf.c
@ -102,16 +109,16 @@ static void LogMessageInternal(int priority, const char *format, ...) {
void LogMessage(int priority, const char *format, ...) { void LogMessage(int priority, const char *format, ...) {
if (logFunction == NULL) return; if (logFunction == NULL) return;
pthread_mutex_lock(&::lock); if (lock_needed) pthread_mutex_lock(&::lock);
va_list argptr; va_list argptr;
va_start(argptr, format); va_start(argptr, format);
logFunction(priority, format, argptr); logFunction(priority, format, argptr);
va_end(argptr); va_end(argptr);
pthread_mutex_unlock(&::lock); if (lock_needed) pthread_mutex_unlock(&::lock);
} }
void LogInit(void) { void LogInit(void) {
if (pthread_mutex_init(&::lock, NULL) != 0) { if (lock_needed && pthread_mutex_init(&::lock, NULL) != 0) {
LogMessageInternal(LOG_ERROR, "Logger mutex init failed\n"); LogMessageInternal(LOG_ERROR, "Logger mutex init failed\n");
return; return;
} }

View File

@ -0,0 +1,22 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="24dp"
android:height="24dp"
android:viewportWidth="429.43842"
android:viewportHeight="429.43842"
android:tint="#FFFFFF">
<group android:translateX="-0.7807971"
android:translateY="42.219204">
<path
android:fillColor="#FF000000"
android:pathData="M71.148,325.001L182.245,179.745L67.868,20.032L141.383,20.093L232.521,145.676L96.25,325.001L71.148,325.001zM288.595,325.104L198.087,199.306L335.491,19.896L359.973,19.896L248.423,165.838L362.111,325.104L288.595,325.104z"/>
<path
android:fillColor="#FF000000"
android:pathData="M215.484,78.126C206.633,78.126 197.177,78.811 188.664,79.628C203.83,99.408 216.414,116.158 230.897,135.18C222.768,117.687 206.062,100.912 211.837,92.285C217.554,83.746 228.088,85.183 228.891,85.183C245.779,85.183 262.036,87.09 277.234,90.621L280.797,85.714C260.526,80.496 238.508,78.126 215.484,78.126zM306.953,94.089L303.672,98.777C350.498,117.113 382.172,152.137 382.172,192.277C382.172,251.383 313.507,299.339 228.891,299.339C144.274,299.339 75.578,251.383 75.578,192.277C75.578,160.089 95.95,131.197 128.172,111.558L118.266,96.464C67.72,117.896 34.172,155.514 34.172,198.339C34.172,264.973 115.394,319.027 215.484,319.027C315.574,319.027 396.828,264.973 396.828,198.339C396.828,153.906 360.706,115.047 306.953,94.089z"/>
<path
android:pathData="M464.649,155.856h0.505v0h-0.505z"
android:strokeAlpha="0.4074074"
android:fillColor="#000cff"
android:fillAlpha="0.4074074"/>
</group>
</vector>

Binary file not shown.

After

Width:  |  Height:  |  Size: 786 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 522 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent" android:layout_height="match_parent">
<SurfaceView
android:id="@+id/lorieView"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<ScrollView
android:id="@+id/sv"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@id/additionalKbd" />
<com.termux.x11.AdditionalKeyboardView
android:id="@+id/additionalKbd"
android:layout_width="match_parent"
android:layout_height="35dp"
android:layout_alignParentBottom="true" />
</RelativeLayout>
</FrameLayout>

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.Toolbar xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="?attr/actionBarSize"
android:background="?attr/colorPrimary"/>

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string-array name="touchscreenInputModesEntries">
<item>Direct mode</item>
<item>Emulate mouse</item>
<item>Emulate touchpad</item>
</string-array>
<string-array name="touchscreenInputModesValues">
<item>1</item>
<item>2</item>
<item>3</item>
</string-array>
</resources>

View File

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<CheckBoxPreference android:title="Show additional keyboard"
android:defaultValue="true"
android:summary="Show keyboard with additional keys if soft keyboard is visible"
android:key="showAdditionalKbd" />
<CheckBoxPreference android:title="Fullscreen mode"
android:defaultValue="false"
android:key="fullscreen" />
<ListPreference
android:title="Touchscreen input mode"
android:key="touchMode"
android:defaultValue="1"
android:entries="@array/touchscreenInputModesEntries"
android:entryValues="@array/touchscreenInputModesValues" />
<CheckBoxPreference
android:title="Show IME with external keyboard"
android:defaultValue="false"
android:key="showIMEWhileExternalConnected"
android:summary="Show software keyboard while hardware keyboard is connected" />
</PreferenceScreen>