diff --git a/edxp-core/build.gradle b/edxp-core/build.gradle
index 712fe618..c13d76a6 100644
--- a/edxp-core/build.gradle
+++ b/edxp-core/build.gradle
@@ -38,6 +38,7 @@ dependencies {
implementation 'rikka.ndk:riru:10'
implementation project(path: ':sandhook-hooklib')
compileOnly project(':hiddenapi-stubs')
+ compileOnly project(':key-selector')
compileOnly 'androidx.annotation:annotation:1.1.0'
}
@@ -152,6 +153,7 @@ afterEvaluate {
def prepareMagiskFilesTask = task("prepareMagiskFiles${variantCapped}", type: Delete) {
dependsOn "assemble${variantCapped}"
dependsOn tasks.getByPath(":sandhook-hooklib:copySandHook${variantCapped}LibraryToMagiskTemplate")
+ dependsOn tasks.getByPath(":key-selector:copyKeySelector${variantCapped}LibraryToMagiskTemplate")
doFirst {
copy {
from "${projectDir}/tpl/module.prop.tpl"
diff --git a/key-selector/.gitignore b/key-selector/.gitignore
new file mode 100644
index 00000000..42afabfd
--- /dev/null
+++ b/key-selector/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/key-selector/build.gradle b/key-selector/build.gradle
new file mode 100644
index 00000000..b3b8d2d7
--- /dev/null
+++ b/key-selector/build.gradle
@@ -0,0 +1,76 @@
+apply plugin: 'com.android.library'
+
+android {
+ compileSdkVersion androidCompileSdkVersion.toInteger()
+
+ defaultConfig {
+ minSdkVersion androidMinSdkVersion.toInteger()
+ targetSdkVersion androidTargetSdkVersion.toInteger()
+
+ externalNativeBuild {
+ cmake {
+ abiFilters 'arm64-v8a', 'armeabi-v7a', 'x86', 'x86_64'
+ cppFlags "-std=c++17"
+ cFlags "-std=gnu99"
+ }
+ }
+ }
+
+ buildTypes {
+ debug {
+ externalNativeBuild {
+ cmake {
+ cppFlags "-O0"
+ cFlags "-O0"
+ }
+ }
+ }
+ release {
+ externalNativeBuild {
+ cmake {
+ cppFlags "-fvisibility=hidden -fvisibility-inlines-hidden -O2 -s -Wno-unused-value"
+ cFlags "-fvisibility=hidden -fvisibility-inlines-hidden -O2 -s -Wno-unused-value"
+ }
+ }
+ }
+ }
+ externalNativeBuild {
+ cmake {
+ path "src/main/cpp/CMakeLists.txt"
+ }
+ }
+}
+
+afterEvaluate {
+ android.libraryVariants.all { variant ->
+ def variantNameCapped = variant.name.capitalize()
+ def variantNameLowered = variant.name.toLowerCase()
+
+ task("copyKeySelector${variantNameCapped}LibraryToMagiskTemplate") {
+ def libPathRelease = "${buildDir}/intermediates/cmake/${variantNameLowered}/obj"
+ doLast {
+ copy {
+ include "key_selector"
+ from "${libPathRelease}/armeabi-v7a"
+ into "${zipPathMagiskReleasePath}/system/bin"
+ }
+ copy {
+ include "key_selector"
+ from "${libPathRelease}/arm64-v8a"
+ into "${zipPathMagiskReleasePath}/system/bin64"
+ }
+ copy {
+ include "key_selector"
+ from "${libPathRelease}/x86"
+ into "${zipPathMagiskReleasePath}/system_x86/bin"
+ }
+ copy {
+ include "key_selector"
+ from "${libPathRelease}/x86_64"
+ into "${zipPathMagiskReleasePath}/system_x86/bin64"
+ }
+ }
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/key-selector/src/main/AndroidManifest.xml b/key-selector/src/main/AndroidManifest.xml
new file mode 100644
index 00000000..cbe21451
--- /dev/null
+++ b/key-selector/src/main/AndroidManifest.xml
@@ -0,0 +1,5 @@
+
+
+
+
\ No newline at end of file
diff --git a/key-selector/src/main/cpp/CMakeLists.txt b/key-selector/src/main/cpp/CMakeLists.txt
new file mode 100644
index 00000000..10e183ff
--- /dev/null
+++ b/key-selector/src/main/cpp/CMakeLists.txt
@@ -0,0 +1,5 @@
+cmake_minimum_required(VERSION 3.4.1)
+
+project(key_selector)
+
+add_executable(key_selector key_selector.cpp)
diff --git a/key-selector/src/main/cpp/key_selector.cpp b/key-selector/src/main/cpp/key_selector.cpp
new file mode 100644
index 00000000..7e31fe1c
--- /dev/null
+++ b/key-selector/src/main/cpp/key_selector.cpp
@@ -0,0 +1,298 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ * Copyright (C) 2015-2016 The CyanogenMod Project
+ * Copyright (C) 2021 LSPosed
+ *
+ * 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.
+ */
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "key_selector.h"
+
+// Global variables
+static struct pollfd *ufds;
+static char **device_names;
+static int nfds;
+
+static int open_device(const char *device)
+{
+ int version;
+ int fd;
+ int clkid = CLOCK_MONOTONIC;
+ struct pollfd *new_ufds;
+ char **new_device_names;
+ char name[80];
+ char location[80];
+ char idstr[80];
+ struct input_id id;
+
+ fd = open(device, O_RDWR);
+ if (fd < 0) {
+ return -1;
+ }
+
+ if (ioctl(fd, EVIOCGVERSION, &version)) {
+ return -1;
+ }
+
+ if (ioctl(fd, EVIOCGID, &id)) {
+ return -1;
+ }
+
+ name[sizeof(name) - 1] = '\0';
+ location[sizeof(location) - 1] = '\0';
+ idstr[sizeof(idstr) - 1] = '\0';
+
+ if (ioctl(fd, EVIOCGNAME(sizeof(name) - 1), &name) < 1) {
+ name[0] = '\0';
+ }
+
+ if (ioctl(fd, EVIOCGPHYS(sizeof(location) - 1), &location) < 1) {
+ location[0] = '\0';
+ }
+
+ if (ioctl(fd, EVIOCGUNIQ(sizeof(idstr) - 1), &idstr) < 1) {
+ idstr[0] = '\0';
+ }
+
+ if (ioctl(fd, EVIOCSCLOCKID, &clkid) != 0) {
+ // a non-fatal error
+ }
+
+ new_ufds = static_cast(realloc(ufds, sizeof(ufds[0]) * (nfds + 1)));
+ if (new_ufds == nullptr) {
+ return -1;
+ }
+
+ ufds = new_ufds;
+ new_device_names = static_cast(realloc(device_names,
+ sizeof(device_names[0]) * (nfds + 1)));
+ if (new_device_names == nullptr) {
+ return -1;
+ }
+
+ device_names = new_device_names;
+
+ ufds[nfds].fd = fd;
+ ufds[nfds].events = POLLIN;
+ device_names[nfds] = strdup(device);
+ nfds++;
+
+ return 0;
+}
+
+int close_device(const char *device)
+{
+ int i;
+ for (i = 1; i < nfds; i++) {
+ if (strcmp(device_names[i], device) == 0) {
+ int count = nfds - i - 1;
+ free(device_names[i]);
+ memmove(device_names + i, device_names + i + 1,
+ sizeof(device_names[0]) * count);
+ memmove(ufds + i, ufds + i + 1, sizeof(ufds[0]) * count);
+ nfds--;
+ return 0;
+ }
+ }
+ return -1;
+}
+
+static int read_notify(const char *dirname, int nfd)
+{
+ int res;
+ char devname[PATH_MAX];
+ char *filename;
+ char event_buf[512];
+ int event_size;
+ int event_pos = 0;
+ struct inotify_event *event;
+
+ res = read(nfd, event_buf, sizeof(event_buf));
+ if (res < (int)sizeof(*event)) {
+ if (errno == EINTR) {
+ return 0;
+ }
+ return 1;
+ }
+
+ strcpy(devname, dirname);
+ filename = devname + strlen(devname);
+ *filename++ = '/';
+
+ while (res >= (int)sizeof(*event)) {
+ event = (struct inotify_event *)(event_buf + event_pos);
+ if (event->len) {
+ strcpy(filename, event->name);
+ if (event->mask & IN_CREATE) {
+ open_device(devname);
+ } else {
+ close_device(devname);
+ }
+ }
+ event_size = sizeof(*event) + event->len;
+ res -= event_size;
+ event_pos += event_size;
+ }
+ return 0;
+}
+
+static int scan_dir(const char *dirname)
+{
+ char devname[PATH_MAX];
+ char *filename;
+ DIR *dir;
+ struct dirent *de;
+
+ dir = opendir(dirname);
+ if (dir == NULL) {
+ return -1;
+ }
+
+ strcpy(devname, dirname);
+ filename = devname + strlen(devname);
+ *filename++ = '/';
+
+ while ((de = readdir(dir))) {
+ if (de->d_name[0] == '.' && (de->d_name[1] == '\0' ||
+ (de->d_name[1] == '.' && de->d_name[2] == '\0'))) {
+ continue;
+ }
+
+ strcpy(filename, de->d_name);
+ open_device(devname);
+ }
+ closedir(dir);
+ return 0;
+}
+
+
+uint32_t get_event() {
+ int i;
+ int res;
+ struct input_event event;
+ const char *device_path = "/dev/input";
+ unsigned char keys;
+
+ keys = KEYCHECK_CHECK_VOLUMEDOWN | KEYCHECK_CHECK_VOLUMEUP;
+ nfds = 1;
+ ufds = static_cast(calloc(1, sizeof(ufds[0])));
+ ufds[0].fd = inotify_init();
+ ufds[0].events = POLLIN;
+
+ res = inotify_add_watch(ufds[0].fd, device_path, IN_DELETE | IN_CREATE);
+ if (res < 0) {
+ throw std::logic_error("inotify_add_watch failed");
+ }
+
+ res = scan_dir(device_path);
+ if (res < 0) {
+ throw std::logic_error("scan dev failed");
+ }
+
+ while (true) {
+ poll(ufds, nfds, -1);
+ if (ufds[0].revents & POLLIN) {
+ read_notify(device_path, ufds[0].fd);
+ }
+
+ for (i = 1; i < nfds; i++) {
+ if ((ufds[i].revents) && (ufds[i].revents & POLLIN)) {
+ res = read(ufds[i].fd, &event, sizeof(event));
+ if (res < (int)sizeof(event)) {
+ return 1;
+ }
+
+ // keypress only
+ if (event.value == 1) {
+ if (event.code == KEY_VOLUMEDOWN &&
+ (keys & KEYCHECK_CHECK_VOLUMEDOWN) != 0) {
+ return KEYCHECK_PRESSED_VOLUMEDOWN;
+ }
+ else if (event.code == KEY_VOLUMEUP &&
+ (keys & KEYCHECK_CHECK_VOLUMEUP) != 0) {
+ return KEYCHECK_PRESSED_VOLUMEUP;
+ }
+ }
+ }
+ }
+ }
+}
+
+int main() {
+ if (getuid() != 0) {
+ std::cout << "Root required" << std::endl;
+ exit(1);
+ }
+
+ // for phone which has no button
+ const uint16_t timeout = 20;
+ alarm(timeout);
+ auto sig_handler = [](int){
+ std::cout << "No operation after " << timeout << " seconds" << std::endl;
+ exit(YAHFA);
+ };
+ signal(SIGALRM, sig_handler);
+
+ int cursor = YAHFA;
+ const int cursor_max = SandHook;
+
+ auto print_status = [&cursor](){
+ std::cout << "\33[2K\r"; // clear this line
+ std::cout << "[";
+ std::cout << (cursor == YAHFA ? "x" : " ");
+ std::cout << "] YAHFA [";
+ std::cout << (cursor == SandHook ? "x" : " ");
+ std::cout << "] SandHook" << std::flush;
+ };
+
+ std::cout << "Select variant. Use Volume Down to move and Volume Up to confirm." << std::endl;
+ std::cout << "The program will select YAHFA for you in " << timeout << " seconds if you don't have a physical volume button. " << std::endl;
+ print_status();
+ while (int event = get_event()) {
+ bool leave = false;
+ //std::cout << event << " " << cursor << std::endl;
+ switch (event) {
+ case KEYCHECK_PRESSED_VOLUMEUP:
+ leave = true;
+ break;
+ case KEYCHECK_PRESSED_VOLUMEDOWN:
+ cursor++;
+ if (cursor > cursor_max) {
+ cursor = YAHFA;
+ }
+ break;
+ default:
+ std::cout << "ERROR\n";
+ }
+ print_status();
+ if (leave) {
+ break;
+ }
+ }
+
+ // std::cout << std::endl << cursor << std::endl;
+ return cursor;
+}
\ No newline at end of file
diff --git a/key-selector/src/main/cpp/key_selector.h b/key-selector/src/main/cpp/key_selector.h
new file mode 100644
index 00000000..730c4f0f
--- /dev/null
+++ b/key-selector/src/main/cpp/key_selector.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2015-2016 The CyanogenMod Project
+ * Copyright (C) 2021 LSPosed
+ *
+ * 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.
+ */
+
+#ifndef __KEYCHECK_H__
+#define __KEYCHECK_H__
+
+// Constants: pressed keys
+#define KEYCHECK_CHECK_VOLUMEDOWN 0x01u
+#define KEYCHECK_CHECK_VOLUMEUP 0x02u
+#define KEYCHECK_PRESSED_VOLUMEDOWN 41u
+#define KEYCHECK_PRESSED_VOLUMEUP 42u
+
+enum Variant {
+ YAHFA = 0x11,
+ SandHook = 0x12,
+};
+
+#endif // __KEYCHECK_H__
\ No newline at end of file
diff --git a/settings.gradle b/settings.gradle
index 82bdba0d..e8f128de 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -1 +1 @@
-include ':edxp-core', ':hiddenapi-stubs', ':sandhook-hooklib', ':sandhook-annotation', ':app'
+include ':edxp-core', ':hiddenapi-stubs', ':sandhook-hooklib', ':sandhook-annotation', ':app', ':key-selector'