Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/ClusterM/clukeyboard.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlexey 'Cluster' Avdyukhin <clusterrr@clusterrr.com>2019-12-11 09:26:10 +0300
committerAlexey 'Cluster' Avdyukhin <clusterrr@clusterrr.com>2019-12-11 09:26:10 +0300
commita583a355a1bd64cbae173822f34f19b9ace0a9eb (patch)
treed5495cd0ba2672e22fe28d46e3708ee59cad846d
First commit
-rw-r--r--.gitignore13
-rw-r--r--app/.gitignore1
-rw-r--r--app/build.gradle34
-rw-r--r--app/proguard-rules.pro21
-rw-r--r--app/src/main/AndroidManifest.xml29
-rw-r--r--app/src/main/java/com/clusterrr/hardwarekeyboard/KeyMapping.kt12
-rw-r--r--app/src/main/java/com/clusterrr/hardwarekeyboard/Keyboard.kt385
-rw-r--r--app/src/main/java/com/clusterrr/hardwarekeyboard/RusMapping.kt469
-rw-r--r--app/src/main/java/com/clusterrr/hardwarekeyboard/ScanCodes.kt196
-rw-r--r--app/src/main/res/drawable-v24/ic_launcher_foreground.xml34
-rw-r--r--app/src/main/res/drawable/ic_launcher_background.xml170
-rw-r--r--app/src/main/res/layout/keyboard_layout.xml48
-rw-r--r--app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml5
-rw-r--r--app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml5
-rw-r--r--app/src/main/res/mipmap-hdpi/ic_launcher.pngbin0 -> 2963 bytes
-rw-r--r--app/src/main/res/mipmap-hdpi/ic_launcher_round.pngbin0 -> 4905 bytes
-rw-r--r--app/src/main/res/mipmap-mdpi/ic_launcher.pngbin0 -> 2060 bytes
-rw-r--r--app/src/main/res/mipmap-mdpi/ic_launcher_round.pngbin0 -> 2783 bytes
-rw-r--r--app/src/main/res/mipmap-xhdpi/ic_launcher.pngbin0 -> 4490 bytes
-rw-r--r--app/src/main/res/mipmap-xhdpi/ic_launcher_round.pngbin0 -> 6895 bytes
-rw-r--r--app/src/main/res/mipmap-xxhdpi/ic_launcher.pngbin0 -> 6387 bytes
-rw-r--r--app/src/main/res/mipmap-xxhdpi/ic_launcher_round.pngbin0 -> 10413 bytes
-rw-r--r--app/src/main/res/mipmap-xxxhdpi/ic_launcher.pngbin0 -> 9128 bytes
-rw-r--r--app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.pngbin0 -> 15132 bytes
-rw-r--r--app/src/main/res/values/colors.xml9
-rw-r--r--app/src/main/res/values/strings.xml3
-rw-r--r--app/src/main/res/values/styles.xml10
-rw-r--r--app/src/main/res/xml/method.xml5
-rw-r--r--build.gradle29
-rw-r--r--gradle.properties15
-rw-r--r--gradle/wrapper/gradle-wrapper.jarbin0 -> 54329 bytes
-rw-r--r--gradle/wrapper/gradle-wrapper.properties6
-rw-r--r--gradlew172
-rw-r--r--gradlew.bat84
-rw-r--r--settings.gradle1
35 files changed, 1756 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..e76c77c
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,13 @@
+*.iml
+.gradle
+/local.properties
+/.idea/caches
+/.idea/libraries
+/.idea/modules.xml
+/.idea/workspace.xml
+/.idea/navEditor.xml
+/.idea/assetWizardSettings.xml
+.DS_Store
+/build
+/captures
+.externalNativeBuild
diff --git a/app/.gitignore b/app/.gitignore
new file mode 100644
index 0000000..3543521
--- /dev/null
+++ b/app/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/app/build.gradle b/app/build.gradle
new file mode 100644
index 0000000..8f7794b
--- /dev/null
+++ b/app/build.gradle
@@ -0,0 +1,34 @@
+apply plugin: 'com.android.application'
+apply plugin: 'kotlin-android-extensions'
+apply plugin: 'kotlin-android'
+
+android {
+ compileSdkVersion 28
+ defaultConfig {
+ applicationId "com.clusterrr.hardwarekeyboard"
+ minSdkVersion 28
+ targetSdkVersion 28
+ versionCode 1
+ versionName "1.0"
+ testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
+ }
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ }
+ }
+}
+
+dependencies {
+ implementation fileTree(dir: 'libs', include: ['*.jar'])
+ implementation 'com.android.support:appcompat-v7:28.0.0'
+ implementation 'com.android.support.constraint:constraint-layout:1.1.3'
+ testImplementation 'junit:junit:4.12'
+ androidTestImplementation 'com.android.support.test:runner:1.0.2'
+ androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
+ implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
+}
+repositories {
+ mavenCentral()
+}
diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro
new file mode 100644
index 0000000..6e7ffa9
--- /dev/null
+++ b/app/proguard-rules.pro
@@ -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
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..22ec5f7
--- /dev/null
+++ b/app/src/main/AndroidManifest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.clusterrr.hardwarekeyboard">
+
+ <uses-permission android:name="android.permission.WRITE_SETTINGS" />
+
+ <application
+ android:allowBackup="true"
+ android:icon="@mipmap/ic_launcher"
+ android:label="@string/app_name"
+ android:roundIcon="@mipmap/ic_launcher_round"
+ android:resizeableActivity="true"
+ android:theme="@style/AppTheme">
+
+ <service
+ android:name=".Keyboard"
+ android:enabled="true"
+ android:label="@string/app_name"
+ android:exported="true"
+ 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> \ No newline at end of file
diff --git a/app/src/main/java/com/clusterrr/hardwarekeyboard/KeyMapping.kt b/app/src/main/java/com/clusterrr/hardwarekeyboard/KeyMapping.kt
new file mode 100644
index 0000000..2007d84
--- /dev/null
+++ b/app/src/main/java/com/clusterrr/hardwarekeyboard/KeyMapping.kt
@@ -0,0 +1,12 @@
+package com.clusterrr.hardwarekeyboard
+
+open class KeyMapping {
+ var ReplaceKeyCode: Int = 0
+ var ReplaceScanCode: Int = 0
+ var AlternateKeyCode: Int = 0
+ var AlternateScanCode: Int = 0
+ var AlternateFix: Boolean = false
+ var Char: String? = null
+ var ShiftChar: String? = null
+ var IgnoreCapsLock: Boolean = false
+}
diff --git a/app/src/main/java/com/clusterrr/hardwarekeyboard/Keyboard.kt b/app/src/main/java/com/clusterrr/hardwarekeyboard/Keyboard.kt
new file mode 100644
index 0000000..2314440
--- /dev/null
+++ b/app/src/main/java/com/clusterrr/hardwarekeyboard/Keyboard.kt
@@ -0,0 +1,385 @@
+package com.clusterrr.hardwarekeyboard
+
+import android.inputmethodservice.InputMethodService
+import android.util.Log
+import android.view.KeyEvent
+import android.view.View
+import android.widget.CompoundButton
+import android.widget.Switch
+import android.widget.TextView
+import android.widget.Toast
+import java.util.ArrayList
+import android.provider.Settings
+import java.lang.Exception
+
+class Keyboard : InputMethodService(), View.OnClickListener, CompoundButton.OnCheckedChangeListener {
+ private val FN_KEY = ScanCodes.SCANCODE_SHOW_KEYBOARD
+ private val LANGUAGE_KEYS = arrayOf(
+ intArrayOf(ScanCodes.SCANCODE_LANGUAGE),
+ intArrayOf(ScanCodes.SCANCODE_SHIFT_LEFT, ScanCodes.SCANCODE_CTRL_LEFT)
+ )
+ private val FN_FIX_KEYS = arrayOf(
+ intArrayOf(ScanCodes.SCANCODE_SHOW_KEYBOARD, ScanCodes.SCANCODE_SPACE)
+ )
+ private val SHOW_HIDE_KEYS = arrayOf(intArrayOf(ScanCodes.SCANCODE_SHOW_KEYBOARD, ScanCodes.SCANCODE_TAB))
+ private val BRIGHTNESS_DOWN_KEYS = arrayOf(intArrayOf(ScanCodes.SCANCODE_SHOW_KEYBOARD, ScanCodes.SCANCODE_J))
+ private val BRIGHTNESS_UP_KEYS = arrayOf(intArrayOf(ScanCodes.SCANCODE_SHOW_KEYBOARD, ScanCodes.SCANCODE_K))
+ private val SCANCODE_ONLY_APPS = arrayOf("com.microsoft.rdc.android")
+
+ private var altLanguage = false
+ private var fnLock = false
+ private val keysPressed = ArrayList<Int>()
+ private var capsLock = false
+
+ internal var textViewLanguage: TextView? = null
+ internal var switchCapsLock: Switch? = null
+ internal var switchFnLock: Switch? = null
+
+ override fun onEvaluateFullscreenMode(): Boolean {
+ return false
+ }
+
+ override fun onCreateInputView(): View {
+ val inputView = layoutInflater.inflate(R.layout.keyboard_layout, null)
+ textViewLanguage = inputView.findViewById(R.id.textViewLanguage)
+ switchCapsLock = inputView.findViewById(R.id.switchCapsLock)
+ switchFnLock = inputView.findViewById(R.id.switchFnLock)
+ textViewLanguage!!.setOnClickListener(this)
+ switchCapsLock!!.setOnCheckedChangeListener(this)
+ switchFnLock!!.setOnCheckedChangeListener(this)
+ updateLangage()
+ updateFnFixSwitch()
+ updateCapsLockSwitch()
+ return inputView
+ }
+
+ private fun updateLangage(): String {
+ var languageName: String? = null
+ if (!altLanguage)
+ languageName = "EN"
+ else
+ languageName = "RU"
+ if (textViewLanguage != null)
+ textViewLanguage!!.text = languageName
+ return languageName
+ }
+
+ fun toggleLanguage() {
+ altLanguage = !altLanguage
+ val languageName = updateLangage()
+ Toast.makeText(this, languageName, Toast.LENGTH_SHORT).show()
+ }
+
+ private fun updateFnFixSwitch() {
+ if (switchFnLock == null)
+ return
+ if (switchFnLock!!.isChecked != fnLock) {
+ switchFnLock!!.setOnCheckedChangeListener(null)
+ switchFnLock!!.isChecked = fnLock
+ switchFnLock!!.setOnCheckedChangeListener(this)
+ }
+ }
+
+ fun toggleFnLock() {
+ if (!fnLock) {
+ fnLock = true
+ Toast.makeText(this, "FN lock: on", Toast.LENGTH_SHORT).show()
+ } else {
+ fnLock = false
+ Toast.makeText(this, "FN lock: off", Toast.LENGTH_SHORT).show()
+ }
+ updateFnFixSwitch()
+ }
+
+ private fun updateCapsLockSwitch() {
+ if (switchCapsLock == null)
+ return
+ if (switchCapsLock!!.isChecked != capsLock)
+ switchCapsLock!!.isChecked = capsLock
+ }
+
+ private fun shouldUseScanCodes(): Boolean {
+ val packageName = currentInputEditorInfo.packageName
+ return packageName in SCANCODE_ONLY_APPS
+ }
+
+ override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean {
+ Log.d("keyboard", " down key_code=" + event.keyCode
+ + ", scan_code=" + event.scanCode
+ + ", device_code=" + event.deviceId
+ + ", repeat=" + event.repeatCount
+ + ", shift=" + event.isShiftPressed
+ + ", ctrl=" + event.isCtrlPressed
+ + ", alt=" + event.isAltPressed
+ )
+
+ val scanCode = event.scanCode
+
+ // Hide keyboard on back
+ if (keyCode == KeyEvent.KEYCODE_BACK) {
+ updateInputViewShown()
+ val current = isInputViewShown
+ if (current) {
+ requestHideSelf(0)
+ return true
+ }
+ }
+
+ if (scanCode <= 0 || event.deviceId <= 0)
+ return false
+
+ // Handle caps lock to show current status
+ if (scanCode == ScanCodes.SCANCODE_CAPS_LOCK) {
+ if (!keysPressed.contains(ScanCodes.SCANCODE_CAPS_LOCK)) {
+ capsLock = !event.isCapsLockOn
+ if (capsLock)
+ Toast.makeText(this, "Сaps Lock: ON", Toast.LENGTH_SHORT).show()
+ else
+ Toast.makeText(this, "Caps Lock: off", Toast.LENGTH_SHORT).show()
+ }
+ } else {
+ capsLock = event.isCapsLockOn
+ }
+ updateCapsLockSwitch()
+
+ // Toggle language
+ if (!shouldUseScanCodes() && checkCombinations(LANGUAGE_KEYS, scanCode)) {
+ keysPressed.add(scanCode) // do not repeat
+ toggleLanguage()
+ return true
+ }
+
+ // Toggle FN fix
+ if (checkCombinations(FN_FIX_KEYS, scanCode)) {
+ keysPressed.add(scanCode) // do not repeat
+ toggleFnLock()
+ return true
+ }
+
+ // Toggle GUI
+ if (checkCombinations(SHOW_HIDE_KEYS, scanCode)) {
+ keysPressed.add(scanCode) // do not repeat
+ toggleVisibility()
+ return true
+ }
+
+ if (checkCombinations(BRIGHTNESS_DOWN_KEYS, scanCode)) {
+ adjustBrightness(-5)
+ return true
+ }
+
+ if (checkCombinations(BRIGHTNESS_UP_KEYS, scanCode)) {
+ adjustBrightness(5)
+ return true
+ }
+
+ if (!keysPressed.contains(scanCode)) {
+ keysPressed.add(scanCode)
+ }
+
+ if (scanCode == FN_KEY) {
+ return true
+ }
+
+ // Get mapping for key
+ val key = mapping.getMapping(scanCode)
+ if (key != null) {
+
+ // Is the FN key hold?
+ if (keysPressed.contains(FN_KEY) xor (fnLock && key.AlternateFix)) {
+ // Does key has some FN-function?
+ if (key.AlternateScanCode != 0 || key.AlternateKeyCode != 0) {
+ sendKeyCode(event, key.AlternateKeyCode, key.AlternateScanCode)
+ return true
+ }
+ }
+
+ // Does the key have an alternative function?
+ if (key.ReplaceKeyCode != 0 || key.ReplaceScanCode != 0) {
+ sendKeyCode(event, key.ReplaceKeyCode, key.ReplaceScanCode)
+ return true
+ }
+
+ // Is it alternative language?
+ if (!shouldUseScanCodes() // Workaround for some apps: send scancode only
+ && altLanguage && !event.isAltPressed && !event.isCtrlPressed) {
+ val shift = event.isShiftPressed xor (event.isCapsLockOn && !key.IgnoreCapsLock)
+ var text: String? = null
+ if (!shift)
+ text = key.Char
+ else
+ text = key.ShiftChar
+ // Send keypresses as text
+ if (text != null) {
+ sendText(text)
+ return true
+ }
+ }
+
+ // Do not alter key function
+ }
+
+ // Workaround for some apps: send scancode only
+ if (shouldUseScanCodes()) {
+ sendScanCode(event)
+ return true
+ }
+
+ // Passthru this key without overrides
+ return false
+ }
+
+ override fun onKeyUp(keyCode: Int, event: KeyEvent): Boolean {
+ Log.d("keyboard", " up key_code=" + event.keyCode
+ + ", scan_code=" + event.scanCode
+ + ", device_code=" + event.deviceId
+ + ", repeat=" + event.repeatCount
+ + ", shift=" + event.isShiftPressed
+ + ", ctrl=" + event.isCtrlPressed
+ + ", alt=" + event.isAltPressed
+ )
+
+ val scanCode = event.scanCode
+
+ if (scanCode <= 0 || event.deviceId <= 0)
+ return false
+
+ if (keysPressed.contains(scanCode))
+ keysPressed.remove(scanCode)
+
+ if (scanCode == FN_KEY) {
+ return true
+ }
+
+ val key = mapping.getMapping(scanCode)
+ if (key != null) {
+
+ if (keysPressed.contains(FN_KEY) || fnLock && key.AlternateFix) {
+ if (key.AlternateScanCode != 0 || key.AlternateKeyCode != 0) {
+ sendKeyCode(event, key.AlternateKeyCode, key.AlternateScanCode)
+ return true
+ }
+ }
+
+ if (key.ReplaceKeyCode != 0 || key.ReplaceScanCode != 0) {
+ sendKeyCode(event, key.ReplaceKeyCode, key.ReplaceScanCode)
+ return true
+ }
+
+ if (shouldUseScanCodes()) {
+ sendScanCode(event)
+ return true
+ }
+
+ if (altLanguage && !event.isAltPressed && !event.isCtrlPressed) {
+ val shift = event.isShiftPressed xor (event.isCapsLockOn && !key.IgnoreCapsLock)
+ var text: String? = null
+ if (!shift)
+ text = key.Char
+ else
+ text = key.ShiftChar
+ if (text != null) {
+ //sendText(text);
+ return true
+ }
+ }
+
+ }
+
+ if (shouldUseScanCodes()) {
+ sendScanCode(event)
+ return true
+ }
+
+ return false
+ }
+
+ private fun checkCombinations(combinations: Array<IntArray>, scanCode: Int): Boolean {
+ for (c in combinations.indices) {
+ val combination = combinations[c]
+ if (combination.size - 1 != keysPressed.size)
+ continue
+ var matched = true
+ for (k in combination.indices) {
+ if (!(scanCode != combination[k] && keysPressed.contains(combination[k]) || scanCode == combination[k] && !keysPressed.contains(combination[k]))) {
+ matched = false
+ break
+ }
+ }
+ if (matched) {
+ if (keysPressed.contains(scanCode))
+ keysPressed.add(scanCode)
+ return true
+ }
+ }
+ return false
+ }
+
+ private fun sendKeyCode(sourceEvent: KeyEvent, keyCode: Int, scanCode: Int) {
+ var keyCode = keyCode
+ val ic = currentInputConnection
+
+ if (shouldUseScanCodes() && scanCode != 0)
+ keyCode = 0
+ val event = KeyEvent(sourceEvent.downTime, sourceEvent.eventTime, sourceEvent.action,
+ keyCode, sourceEvent.repeatCount, sourceEvent.metaState,
+ sourceEvent.deviceId, scanCode,
+ 0,
+ sourceEvent.source)
+ ic.sendKeyEvent(event)
+ }
+
+ private fun sendScanCode(sourceEvent: KeyEvent) {
+ val ic = currentInputConnection
+ val event = KeyEvent(sourceEvent.downTime, sourceEvent.eventTime, sourceEvent.action,
+ 0, sourceEvent.repeatCount, sourceEvent.metaState,
+ sourceEvent.deviceId, sourceEvent.scanCode,
+ 0,
+ sourceEvent.source)
+ ic.sendKeyEvent(event)
+ }
+
+ private fun sendText(text: String) {
+ val ic = currentInputConnection
+ ic.commitText(text, 1)
+ }
+
+ override fun onClick(v: View) {
+ when (v.id) {
+ R.id.textViewLanguage -> toggleLanguage()
+ }
+ }
+
+ override fun onCheckedChanged(buttonView: CompoundButton, isChecked: Boolean) {
+ when (buttonView.id) {
+ R.id.switchCapsLock -> updateCapsLockSwitch()
+ R.id.switchFnLock -> toggleFnLock()
+ }
+ }
+
+ fun toggleVisibility() {
+ updateInputViewShown()
+ val current = isInputViewShown
+ if (!current)
+ requestShowSelf(0)
+ else
+ requestHideSelf(0)
+ }
+
+ private fun adjustBrightness(delta: Int) {
+ try {
+ var brightness = Settings.System.getInt(getContentResolver(),
+ Settings.System.SCREEN_BRIGHTNESS, 0)
+ brightness += delta
+ Settings.System.putInt(getContentResolver(),
+ Settings.System.SCREEN_BRIGHTNESS, brightness);
+ }
+ catch (ex: Exception) {
+ ex.printStackTrace()
+ }
+ }
+
+ companion object {
+ private val mapping = RusMapping()
+ }
+}
diff --git a/app/src/main/java/com/clusterrr/hardwarekeyboard/RusMapping.kt b/app/src/main/java/com/clusterrr/hardwarekeyboard/RusMapping.kt
new file mode 100644
index 0000000..7038bbc
--- /dev/null
+++ b/app/src/main/java/com/clusterrr/hardwarekeyboard/RusMapping.kt
@@ -0,0 +1,469 @@
+package com.clusterrr.hardwarekeyboard
+
+import android.view.KeyEvent
+
+import java.util.HashMap
+
+class RusMapping {
+ internal var mapping: HashMap<Int, KeyMapping> = object : HashMap<Int, KeyMapping>() {
+ init {
+ put(ScanCodes.SCANCODE_GRAVE,
+ object : KeyMapping() {
+ init {
+ AlternateKeyCode = KeyEvent.KEYCODE_ESCAPE
+ AlternateScanCode = ScanCodes.SCANCODE_ESCAPE
+ AlternateFix = true
+ Char = "ё"
+ ShiftChar = "Ё"
+ }
+ })
+ put(ScanCodes.SCANCODE_1,
+ object : KeyMapping() {
+ init {
+ AlternateKeyCode = KeyEvent.KEYCODE_F1
+ AlternateScanCode = ScanCodes.SCANCODE_F1
+ AlternateFix = true
+ }
+ })
+ put(ScanCodes.SCANCODE_2,
+ object : KeyMapping() {
+ init {
+ AlternateKeyCode = KeyEvent.KEYCODE_F2
+ AlternateScanCode = ScanCodes.SCANCODE_F2
+ AlternateFix = true
+ ShiftChar = "\""
+ IgnoreCapsLock = true
+ }
+ })
+ put(ScanCodes.SCANCODE_3,
+ object : KeyMapping() {
+ init {
+ AlternateKeyCode = KeyEvent.KEYCODE_F3
+ AlternateScanCode = ScanCodes.SCANCODE_F3
+ AlternateFix = true
+ ShiftChar = "№"
+ IgnoreCapsLock = true
+ }
+ })
+ put(ScanCodes.SCANCODE_4,
+ object : KeyMapping() {
+ init {
+ AlternateKeyCode = KeyEvent.KEYCODE_F4
+ AlternateScanCode = ScanCodes.SCANCODE_F4
+ AlternateFix = true
+ ShiftChar = ";"
+ IgnoreCapsLock = true
+ }
+ })
+ put(ScanCodes.SCANCODE_5,
+ object : KeyMapping() {
+ init {
+ AlternateKeyCode = KeyEvent.KEYCODE_F5
+ AlternateScanCode = ScanCodes.SCANCODE_F5
+ AlternateFix = true
+ }
+ })
+ put(ScanCodes.SCANCODE_6,
+ object : KeyMapping() {
+ init {
+ AlternateKeyCode = KeyEvent.KEYCODE_F6
+ AlternateScanCode = ScanCodes.SCANCODE_F6
+ AlternateFix = true
+ ShiftChar = ":"
+ IgnoreCapsLock = true
+ }
+ })
+ put(ScanCodes.SCANCODE_7,
+ object : KeyMapping() {
+ init {
+ AlternateKeyCode = KeyEvent.KEYCODE_F7
+ AlternateScanCode = ScanCodes.SCANCODE_F7
+ AlternateFix = true
+ ShiftChar = "?"
+ IgnoreCapsLock = true
+ }
+ })
+ put(ScanCodes.SCANCODE_8,
+ object : KeyMapping() {
+ init {
+ AlternateKeyCode = KeyEvent.KEYCODE_F8
+ AlternateScanCode = ScanCodes.SCANCODE_F8
+ AlternateFix = true
+ }
+ })
+ put(ScanCodes.SCANCODE_9,
+ object : KeyMapping() {
+ init {
+ AlternateKeyCode = KeyEvent.KEYCODE_F9
+ AlternateScanCode = ScanCodes.SCANCODE_F9
+ AlternateFix = true
+ }
+ })
+ put(ScanCodes.SCANCODE_0,
+ object : KeyMapping() {
+ init {
+ AlternateKeyCode = KeyEvent.KEYCODE_F10
+ AlternateScanCode = ScanCodes.SCANCODE_F10
+ AlternateFix = true
+ }
+ })
+ put(ScanCodes.SCANCODE_MINUS,
+ object : KeyMapping() {
+ init {
+ AlternateKeyCode = KeyEvent.KEYCODE_F11
+ AlternateScanCode = ScanCodes.SCANCODE_F11
+ AlternateFix = true
+ }
+ })
+ put(ScanCodes.SCANCODE_EQUALS,
+ object : KeyMapping() {
+ init {
+ AlternateKeyCode = KeyEvent.KEYCODE_F12
+ AlternateScanCode = ScanCodes.SCANCODE_F12
+ AlternateFix = true
+ }
+ })
+ put(ScanCodes.SCANCODE_Q,
+ object : KeyMapping() {
+ init {
+ Char = "й"
+ ShiftChar = "Й"
+ AlternateKeyCode = KeyEvent.KEYCODE_BACK
+ }
+ })
+ put(ScanCodes.SCANCODE_W,
+ object : KeyMapping() {
+ init {
+ Char = "ц"
+ ShiftChar = "Ц"
+ }
+ })
+ put(ScanCodes.SCANCODE_E,
+ object : KeyMapping() {
+ init {
+ Char = "у"
+ ShiftChar = "У"
+ }
+ })
+ put(ScanCodes.SCANCODE_R,
+ object : KeyMapping() {
+ init {
+ Char = "к"
+ ShiftChar = "К"
+ }
+ })
+ put(ScanCodes.SCANCODE_T,
+ object : KeyMapping() {
+ init {
+ Char = "е"
+ ShiftChar = "Е"
+ }
+ })
+ put(ScanCodes.SCANCODE_Y,
+ object : KeyMapping() {
+ init {
+ Char = "н"
+ ShiftChar = "Н"
+ }
+ })
+ put(ScanCodes.SCANCODE_U,
+ object : KeyMapping() {
+ init {
+ Char = "г"
+ ShiftChar = "Г"
+ }
+ })
+ put(ScanCodes.SCANCODE_I,
+ object : KeyMapping() {
+ init {
+ Char = "ш"
+ ShiftChar = "Ш"
+ }
+ })
+ put(ScanCodes.SCANCODE_O,
+ object : KeyMapping() {
+ init {
+ AlternateKeyCode = KeyEvent.KEYCODE_NUMPAD_SUBTRACT
+ AlternateScanCode = ScanCodes.SCANCODE_NUMPAD_SUBTRACT
+ AlternateFix = false
+ Char = "щ"
+ ShiftChar = "Щ"
+ }
+ })
+ put(ScanCodes.SCANCODE_P,
+ object : KeyMapping() {
+ init {
+ AlternateKeyCode = KeyEvent.KEYCODE_NUMPAD_ADD
+ AlternateScanCode = ScanCodes.SCANCODE_NUMPAD_ADD
+ AlternateFix = false
+ Char = "з"
+ ShiftChar = "З"
+ }
+ })
+ put(ScanCodes.SCANCODE_LEFT_BRACKET,
+ object : KeyMapping() {
+ init {
+ AlternateKeyCode = KeyEvent.KEYCODE_NUMPAD_DIVIDE
+ AlternateScanCode = ScanCodes.SCANCODE_NUMPAD_DIVIDE
+ AlternateFix = false
+ Char = "х"
+ ShiftChar = "Х"
+ }
+ })
+ put(ScanCodes.SCANCODE_RIGHT_BRACKET,
+ object : KeyMapping() {
+ init {
+ AlternateKeyCode = KeyEvent.KEYCODE_NUMPAD_MULTIPLY
+ AlternateScanCode = ScanCodes.SCANCODE_NUMPAD_MULTIPLY
+ AlternateFix = false
+ Char = "ъ"
+ ShiftChar = "Ъ"
+ }
+ })
+ put(ScanCodes.SCANCODE_A,
+ object : KeyMapping() {
+ init {
+ Char = "ф"
+ ShiftChar = "Ф"
+ }
+ })
+ put(ScanCodes.SCANCODE_S,
+ object : KeyMapping() {
+ init {
+ Char = "ы"
+ ShiftChar = "Ы"
+ }
+ })
+ put(ScanCodes.SCANCODE_D,
+ object : KeyMapping() {
+ init {
+ Char = "в"
+ ShiftChar = "В"
+ }
+ })
+ put(ScanCodes.SCANCODE_F,
+ object : KeyMapping() {
+ init {
+ Char = "а"
+ ShiftChar = "А"
+ }
+ })
+ put(ScanCodes.SCANCODE_G,
+ object : KeyMapping() {
+ init {
+ Char = "п"
+ ShiftChar = "П"
+ }
+ })
+ put(ScanCodes.SCANCODE_H,
+ object : KeyMapping() {
+ init {
+ Char = "р"
+ ShiftChar = "Р"
+ }
+ })
+ put(ScanCodes.SCANCODE_J,
+ object : KeyMapping() {
+ init {
+ Char = "о"
+ ShiftChar = "О"
+ }
+ })
+ put(ScanCodes.SCANCODE_K,
+ object : KeyMapping() {
+ init {
+ Char = "л"
+ ShiftChar = "Л"
+ }
+ })
+ put(ScanCodes.SCANCODE_L,
+ object : KeyMapping() {
+ init {
+ AlternateKeyCode = KeyEvent.KEYCODE_VOLUME_MUTE
+ //AlternateScanCode = ScanCodes.SCANCODE_VOLUME_MUTE;
+ AlternateFix = false
+ Char = "д"
+ ShiftChar = "Д"
+ }
+ })
+ put(ScanCodes.SCANCODE_SEMICOLON,
+ object : KeyMapping() {
+ init {
+ AlternateKeyCode = KeyEvent.KEYCODE_VOLUME_DOWN
+ //AlternateScanCode = ScanCodes.SCANCODE_VOLUME_DOWN;
+ AlternateFix = false
+ Char = "ж"
+ ShiftChar = "Ж"
+ }
+ })
+ put(ScanCodes.SCANCODE_APOSTROPHE,
+ object : KeyMapping() {
+ init {
+ AlternateKeyCode = KeyEvent.KEYCODE_VOLUME_UP
+ //AlternateScanCode = ScanCodes.SCANCODE_VOLUME_UP;
+ AlternateFix = false
+ Char = "э"
+ ShiftChar = "Э"
+ }
+ })
+ put(ScanCodes.SCANCODE_Z,
+ object : KeyMapping() {
+ init {
+ Char = "я"
+ ShiftChar = "Я"
+ }
+ })
+ put(ScanCodes.SCANCODE_X,
+ object : KeyMapping() {
+ init {
+ Char = "ч"
+ ShiftChar = "Ч"
+ }
+ })
+ put(ScanCodes.SCANCODE_C,
+ object : KeyMapping() {
+ init {
+ Char = "с"
+ ShiftChar = "С"
+ }
+ })
+ put(ScanCodes.SCANCODE_V,
+ object : KeyMapping() {
+ init {
+ Char = "м"
+ ShiftChar = "М"
+ }
+ })
+ put(ScanCodes.SCANCODE_B,
+ object : KeyMapping() {
+ init {
+ Char = "и"
+ ShiftChar = "И"
+ }
+ })
+ put(ScanCodes.SCANCODE_N,
+ object : KeyMapping() {
+ init {
+ Char = "т"
+ ShiftChar = "Т"
+ }
+ })
+ put(ScanCodes.SCANCODE_M,
+ object : KeyMapping() {
+ init {
+ AlternateKeyCode = KeyEvent.KEYCODE_MEDIA_REWIND
+ //AlternateScanCode = ScanCodes.SCANCODE_MEDIA_REWIND;
+ AlternateFix = false
+ Char = "ь"
+ ShiftChar = "Ь"
+ }
+ })
+ put(ScanCodes.SCANCODE_COMMA,
+ object : KeyMapping() {
+ init {
+ AlternateKeyCode = KeyEvent.KEYCODE_MEDIA_FAST_FORWARD
+ //AlternateScanCode = ScanCodes.SCANCODE_MEDIA_FAST_FORWARD;
+ AlternateFix = false
+ Char = "б"
+ ShiftChar = "Б"
+ }
+ })
+ put(ScanCodes.SCANCODE_PERIOD,
+ object : KeyMapping() {
+ init {
+ AlternateKeyCode = KeyEvent.KEYCODE_MEDIA_PREVIOUS
+ //AlternateScanCode = ScanCodes.SCANCODE_MEDIA_PREVIOUS;
+ AlternateFix = false
+ Char = "ю"
+ ShiftChar = "Ю"
+ }
+ })
+ put(ScanCodes.SCANCODE_BACKSLASH,
+ object : KeyMapping() {
+ init {
+ AlternateKeyCode = KeyEvent.KEYCODE_INSERT
+ ShiftChar = "/"
+ IgnoreCapsLock = true
+ }
+ })
+ put(ScanCodes.SCANCODE_BACKSLASH_LEFT,
+ object : KeyMapping() {
+ init {
+ ShiftChar = "/"
+ IgnoreCapsLock = true
+ }
+ })
+ put(ScanCodes.SCANCODE_SLASH,
+ object : KeyMapping() {
+ init {
+ Char = "."
+ ShiftChar = ","
+ IgnoreCapsLock = true
+ AlternateKeyCode = KeyEvent.KEYCODE_MEDIA_NEXT
+ //AlternateScanCode = ScanCodes.SCANCODE_MEDIA_NEXT;
+ AlternateFix = false
+ }
+ })
+ put(ScanCodes.SCANCODE_SHIFT_RIGHT,
+ object : KeyMapping() {
+ init {
+ AlternateKeyCode = KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE
+ //AlternateScanCode = ScanCodes.SCANCODE_MEDIA_PLAY_PAUSE;
+ AlternateFix = false
+ }
+ })
+ put(ScanCodes.SCANCODE_DPAD_LEFT,
+ object : KeyMapping() {
+ init {
+ AlternateKeyCode = KeyEvent.KEYCODE_MOVE_HOME
+ AlternateScanCode = ScanCodes.SCANCODE_MOVE_HOME
+ AlternateFix = false
+ }
+ })
+ put(ScanCodes.SCANCODE_DPAD_RIGHT,
+ object : KeyMapping() {
+ init {
+ AlternateKeyCode = KeyEvent.KEYCODE_MOVE_END
+ AlternateScanCode = ScanCodes.SCANCODE_MOVE_END
+ AlternateFix = false
+ }
+ })
+ put(ScanCodes.SCANCODE_DPAD_UP,
+ object : KeyMapping() {
+ init {
+ AlternateKeyCode = KeyEvent.KEYCODE_PAGE_UP
+ AlternateScanCode = ScanCodes.SCANCODE_PAGE_UP
+ AlternateFix = false
+ }
+ })
+ put(ScanCodes.SCANCODE_DPAD_DOWN,
+ object : KeyMapping() {
+ init {
+ AlternateKeyCode = KeyEvent.KEYCODE_PAGE_DOWN
+ AlternateScanCode = ScanCodes.SCANCODE_PAGE_DOWN
+ AlternateFix = false
+ }
+ })
+ put(ScanCodes.SCANCODE_DEL,
+ object : KeyMapping() {
+ init {
+ AlternateKeyCode = KeyEvent.KEYCODE_FORWARD_DEL
+ AlternateScanCode = ScanCodes.SCANCODE_FORWARD_DEL
+ AlternateFix = false
+ }
+ })
+ put(ScanCodes.SCANCODE_ALT_RIGHT,
+ object : KeyMapping() {
+ init {
+ AlternateKeyCode = KeyEvent.KEYCODE_MENU
+ AlternateScanCode = ScanCodes.SCANCODE_MENU
+ AlternateFix = false
+ }
+ })
+ }
+ }
+
+ fun getMapping(scanCode: Int): KeyMapping? {
+ return if (mapping.containsKey(scanCode)) mapping[scanCode] else null
+ }
+}
diff --git a/app/src/main/java/com/clusterrr/hardwarekeyboard/ScanCodes.kt b/app/src/main/java/com/clusterrr/hardwarekeyboard/ScanCodes.kt
new file mode 100644
index 0000000..f6f8e65
--- /dev/null
+++ b/app/src/main/java/com/clusterrr/hardwarekeyboard/ScanCodes.kt
@@ -0,0 +1,196 @@
+package com.clusterrr.hardwarekeyboard
+
+object ScanCodes {
+ val SCANCODE_VOLUME_MUTE = 113
+ val SCANCODE_VOLUME_DOWN = 114
+ val SCANCODE_VOLUME_UP = 115
+ val SCANCODE_MEDIA_NEXT = 163
+ val SCANCODE_MEDIA_PLAY_PAUSE = 164
+ val SCANCODE_MEDIA_PREVIOUS = 165
+ val SCANCODE_SEARCH = 217
+ val SCANCODE_DPAD_LEFT = 105
+ val SCANCODE_DPAD_RIGHT = 106
+ val SCANCODE_DPAD_UP = 103
+ val SCANCODE_DPAD_DOWN = 108
+
+ val SCANCODE_ESCAPE = 1
+ val SCANCODE_F1 = 59
+ val SCANCODE_F2 = 60
+ val SCANCODE_F3 = 61
+ val SCANCODE_F4 = 62
+ val SCANCODE_F5 = 63
+ val SCANCODE_F6 = 64
+ val SCANCODE_F7 = 65
+ val SCANCODE_F8 = 66
+ val SCANCODE_F9 = 67
+ val SCANCODE_F10 = 68
+ val SCANCODE_F11 = 87
+ val SCANCODE_F12 = 88
+ val SCANCODE_FORWARD_DEL = 111
+
+ val SCANCODE_GRAVE = 41
+ val SCANCODE_1 = 2
+ val SCANCODE_2 = 3
+ val SCANCODE_3 = 4
+ val SCANCODE_4 = 5
+ val SCANCODE_5 = 6
+ val SCANCODE_6 = 7
+ val SCANCODE_7 = 8
+ val SCANCODE_8 = 9
+ val SCANCODE_9 = 10
+ val SCANCODE_0 = 11
+ val SCANCODE_DEL = 14
+
+ val SCANCODE_TAB = 15
+ val SCANCODE_Q = 16
+ val SCANCODE_W = 17
+ val SCANCODE_E = 18
+ val SCANCODE_R = 19
+ val SCANCODE_T = 20
+ val SCANCODE_Y = 21
+ val SCANCODE_U = 22
+ val SCANCODE_I = 23
+ val SCANCODE_O = 24
+ val SCANCODE_P = 25
+ val SCANCODE_BACKSLASH = 43
+ val SCANCODE_BACKSLASH_LEFT = 86
+
+ val SCANCODE_CAPS_LOCK = 58
+ val SCANCODE_A = 30
+ val SCANCODE_S = 31
+ val SCANCODE_D = 32
+ val SCANCODE_F = 33
+ val SCANCODE_G = 34
+ val SCANCODE_H = 35
+ val SCANCODE_J = 36
+ val SCANCODE_K = 37
+ val SCANCODE_L = 38
+ val SCANCODE_ENTER = 28
+
+ val SCANCODE_SHIFT_LEFT = 42
+ val SCANCODE_Z = 44
+ val SCANCODE_X = 45
+ val SCANCODE_C = 46
+ val SCANCODE_V = 47
+ val SCANCODE_B = 48
+ val SCANCODE_N = 49
+ val SCANCODE_M = 50
+ val SCANCODE_COMMA = 51
+ val SCANCODE_PERIOD = 52
+ val SCANCODE_SEMICOLON = 39
+ val SCANCODE_APOSTROPHE = 40
+ val SCANCODE_SHIFT_RIGHT = 54
+
+ val SCANCODE_CTRL_LEFT = 29
+ val SCANCODE_SHOW_KEYBOARD = 706
+ val SCANCODE_ALT_LEFT = 56
+ val SCANCODE_SPACE = 57
+ val SCANCODE_LANGUAGE = 122
+ val SCANCODE_ALT_RIGHT = 100
+ val SCANCODE_MINUS = 12
+ val SCANCODE_EQUALS = 13
+ val SCANCODE_LEFT_BRACKET = 26
+ val SCANCODE_RIGHT_BRACKET = 27
+ val SCANCODE_SLASH = 53
+
+ val SCANCODE_PAGE_UP = 104
+ val SCANCODE_PAGE_DOWN = 109
+ val SCANCODE_MOVE_HOME = 102
+ val SCANCODE_MOVE_END = 107
+
+ /**
+ * Key code constant: System Request / Print Screen key.
+ */
+ val SCANCODE_SYSRQ = 84
+ /**
+ * Scan code constant: Scroll Lock key.
+ */
+ val SCANCODE_SCROLL_LOCK = 70
+ /**
+ * Key code constant: Break / Pause key.
+ */
+ val SCANCODE_BREAK = 119
+ /**
+ * Scan code constant: Num Lock key.
+ * This key alters the behavior of other keys on the numeric keypad.
+ */
+ val SCANCODE_NUM_LOCK = 69
+ /**
+ * Scan code constant: Numeric keypad '0' key.
+ */
+ val SCANCODE_NUMPAD_0 = 82
+ /**
+ * Scan code constant: Numeric keypad '1' key.
+ */
+ val SCANCODE_NUMPAD_1 = 79
+ /**
+ * Scan code constant: Numeric keypad '2' key.
+ */
+ val SCANCODE_NUMPAD_2 = 80
+ /**
+ * Scan code constant: Numeric keypad '3' key.
+ */
+ val SCANCODE_NUMPAD_3 = 81
+ /**
+ * Scan code constant: Numeric keypad '4' key.
+ */
+ val SCANCODE_NUMPAD_4 = 75
+ /**
+ * Scan code constant: Numeric keypad '5' key.
+ */
+ val SCANCODE_NUMPAD_5 = 76
+ /**
+ * Scan code constant: Numeric keypad '6' key.
+ */
+ val SCANCODE_NUMPAD_6 = 77
+ /**
+ * Scan code constant: Numeric keypad '7' key.
+ */
+ val SCANCODE_NUMPAD_7 = 71
+ /**
+ * Scan code constant: Numeric keypad '8' key.
+ */
+ val SCANCODE_NUMPAD_8 = 72
+ /**
+ * Scan code constant: Numeric keypad '9' key.
+ */
+ val SCANCODE_NUMPAD_9 = 73
+ /**
+ * Scan code constant: Numeric keypad '/' key (for division).
+ */
+ val SCANCODE_NUMPAD_DIVIDE = 98
+ /**
+ * Scan code constant: Numeric keypad '*' key (for multiplication).
+ */
+ val SCANCODE_NUMPAD_MULTIPLY = 55
+ /**
+ * Scan code constant: Numeric keypad '-' key (for subtraction).
+ */
+ val SCANCODE_NUMPAD_SUBTRACT = 74
+ /**
+ * Scan code constant: Numeric keypad '+' key (for addition).
+ */
+ val SCANCODE_NUMPAD_ADD = 78
+ /**
+ * Scan code constant: Numeric keypad '.' key (for decimals or digit grouping).
+ */
+ val SCANCODE_NUMPAD_DOT = 83
+ /**
+ * Scan code constant: Numeric keypad ',' key (for decimals or digit grouping).
+ */
+ val SCANCODE_NUMPAD_COMMA = 159
+ /**
+ * Scan code constant: Numeric keypad Enter key.
+ */
+ val SCANCODE_NUMPAD_ENTER = 96
+
+ /**
+ * Scan code constant: Menu key.
+ */
+ val SCANCODE_MENU = 127
+ /**
+ * Scan code constant: Calculator special function key.
+ * Used to launch a calculator application.
+ */
+ val SCANCODE_CALCULATOR = 140
+} \ No newline at end of file
diff --git a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
new file mode 100644
index 0000000..971add5
--- /dev/null
+++ b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
@@ -0,0 +1,34 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:aapt="http://schemas.android.com/aapt"
+ android:width="108dp"
+ android:height="108dp"
+ android:viewportWidth="108"
+ android:viewportHeight="108">
+ <path
+ android:fillType="evenOdd"
+ android:pathData="M32,64C32,64 38.39,52.99 44.13,50.95C51.37,48.37 70.14,49.57 70.14,49.57L108.26,87.69L108,109.01L75.97,107.97L32,64Z"
+ android:strokeWidth="1"
+ android:strokeColor="#00000000">
+ <aapt:attr name="android:fillColor">
+ <gradient
+ android:endX="78.5885"
+ android:endY="90.9159"
+ android:startX="48.7653"
+ android:startY="61.0927"
+ android:type="linear">
+ <item
+ android:color="#44000000"
+ android:offset="0.0" />
+ <item
+ android:color="#00000000"
+ android:offset="1.0" />
+ </gradient>
+ </aapt:attr>
+ </path>
+ <path
+ android:fillColor="#FFFFFF"
+ android:fillType="nonZero"
+ android:pathData="M66.94,46.02L66.94,46.02C72.44,50.07 76,56.61 76,64L32,64C32,56.61 35.56,50.11 40.98,46.06L36.18,41.19C35.45,40.45 35.45,39.3 36.18,38.56C36.91,37.81 38.05,37.81 38.78,38.56L44.25,44.05C47.18,42.57 50.48,41.71 54,41.71C57.48,41.71 60.78,42.57 63.68,44.05L69.11,38.56C69.84,37.81 70.98,37.81 71.71,38.56C72.44,39.3 72.44,40.45 71.71,41.19L66.94,46.02ZM62.94,56.92C64.08,56.92 65,56.01 65,54.88C65,53.76 64.08,52.85 62.94,52.85C61.8,52.85 60.88,53.76 60.88,54.88C60.88,56.01 61.8,56.92 62.94,56.92ZM45.06,56.92C46.2,56.92 47.13,56.01 47.13,54.88C47.13,53.76 46.2,52.85 45.06,52.85C43.92,52.85 43,53.76 43,54.88C43,56.01 43.92,56.92 45.06,56.92Z"
+ android:strokeWidth="1"
+ android:strokeColor="#00000000" />
+</vector>
diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml
new file mode 100644
index 0000000..eed7a42
--- /dev/null
+++ b/app/src/main/res/drawable/ic_launcher_background.xml
@@ -0,0 +1,170 @@
+<?xml version="1.0" encoding="utf-8"?>
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="108dp"
+ android:height="108dp"
+ android:viewportWidth="108"
+ android:viewportHeight="108">
+ <path
+ android:fillColor="#008577"
+ android:pathData="M0,0h108v108h-108z" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M9,0L9,108"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M19,0L19,108"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M29,0L29,108"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M39,0L39,108"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M49,0L49,108"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M59,0L59,108"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M69,0L69,108"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M79,0L79,108"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M89,0L89,108"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M99,0L99,108"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M0,9L108,9"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M0,19L108,19"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M0,29L108,29"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M0,39L108,39"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M0,49L108,49"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M0,59L108,59"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M0,69L108,69"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M0,79L108,79"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M0,89L108,89"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M0,99L108,99"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M19,29L89,29"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M19,39L89,39"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M19,49L89,49"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M19,59L89,59"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M19,69L89,69"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M19,79L89,79"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M29,19L29,89"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M39,19L39,89"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M49,19L49,89"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M59,19L59,89"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M69,19L69,89"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M79,19L79,89"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+</vector>
diff --git a/app/src/main/res/layout/keyboard_layout.xml b/app/src/main/res/layout/keyboard_layout.xml
new file mode 100644
index 0000000..8a11266
--- /dev/null
+++ b/app/src/main/res/layout/keyboard_layout.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="utf-8"?>
+<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="24dp">
+
+ <Switch
+ android:id="@+id/switchCapsLock"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="8dp"
+ android:checked="true"
+ android:switchPadding="5dp"
+ android:text="Caps Lock"
+ android:textColor="@color/colorText"
+ android:focusable="false"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toTopOf="parent" />
+
+ <Switch
+ android:id="@+id/switchFnLock"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="32dp"
+ android:switchPadding="5dp"
+ android:text="FN Lock"
+ android:textColor="@color/colorText"
+ android:focusable="false"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintStart_toEndOf="@+id/switchCapsLock"
+ app:layout_constraintTop_toTopOf="parent" />
+
+ <TextView
+ android:id="@+id/textViewLanguage"
+ android:layout_width="24dp"
+ android:layout_height="24dp"
+ android:layout_marginEnd="8dp"
+ android:background="@color/colorLanguageBackground"
+ android:textColor="@color/colorLanguageText"
+ android:gravity="center"
+ android:text="EN"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintTop_toTopOf="parent" />
+
+</android.support.constraint.ConstraintLayout>
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 0000000..a26f6fb
--- /dev/null
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+ <background android:drawable="@drawable/ic_launcher_background" />
+ <foreground android:drawable="@drawable/ic_launcher_foreground" />
+</adaptive-icon> \ No newline at end of file
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
new file mode 100644
index 0000000..a26f6fb
--- /dev/null
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+ <background android:drawable="@drawable/ic_launcher_background" />
+ <foreground android:drawable="@drawable/ic_launcher_foreground" />
+</adaptive-icon> \ No newline at end of file
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.png b/app/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..898f3ed
--- /dev/null
+++ b/app/src/main/res/mipmap-hdpi/ic_launcher.png
Binary files differ
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
new file mode 100644
index 0000000..dffca36
--- /dev/null
+++ b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
Binary files differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.png b/app/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..64ba76f
--- /dev/null
+++ b/app/src/main/res/mipmap-mdpi/ic_launcher.png
Binary files differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
new file mode 100644
index 0000000..dae5e08
--- /dev/null
+++ b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
Binary files differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/app/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..e5ed465
--- /dev/null
+++ b/app/src/main/res/mipmap-xhdpi/ic_launcher.png
Binary files differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..14ed0af
--- /dev/null
+++ b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
Binary files differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..b0907ca
--- /dev/null
+++ b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
Binary files differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..d8ae031
--- /dev/null
+++ b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
Binary files differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000..2c18de9
--- /dev/null
+++ b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Binary files differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..beed3cd
--- /dev/null
+++ b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
Binary files differ
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
new file mode 100644
index 0000000..0145c7c
--- /dev/null
+++ b/app/src/main/res/values/colors.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <color name="colorText">#408040</color>
+ <color name="colorLanguageText">#B0B0B0</color>
+ <color name="colorLanguageBackground">#0000FF</color>
+ <color name="colorPrimary">#008577</color>
+ <color name="colorPrimaryDark">#00574B</color>
+ <color name="colorAccent">#D81B60</color>
+</resources>
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
new file mode 100644
index 0000000..c5a811b
--- /dev/null
+++ b/app/src/main/res/values/strings.xml
@@ -0,0 +1,3 @@
+<resources>
+ <string name="app_name">Hardware Keyboard</string>
+</resources>
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
new file mode 100644
index 0000000..7532894
--- /dev/null
+++ b/app/src/main/res/values/styles.xml
@@ -0,0 +1,10 @@
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <!-- Base application theme. -->
+ <style name="AppTheme" parent="Theme.AppCompat.DayNight">
+ <item name="android:colorPrimary">@color/colorPrimary</item>
+ <item name="android:colorPrimaryDark">@color/colorPrimaryDark</item>
+ <item name="android:colorAccent">@color/colorAccent</item>
+ </style>
+
+</resources>
diff --git a/app/src/main/res/xml/method.xml b/app/src/main/res/xml/method.xml
new file mode 100644
index 0000000..55e7ab2
--- /dev/null
+++ b/app/src/main/res/xml/method.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<input-method xmlns:android="http://schemas.android.com/apk/res/android"
+ android:settingsActivity="com.clusterrr.hardwarekeyboard.MainActivity"
+ android:icon="@drawable/ic_launcher_foreground">
+</input-method> \ No newline at end of file
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 0000000..036271b
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,29 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+
+buildscript {
+ ext.kotlin_version = '1.3.50'
+ repositories {
+ google()
+ jcenter()
+
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:3.5.2'
+ classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
+
+ // NOTE: Do not place your application dependencies here; they belong
+ // in the individual module build.gradle files
+ }
+}
+
+allprojects {
+ repositories {
+ google()
+ jcenter()
+
+ }
+}
+
+task clean(type: Delete) {
+ delete rootProject.buildDir
+}
diff --git a/gradle.properties b/gradle.properties
new file mode 100644
index 0000000..9f85f38
--- /dev/null
+++ b/gradle.properties
@@ -0,0 +1,15 @@
+# Project-wide Gradle settings.
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+org.gradle.jvmargs=-Xmx1536m
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. More details, visit
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+# org.gradle.parallel=true
+
+
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..f6b961f
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.jar
Binary files differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..7ae826d
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Wed Aug 28 14:51:31 MSK 2019
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip
diff --git a/gradlew b/gradlew
new file mode 100644
index 0000000..cccdd3d
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,172 @@
+#!/usr/bin/env sh
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn () {
+ echo "$*"
+}
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+ NONSTOP* )
+ nonstop=true
+ ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+ JAVACMD=`cygpath --unix "$JAVACMD"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=$((i+1))
+ done
+ case $i in
+ (0) set -- ;;
+ (1) set -- "$args0" ;;
+ (2) set -- "$args0" "$args1" ;;
+ (3) set -- "$args0" "$args1" "$args2" ;;
+ (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Escape application args
+save () {
+ for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+ echo " "
+}
+APP_ARGS=$(save "$@")
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
+if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
+ cd "$(dirname "$0")"
+fi
+
+exec "$JAVACMD" "$@"
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100644
index 0000000..e95643d
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,84 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windows variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/settings.gradle b/settings.gradle
new file mode 100644
index 0000000..d3db109
--- /dev/null
+++ b/settings.gradle
@@ -0,0 +1 @@
+include ':app'