├── .github
└── FUNDING.yml
├── .gitignore
├── .idea
├── codeStyles
│ ├── Project.xml
│ └── codeStyleConfig.xml
├── compiler.xml
├── encodings.xml
├── gradle.xml
├── jarRepositories.xml
├── markdown-navigator-enh.xml
├── markdown-navigator.xml
├── misc.xml
├── runConfigurations.xml
└── vcs.xml
├── LICENSE
├── README.md
├── app
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── com
│ │ └── github
│ │ └── roarappstudio
│ │ └── btkontroller
│ │ ├── BluetoothController.kt
│ │ ├── ConfigActivity.kt
│ │ ├── DescriptorCollection.kt
│ │ ├── DescriptorCollectionNotUsed.kt
│ │ ├── Kontroller.kt
│ │ ├── SelectDeviceActivity.kt
│ │ ├── SplashScreen.kt
│ │ ├── Unhide.kt
│ │ ├── Value.kt
│ │ ├── WheelPicker.java
│ │ ├── extraLibraries
│ │ └── CustomGestureDetector.kt
│ │ ├── listeners
│ │ ├── CompositeListener.kt
│ │ ├── GestureDetectListener.kt
│ │ └── ViewListener.kt
│ │ ├── reports
│ │ ├── AbsMouseReport.kt
│ │ ├── FeatureReport.kt
│ │ ├── KeyboardReport.kt
│ │ ├── MouseReport.kt
│ │ ├── RadialControllerInput.kt
│ │ ├── RadialControllerOutput.kt
│ │ ├── ScrollableTrackpadMouseReport.kt
│ │ ├── TestTrackpadMouseReport.kt
│ │ └── TrackpadMouseReport.kt
│ │ └── senders
│ │ ├── KeyboardSender.kt
│ │ ├── RadialSender.kt
│ │ ├── RadialSender2.kt
│ │ ├── RadialSender3.kt
│ │ ├── RelativeMouseSender.kt
│ │ ├── Sender.kt
│ │ └── SensorSender.kt
│ └── res
│ ├── drawable-anydpi
│ ├── ic_action_app_connected.xml
│ ├── ic_action_app_not_connected.xml
│ └── ic_action_keyboard.xml
│ ├── drawable-hdpi
│ ├── ic_action_app_connected.png
│ ├── ic_action_app_not_connected.png
│ └── ic_action_keyboard.png
│ ├── drawable-mdpi
│ ├── ic_action_app_connected.png
│ ├── ic_action_app_not_connected.png
│ ├── ic_action_keyboard.png
│ └── ripple_item_no.xml
│ ├── drawable-v24
│ └── ic_launcher_foreground.xml
│ ├── drawable-xhdpi
│ ├── ic_action_app_connected.png
│ ├── ic_action_app_not_connected.png
│ └── ic_action_keyboard.png
│ ├── drawable-xxhdpi
│ ├── ic_action_app_connected.png
│ ├── ic_action_app_not_connected.png
│ └── ic_action_keyboard.png
│ ├── drawable
│ ├── btn_black.xml
│ ├── code_logo_0.png
│ ├── code_logo_1.png
│ ├── code_logo_2.png
│ ├── ic_icon_ansi.xml
│ ├── ic_icon_hid.xml
│ ├── ic_icon_unicode.xml
│ ├── ic_launcher_background.xml
│ ├── ic_launcher_background_ble.xml
│ ├── ic_launcher_foreground_ble.xml
│ ├── surface_dial.png
│ └── view_border.xml
│ ├── layout-land
│ └── layout.xml
│ ├── layout
│ ├── activity_config.xml
│ ├── keyboard_num.xml
│ ├── keyboard_num2.xml
│ └── layout.xml
│ ├── menu
│ └── select_device_activity_menu.xml
│ ├── mipmap-anydpi-v26
│ ├── ic_launcher.xml
│ ├── ic_launcher_ble.xml
│ └── ic_launcher_ble_round.xml
│ ├── values-zh
│ └── strings.xml
│ └── values
│ ├── attrs.xml
│ ├── colors.xml
│ ├── dimens.xml
│ ├── ids.xml
│ ├── strings.xml
│ └── styles.xml
├── build.gradle
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
└── settings.gradle
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
4 | patreon: # Replace with a single Patreon username
5 | open_collective: # Replace with a single Open Collective username
6 | ko_fi: # Replace with a single Ko-fi username
7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
9 | liberapay: # Replace with a single Liberapay username
10 | issuehunt: # Replace with a single IssueHunt username
11 | otechie: # Replace with a single Otechie username
12 | custom: ['https://www.paypal.me/raghavpay/USD']
13 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/caches
5 | /.idea/libraries
6 | /.idea/modules.xml
7 | /.idea/workspace.xml
8 | /.idea/navEditor.xml
9 | /.idea/assetWizardSettings.xml
10 | /.idea/codeStyles
11 | .DS_Store
12 | /build
13 | /captures
14 | .externalNativeBuild
15 | /app/release/
16 |
--------------------------------------------------------------------------------
/.idea/codeStyles/Project.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | xmlns:android
14 |
15 | ^$
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 | xmlns:.*
25 |
26 | ^$
27 |
28 |
29 | BY_NAME
30 |
31 |
32 |
33 |
34 |
35 |
36 | .*:id
37 |
38 | http://schemas.android.com/apk/res/android
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 | .*:name
48 |
49 | http://schemas.android.com/apk/res/android
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 | name
59 |
60 | ^$
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 | style
70 |
71 | ^$
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 | .*
81 |
82 | ^$
83 |
84 |
85 | BY_NAME
86 |
87 |
88 |
89 |
90 |
91 |
92 | .*
93 |
94 | http://schemas.android.com/apk/res/android
95 |
96 |
97 | ANDROID_ATTRIBUTE_ORDER
98 |
99 |
100 |
101 |
102 |
103 |
104 | .*
105 |
106 | .*
107 |
108 |
109 | BY_NAME
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
--------------------------------------------------------------------------------
/.idea/codeStyles/codeStyleConfig.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/.idea/compiler.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/encodings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/.idea/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/.idea/jarRepositories.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/.idea/markdown-navigator-enh.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/.idea/markdown-navigator.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/.idea/runConfigurations.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # AB Dial (Android Bluetooth Radial Controler)
2 |
3 | This is an application to control your pc as a radial controler (like Surface Dial) or keyboard using the Bluetooth HID Device profile in Android 9(Pie) & above devices. App is currently in development.
4 |
5 | It is forked from Kontroller(https://github.com/raghavk92/Kontroller) , an application to control your pc,mac,tv,ipad etc as a mouse or keyboard using the Bluetooth HID Device profile in Android 9(Pie) & above devices. App is currently in development.
6 |
7 | Get it from Coolapk:
8 |
9 |
10 | Procedure to use the app:
11 | 1) Remove previous pairings with the host device in bluetooth settings(This has to be done once)
12 | 2) Open the app
13 | 3) Send a pairing request from the host device to the controlling device
14 | 4) Accept the pairing on the device running Kontroller
15 |
16 | Many device manufacturers have disabled the Bluetooth HID device profile on their devices. You would have to ask your device manufacturers to enable it. You can check with this app {[Bluetooth HID Device Profile Compatibility Checker](https://play.google.com/store/apps/details?id=com.rkaneapplabs.bluetooth_hid.bluetoothproxy)} if the Bluetooth HID device profile is disabled for you or not.
17 |
18 | For OnePlus users- you can upvote this [Idea](https://forums.oneplus.com/threads/converting-one-plus-devices-into-a-bluetooth-controller-mouse-keyboard-etc.1192272/) in the community forums after login(This may help in bringing the issue into notice to One Plus)
19 |
20 |
21 | # 中文说明
22 | 这是一个把Android手机虚拟为蓝牙外设的工具。眼馋surface dial好久了,现在终于手搓出一个虚拟设备!
23 | 这是从 Kontroller(https://github.com/raghavk92/Kontroller) fork而来,增加了radial controler的功能并修改了包名。
24 |
25 | ### 使用前提:
26 | 1) 手机系统Android9以上。(硬性要求)
27 | 2) 电脑需要运行Windows 10 周年更新或更高版本并且具有蓝牙 4.0 LE。(这是微软官网要求,别问我周年更新是啥,我也不懂。)蓝牙4.0实际上是非必须的,我的笔记本的蓝牙网卡坏掉了,插了一个2.1的适配器,也能正常工作
28 |
29 | ### 使用方法:
30 | 1) 从电脑上删除手机和电脑的蓝牙配对
31 | 2) 在手机上打开APP
32 | 3) 在选项中为auto pair打勾
33 | 4) 用电脑搜索手机并配对。在APP的顶部弹出配对请求时,点击同意(很重要,如果顶部没有弹出请求而是底部直接弹出,操作就失败了)
34 |
35 | 通过点击图标中央,或者在环状区域滑动,可以实现surface dial的操作及功能。
36 | 点击键盘图标,可以用此APP模拟蓝牙键盘(只能用键盘输入英文和数字符号,不可拼音打字)
37 |
38 | ### 异常处理:
39 | 1) 确认操作要点。先删除之前的配对/打开APP后再开始配对/配对过程APP不可以切换到后台/配对请求是从通知栏顶部弹出的,而不是直接从屏幕底部弹出
40 | 2) 如果始终不能连上:
41 | 你的手机的系统可能被厂商删除了一部分协议支持。可以通过安装Bluetooth HID Device Profile Compatibility Checker进行检查:
42 | 谷歌市场 https://play.google.com/store/apps/details?id=com.rkaneapplabs.bluetooth_hid.bluetoothproxy
43 | 百度网盘 https://pan.baidu.com/s/12aiHsOAUT696E7v2P60Iyw 提取码:66k5
44 | 如果是系统不支持,那就不要挣扎了。(据说1+是不行的)
45 | 3) APP一开始可以用,自动断开不能自动连上:
46 | 先检查APP设置的Enable autopair是否打勾。
47 | APP保持在前台,关闭手机蓝牙再重新打开,就能连上了。(开关电脑的蓝牙是无效的)
48 |
49 | 视频说明:http://www.bilibili.com/video/BV1tV411k73h
50 | 项目地址:https://github.com/tumuyan/Kontroller
51 | 下载地址:https://www.coolapk.com/apk/266123
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 | apply plugin: 'kotlin-android'
3 | apply plugin: 'kotlin-android-extensions'
4 |
5 | android {
6 |
7 | signingConfigs {
8 | release {
9 | storeFile file(RELEASE_STORE_FILE)
10 | keyAlias RELEASE_KEY_ALIAS
11 | storePassword RELEASE_KEY_PASSWORD
12 | keyPassword RELEASE_STORE_PASSWORD
13 | }
14 | }
15 |
16 | compileSdkVersion 30
17 | defaultConfig {
18 | applicationId "com.tumuyan.abdial"
19 | minSdkVersion 28
20 | targetSdkVersion 30
21 | versionCode 9
22 | versionName "0.0.9"
23 | signingConfig signingConfigs.release
24 | }
25 |
26 | compileOptions {
27 | targetCompatibility 1.8
28 | sourceCompatibility 1.8
29 | }
30 | buildTypes {
31 | release {
32 | minifyEnabled true
33 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
34 | }
35 | }
36 | buildToolsVersion = '30.0.2'
37 |
38 |
39 | bundle {
40 | language {
41 | enableSplit = true
42 | }
43 | density {
44 | enableSplit = true
45 | }
46 | abi {
47 | enableSplit = true
48 | }
49 | }
50 | }
51 |
52 | dependencies {
53 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
54 | implementation "org.jetbrains.anko:anko-commons:$anko_version"
55 | implementation "org.jetbrains.anko:anko-sdk25:$anko_version"
56 | implementation 'com.chibatching.kotpref:kotpref:2.6.0'
57 | }
58 |
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 |
23 | -assumenosideeffects class android.util.Log {
24 | public static boolean isLoggable(java.lang.String, int);
25 | public static int v(...);
26 | public static int d(...);
27 | public static int i(...);
28 | }
29 |
30 | -dontwarn **
31 | -target 1.7
32 | -dontusemixedcaseclassnames
33 | -dontskipnonpubliclibraryclasses
34 | -dontpreverify
35 | -verbose
36 | -optimizations !code/simplification/arithmetic,!code/allocation/variable
37 | -keep class **
38 | -keepclassmembers class *{*;}
39 | -keepattributes *
40 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
15 |
18 |
21 |
22 |
30 |
31 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/roarappstudio/btkontroller/BluetoothController.kt:
--------------------------------------------------------------------------------
1 | package com.github.roarappstudio.btkontroller
2 |
3 | import android.bluetooth.*
4 | import android.content.Context
5 | import android.util.Log
6 | import com.github.roarappstudio.btkontroller.reports.FeatureReport
7 | import android.content.Intent
8 |
9 |
10 | @Suppress("MemberVisibilityCanBePrivate")
11 | object BluetoothController: BluetoothHidDevice.Callback(), BluetoothProfile.ServiceListener {
12 |
13 | val featureReport = FeatureReport()
14 |
15 |
16 |
17 | override fun onSetReport(device: BluetoothDevice?, type: Byte, id: Byte, data: ByteArray?) {
18 | Log.i("setfirst","setfirst")
19 | super.onSetReport(device, type, id, data)
20 | Log.i("setreport","this $device and $type and $id and $data")
21 |
22 | }
23 |
24 |
25 | override fun onGetReport(device: BluetoothDevice?, type: Byte, id: Byte, bufferSize: Int) {
26 |
27 | Log.i("getbefore", "first")
28 | super.onGetReport(device, type, id, bufferSize)
29 |
30 | Log.i("get", "second")
31 | if (type == BluetoothHidDevice.REPORT_TYPE_FEATURE) {
32 | featureReport.wheelResolutionMultiplier = true
33 | featureReport.acPanResolutionMultiplier = true
34 | Log.i("getbthid","$btHid")
35 |
36 | var wasrs=btHid?.replyReport(device, type, FeatureReport.ID, featureReport.bytes)
37 | Log.i("replysuccess flag ",wasrs.toString())
38 | }
39 |
40 |
41 | }
42 |
43 |
44 | val btAdapter by lazy { BluetoothAdapter.getDefaultAdapter()!! }
45 | var btHid: BluetoothHidDevice? = null
46 | var context: Context? = null
47 | var hostDevice: BluetoothDevice? = null
48 | var autoPairFlag = false
49 |
50 | var mpluggedDevice :BluetoothDevice? = null
51 |
52 |
53 |
54 | private var deviceListener: ((BluetoothHidDevice, BluetoothDevice)->Unit)? = null
55 | private var disconnectListener: (()->Unit)? = null
56 |
57 | fun init(ctx: Context) {
58 | context = ctx
59 | if (btHid != null)
60 | return
61 | btAdapter.getProfileProxy(ctx, this, BluetoothProfile.HID_DEVICE)
62 | }
63 |
64 | fun getSender(callback: (BluetoothHidDevice, BluetoothDevice)->Unit) {
65 | btHid?.let { hidd ->
66 | hostDevice?.let { host ->
67 | callback(hidd, host)
68 | return
69 | }
70 | }
71 | deviceListener = callback
72 | }
73 |
74 |
75 | fun getDisconnector(callback: ()->Unit) {
76 | Log.e(TAG, "getDisconnector")
77 | disconnectListener = callback
78 | }
79 |
80 | /*****************************************************/
81 | /** BluetoothProfile.ServiceListener implementation **/
82 | /*****************************************************/
83 |
84 | override fun onServiceDisconnected(profile: Int) {
85 | Log.e(TAG, "Service disconnected!")
86 | disconnectListener?.invoke()
87 | if (profile == BluetoothProfile.HID_DEVICE)
88 | btHid = null
89 | }
90 |
91 | override fun onServiceConnected(profile: Int, proxy: BluetoothProfile) {
92 | Log.i(TAG, "Connected to service")
93 | if (profile != BluetoothProfile.HID_DEVICE) {
94 | Log.wtf(TAG, "WTF? $profile")
95 | return
96 | }
97 |
98 | val btHid = proxy as? BluetoothHidDevice
99 | if (btHid == null) {
100 | Log.wtf(TAG, "WTF? Proxy received but it's not BluetoothHidDevice")
101 |
102 | return
103 | }
104 | this.btHid = btHid
105 | btHid.registerApp(sdpRecord, null, qosOut, {it.run()}, this)//--
106 | // btAdapter.setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE, 300000)
107 | if (mpluggedDevice == null && !btAdapter.isDiscovering) {
108 | context?.startActivity(Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE))
109 | }
110 |
111 | }
112 |
113 |
114 |
115 | /************************************************/
116 | /** BluetoothHidDevice.Callback implementation **/
117 | /************************************************/
118 |
119 | override open fun onInterruptData(
120 | device: BluetoothDevice,
121 | reportId: Byte,
122 | data: ByteArray?
123 | ): Unit {
124 | Log.d(
125 | TAG,
126 | "onInterruptData: device=$device reportId=$reportId"
127 | )
128 | super.onInterruptData(device, reportId, data)
129 | }
130 |
131 | /**
132 | * Callback called when Virtual Cable is removed. After this callback is received connection
133 | * will be disconnected automatically.
134 | */
135 | override fun onVirtualCableUnplug(device: BluetoothDevice) {
136 | Log.d(
137 | TAG,
138 | "onVirtualCableUnplug: device=$device"
139 | )
140 | super.onVirtualCableUnplug(device)
141 | }
142 |
143 |
144 |
145 | override fun onConnectionStateChanged(device: BluetoothDevice?, state: Int) {
146 | super.onConnectionStateChanged(device, state)
147 | Log.i(TAG, "Connection state ${when(state) {
148 | BluetoothProfile.STATE_CONNECTING -> "CONNECTING"
149 | BluetoothProfile.STATE_CONNECTED -> "CONNECTED"
150 | BluetoothProfile.STATE_DISCONNECTING -> "DISCONNECTING"
151 | BluetoothProfile.STATE_DISCONNECTED -> "DISCONNECTED"
152 |
153 | else -> state.toString()
154 | }}")
155 | if (state == BluetoothProfile.STATE_CONNECTED) {
156 | if (device != null) {
157 | hostDevice = device
158 |
159 | deviceListener?.invoke(btHid!!, device)
160 |
161 | //deviceListener = null
162 | } else {
163 | Log.e(TAG, "Device not connected")
164 | }
165 | } else {
166 | hostDevice = null
167 | if(state == BluetoothProfile.STATE_DISCONNECTED)
168 | {
169 | Log.e(TAG, "onConnectionStateChanged disconnect)")
170 | disconnectListener?.invoke()
171 | }
172 |
173 | }
174 | }
175 |
176 | override fun onAppStatusChanged(pluggedDevice: BluetoothDevice?, registered: Boolean) {
177 | super.onAppStatusChanged(pluggedDevice, registered)
178 | if(registered)
179 | {
180 | var pairedDevices = btHid?.getDevicesMatchingConnectionStates(intArrayOf(BluetoothProfile.STATE_CONNECTING,BluetoothProfile.STATE_CONNECTED,BluetoothProfile.STATE_DISCONNECTED,BluetoothProfile.STATE_DISCONNECTING))
181 | Log.d("paired d", "paired devices are : $pairedDevices")
182 | // Log.d("paired d","${btHid?.getConnectionState(pairedDevices?.get(0))}")
183 | mpluggedDevice = pluggedDevice
184 |
185 | if (autoPairFlag) {
186 | if (pluggedDevice != null && btHid?.getConnectionState(pluggedDevice) == BluetoothProfile.STATE_DISCONNECTED) {
187 | btHid?.connect(pluggedDevice)
188 | //hostDevice.toString()
189 | } else {
190 | pairedDevices?.firstOrNull()?.let {
191 | val pairedDState = btHid?.getConnectionState(it)
192 | Log.d("paired d", pairedDState.toString())
193 | if (pairedDState == BluetoothProfile.STATE_DISCONNECTED) {
194 | btHid?.connect(it)
195 | }
196 | }
197 | }
198 |
199 | }
200 |
201 | }
202 | }
203 |
204 |
205 |
206 |
207 |
208 | /*************/
209 | /** Garbage **/
210 | /*************/
211 |
212 | const val TAG = "BluetoothController"
213 |
214 | private val sdpRecord by lazy {
215 | BluetoothHidDeviceAppSdpSettings(
216 | "Pixel HID1",
217 | "Mobile BController",
218 | "bla",
219 | BluetoothHidDevice.SUBCLASS1_COMBO,
220 | DescriptorCollection.MOUSE_KEYBOARD_COMBO_RADIAL
221 | )
222 | }
223 |
224 |
225 |
226 | private val qosOut by lazy {
227 | BluetoothHidDeviceAppQosSettings(
228 | BluetoothHidDeviceAppQosSettings.SERVICE_BEST_EFFORT,
229 | 800,
230 | 9,
231 | 0,
232 | 11250,
233 | BluetoothHidDeviceAppQosSettings.MAX
234 | )
235 | }
236 |
237 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/github/roarappstudio/btkontroller/ConfigActivity.kt:
--------------------------------------------------------------------------------
1 | package com.github.roarappstudio.btkontroller
2 |
3 | import android.app.Activity
4 | import android.app.AlertDialog
5 | import android.content.Context
6 | import android.os.Bundle
7 | import android.widget.Button
8 | import android.widget.EditText
9 | import android.widget.ToggleButton
10 | import org.jetbrains.anko.find
11 |
12 |
13 | class ConfigActivity : Activity() {
14 |
15 | private var text_haptic_use: Button ?=null
16 | private var edit_wheel_haptic_min_time:EditText?=null
17 | private var edit_wheel_haptic_skip_count:EditText?=null
18 | // private var edit_wheel_haptic_vibrate_time:EditText?=null
19 | private var text_wheel_haptic_vibrate_time:Button?=null
20 |
21 | private var edit_delay_time:EditText?=null
22 | private var toggle_replace_newline:ToggleButton?=null
23 | private var toggle_sendkey_fast:ToggleButton?=null
24 |
25 | private var toggle_wheel_color:ToggleButton?=null
26 |
27 | override fun onCreate(savedInstanceState: Bundle?) {
28 | super.onCreate(savedInstanceState)
29 | setContentView(R.layout.activity_config)
30 | haptic_use_items = arrayOf(getString(R.string.haptic_use_system),getString(R.string.haptic_use_mandatory),getString(R.string.haptic_use_null))
31 |
32 | text_haptic_use= find(R.id.text_haptic_use)
33 | text_haptic_use!!.setOnClickListener {
34 |
35 | val dialog = AlertDialog.Builder(this)
36 | dialog.setTitle(getString(R.string.haptic_use)).setItems(haptic_use_items){
37 | _,which ->
38 | text_haptic_use!!.setText(haptic_use_items[which])
39 | haptic_use_flag=which
40 | }.create().show()
41 | }
42 |
43 | text_wheel_haptic_vibrate_time= find(R.id.text_wheel_haptic_vibrate_time)
44 | text_wheel_haptic_vibrate_time!!.setOnClickListener {
45 |
46 | val dialog = AlertDialog.Builder(this)
47 | dialog.setTitle(getString(R.string.wheel_haptic_vib)).setItems(wheel_haptic_vibrate_name_items){
48 | _,which ->
49 | text_wheel_haptic_vibrate_time!!.setText(wheel_haptic_vibrate_name_items[which])
50 | wheel_haptic_vibrate_time_flag=Value.wheel_haptic_vibrate_value_items[which]
51 | }.create().show()
52 | }
53 |
54 | edit_wheel_haptic_min_time=find(R.id.edit_wheel_haptic_min_time)
55 |
56 | edit_wheel_haptic_skip_count=find(R.id.edit_wheel_haptic_skip_count)
57 |
58 | edit_delay_time=find(R.id.edit_sendkey_delay)
59 |
60 | toggle_replace_newline=find(R.id.toggle_replace_newline)
61 |
62 | toggle_sendkey_fast=find(R.id.toggle_sendkey_fast)
63 |
64 | toggle_wheel_color=find(R.id.toggle_wheel_color)
65 |
66 | // edit_wheel_haptic_vibrate_time=find(R.id.edit_wheel_haptic_vibrate_time)
67 | load()
68 |
69 | }
70 |
71 | override fun onPause() {
72 | super.onPause()
73 | save()
74 | }
75 |
76 | fun save(){
77 | var v=edit_wheel_haptic_skip_count!!.text.toString().toInt()
78 |
79 | if(v>=0){
80 | if(v>3000)
81 | v=3000
82 | wheel_haptic_skip_count_flag=v
83 | }
84 |
85 |
86 | v=edit_wheel_haptic_min_time!!.text.toString().toInt()
87 | if(v>=0){
88 | if (v>3000)
89 | v=3000
90 | wheel_haptic_min_time_falg=v
91 | }
92 |
93 | v=edit_delay_time!!.text.toString().toInt()
94 | if(v>=0){
95 | if (v>3000)
96 | v=3000
97 | delay_time=v
98 | }
99 |
100 | fast_mode_flag=toggle_sendkey_fast!!.isChecked
101 | replace_newline_flag=toggle_replace_newline!!.isChecked
102 | wheel_color_flag=toggle_wheel_color!!.isChecked
103 |
104 | // v=edit_wheel_haptic_vibrate_time!!.text.toString().toInt()
105 |
106 |
107 | val sharedPref = this?.getSharedPreferences("setting",Context.MODE_PRIVATE)
108 | with(sharedPref.edit())
109 | {
110 | putInt("haptic_use_flag", haptic_use_flag)
111 | putInt("wheel_haptic_vibrate_time_flag",wheel_haptic_vibrate_time_flag)
112 | putInt("wheel_haptic_skip_count_flag",wheel_haptic_skip_count_flag)
113 | putInt("wheel_haptic_min_time_falg",wheel_haptic_min_time_falg)
114 |
115 | putBoolean("fast_mode_flag",fast_mode_flag)
116 | putBoolean("replace_newline_flag",replace_newline_flag)
117 | putInt("delay_time",delay_time)
118 |
119 | putBoolean("wheel_color_flag",wheel_color_flag)
120 |
121 | commit()
122 | }
123 |
124 | load()
125 | }
126 |
127 | fun load(){
128 | val sharedPref = this?.getSharedPreferences("setting",Context.MODE_PRIVATE)
129 | haptic_use_flag=sharedPref.getInt("haptic_use_flag",haptic_use_flag)
130 | text_haptic_use!!.setText(haptic_use_items[haptic_use_flag])
131 |
132 | wheel_haptic_skip_count_flag=sharedPref.getInt("wheel_haptic_skip_count_flag",wheel_haptic_skip_count_flag)
133 | edit_wheel_haptic_skip_count!!.setText(wheel_haptic_skip_count_flag.toString())
134 |
135 | wheel_haptic_min_time_falg=sharedPref.getInt("wheel_haptic_min_time_falg",wheel_haptic_min_time_falg)
136 | edit_wheel_haptic_min_time!!.setText(wheel_haptic_min_time_falg.toString())
137 |
138 | wheel_haptic_vibrate_time_flag=sharedPref.getInt("wheel_haptic_vibrate_time_flag",wheel_haptic_vibrate_time_flag)
139 | // edit_wheel_haptic_vibrate_time!!.setText(wheel_haptic_vibrate_time_flag)
140 | text_wheel_haptic_vibrate_time!!.setText(wheel_haptic_vibrate_name_items[Value.wheel_haptic_vibrate_value_items.indexOf(wheel_haptic_vibrate_time_flag)])
141 |
142 | fast_mode_flag=sharedPref.getBoolean("fast_mode_flag",false)
143 | toggle_sendkey_fast!!.setChecked(fast_mode_flag)
144 |
145 | replace_newline_flag=sharedPref.getBoolean("replace_newline_flag",false)
146 | toggle_replace_newline!!.setChecked(replace_newline_flag)
147 |
148 | delay_time=sharedPref.getInt("delay_time",Value.delay_time)
149 | edit_delay_time!!.setText(delay_time.toString())
150 |
151 | wheel_color_flag=sharedPref.getBoolean("wheel_color_flag",Value.wheel_color_flag)
152 | toggle_wheel_color!!.setChecked(wheel_color_flag)
153 | }
154 |
155 |
156 |
157 | var replace_newline_flag=false
158 | var fast_mode_flag=false
159 | var delay_time=Value.delay_time
160 |
161 |
162 | var haptic_use_flag = Value.haptic_use_flag
163 | var wheel_haptic_min_time_falg = Value.wheel_haptic_minimum_time_flag
164 | var wheel_haptic_skip_count_flag =
165 | Value.wheel_haptic_skip_count_flag
166 | var wheel_haptic_vibrate_time_flag =
167 | Value.wheel_haptic_vibrate_time_flag
168 | var wheel_color_flag=Value.wheel_color_flag
169 |
170 |
171 | var haptic_use_items: Array = arrayOf("default", "no", "awlays")
172 |
173 | var wheel_haptic_vibrate_name_items= arrayOf(
174 | "LONG_PRESS",
175 | "CONTEXT_CLICK",
176 | "VIRTUAL_KEY",
177 | "KEYBOARD_TAP",
178 | "KEYBOARD_PRESS",
179 | "CLOCK_TICK"
180 | )
181 |
182 |
183 |
184 | }
185 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/roarappstudio/btkontroller/DescriptorCollection.kt:
--------------------------------------------------------------------------------
1 | package com.github.roarappstudio.btkontroller
2 |
3 | object DescriptorCollection {
4 | val MOUSE_KEYBOARD_COMBO_RADIAL = byteArrayOf(
5 |
6 | // Integrated Radial Controller TLC
7 | 0x05.toByte(), 0x01.toByte(), // USAGE_PAGE (Generic Desktop)
8 | 0x09.toByte(), 0x0e.toByte(), // USAGE (System Multi-Axis Controller)
9 | 0xa1.toByte(), 0x01.toByte(), // COLLECTION (Application)
10 | 0x85.toByte(), 0x01.toByte(), // REPORT_ID (Radial Controller)
11 | 0x05.toByte(), 0x0d.toByte(), // USAGE_PAGE (Digitizers)
12 | 0x09.toByte(), 0x21.toByte(), // USAGE (Puck)
13 | 0xa1.toByte(), 0x00.toByte(), // COLLECTION (Physical)
14 | 0x05.toByte(), 0x09.toByte(), // USAGE_PAGE (Buttons)
15 | 0x09.toByte(), 0x01.toByte(), // USAGE (Button 1)
16 | 0x95.toByte(), 0x01.toByte(), // REPORT_COUNT (1)
17 | 0x75.toByte(), 0x01.toByte(), // REPORT_SIZE (1)
18 | 0x15.toByte(), 0x00.toByte(), // LOGICAL_MINIMUM (0)
19 | 0x25.toByte(), 0x01.toByte(), // LOGICAL_MAXIMUM (1)
20 | 0x81.toByte(), 0x02.toByte(), // INPUT (Data,Var,Abs)
21 | 0x05.toByte(), 0x01.toByte(), // USAGE_PAGE (Generic Desktop)
22 | 0x09.toByte(), 0x37.toByte(), // USAGE (Dial)
23 | 0x95.toByte(), 0x01.toByte(), // REPORT_COUNT (1)
24 | 0x75.toByte(), 0x0f.toByte(), // REPORT_SIZE (15)
25 | 0x55.toByte(), 0x0f.toByte(), // UNIT_EXPONENT (-1)
26 | 0x65.toByte(), 0x14.toByte(), // UNIT (Degrees, English Rotation)
27 | 0x36.toByte(), 0xf0.toByte(), 0xf1.toByte(), // PHYSICAL_MINIMUM (-3600)
28 | 0x46.toByte(), 0x10.toByte(), 0x0e.toByte(), // PHYSICAL_MAXIMUM (3600)
29 | 0x16.toByte(), 0xf0.toByte(), 0xf1.toByte(), // LOGICAL_MINIMUM (-3600)
30 | 0x26.toByte(), 0x10.toByte(), 0x0e.toByte(), // LOGICAL_MAXIMUM (3600)
31 | 0x81.toByte(), 0x06.toByte(), // INPUT (Data,Var,Rel)
32 | /*
33 |
34 | 0x09.toByte(), 0x30.toByte(), // USAGE (X)
35 | 0x75.toByte(), 0x10.toByte(), // REPORT_SIZE (16)
36 | 0x55.toByte(), 0x0d.toByte(), // UNIT_EXPONENT (-3)
37 | 0x65.toByte(), 0x13.toByte(), // UNIT (Inch,EngLinear)
38 | 0x35.toByte(), 0x00.toByte(), // PHYSICAL_MINIMUM (0)
39 | 0x46.toByte(), 0xc0.toByte(), 0x5d.toByte(), // PHYSICAL_MAXIMUM (24000)
40 | 0x15.toByte(), 0x00.toByte(), // LOGICAL_MINIMUM (0)
41 | 0x26.toByte(), 0xff.toByte(), 0x7f.toByte(), // LOGICAL_MAXIMUM (32767)
42 | 0x81.toByte(), 0x02.toByte(), // INPUT (Data,Var,Abs)
43 |
44 | // 示例report似乎不对劲,使用x复制了一个report,也就是说构成了一个正方形
45 | // report 默认的max其实是 0x7FFF,因此屏幕中心应该是0x4000
46 | 0x09.toByte(), 0x31.toByte(), // USAGE (Y)
47 | // 0x46.toByte(), 0xb0.toByte(), 0x36.toByte(), // PHYSICAL_MAXIMUM (14000)
48 | 0x75.toByte(), 0x10.toByte(), // REPORT_SIZE (16)
49 | 0x55.toByte(), 0x0d.toByte(), // UNIT_EXPONENT (-3)
50 | 0x65.toByte(), 0x13.toByte(), // UNIT (Inch,EngLinear)
51 | 0x35.toByte(), 0x00.toByte(), // PHYSICAL_MINIMUM (0)
52 | 0x46.toByte(), 0xc0.toByte(), 0x5d.toByte(), // PHYSICAL_MAXIMUM (24000)
53 | 0x15.toByte(), 0x00.toByte(), // LOGICAL_MINIMUM (0)
54 | 0x26.toByte(), 0xff.toByte(), 0x7f.toByte(), // LOGICAL_MAXIMUM (32767)
55 | 0x81.toByte(), 0x02.toByte(), // INPUT (Data,Var,Abs)
56 |
57 | // 0x05.toByte(), 0x0d.toByte(), // USAGE_PAGE (Digitizers)
58 | // 0x09.toByte(), 0x48.toByte(), // USAGE (Width)
59 | // 0x36.toByte(), 0xb8.toByte(), 0x0b.toByte(), // PHYSICAL_MINIMUM (3000)
60 | // 0x46.toByte(), 0xb8.toByte(), 0x0b.toByte(), // PHYSICAL_MAXIMUM (3000)
61 | // 0x16.toByte(), 0xb8.toByte(), 0x0b.toByte(), // LOGICAL_MINIMUM (3000)
62 | // 0x26.toByte(), 0xb8.toByte(), 0x0b.toByte(), // LOGICAL_MAXIMUM (3000)
63 | // 0x81.toByte(), 0x03.toByte(), // INPUT (Cnst,Var,Abs)
64 | */
65 |
66 | 0xc0.toByte(), // END_COLLECTION
67 | 0xc0.toByte(), // END_COLLECTION
68 |
69 |
70 |
71 | /**/
72 | //MOUSE TLC
73 | 0x05.toByte(), 0x01.toByte(), // USAGE_PAGE (Generic Desktop)
74 | 0x09.toByte(), 0x02.toByte(), // USAGE (Mouse)
75 |
76 | 0xa1.toByte(), 0x01.toByte(), // COLLECTION (Application)
77 | 0x05.toByte(), 0x01.toByte(), // USAGE_PAGE (Generic Desktop)
78 | 0x09.toByte(), 0x02.toByte(), // USAGE (Mouse)
79 | 0xa1.toByte(), 0x02.toByte(), // COLLECTION (Logical)
80 |
81 | 0x85.toByte(), 0x04.toByte(), // REPORT_ID (Mouse)
82 | 0x09.toByte(), 0x01.toByte(), // USAGE (Pointer)
83 | 0xa1.toByte(), 0x00.toByte(), // COLLECTION (Physical)
84 | 0x05.toByte(), 0x09.toByte(), // USAGE_PAGE (Button)
85 | 0x19.toByte(), 0x01.toByte(), // USAGE_MINIMUM (Button 1)
86 | 0x29.toByte(), 0x02.toByte(), // USAGE_MAXIMUM (Button 2)
87 | 0x15.toByte(), 0x00.toByte(), // LOGICAL_MINIMUM (0)
88 | 0x25.toByte(), 0x01.toByte(), // LOGICAL_MAXIMUM (1)
89 | 0x75.toByte(), 0x01.toByte(), // REPORT_SIZE (1)
90 | 0x95.toByte(), 0x02.toByte(), // REPORT_COUNT (2)
91 | 0x81.toByte(), 0x02.toByte(), // INPUT (Data,Var,Abs)
92 | 0x95.toByte(), 0x01.toByte(), // REPORT_COUNT (1)
93 | 0x75.toByte(), 0x06.toByte(), // REPORT_SIZE (6)
94 | 0x81.toByte(), 0x03.toByte(), // INPUT (Cnst,Var,Abs)
95 | 0x05.toByte(), 0x01.toByte(), // USAGE_PAGE (Generic Desktop)
96 | 0x09.toByte(), 0x30.toByte(), // USAGE (X)
97 | 0x09.toByte(), 0x31.toByte(), // USAGE (Y)
98 | 0x16.toByte(), 0x01.toByte(),0xf8.toByte(), // LOGICAL_MINIMUM (-2047)
99 | 0x26.toByte(), 0xff.toByte(),0x07.toByte(), // LOGICAL_MAXIMUM (2047)
100 | 0x75.toByte(), 0x10.toByte(), // REPORT_SIZE (16)
101 | 0x95.toByte(), 0x02.toByte(), // REPORT_COUNT (2)
102 | 0x81.toByte(), 0x06.toByte(), // INPUT (Data,Var,Rel)
103 |
104 | 0xa1.toByte(), 0x02.toByte(), // COLLECTION (Logical)
105 | 0x85.toByte(), 0x06.toByte(), // REPORT_ID (Feature) ???report_id(6)
106 | 0x09.toByte(), 0x48.toByte(), // USAGE (Resolution Multiplier)
107 |
108 | 0x15.toByte(), 0x00.toByte(), // LOGICAL_MINIMUM (0)
109 | 0x25.toByte(), 0x01.toByte(), // LOGICAL_MAXIMUM (1)
110 | 0x35.toByte(), 0x01.toByte(), // PHYSICAL_MINIMUM (1)
111 | 0x45.toByte(), 0x04.toByte(), // PHYSICAL_MAXIMUM (4)
112 | 0x75.toByte(), 0x02.toByte(), // REPORT_SIZE (2)
113 | 0x95.toByte(), 0x01.toByte(), // REPORT_COUNT (1)
114 |
115 | 0xb1.toByte(), 0x02.toByte(), // FEATURE (Data,Var,Abs)
116 |
117 |
118 | 0x85.toByte(), 0x04.toByte(), // REPORT_ID (Mouse)
119 | //0x05.toByte(), 0x01.toByte(), // USAGE_PAGE (Generic Desktop)
120 | 0x09.toByte(), 0x38.toByte(), // USAGE (Wheel)
121 |
122 | 0x15.toByte(), 0x81.toByte(), // LOGICAL_MINIMUM (-127)
123 | 0x25.toByte(), 0x7f.toByte(), // LOGICAL_MAXIMUM (127)
124 | 0x35.toByte(), 0x00.toByte(), // PHYSICAL_MINIMUM (0) - reset physical
125 | 0x45.toByte(), 0x00.toByte(), // PHYSICAL_MAXIMUM (0)
126 | 0x75.toByte(), 0x08.toByte(), // REPORT_SIZE (8)
127 | 0x95.toByte(), 0x01.toByte(), // REPORT_COUNT (1)
128 | 0x81.toByte(), 0x06.toByte(), // INPUT (Data,Var,Rel)
129 | 0xc0.toByte(), // END_COLLECTION
130 |
131 | 0xa1.toByte(), 0x02.toByte(), // COLLECTION (Logical)
132 | 0x85.toByte(), 0x06.toByte(), // REPORT_ID (Feature)
133 | 0x09.toByte(), 0x48.toByte(), // USAGE (Resolution Multiplier)
134 |
135 | 0x15.toByte(), 0x00.toByte(), // LOGICAL_MINIMUM (0)
136 | 0x25.toByte(), 0x01.toByte(), // LOGICAL_MAXIMUM (1)
137 | 0x35.toByte(), 0x01.toByte(), // PHYSICAL_MINIMUM (1)
138 | 0x45.toByte(), 0x04.toByte(), // PHYSICAL_MAXIMUM (4)
139 | 0x75.toByte(), 0x02.toByte(), // REPORT_SIZE (2)
140 | 0x95.toByte(), 0x01.toByte(), // REPORT_COUNT (1)
141 |
142 | 0xb1.toByte(), 0x02.toByte(), // FEATURE (Data,Var,Abs)
143 |
144 | 0x35.toByte(), 0x00.toByte(), // PHYSICAL_MINIMUM (0) - reset physical
145 | 0x45.toByte(), 0x00.toByte(), // PHYSICAL_MAXIMUM (0)
146 | 0x75.toByte(), 0x04.toByte(), // REPORT_SIZE (4)
147 | 0xb1.toByte(), 0x03.toByte(), // FEATURE (Cnst,Var,Abs)
148 |
149 |
150 |
151 | 0x85.toByte(), 0x04.toByte(), // REPORT_ID (Mouse)
152 | 0x05.toByte(), 0x0c.toByte(), // USAGE_PAGE (Consumer Devices)
153 | 0x0a.toByte(), 0x38.toByte(), 0x02.toByte(), // USAGE (AC Pan)
154 |
155 | 0x15.toByte(), 0x81.toByte(), // LOGICAL_MINIMUM (-127)
156 | 0x25.toByte(), 0x7f.toByte(), // LOGICAL_MAXIMUM (127)
157 | 0x75.toByte(), 0x08.toByte(), // REPORT_SIZE (8)
158 | 0x95.toByte(), 0x01.toByte(), // REPORT_COUNT (1)
159 | 0x81.toByte(), 0x06.toByte(), // INPUT (Data,Var,Rel)
160 | 0xc0.toByte(), // END_COLLECTION
161 | 0xc0.toByte(), // END_COLLECTION
162 |
163 | 0xc0.toByte(), // END_COLLECTION
164 | 0xc0.toByte(), //END_COLLECTION
165 |
166 | // keyboard report_id=8 此部分基本无误
167 | 0x05.toByte(), 0x01.toByte(), // USAGE_PAGE (Generic Desktop)
168 | 0x09.toByte(), 0x06.toByte(), // Usage (Keyboard)
169 | 0xA1.toByte(), 0x01.toByte(), // Collection (Application)
170 | 0x85.toByte(), 0x08.toByte(), // REPORT_ID (Keyboard)
171 | 0x05.toByte(), 0x07.toByte(), // Usage Page (Key Codes)
172 | 0x19.toByte(), 0xe0.toByte(), // Usage Minimum (224)
173 | 0x29.toByte(), 0xe7.toByte(), // Usage Maximum (231)
174 | 0x15.toByte(), 0x00.toByte(), // Logical Minimum (0)
175 | 0x25.toByte(), 0x01.toByte(), // Logical Maximum (1)
176 | 0x75.toByte(), 0x01.toByte(), // Report Size (1)
177 | 0x95.toByte(), 0x08.toByte(), // Report Count (8)
178 | 0x81.toByte(), 0x02.toByte(), // Input (Data, Variable, Absolute)
179 |
180 | 0x95.toByte(), 0x01.toByte(), // Report Count (1)
181 | 0x75.toByte(), 0x08.toByte(), // Report Size (8)
182 | 0x81.toByte(), 0x01.toByte(), // Input (Constant) reserved byte(1)
183 |
184 | 0x95.toByte(), 0x01.toByte(), // Report Count (1)
185 | 0x75.toByte(), 0x08.toByte(), // Report Size (8)
186 | 0x15.toByte(), 0x00.toByte(), // Logical Minimum (0)
187 | 0x25.toByte(), 0x65.toByte(), // Logical Maximum (101)
188 | 0x05.toByte(), 0x07.toByte(), // Usage Page (Key codes)
189 | 0x19.toByte(), 0x00.toByte(), // Usage Minimum (0)
190 | 0x29.toByte(), 0x65.toByte(), // Usage Maximum (101)
191 | 0x81.toByte(), 0x00.toByte(), // Input (Data, Array) Key array(6 bytes)
192 |
193 | /*
194 | // add keyboard output (state leds)
195 | 0x95.toByte(), 0x05.toByte(), // Report Count (5)
196 | 0x75.toByte(), 0x01.toByte(), // Report Size (1)
197 | 0x05.toByte(), 0x08.toByte(), // Usage Page (Page# for LEDs)
198 | 0x19.toByte(), 0x01.toByte(), // Usage Minimum (1)
199 | 0x29.toByte(), 0x05.toByte(), // Usage Maximum (5)
200 | 0x91.toByte(), 0x02.toByte(), // Output (Data, Variable, Absolute), Led report
201 | 0x95.toByte(), 0x01.toByte(), // Report Count (1)
202 | 0x75.toByte(), 0x03.toByte(), // Report Size (3)
203 | 0x91.toByte(), 0x01.toByte(), // Output (Data, Variable, Absolute), Led report padding
204 | */
205 |
206 | 0xc0.toByte() // End Collection (Application)
207 |
208 |
209 | )
210 |
211 |
212 |
213 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/github/roarappstudio/btkontroller/Kontroller.kt:
--------------------------------------------------------------------------------
1 | package com.github.roarappstudio.btkontroller
2 |
3 | import android.app.Application
4 |
5 | @Suppress("unused") // It's in fucking manifest -_-
6 | class Kontroller: Application() {
7 |
8 | override fun onCreate() {
9 | super.onCreate()
10 | }
11 |
12 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/github/roarappstudio/btkontroller/SplashScreen.kt:
--------------------------------------------------------------------------------
1 | package com.github.roarappstudio.btkontroller
2 |
3 | import android.Manifest
4 | import android.app.Activity
5 | import android.content.pm.PackageManager
6 | import android.os.Bundle
7 | import org.jetbrains.anko.startActivity
8 |
9 | class SplashScreen: Activity() {
10 |
11 | override fun onCreate(savedInstanceState: Bundle?) {
12 | super.onCreate(savedInstanceState)
13 | if (checkSelfPermission(Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
14 | requestPermissions(
15 | arrayOf(Manifest.permission.ACCESS_COARSE_LOCATION), 0
16 | )
17 | } else {
18 |
19 | startActivity()
20 |
21 | finish()
22 | }
23 | }
24 |
25 |
26 |
27 | override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) {
28 | if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED)
29 | startActivity()
30 | finish()
31 | }
32 |
33 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/github/roarappstudio/btkontroller/Unhide.kt:
--------------------------------------------------------------------------------
1 | package com.github.roarappstudio.btkontroller
2 |
3 | import android.bluetooth.BluetoothAdapter
4 | import android.bluetooth.BluetoothDevice
5 |
6 | /** Extensions which expose hidden API **/
7 |
8 | fun BluetoothAdapter.setScanMode(mode: Int, duration: Int): Boolean =
9 | this::class.java.getMethod("setScanMode", Int::class.java, Int::class.java).invoke(this, mode, duration) as Boolean
10 |
11 | fun BluetoothDevice.cancelBondProcess(): Boolean =
12 | this::class.java.getMethod("cancelBondProcess").invoke(this) as Boolean
13 |
14 | fun BluetoothDevice.removeBond(): Boolean =
15 | this::class.java.getMethod("removeBond").invoke(this) as Boolean
--------------------------------------------------------------------------------
/app/src/main/java/com/github/roarappstudio/btkontroller/Value.kt:
--------------------------------------------------------------------------------
1 | package com.github.roarappstudio.btkontroller
2 |
3 | import android.view.HapticFeedbackConstants
4 |
5 | class Value {
6 |
7 | companion object{
8 | val wheel_haptic_vibrate_value_items= arrayOf(
9 | HapticFeedbackConstants.LONG_PRESS,
10 | HapticFeedbackConstants.CONTEXT_CLICK,
11 | HapticFeedbackConstants.VIRTUAL_KEY,
12 | HapticFeedbackConstants.KEYBOARD_TAP,
13 | HapticFeedbackConstants.KEYBOARD_PRESS,
14 | HapticFeedbackConstants.CLOCK_TICK
15 | )
16 |
17 | val haptic_use_flag=0
18 | val wheel_haptic_minimum_time_flag=0
19 | val wheel_haptic_vibrate_time_flag=HapticFeedbackConstants.LONG_PRESS
20 | val wheel_haptic_skip_count_flag=30
21 |
22 | val wheel_color_flag=true
23 |
24 | val haptic_use_name="haptic_use_flag"
25 | val wheel_haptic_minimum_time_name="wheel_haptic_minimum_time_flag"
26 | val wheel_haptic_vibrate_time_name="wheel_haptic_vibrate_time_flag"
27 | val wheel_haptic_skip_count_name="wheel_haptic_skip_count_flag"
28 |
29 | val WINDOWS_NEW_LINE=1
30 | val UNIX_NEW_LINE=0
31 | val OSX_NEW_LINE=2
32 |
33 | val delay_time=13
34 |
35 | }
36 |
37 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/github/roarappstudio/btkontroller/extraLibraries/CustomGestureDetector.kt:
--------------------------------------------------------------------------------
1 | package com.github.roarappstudio.btkontroller.extraLibraries
2 |
3 | import android.content.Context
4 | import android.view.GestureDetector
5 | import android.view.MotionEvent
6 | import com.github.roarappstudio.btkontroller.listeners.GestureDetectListener
7 |
8 | class CustomGestureDetector(context: Context, internal var mListener: GestureDetectListener) :
9 | GestureDetector(context, mListener) {
10 |
11 | override fun onTouchEvent(ev: MotionEvent?): Boolean {
12 | val consume = if (mListener != null) mListener!!.onTouchEvent(ev) else false
13 | return consume || super.onTouchEvent(ev)
14 | }
15 |
16 |
17 | }
18 |
--------------------------------------------------------------------------------
/app/src/main/java/com/github/roarappstudio/btkontroller/listeners/CompositeListener.kt:
--------------------------------------------------------------------------------
1 | package com.github.roarappstudio.btkontroller.listeners
2 |
3 | import android.view.MotionEvent
4 | import android.view.View
5 |
6 | class CompositeListener : View.OnTouchListener {
7 |
8 | private var registeredListeners : MutableList = ArrayList()
9 |
10 |
11 | fun registerListener(listener : View.OnTouchListener): Unit{
12 | registeredListeners.add(listener)
13 |
14 | }
15 | override fun onTouch(v: View?, event: MotionEvent?): Boolean {
16 |
17 | for(listener:View.OnTouchListener in registeredListeners)
18 | {
19 | listener.onTouch(v,event)
20 | }
21 | return true
22 |
23 | }
24 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/github/roarappstudio/btkontroller/listeners/GestureDetectListener.kt:
--------------------------------------------------------------------------------
1 | package com.github.roarappstudio.btkontroller.listeners
2 |
3 | import android.util.Log
4 | import android.view.GestureDetector
5 | import android.view.MotionEvent
6 | import android.view.ViewConfiguration
7 | import com.github.roarappstudio.btkontroller.senders.RelativeMouseSender
8 | import java.util.*
9 | import kotlin.concurrent.schedule
10 |
11 | class GestureDetectListener(val rMouseSender : RelativeMouseSender) : GestureDetector.OnGestureListener, GestureDetector.OnDoubleTapListener {
12 |
13 | val TAP = 0
14 | val DOUBLE_TAP = 1
15 |
16 | val DOUBLE_TAP_TIMEOUT = ViewConfiguration
17 | .getDoubleTapTimeout().toLong()
18 | var mViewScaledTouchSlop: Float = 0.toFloat()
19 | //internal var mGestureName: EditText
20 |
21 | private var mCurrentDownEvent: MotionEvent? = null
22 |
23 | private var mPtrCount = 0
24 |
25 |
26 | private var possibleTwoFingerTapFlag =0
27 | private var mPossibleTwoFingerTapStartTime=System.currentTimeMillis()
28 | private var mPrimStartTouchEventX = 0f
29 | private var mPrimStartTouchEventY = 0f
30 | private var mSecStartTouchEventX = 0f
31 | private var mSecStartTouchEventY = 0f
32 | private var mPrimSecStartTouchDistance = 0f
33 | private var notAConfirmedDoubleTapFlag=0
34 | private var disableSingleTapFlag=0
35 | private var previousScrollX : Float = 0f
36 | private var previousScrollY : Float = 0f
37 |
38 |
39 | private var testerp1=0
40 | private var testerp2 =0
41 | private var stopScrollFlag=0
42 |
43 | internal var downTimestamp = System.currentTimeMillis()
44 | fun onTouchEvent(ev: MotionEvent?): Boolean {
45 | if(ev !=null) {
46 | val action = ev.action and MotionEvent.ACTION_MASK
47 | if(ev.pointerCount==1)
48 | {
49 | if(stopScrollFlag==1)
50 | {
51 | rMouseSender.mouseReport.hScroll=0
52 | rMouseSender.mouseReport.vScroll=0
53 | stopScrollFlag=0
54 | }
55 |
56 | }
57 |
58 | // prepend("onTouchEvent() ptrs:" + ev.getPointerCount() + " "
59 | // + actionToString(action));
60 | when (action) {
61 | MotionEvent.ACTION_POINTER_DOWN -> {
62 | mPtrCount++
63 | if (ev.pointerCount > 1) {
64 |
65 | testerp1=ev.getPointerId(0)//remove at end of testing
66 | testerp2=ev.getPointerId(1)
67 |
68 |
69 |
70 | mSecStartTouchEventX = ev.getX(1)
71 | mSecStartTouchEventY = ev.getY(1)
72 | mPrimSecStartTouchDistance = distance(ev, 0, 1)
73 | if (ev.pointerCount == 2) possibleTwoFingerTapFlag = 1
74 | // if (mCurrentDownEvent != null)
75 | // mCurrentDownEvent.recycle()
76 | mCurrentDownEvent = MotionEvent.obtain(ev)
77 |
78 | // if (System.currentTimeMillis() - downTimestamp > 50) {
79 |
80 | // if (!mHandler.hasMessages(TAP)) {
81 | // mHandler.sendEmptyMessageDelayed(
82 | // TAP,
83 | // DOUBLE_TAP_TIMEOUT
84 | // )
85 | // } else {
86 | // mHandler.removeMessages(TAP)
87 | // mHandler.sendEmptyMessageDelayed(
88 | // DOUBLE_TAP,
89 | // DOUBLE_TAP_TIMEOUT
90 | // )
91 | // }
92 |
93 | // }
94 |
95 | downTimestamp = System.currentTimeMillis()
96 |
97 | // return true to prevent other actions.
98 | return true
99 | }
100 | }
101 | MotionEvent.ACTION_POINTER_UP -> mPtrCount--
102 | MotionEvent.ACTION_DOWN -> {
103 |
104 | mPtrCount++
105 |
106 | mPossibleTwoFingerTapStartTime = System.currentTimeMillis()
107 | }
108 | MotionEvent.ACTION_UP -> {
109 | mPtrCount--
110 |
111 | if(possibleTwoFingerTapFlag == 1)
112 | {
113 | possibleTwoFingerTapFlag = 0
114 |
115 | if (mPtrCount == 0 && ((System.currentTimeMillis() - mPossibleTwoFingerTapStartTime) <= ViewConfiguration.getTapTimeout()) ) {
116 |
117 | disableSingleTapFlag =1
118 | Log.i("thisistwofinger", "two finger single tap is implemented")
119 |
120 | rMouseSender.sendRightClick()
121 | }
122 |
123 | }
124 |
125 |
126 | }
127 | }
128 | }
129 | return false
130 | }
131 | override fun onDoubleTap(e: MotionEvent?): Boolean {
132 | Log.i("doubleddht","this is on double tap $e")
133 |
134 | return false
135 | }
136 |
137 | override fun onDoubleTapEvent(e: MotionEvent?): Boolean {
138 | Log.i("doubleddhe","this is on double tap event $e")
139 | if(mPtrCount==1)
140 | {
141 | if(e!=null) {
142 | if (e.action == MotionEvent.ACTION_DOWN)
143 | Timer().schedule(150L) {
144 | if(mPtrCount==1)
145 | {
146 | notAConfirmedDoubleTapFlag=1;
147 | rMouseSender.sendLeftClickOn()
148 | Log.i("doubleddhtnew","this is on double tap and hold and also $DOUBLE_TAP_TIMEOUT and $e ")
149 |
150 | }
151 | }
152 |
153 |
154 | }
155 |
156 |
157 | }
158 | if(mPtrCount==0)
159 | {
160 | if(e!=null) {
161 | if (e.action == MotionEvent.ACTION_UP) {
162 | if (notAConfirmedDoubleTapFlag == 0) {
163 | rMouseSender.sendDoubleTapClick()
164 | Log.i("doubleddhtnew", "this is on double tap confirmed $e")
165 | } else {
166 | notAConfirmedDoubleTapFlag=0
167 | rMouseSender.sendLeftClickOff()
168 |
169 |
170 | }
171 | }
172 | }
173 | }
174 |
175 | return false
176 | }
177 |
178 | override fun onSingleTapConfirmed(e: MotionEvent?): Boolean {
179 | Log.i("doubleddhs","this is on single tap confirmed $e")
180 | if(disableSingleTapFlag==1)
181 | {
182 | disableSingleTapFlag=0
183 | }
184 | else {
185 | rMouseSender.sendTestClick()
186 | }
187 | return false
188 | }
189 |
190 | override fun onSingleTapUp(e: MotionEvent?): Boolean {
191 | Log.i("doubleddhu","this is on single tap up $e")
192 | //
193 | return true
194 | }
195 |
196 | override fun onDown(e: MotionEvent?): Boolean {
197 | Log.d("ggkjh", "onDown: $e")
198 | return false
199 |
200 | }
201 |
202 | override fun onFling(e1: MotionEvent?, e2: MotionEvent?, velocityX: Float, velocityY: Float): Boolean {
203 | Log.i("this is a fling e1 ","$e1")
204 | Log.i("this is a fling e2 ","$e2")
205 | Log.i("this is a fling vx ","$velocityX")
206 | Log.i("this is a fling vy ","$velocityY")
207 |
208 |
209 | return false
210 | }
211 |
212 | override fun onScroll(e1: MotionEvent?, e2: MotionEvent?, distanceX: Float, distanceY: Float): Boolean {
213 |
214 |
215 | if(mPtrCount==2) {
216 |
217 | // var dy :Int = distanceY.roundToInt()
218 | // if (dy> 127) dy=127
219 | // else if (dy< -127) dy=-127
220 | //
221 | // var dx :Int = distanceX.roundToInt()
222 | // if (dx> 127) dx=127
223 | // else if (dx< -127) dx=-127
224 | //
225 | //
226 | // rMouseSender.sendScroll(dy,dx)
227 |
228 | var dy: Int =0
229 | var dx :Int =0
230 | if(distanceY>0) dy= -1
231 |
232 | else if(distanceY<0) dy = 1
233 | else if(distanceY==0f) dy=0
234 | //else dy=0
235 |
236 | if(distanceX>2) dx= 1
237 |
238 | else if(distanceX<-2) dx = -1
239 | //else if(distanceX==0f) dx=0
240 | else dx=0
241 |
242 |
243 | if (dx > 127) dx = 127
244 | else if (dx < -127) dx = -127
245 |
246 |
247 |
248 |
249 |
250 |
251 |
252 | rMouseSender.sendScroll(dy, dx)
253 |
254 |
255 | stopScrollFlag=1
256 | // if(e1?.getPointerId(0)==testerp1) {
257 | //
258 | // Log.i("scroller", "This is e1 as $e1")
259 | //
260 | // Log.i("scroller", "This is e2 as $e2")
261 | // Log.i("scroller", "This is distanceX as $distanceX")
262 | // Log.i("scroller", "This is distanceY as $distanceY")
263 | // }
264 | // else if(e1?.getPointerId(1)==testerp2 )
265 | // {
266 | // Log.i("scrollex", "This is e1 as $e1")
267 | //
268 | // Log.i("scrollex", "This is e2 as $e2")
269 | // Log.i("scrollex", "This is distanceX as $distanceX")
270 | // Log.i("scrollex", "This is distanceY as $distanceY")
271 | // }
272 |
273 |
274 | }
275 | return false
276 | }
277 |
278 | override fun onLongPress(e: MotionEvent?) {
279 |
280 | }
281 |
282 | override fun onShowPress(e: MotionEvent?) {
283 |
284 | }
285 |
286 | fun distance(event: MotionEvent, first: Int, second: Int): Float {
287 | if (event.pointerCount >= 2) {
288 | val x = event.getX(first) - event.getX(second)
289 | val y = event.getY(first) - event.getY(second)
290 |
291 | return Math.sqrt((x * x + y * y).toDouble()).toFloat()
292 | } else {
293 | return 0f
294 | }
295 | }
296 |
297 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/github/roarappstudio/btkontroller/listeners/ViewListener.kt:
--------------------------------------------------------------------------------
1 | package com.github.roarappstudio.btkontroller.listeners
2 |
3 | import android.bluetooth.BluetoothDevice
4 | import android.bluetooth.BluetoothHidDevice
5 | import android.util.Log
6 | import android.view.MotionEvent
7 | import android.view.View
8 | import com.github.roarappstudio.btkontroller.senders.RelativeMouseSender
9 | import java.nio.ByteBuffer
10 | import kotlin.math.roundToInt
11 |
12 | @ExperimentalUnsignedTypes
13 | class ViewListener(val hidDevice: BluetoothHidDevice, val host: BluetoothDevice, val rMouseSender : RelativeMouseSender): View.OnTouchListener{
14 |
15 |
16 | //val absMouseReport = AbsMouseReport()
17 |
18 | private var previousX: Float = 0f
19 | private var previousY: Float = 0f
20 | private var pointerMotionStopFlag :Int =0
21 |
22 |
23 | override fun onTouch(v: View, event: MotionEvent): Boolean {
24 |
25 | //this.gDetector.onTouchEvent(event)
26 |
27 | val x: Float = event.x
28 | val y: Float = event.y
29 | when (event.action) {
30 | MotionEvent.ACTION_MOVE -> {
31 |
32 |
33 | Log.d("pointerCount_is",event.pointerCount.toString())
34 | if(event.pointerCount==1) {
35 | if(pointerMotionStopFlag==1) pointerMotionStopFlag=0
36 | Log.d("is this working",event.pointerCount.toString())
37 | var dx: Float = x - previousX
38 | var dxInt: Int = dx.roundToInt()
39 |
40 |
41 | if (dxInt > 2047) dxInt = 2047
42 |
43 | if (dxInt < -2047) dxInt = -2047
44 |
45 | var dy: Float = y - previousY
46 | var dyInt: Int = dy.roundToInt()
47 | if (dyInt > 2047) dyInt = 2047
48 |
49 |
50 | if (dyInt < -2047) dyInt = -2047
51 |
52 | var bytesArrX = ByteArray(2) { 0 }
53 | var buffX: ByteBuffer = ByteBuffer.wrap(bytesArrX)
54 | buffX.putShort(dxInt.toShort())
55 |
56 | var bytesArrY = ByteArray(2) { 0 }
57 | var buffY: ByteBuffer = ByteBuffer.wrap(bytesArrY)
58 | buffY.putShort(dyInt.toShort())
59 |
60 | rMouseSender.mouseReport.dxMsb = bytesArrX[0]
61 | rMouseSender.mouseReport.dxLsb = bytesArrX[1]
62 |
63 | rMouseSender.mouseReport.dyMsb = bytesArrY[0]
64 | rMouseSender.mouseReport.dyLsb = bytesArrY[1]
65 | //bytes2[0]= bytes1[2]
66 | //bytes2[1]= bytes1[3]
67 |
68 |
69 | Log.d("ddf2", bytesArrX.contentToString())
70 |
71 |
72 |
73 | hidDevice.sendReport(this.host, 4, rMouseSender.mouseReport.bytes)
74 |
75 |
76 | // reverse direction of rotation above the mid-line
77 | // if (y > height / 2) {
78 | // dx *= -1
79 | // }
80 | //
81 | // // reverse direction of rotation to left of the mid-line
82 | // if (x < width / 2) {
83 | // dy *= -1
84 | // }
85 |
86 | // renderer.angle += (dx + dy) * TOUCH_SCALE_FACTOR
87 | // requestRender()
88 | }
89 | else {
90 | if(pointerMotionStopFlag==0)
91 | {
92 | rMouseSender.mouseReport.dxMsb =0
93 | rMouseSender.mouseReport.dxLsb = 0
94 |
95 | rMouseSender.mouseReport.dyMsb = 0
96 | rMouseSender.mouseReport.dyLsb = 0
97 | //bytes2[0]= bytes1[2]
98 | //bytes2[1]= bytes1[3]
99 |
100 |
101 | Log.d("ddf2", rMouseSender.mouseReport.dxMsb.toString() +"," +rMouseSender.mouseReport.dxLsb.toString())
102 |
103 |
104 |
105 | hidDevice.sendReport(this.host, 4, rMouseSender.mouseReport.bytes)
106 | }
107 | pointerMotionStopFlag=1;
108 |
109 | }
110 |
111 | }
112 | MotionEvent.ACTION_UP->{
113 | rMouseSender.mouseReport.dxMsb =0
114 | rMouseSender.mouseReport.dxLsb = 0
115 |
116 | rMouseSender.mouseReport.dyMsb = 0
117 | rMouseSender.mouseReport.dyLsb = 0
118 | //bytes2[0]= bytes1[2]
119 | //bytes2[1]= bytes1[3]
120 |
121 |
122 | Log.d("up action ddf2", rMouseSender.mouseReport.dxMsb.toString() +"," +rMouseSender.mouseReport.dxLsb.toString())
123 |
124 |
125 |
126 | hidDevice.sendReport(this.host, 4, rMouseSender.mouseReport.bytes)
127 | }
128 | }
129 |
130 |
131 | previousX = x
132 | previousY = y
133 |
134 |
135 | return true
136 |
137 | }
138 | // override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) {
139 | // Log.d(TAG, "Accuracy changed ${when(accuracy) {
140 | // SensorManager.SENSOR_STATUS_ACCURACY_LOW -> "LOW"
141 | // SensorManager.SENSOR_STATUS_ACCURACY_MEDIUM -> "MEDIUM"
142 | // SensorManager.SENSOR_STATUS_ACCURACY_HIGH -> "HIGH"
143 | // else -> accuracy.toString()
144 | // }}")
145 | // }
146 | //
147 | // override fun onSensorChanged(event: SensorEvent) {
148 | // val angleX = event.values[2].toDouble()
149 | // val angleY = event.values[0].toDouble()
150 | // val pixelsX = (angleX * 3840 / PI).roundToInt() + 1920
151 | // val pixelsY = (angleY * 2160 / PI).roundToInt() + 1080
152 | // Log.wtf("WTF", "$pixelsX x $pixelsY")
153 | // if (pixelsX != absMouseReport.X || pixelsY != absMouseReport.Y) {
154 | // absMouseReport.X = pixelsX
155 | // absMouseReport.Y = pixelsY
156 | // hidDevice.sendReport(this.host, 2, absMouseReport.bytes)
157 | //
158 | // } else {
159 | // Log.i("WTF", "No changes")
160 | // }
161 | // }
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/github/roarappstudio/btkontroller/reports/AbsMouseReport.kt:
--------------------------------------------------------------------------------
1 | package com.github.roarappstudio.btkontroller.reports
2 |
3 | @ExperimentalUnsignedTypes
4 | @Suppress("EXPERIMENTAL_FEATURE_WARNING")
5 | inline class AbsMouseReport(
6 | val bytes: ByteArray = ByteArray(4)
7 | ) {
8 |
9 | var X: Int
10 | get() = (bytes[1].toUByte().toInt() shl 8) or bytes[0].toUByte().toInt()
11 | set(value) {
12 | bytes[0] = (value and 0xff).toByte()
13 | bytes[1] = ((value shr 8) and 0xff).toByte()
14 | }
15 |
16 | var Y: Int
17 | get() = (bytes[3].toUByte().toInt() shl 8) or bytes[2].toUByte().toInt()
18 | set(value) {
19 | bytes[2] = (value and 0xff).toByte()
20 | bytes[3] = ((value shr 8) and 0xff).toByte()
21 | }
22 |
23 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/github/roarappstudio/btkontroller/reports/FeatureReport.kt:
--------------------------------------------------------------------------------
1 | package com.github.roarappstudio.btkontroller.reports
2 |
3 | import kotlin.experimental.and
4 | import kotlin.experimental.or
5 |
6 | @Suppress("EXPERIMENTAL_FEATURE_WARNING")
7 | inline class FeatureReport (
8 | val bytes: ByteArray = ByteArray(1) {0}
9 | ) {
10 |
11 |
12 | var wheelResolutionMultiplier: Boolean
13 | get() = bytes[0] and 0b1 != 0.toByte()
14 | set(value) {
15 | bytes[0] = if (value)
16 | bytes[0] or 0b1
17 | else
18 | bytes[0] and 0b1110
19 | }
20 |
21 | var acPanResolutionMultiplier: Boolean
22 | get() = bytes[0] and 0b100 != 0.toByte()
23 | set(value) {
24 | bytes[0] = if (value)
25 | bytes[0] or 0b100
26 | else
27 | bytes[0] and 0b1011
28 | }
29 |
30 |
31 |
32 |
33 | fun reset() = bytes.fill(0)
34 |
35 | companion object {
36 | const val ID = 6.toByte()
37 | }
38 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/github/roarappstudio/btkontroller/reports/KeyboardReport.kt:
--------------------------------------------------------------------------------
1 | package com.github.roarappstudio.btkontroller.reports
2 |
3 | import android.view.KeyEvent
4 | import kotlin.experimental.and
5 | import kotlin.experimental.or
6 |
7 | @Suppress("EXPERIMENTAL_FEATURE_WARNING")
8 | inline class KeyboardReport (
9 | val bytes: ByteArray = ByteArray(3) {0}
10 | ) {
11 |
12 |
13 | var leftControl: Boolean
14 | get() = bytes[0] and 0b1 != 0.toByte()
15 | set(value) {
16 | bytes[0] = if (value)
17 | bytes[0] or 0b1
18 | else
19 | bytes[0] and 0b11111110.toByte()
20 | }
21 |
22 | var leftShift: Boolean
23 | get() = bytes[0] and 0b10 != 0.toByte()
24 | set(value) {
25 | bytes[0] = if (value)
26 | bytes[0] or 0b10
27 | else
28 | bytes[0] and 0b11111101.toByte()
29 | }
30 |
31 | var leftAlt: Boolean
32 | get() = bytes[0] and 0b10 != 0.toByte()
33 | set(value) {
34 | bytes[0] = if (value)
35 | bytes[0] or 0b100
36 | else
37 | bytes[0] and 0b11111011.toByte()
38 | }
39 | var leftGui: Boolean
40 | get() = bytes[0] and 0b10 != 0.toByte()
41 | set(value) {
42 | bytes[0] = if (value)
43 | bytes[0] or 0b1000
44 | else
45 | bytes[0] and 0b11110111.toByte()
46 | }
47 |
48 | var rightControl: Boolean
49 | get() = bytes[0] and 0b1 != 0.toByte()
50 | set(value) {
51 | bytes[0] = if (value)
52 | bytes[0] or 0b10000
53 | else
54 | bytes[0] and 0b11101111.toByte()
55 | }
56 |
57 | var rightShift: Boolean
58 | get() = bytes[0] and 0b10 != 0.toByte()
59 | set(value) {
60 | bytes[0] = if (value)
61 | bytes[0] or 0b100000
62 | else
63 | bytes[0] and 0b11011111.toByte()
64 | }
65 |
66 | var rightAlt: Boolean
67 | get() = bytes[0] and 0b10 != 0.toByte()
68 | set(value) {
69 | bytes[0] = if (value)
70 | bytes[0] or 0b1000000
71 | else
72 | bytes[0] and 0b10111111.toByte()
73 | }
74 | var rightGui: Boolean
75 | get() = bytes[0] and 0b10 != 0.toByte()
76 | set(value) {
77 | bytes[0] = if (value)
78 | bytes[0] or 0b10000000.toByte()
79 | else
80 | bytes[0] and 0b01111111
81 | }
82 |
83 | var key1: Byte
84 | get() = bytes[2]
85 | set(value) {
86 | bytes[2] = value
87 |
88 | }
89 |
90 | fun reset() = bytes.fill(0)
91 |
92 | fun reset_function_key(){ bytes[0]=0}
93 | fun new_normal_key():ByteArray{
94 | var v=ByteArray(3) {0}
95 | v[0]=bytes[0]
96 | return v
97 | }
98 |
99 | companion object {
100 | const val ID = 8
101 |
102 | val KeyEventMap = mapOf(
103 | KeyEvent.KEYCODE_A to 4,
104 | KeyEvent.KEYCODE_B to 5,
105 | KeyEvent.KEYCODE_C to 6,
106 | KeyEvent.KEYCODE_D to 7,
107 | KeyEvent.KEYCODE_E to 8,
108 | KeyEvent.KEYCODE_F to 9,
109 | KeyEvent.KEYCODE_G to 10,
110 | KeyEvent.KEYCODE_H to 11,
111 | KeyEvent.KEYCODE_I to 12,
112 | KeyEvent.KEYCODE_J to 13,
113 | KeyEvent.KEYCODE_K to 14,
114 | KeyEvent.KEYCODE_L to 15,
115 | KeyEvent.KEYCODE_M to 16,
116 | KeyEvent.KEYCODE_N to 17,
117 | KeyEvent.KEYCODE_O to 18,
118 | KeyEvent.KEYCODE_P to 19,
119 | KeyEvent.KEYCODE_Q to 20,
120 | KeyEvent.KEYCODE_R to 21,
121 | KeyEvent.KEYCODE_S to 22,
122 | KeyEvent.KEYCODE_T to 23,
123 | KeyEvent.KEYCODE_U to 24,
124 | KeyEvent.KEYCODE_V to 25,
125 | KeyEvent.KEYCODE_W to 26,
126 | KeyEvent.KEYCODE_X to 27,
127 | KeyEvent.KEYCODE_Y to 28,
128 | KeyEvent.KEYCODE_Z to 29,
129 |
130 |
131 | KeyEvent.KEYCODE_1 to 30,
132 | KeyEvent.KEYCODE_2 to 31,
133 | KeyEvent.KEYCODE_3 to 32,
134 | KeyEvent.KEYCODE_4 to 33,
135 | KeyEvent.KEYCODE_5 to 34,
136 | KeyEvent.KEYCODE_6 to 35,
137 | KeyEvent.KEYCODE_7 to 36,
138 | KeyEvent.KEYCODE_8 to 37,
139 | KeyEvent.KEYCODE_9 to 38,
140 | KeyEvent.KEYCODE_0 to 39,
141 |
142 | KeyEvent.KEYCODE_F1 to 58,
143 | KeyEvent.KEYCODE_F2 to 59,
144 | KeyEvent.KEYCODE_F3 to 60,
145 | KeyEvent.KEYCODE_F4 to 61,
146 | KeyEvent.KEYCODE_F5 to 62,
147 | KeyEvent.KEYCODE_F6 to 63,
148 | KeyEvent.KEYCODE_F7 to 64,
149 | KeyEvent.KEYCODE_F8 to 65,
150 | KeyEvent.KEYCODE_F9 to 66,
151 | KeyEvent.KEYCODE_F10 to 67,
152 | KeyEvent.KEYCODE_F11 to 68,
153 | KeyEvent.KEYCODE_F12 to 69,
154 |
155 | KeyEvent.KEYCODE_ENTER to 40,
156 | KeyEvent.KEYCODE_ESCAPE to 41,
157 | KeyEvent.KEYCODE_DEL to 42,
158 | KeyEvent.KEYCODE_TAB to 43,
159 | KeyEvent.KEYCODE_SPACE to 44,
160 | KeyEvent.KEYCODE_MINUS to 45,
161 | KeyEvent.KEYCODE_EQUALS to 46,
162 | KeyEvent.KEYCODE_LEFT_BRACKET to 47,
163 | KeyEvent.KEYCODE_RIGHT_BRACKET to 48,
164 | KeyEvent.KEYCODE_BACKSLASH to 49,
165 | KeyEvent.KEYCODE_SEMICOLON to 51,
166 | KeyEvent.KEYCODE_APOSTROPHE to 52,
167 | KeyEvent.KEYCODE_GRAVE to 53,
168 | KeyEvent.KEYCODE_COMMA to 54,
169 | KeyEvent.KEYCODE_PERIOD to 55,
170 | KeyEvent.KEYCODE_SLASH to 56,
171 |
172 | KeyEvent.KEYCODE_SCROLL_LOCK to 71,
173 | KeyEvent.KEYCODE_INSERT to 73,
174 | KeyEvent.KEYCODE_HOME to 74,
175 | KeyEvent.KEYCODE_PAGE_UP to 75,
176 | KeyEvent.KEYCODE_FORWARD_DEL to 76,
177 | KeyEvent.KEYCODE_MOVE_END to 77,
178 | KeyEvent.KEYCODE_PAGE_DOWN to 78,
179 | KeyEvent.KEYCODE_NUM_LOCK to 83,
180 |
181 | KeyEvent.KEYCODE_DPAD_RIGHT to 79,
182 | KeyEvent.KEYCODE_DPAD_LEFT to 80,
183 | KeyEvent.KEYCODE_DPAD_DOWN to 81,
184 | KeyEvent.KEYCODE_DPAD_UP to 82,
185 |
186 | KeyEvent.KEYCODE_AT to 31,
187 | KeyEvent.KEYCODE_POUND to 32,
188 | KeyEvent.KEYCODE_STAR to 37
189 | )
190 |
191 |
192 |
193 | val UnicodeKeyEventMap = mapOf(
194 | 97 to 4, //KeyEvent.KEYCODE_A
195 | 98 to 5, //KeyEvent.KEYCODE_B
196 | 99 to 6, //KeyEvent.KEYCODE_C
197 | 100 to 7, //KeyEvent.KEYCODE_D
198 | 101 to 8, //KeyEvent.KEYCODE_E
199 | 102 to 9, //KeyEvent.KEYCODE_F
200 | 103 to 10,//KeyEvent.KEYCODE_G,
201 | 104 to 11,//KeyEvent.KEYCODE_H,
202 | 105 to 12,//KeyEvent.KEYCODE_I,
203 | 106 to 13,//KeyEvent.KEYCODE_J,
204 | 107 to 14,//KeyEvent.KEYCODE_K,
205 | 108 to 15,//KeyEvent.KEYCODE_L,
206 | 109 to 16,//KeyEvent.KEYCODE_M,
207 | 110 to 17,//KeyEvent.KEYCODE_N,
208 | 111 to 18,//KeyEvent.KEYCODE_O,
209 | 112 to 19,//KeyEvent.KEYCODE_P,
210 | 113 to 20,//KeyEvent.KEYCODE_Q,
211 | 114 to 21,//KeyEvent.KEYCODE_R,
212 | 115 to 22,//KeyEvent.KEYCODE_S,
213 | 116 to 23,//KeyEvent.KEYCODE_T,
214 | 117 to 24,//KeyEvent.KEYCODE_U,
215 | 118 to 25,//KeyEvent.KEYCODE_V,
216 | 119 to 26,//KeyEvent.KEYCODE_W,
217 | 120 to 27,//KeyEvent.KEYCODE_X,
218 | 121 to 28,//KeyEvent.KEYCODE_Y,
219 | 122 to 29,//KeyEvent.KEYCODE_Z,
220 | 49 to 30, // KeyEvent.KEYCODE_1
221 | 50 to 31, // KeyEvent.KEYCODE_2
222 | 51 to 32, // KeyEvent.KEYCODE_3
223 | 52 to 33, // KeyEvent.KEYCODE_4
224 | 53 to 34, // KeyEvent.KEYCODE_5
225 | 54 to 35, // KeyEvent.KEYCODE_6
226 | 55 to 36, // KeyEvent.KEYCODE_7
227 | 56 to 37, // KeyEvent.KEYCODE_8
228 | 57 to 38, // KeyEvent.KEYCODE_9
229 | 48 to 39, // KeyEvent.KEYCODE_0
230 | 9 to 43, // KeyEvent.KEYCODE_TAB
231 | 32 to 44 // KeyEvent.KEYCODE_SPACE
232 | )
233 |
234 |
235 |
236 | val UnicodeKeyEvent2ndMap = mapOf(
237 | 65 to 4, //KeyEvent.KEYCODE_A
238 | 66 to 5, //KeyEvent.KEYCODE_B
239 | 67 to 6, //KeyEvent.KEYCODE_C
240 | 68 to 7, //KeyEvent.KEYCODE_D
241 | 69 to 8, //KeyEvent.KEYCODE_E
242 | 70 to 9, //KeyEvent.KEYCODE_F
243 | 71 to 10,//KeyEvent.KEYCODE_G,
244 | 72 to 11,//KeyEvent.KEYCODE_H,
245 | 73 to 12,//KeyEvent.KEYCODE_I,
246 | 74 to 13,//KeyEvent.KEYCODE_J,
247 | 75 to 14,//KeyEvent.KEYCODE_K,
248 | 76 to 15,//KeyEvent.KEYCODE_L,
249 | 77 to 16,//KeyEvent.KEYCODE_M,
250 | 78 to 17,//KeyEvent.KEYCODE_N,
251 | 79 to 18,//KeyEvent.KEYCODE_O,
252 | 80 to 19,//KeyEvent.KEYCODE_P,
253 | 81 to 20,//KeyEvent.KEYCODE_Q,
254 | 82 to 21,//KeyEvent.KEYCODE_R,
255 | 83 to 22,//KeyEvent.KEYCODE_S,
256 | 84 to 23,//KeyEvent.KEYCODE_T,
257 | 85 to 24,//KeyEvent.KEYCODE_U,
258 | 86 to 25,//KeyEvent.KEYCODE_V,
259 | 87 to 26,//KeyEvent.KEYCODE_W,
260 | 88 to 27,//KeyEvent.KEYCODE_X,
261 | 89 to 28,//KeyEvent.KEYCODE_Y,
262 | 90 to 29//,//KeyEvent.KEYCODE_Z,
263 | // 49 to 30, // KeyEvent.KEYCODE_1
264 | // 50 to 31, // KeyEvent.KEYCODE_2
265 | // 51 to 32, // KeyEvent.KEYCODE_3
266 | // 52 to 33, // KeyEvent.KEYCODE_4
267 | // 53 to 34, // KeyEvent.KEYCODE_5
268 | // 54 to 35, // KeyEvent.KEYCODE_6
269 | // 55 to 36, // KeyEvent.KEYCODE_7
270 | // 56 to 37, // KeyEvent.KEYCODE_8
271 | // 57 to 38, // KeyEvent.KEYCODE_9
272 | // 48 to 39, // KeyEvent.KEYCODE_0
273 | // 9 to 43, // KeyEvent.KEYCODE_TAB
274 | // 32 to 44 // KeyEvent.KEYCODE_SPACE
275 | )
276 |
277 |
278 | // 89 0x59 Keypad 1 and End 93 √ √ √ 4/101/104
279 | // 83 0x53 Keypad Num Lock and Clear11
280 | val KeyCodeNumberPad: ByteArray = byteArrayOf(
281 | 98,
282 | 89, 90, 91,
283 | 92, 93, 94,
284 | 95, 96, 97,
285 | 83
286 | )
287 | }
288 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/github/roarappstudio/btkontroller/reports/MouseReport.kt:
--------------------------------------------------------------------------------
1 | package com.github.roarappstudio.btkontroller.reports
2 |
3 | import kotlin.experimental.and
4 | import kotlin.experimental.or
5 |
6 | @Suppress("EXPERIMENTAL_FEATURE_WARNING")
7 | inline class MouseReport(
8 | val bytes: ByteArray = ByteArray(4) {0}
9 | ) {
10 |
11 |
12 | var leftButton: Boolean
13 | get() = bytes[0] and 0b1 != 0.toByte()
14 | set(value) {
15 | bytes[0] = if (value)
16 | bytes[0] or 0b1
17 | else
18 | bytes[0] and 0b110
19 | }
20 |
21 | var rightButton: Boolean
22 | get() = bytes[0] and 0b10 != 0.toByte()
23 | set(value) {
24 | bytes[0] = if (value)
25 | bytes[0] or 0b10
26 | else
27 | bytes[0] and 0b101
28 | }
29 |
30 | var dx: Byte
31 | get() = bytes[1]
32 | set(value) { bytes[1] = value }
33 |
34 | var dy: Byte
35 | get() = bytes[2]
36 | set(value) { bytes[2] = value }
37 |
38 | fun reset() = bytes.fill(0)
39 |
40 | companion object {
41 | const val ID = 2
42 | }
43 |
44 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/github/roarappstudio/btkontroller/reports/RadialControllerInput.kt:
--------------------------------------------------------------------------------
1 | package com.github.roarappstudio.btkontroller.reports
2 | /*
3 | The host makes use of the following usages when extracting data from an input report:
4 |
5 | Member Description Page ID Mandatory/Optional
6 | Button State of the button located on radial controller 0x09 0x01 Mandatory
7 | Dial Relative rotation of the radial controller 0x01 0x37 Mandatory
8 | X X coordinate of contact position 0x01 0x30 Optional
9 | Y Y coordinate of contact position 0x01 0x31 Optional
10 | Width Width of bounding box around a contact 0x0D 0x48 Optional
11 | Height Height of bounding box around a contact 0x0D 0x49 Optional
12 |
13 | The class only has the Mandatory function
14 | */
15 | import android.util.Log
16 | import com.github.roarappstudio.btkontroller.senders.RadialSender
17 | import java.nio.ByteBuffer
18 | import kotlin.experimental.and
19 | import kotlin.experimental.or
20 |
21 | @Suppress("EXPERIMENTAL_FEATURE_WARNING")
22 | inline class RadialControllerInput(
23 | val bytes: ByteArray = ByteArray(6) { 0 }
24 | ) {
25 |
26 | //
27 | // var x: Int = 0x4000
28 | // var y: Int = 0x4000
29 | //
30 | // val maxX = 0x7fff
31 | // val maxY = 0x7fff
32 |
33 |
34 | var Button: Boolean
35 | get() = bytes[0] and 0b1 != 0.toByte()
36 | set(value) {
37 | bytes[0] = if (value)
38 | bytes[0] or 0b1
39 | else
40 | bytes[0] and 0b1111110
41 | }
42 |
43 |
44 | var Dial: Byte
45 | get() = (bytes[0].toInt() shr 1).toByte()
46 | set(value) {
47 | bytes[0] = ((value.toInt() shl 1).toByte() or (bytes[0] and 0b1))
48 | if (value > 0)
49 | bytes[1] = 0;
50 | else
51 | bytes[1] = 0xff.toByte();
52 | }
53 |
54 | /* 2byte数据转int
55 |
56 | {
57 | // 有符号
58 | fun convertTwoSignInt(byteArray: ByteArray): Int =
59 | (byteArray[1].toInt() shl 8) or (byteArray[0].toInt() and 0xFF)
60 |
61 | fun convertTwoUnSignInt(byteArray: ByteArray): Int =
62 | (byteArray[3].toInt() shl 24) or (byteArray[2].toInt() and 0xFF) or (byteArray[1].toInt() shl 8) or (byteArray[0].toInt() and 0xFF)
63 |
64 | // 无符号
65 | fun convertFourUnSignInt(byteArray: ByteArray): Int =
66 | (byteArray[1].toInt() and 0xFF) shl 8 or (byteArray[0].toInt() and 0xFF)
67 |
68 | fun convertFourUnSignLong(byteArray: ByteArray): Long =
69 | ((byteArray[3].toInt() and 0xFF) shl 24 or (byteArray[2].toInt() and 0xFF) shl 16 or (byteArray[1].toInt() and 0xFF) shl 8 or (byteArray[0].toInt() and 0xFF)).toLong()
70 |
71 | }
72 |
73 | */
74 | fun conver_2byte_int(byteArray: ByteArray): Int =
75 | (byteArray[1].toInt() shl 8) or (byteArray[0].toInt() and 0xFF)
76 |
77 | fun conver_int_2byte(value: Int): ByteArray {
78 | var byte4 = ByteBuffer.allocate(4).putInt(value).array();
79 | var bytes: ByteArray = ByteArray(2) { 0 }
80 | bytes[0] = byte4[0]
81 | bytes[1] = byte4[1]
82 | return bytes
83 | }
84 |
85 |
86 | private fun calPosition(x: Int, dx: Int, max: Int): Int {
87 | var y = x + dx;
88 | if (y < 0)
89 | y = 0
90 | if (y > max)
91 | y = max
92 | return y
93 | }
94 |
95 |
96 |
97 | var dx: Byte
98 | get() = bytes[1]
99 | set(value) {
100 | ByteBuffer.allocate(4).putInt(value.toInt()).array();
101 | bytes[1] = value
102 | }
103 |
104 | var dy: Byte
105 | get() = bytes[2]
106 | set(value) {
107 | bytes[2] = value
108 | }
109 |
110 | fun reset() = bytes.fill(0)
111 |
112 | companion object {
113 | const val ID = 1
114 | }
115 |
116 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/github/roarappstudio/btkontroller/reports/RadialControllerOutput.kt:
--------------------------------------------------------------------------------
1 | package com.github.roarappstudio.btkontroller.reports
2 | /*
3 | Haptic feedback collection (optional)
4 | If a Windows radial controller device supports haptic feedback, it can allow the system and applications to take advantage of it by including a haptic feedback collection (Page 0x0E, Usage 0x01) within the Windows radial controller TLC. For more information on how the HID specification supports haptic feedback, please consult the Haptics page ratification to the HID specification.
5 |
6 | The host uses the following usages in an output report (through the haptic feedback collection) to allow the host to issue haptic feedback events to the Windows radial controller device. If a device chooses to expose a haptic feedback collection, some usages are mandatory to allow host-initiated haptic feedback to be supported.
7 |
8 | Member Description Page ID Mandatory/Optional
9 | Manual Trigger Waveform to fire as explicit command from the host. 0x0E 0x21 Mandatory
10 | Intensity Output – Intensity of Manual Trigger waveform as a percentage 0x0E 0x23 Optional
11 | Repeat Count Output – Number of times to play Manual Trigger waveform after initial play 0x0E 0x24 Optional
12 | Retrigger Period Output – Duration of time to wait before retriggering Manual Trigger when repeating
13 |
14 |
15 | The class only has the Mandatory function
16 | */
17 | class RadialControllerOutput {
18 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/github/roarappstudio/btkontroller/reports/ScrollableTrackpadMouseReport.kt:
--------------------------------------------------------------------------------
1 | package com.github.roarappstudio.btkontroller.reports
2 |
3 | import kotlin.experimental.and
4 | import kotlin.experimental.or
5 |
6 | @Suppress("EXPERIMENTAL_FEATURE_WARNING")
7 | inline class ScrollableTrackpadMouseReport (
8 | val bytes: ByteArray = ByteArray(7) {0}
9 | ) {
10 |
11 |
12 | var leftButton: Boolean
13 | get() = bytes[0] and 0b1 != 0.toByte()
14 | set(value) {
15 | bytes[0] = if (value)
16 | bytes[0] or 0b1
17 | else
18 | bytes[0] and 0b110
19 | }
20 |
21 | var rightButton: Boolean
22 | get() = bytes[0] and 0b10 != 0.toByte()
23 | set(value) {
24 | bytes[0] = if (value)
25 | bytes[0] or 0b10
26 | else
27 | bytes[0] and 0b101
28 | }
29 |
30 | var dxLsb: Byte
31 | get() = bytes[1]
32 | set(value) { bytes[1] = value }
33 |
34 | var dxMsb: Byte
35 | get() = bytes[2]
36 | set(value) { bytes[2] = value }
37 |
38 |
39 | var dyLsb: Byte
40 | get() = bytes[3]
41 | set(value) { bytes[3] = value }
42 |
43 | var dyMsb: Byte
44 | get() = bytes[4]
45 | set(value) { bytes[4] = value }
46 |
47 | var vScroll : Byte
48 | get() = bytes[5]
49 | set(value) {
50 | bytes[5]=value
51 | }
52 |
53 | var hScroll : Byte
54 | get() = bytes[6]
55 | set(value) {
56 | bytes[6]=value
57 | }
58 |
59 |
60 |
61 | fun reset() = bytes.fill(0)
62 |
63 | companion object {
64 | const val ID = 4
65 | }
66 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/github/roarappstudio/btkontroller/reports/TestTrackpadMouseReport.kt:
--------------------------------------------------------------------------------
1 | package com.github.roarappstudio.btkontroller.reports
2 |
3 | import kotlin.experimental.and
4 | import kotlin.experimental.or
5 |
6 | @Suppress("EXPERIMENTAL_FEATURE_WARNING")
7 | inline class TestTrackpadMouseReport (
8 | val bytes: ByteArray = ByteArray(3) {0}
9 | ) {
10 |
11 |
12 | var leftButton: Boolean
13 | get() = bytes[0] and 0b1 != 0.toByte()
14 | set(value) {
15 | bytes[0] = if (value)
16 | bytes[0] or 0b1
17 | else
18 | bytes[0] and 0b110
19 | }
20 |
21 | var rightButton: Boolean
22 | get() = bytes[0] and 0b10 != 0.toByte()
23 | set(value) {
24 | bytes[0] = if (value)
25 | bytes[0] or 0b10
26 | else
27 | bytes[0] and 0b101
28 | }
29 |
30 | var dx: Byte
31 | get() = bytes[1]
32 | set(value) { bytes[1] = value }
33 |
34 |
35 | var dy: Byte
36 | get() = bytes[2]
37 | set(value) { bytes[2] = value }
38 |
39 |
40 | fun reset() = bytes.fill(0)
41 |
42 | companion object {
43 | const val ID = 4
44 | }
45 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/github/roarappstudio/btkontroller/reports/TrackpadMouseReport.kt:
--------------------------------------------------------------------------------
1 | package com.github.roarappstudio.btkontroller.reports
2 |
3 | import kotlin.experimental.and
4 | import kotlin.experimental.or
5 |
6 | @Suppress("EXPERIMENTAL_FEATURE_WARNING")
7 | inline class TrackpadMouseReport (
8 | val bytes: ByteArray = ByteArray(5) {0}
9 | ) {
10 |
11 |
12 | var leftButton: Boolean
13 | get() = bytes[0] and 0b1 != 0.toByte()
14 | set(value) {
15 | bytes[0] = if (value)
16 | bytes[0] or 0b1
17 | else
18 | bytes[0] and 0b110
19 | }
20 |
21 | var rightButton: Boolean
22 | get() = bytes[0] and 0b10 != 0.toByte()
23 | set(value) {
24 | bytes[0] = if (value)
25 | bytes[0] or 0b10
26 | else
27 | bytes[0] and 0b101
28 | }
29 |
30 | var dxLsb: Byte
31 | get() = bytes[1]
32 | set(value) { bytes[1] = value }
33 |
34 | var dxMsb: Byte
35 | get() = bytes[2]
36 | set(value) { bytes[2] = value }
37 |
38 |
39 | var dyLsb: Byte
40 | get() = bytes[3]
41 | set(value) { bytes[3] = value }
42 |
43 | var dyMsb: Byte
44 | get() = bytes[4]
45 | set(value) { bytes[4] = value }
46 |
47 | fun reset() = bytes.fill(0)
48 |
49 | companion object {
50 | const val ID = 4
51 | }
52 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/github/roarappstudio/btkontroller/senders/RadialSender.kt:
--------------------------------------------------------------------------------
1 | package com.github.roarappstudio.btkontroller.senders
2 |
3 |
4 |
5 | import android.bluetooth.BluetoothDevice
6 | import android.bluetooth.BluetoothHidDevice
7 | import android.util.Log
8 | import android.view.KeyEvent
9 | import com.github.roarappstudio.btkontroller.reports.KeyboardReport
10 | import com.github.roarappstudio.btkontroller.reports.RadialControllerInput
11 |
12 | @Suppress("MemberVisibilityCanBePrivate")
13 | open class RadialSender(
14 | val hidDevice: BluetoothHidDevice,
15 | val host: BluetoothDevice
16 |
17 | ) {
18 | var radialControllerInput = RadialControllerInput()
19 | var x:Int =0x4000
20 | var y:Int=0x4000
21 |
22 | val maxX=0x7fff
23 | val maxY=0x7fff
24 |
25 |
26 | protected open fun sendKeys() {
27 | if (!hidDevice.sendReport(host, RadialControllerInput.ID, radialControllerInput.bytes)) {
28 | Log.e(TAG, "Report wasn't sent")
29 | }
30 | }
31 |
32 | private fun calPosition(x:Int,dx:Int,max:Int):Int{
33 | var y=x+dx;
34 | if(y<0)
35 | y=0
36 | if(y>max)
37 | y=max
38 | return y
39 | }
40 |
41 | open fun moveX(dx:Int){
42 | x=calPosition(x,dx,maxX)
43 | }
44 |
45 | open fun moveY(dy:Int){
46 | y=calPosition(y,dy,maxY)
47 | }
48 |
49 | open fun setModifiers(state:Boolean) {
50 | radialControllerInput.Button=state
51 | sendKeys()
52 | }
53 |
54 | companion object {
55 | const val TAG = "RadialSender"
56 | }
57 |
58 |
59 |
60 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/github/roarappstudio/btkontroller/senders/RadialSender2.kt:
--------------------------------------------------------------------------------
1 | package com.github.roarappstudio.btkontroller.senders
2 |
3 | // 用于连接测试,简化了事件。与轮子不同,不需要reports类即可发送报文
4 | // RadialSender3只包含button和dial的4个事件
5 | // RadialSender2比RadialSender3优化了报文。
6 | // 事实上,dial只有短按/长按/滚动三种操作,不存在下压的同时旋转的动作
7 |
8 | import android.bluetooth.BluetoothDevice
9 | import android.bluetooth.BluetoothHidDevice
10 | import android.util.Log
11 | import android.view.HapticFeedbackConstants
12 | import java.nio.ByteBuffer
13 |
14 | @Suppress("MemberVisibilityCanBePrivate")
15 | open class RadialSender2(
16 | val hidDevice: BluetoothHidDevice,
17 | val host: BluetoothDevice
18 |
19 | ) {
20 | val ID = 1
21 | var angel_0 = 0
22 | var byte_l = byteArrayOfInts(0x38, 0x00)
23 | var byte_r = byteArrayOfInts(0xc8, 0xff)
24 | var byte_press = byteArrayOfInts(1, 0)
25 | var byte_release = byteArrayOfInts(0, 0)
26 |
27 | fun byteArrayOfInts(vararg ints: Int) = ByteArray(ints.size) { pos -> ints[pos].toByte() }
28 | /*
29 |
30 | private fun sendReport(bytes: ByteArray, key: String) {
31 | // Log.i(TAG,"hid="+hidDevice+", host="+host)
32 | Log.i(TAG, "Report sent=" + hidDevice.sendReport(host, ID, byte_press) + ",key=" + key)
33 | }
34 |
35 |
36 | open fun sendDial(angel: Int) {
37 | // 输入当前角度。如果有角度差,则发送dial旋转事件
38 | // 默认已经正则化,一周3600count.如果差异大于半周
39 | var key = angel - angel_0
40 | if (key == 0)
41 | return
42 | if (Math.abs(key) > 1800) {
43 | if (angel > angel_0)
44 | key += -3600
45 | else
46 | key += 3600
47 | }
48 | angel_0 = angel
49 |
50 | // 参数key是前一次报文和当前报文的角度差,故有正负。处理了旋转方向的问题
51 | // key*2 进行作移1bit
52 | var byte4 = ByteBuffer.allocate(4).putInt(key * 2).array();
53 | var bytes: ByteArray = ByteArray(2) { 0 }
54 | bytes[0] = byte4[3]
55 | bytes[1] = byte4[2]
56 | // 事实上,dial只有短按/长按/滚动三种操作,不存在下压的同时旋转的动作
57 | // if (button) {
58 | // bytes[0] = bytes[0] or 1.toByte()
59 | // }
60 | sendReport(
61 | bytes,
62 | "dial count " + key + ", bytes=" + bytes[0] + ":" + bytes[1] + ", byte4=" + byte4[0] + ":" + byte4[1] + ":" + byte4[2] + ":" + byte4[3]
63 | )
64 | }
65 |
66 |
67 | open fun sendKeys(key: Int) {
68 | // 发送button按键事件
69 | if (key == 1)
70 | sendReport(byte_press, key.toString())
71 | else if (key == 2)
72 | sendReport(byte_release, key.toString())
73 | else if (key == 3)
74 | sendReport(byte_l, key.toString())
75 | else if (key == 4)
76 | sendReport(byte_r, key.toString())
77 | else
78 | Log.e(TAG, "error key=" + key)
79 | }
80 |
81 | */
82 |
83 | open fun sendKeys(key: Int) {
84 | if (key == 1)
85 | Log.i(TAG, "Report sent=" + hidDevice.sendReport(host, ID, byte_press) + ",key=" + key)
86 | else if (key == 2)
87 | Log.i(TAG, "Report sent=" + hidDevice.sendReport(host, ID, byte_release) + ",key=" + key)
88 | else if (key == 3)
89 | Log.w(TAG, "Report sent=" + hidDevice.sendReport(host, ID, byte_l) + ",key=" + key)
90 | else if (key == 4)
91 | Log.w(TAG, "Report sent=" + hidDevice.sendReport(host, ID, byte_r) + ",key=" + key)
92 | else
93 | Log.e(TAG, "error key=" + key)
94 | }
95 |
96 | private var dial_report_last_time=0
97 | private var dial_report_comb_count=0
98 |
99 | open fun sendDial(angel: Int) :Boolean{
100 | // 默认已经正则化,一周3600count.如果差异大于半周
101 | // 如果发送`发送了按键事件,返回true。如果操作被过滤了,返回false
102 | var key = angel - angel_0
103 |
104 | if (Math.abs(key) > 1800) {
105 | if (angel > angel_0)
106 | key += -3600
107 | else
108 | key += 3600
109 | }
110 |
111 | if (Math.abs(key) > 900) {
112 | // 旋转角度大于90,跳帧
113 | Log.e(TAG, "skip. angel=" + angel+", angel_0="+angel_0)
114 | angel_0 = angel
115 | return false
116 | }
117 |
118 |
119 | // 衰减系数k。每k个count发送1次事件,从而令旋转操作变慢
120 | val k=2
121 | if(key*key=0){
154 | if(dial_report_comb_count>=comb){
155 | dial_report_comb_count=dial_report_comb_count%comb
156 | return true
157 | }
158 | }else if(dial_report_comb_count<=-comb){
159 | dial_report_comb_count=dial_report_comb_count%comb
160 | return true
161 | }
162 | }
163 | // if(System.currentTimeMillis()-dial_report_last_time>3000)
164 | return false
165 | }
166 |
167 |
168 |
169 | companion object {
170 | const val TAG = "RadialSender2"
171 | }
172 |
173 |
174 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/github/roarappstudio/btkontroller/senders/RadialSender3.kt:
--------------------------------------------------------------------------------
1 | package com.github.roarappstudio.btkontroller.senders
2 |
3 | // 用于连接测试,简化了事件。与轮子不同,不需要reports类即可发送报文
4 | // RadialSender3只包含button和dial的4个事件
5 | // 欠缺之处是,button按下需要手动恢复,并且不能产生下压的同时旋转的动作
6 |
7 | import android.bluetooth.BluetoothDevice
8 | import android.bluetooth.BluetoothHidDevice
9 | import android.util.Log
10 | import android.view.KeyEvent
11 | import android.widget.Switch
12 | import com.github.roarappstudio.btkontroller.reports.KeyboardReport
13 | import com.github.roarappstudio.btkontroller.reports.RadialControllerInput
14 |
15 | @Suppress("MemberVisibilityCanBePrivate")
16 | open class RadialSender3(
17 | val hidDevice: BluetoothHidDevice,
18 | val host: BluetoothDevice
19 |
20 | ) {
21 | val ID=1
22 | // val byte_press: ByteArray = ByteArray(2) {0}
23 |
24 | var byte_l = byteArrayOfInts(0x38,0x00)
25 | var byte_r = byteArrayOfInts(0xc8,0xff)
26 | var byte_press=byteArrayOfInts(1,0)
27 | var byte_release=byteArrayOfInts(0,0)
28 |
29 | fun byteArrayOfInts(vararg ints: Int) = ByteArray(ints.size) { pos -> ints[pos].toByte() }
30 |
31 | val radialControllerInput = RadialControllerInput()
32 |
33 | open fun sendKeys(key:Int) {
34 | var bytes:ByteArray=byteArrayOfInts(0,0)
35 | if(key==1)
36 | bytes=byte_l
37 | else if(key==2)
38 | bytes=byte_r
39 | else if(key==3)
40 | bytes=byte_press
41 | else if(key==4)
42 | bytes=byte_release
43 | else
44 | Log.e(TAG, "error key="+key)
45 |
46 | Log.w(TAG, "Report sent="+hidDevice.sendReport(host, ID,bytes)+",key="+key)
47 |
48 | }
49 | companion object {
50 | const val TAG = "RadialSender2"
51 | }
52 |
53 |
54 |
55 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/github/roarappstudio/btkontroller/senders/RelativeMouseSender.kt:
--------------------------------------------------------------------------------
1 | package com.github.roarappstudio.btkontroller.senders
2 |
3 | import android.bluetooth.BluetoothDevice
4 | import android.bluetooth.BluetoothHidDevice
5 | import android.util.Log
6 | import com.github.roarappstudio.btkontroller.reports.ScrollableTrackpadMouseReport
7 | import java.util.*
8 | import kotlin.concurrent.schedule
9 |
10 | @Suppress("MemberVisibilityCanBePrivate")
11 | open class RelativeMouseSender(
12 | val hidDevice: BluetoothHidDevice,
13 | val host: BluetoothDevice
14 |
15 | ) {
16 | val mouseReport = ScrollableTrackpadMouseReport()
17 | var previousvscroll :Int=0
18 | var previoushscroll :Int =0
19 |
20 |
21 | protected open fun sendMouse() {
22 | if (!hidDevice.sendReport(host, ScrollableTrackpadMouseReport.ID, mouseReport.bytes)) {
23 | Log.e(TAG, "Report wasn't sent")
24 | }
25 | }
26 |
27 | fun sendTestMouseMove() {
28 | mouseReport.dxLsb = 20
29 | mouseReport.dyLsb = 20
30 | mouseReport.dxMsb = 20
31 | mouseReport.dyMsb = 20
32 | sendMouse()
33 | }
34 |
35 | fun sendTestClick() {
36 | mouseReport.leftButton = true
37 | sendMouse()
38 | mouseReport.leftButton = false
39 | sendMouse()
40 | // Timer().schedule(20L) {
41 | //
42 | // }
43 | }
44 | fun sendDoubleTapClick() {
45 | mouseReport.leftButton = true
46 | sendMouse()
47 | Timer().schedule(100L) {
48 | mouseReport.leftButton = false
49 | sendMouse()
50 | Timer().schedule(100L) {
51 | mouseReport.leftButton = true
52 | sendMouse()
53 | Timer().schedule(100L) {
54 | mouseReport.leftButton = false
55 | sendMouse()
56 | }
57 |
58 |
59 |
60 |
61 | }
62 | }
63 | }
64 |
65 |
66 |
67 | fun sendLeftClickOn() {
68 | mouseReport.leftButton = true
69 | sendMouse()
70 |
71 |
72 | }
73 | fun sendLeftClickOff() {
74 | mouseReport.leftButton = false
75 | sendMouse()
76 |
77 | }
78 | fun sendRightClick() {
79 | mouseReport.rightButton = true
80 | sendMouse()
81 | Timer().schedule(50L) {
82 | mouseReport.rightButton= false
83 | sendMouse()
84 | }
85 | }
86 |
87 | fun sendScroll(vscroll:Int,hscroll:Int){
88 |
89 | var hscrollmutable=0
90 | var vscrollmutable =0
91 |
92 | hscrollmutable=hscroll
93 | vscrollmutable= vscroll
94 |
95 | // var dhscroll= hscrollmutable-previoushscroll
96 | // var dvscroll= vscrollmutable-previousvscroll
97 | //
98 | // dhscroll = Math.abs(dhscroll)
99 | // dvscroll = Math.abs(dvscroll)
100 | // if(dvscroll>=dhscroll)
101 | // {
102 | // hscrollmutable=0
103 | //
104 | // }
105 | // else
106 | // {
107 | // vscrollmutable=0
108 | // }
109 | var vs:Int =(vscrollmutable)
110 | var hs:Int =(hscrollmutable)
111 | Log.i("vscroll ",vscroll.toString())
112 | Log.i("vs ",vs.toString())
113 | Log.i("hscroll ",hscroll.toString())
114 | Log.i("hs ",hs.toString())
115 |
116 |
117 | mouseReport.vScroll=vs.toByte()
118 | mouseReport.hScroll= hs.toByte()
119 |
120 | sendMouse()
121 |
122 | // previousvscroll=-1*vscroll
123 | // previoushscroll=hscroll
124 |
125 |
126 | }
127 |
128 |
129 |
130 |
131 | companion object {
132 | const val TAG = "TrackPadSender"
133 | }
134 |
135 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/github/roarappstudio/btkontroller/senders/Sender.kt:
--------------------------------------------------------------------------------
1 | package com.github.roarappstudio.btkontroller.senders
2 |
3 | import android.bluetooth.BluetoothDevice
4 | import android.bluetooth.BluetoothHidDevice
5 | import android.util.Log
6 | import com.github.roarappstudio.btkontroller.reports.MouseReport
7 | import java.util.*
8 | import kotlin.concurrent.schedule
9 |
10 | @Suppress("MemberVisibilityCanBePrivate")
11 | open class Sender(
12 | val hidDevice: BluetoothHidDevice,
13 | val host: BluetoothDevice
14 | ) {
15 | protected val mouseReport = MouseReport()
16 |
17 | protected open fun sendMouse() {
18 | if (!hidDevice.sendReport(host, MouseReport.ID, mouseReport.bytes)) {
19 | Log.e(TAG, "Report wasn't sent")
20 | }
21 | }
22 |
23 | fun sendTestMouseMove() {
24 | mouseReport.dx = 20
25 | mouseReport.dy = 20
26 | sendMouse()
27 | }
28 |
29 | fun sendTestClick() {
30 | mouseReport.leftButton = true
31 | sendMouse()
32 | Timer().schedule(150L) {
33 | mouseReport.leftButton = false
34 | sendMouse()
35 | }
36 | }
37 |
38 | companion object {
39 | const val TAG = "Sender"
40 | }
41 |
42 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/github/roarappstudio/btkontroller/senders/SensorSender.kt:
--------------------------------------------------------------------------------
1 | package com.github.roarappstudio.btkontroller.senders
2 |
3 | import android.bluetooth.BluetoothDevice
4 | import android.bluetooth.BluetoothHidDevice
5 | import android.hardware.Sensor
6 | import android.hardware.SensorEvent
7 | import android.hardware.SensorEventListener
8 | import android.hardware.SensorManager
9 | import android.util.Log
10 | import com.github.roarappstudio.btkontroller.reports.AbsMouseReport
11 | import kotlin.math.PI
12 | import kotlin.math.roundToInt
13 |
14 | @ExperimentalUnsignedTypes
15 | class SensorSender(hidDevice: BluetoothHidDevice, host: BluetoothDevice): Sender(hidDevice, host), SensorEventListener {
16 |
17 | val absMouseReport = AbsMouseReport()
18 |
19 |
20 | override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) {
21 | Log.d(TAG, "Accuracy changed ${when(accuracy) {
22 | SensorManager.SENSOR_STATUS_ACCURACY_LOW -> "LOW"
23 | SensorManager.SENSOR_STATUS_ACCURACY_MEDIUM -> "MEDIUM"
24 | SensorManager.SENSOR_STATUS_ACCURACY_HIGH -> "HIGH"
25 | else -> accuracy.toString()
26 | }}")
27 | }
28 |
29 | override fun onSensorChanged(event: SensorEvent) {
30 | val angleX = event.values[2].toDouble()
31 | val angleY = event.values[0].toDouble()
32 | val pixelsX = (angleX * 3840 / PI).roundToInt() + 1920
33 | val pixelsY = (angleY * 2160 / PI).roundToInt() + 1080
34 | Log.wtf("WTF", "$pixelsX x $pixelsY")
35 | if (pixelsX != absMouseReport.X || pixelsY != absMouseReport.Y) {
36 | absMouseReport.X = pixelsX
37 | absMouseReport.Y = pixelsY
38 | hidDevice.sendReport(this.host, 2, absMouseReport.bytes)
39 |
40 | } else {
41 | Log.i("WTF", "No changes")
42 | }
43 | }
44 |
45 | override fun sendMouse() {
46 |
47 | }
48 |
49 | }
--------------------------------------------------------------------------------
/app/src/main/res/drawable-anydpi/ic_action_app_connected.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-anydpi/ic_action_app_not_connected.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-anydpi/ic_action_keyboard.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_action_app_connected.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tumuyan/Kontroller/e7a29024f59842e55474a0be1dc2c95fea1c3e09/app/src/main/res/drawable-hdpi/ic_action_app_connected.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_action_app_not_connected.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tumuyan/Kontroller/e7a29024f59842e55474a0be1dc2c95fea1c3e09/app/src/main/res/drawable-hdpi/ic_action_app_not_connected.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_action_keyboard.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tumuyan/Kontroller/e7a29024f59842e55474a0be1dc2c95fea1c3e09/app/src/main/res/drawable-hdpi/ic_action_keyboard.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/ic_action_app_connected.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tumuyan/Kontroller/e7a29024f59842e55474a0be1dc2c95fea1c3e09/app/src/main/res/drawable-mdpi/ic_action_app_connected.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/ic_action_app_not_connected.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tumuyan/Kontroller/e7a29024f59842e55474a0be1dc2c95fea1c3e09/app/src/main/res/drawable-mdpi/ic_action_app_not_connected.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/ic_action_keyboard.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tumuyan/Kontroller/e7a29024f59842e55474a0be1dc2c95fea1c3e09/app/src/main/res/drawable-mdpi/ic_action_keyboard.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/ripple_item_no.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
12 |
13 |
19 |
22 |
25 |
26 |
27 |
28 |
34 |
35 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_action_app_connected.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tumuyan/Kontroller/e7a29024f59842e55474a0be1dc2c95fea1c3e09/app/src/main/res/drawable-xhdpi/ic_action_app_connected.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_action_app_not_connected.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tumuyan/Kontroller/e7a29024f59842e55474a0be1dc2c95fea1c3e09/app/src/main/res/drawable-xhdpi/ic_action_app_not_connected.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_action_keyboard.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tumuyan/Kontroller/e7a29024f59842e55474a0be1dc2c95fea1c3e09/app/src/main/res/drawable-xhdpi/ic_action_keyboard.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_action_app_connected.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tumuyan/Kontroller/e7a29024f59842e55474a0be1dc2c95fea1c3e09/app/src/main/res/drawable-xxhdpi/ic_action_app_connected.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_action_app_not_connected.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tumuyan/Kontroller/e7a29024f59842e55474a0be1dc2c95fea1c3e09/app/src/main/res/drawable-xxhdpi/ic_action_app_not_connected.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_action_keyboard.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tumuyan/Kontroller/e7a29024f59842e55474a0be1dc2c95fea1c3e09/app/src/main/res/drawable-xxhdpi/ic_action_keyboard.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/btn_black.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | -
4 |
5 |
7 |
10 |
12 |
17 |
18 |
19 |
20 | -
21 |
22 |
26 |
29 |
31 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/code_logo_0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tumuyan/Kontroller/e7a29024f59842e55474a0be1dc2c95fea1c3e09/app/src/main/res/drawable/code_logo_0.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/code_logo_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tumuyan/Kontroller/e7a29024f59842e55474a0be1dc2c95fea1c3e09/app/src/main/res/drawable/code_logo_1.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/code_logo_2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tumuyan/Kontroller/e7a29024f59842e55474a0be1dc2c95fea1c3e09/app/src/main/res/drawable/code_logo_2.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_icon_ansi.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
9 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_icon_hid.xml:
--------------------------------------------------------------------------------
1 |
6 |
12 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_icon_unicode.xml:
--------------------------------------------------------------------------------
1 |
6 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
10 |
12 |
14 |
16 |
18 |
20 |
22 |
24 |
26 |
28 |
30 |
32 |
34 |
36 |
38 |
40 |
42 |
44 |
46 |
48 |
50 |
52 |
54 |
56 |
58 |
60 |
62 |
64 |
66 |
68 |
70 |
72 |
74 |
75 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_background_ble.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
11 |
12 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_foreground_ble.xml:
--------------------------------------------------------------------------------
1 |
6 |
8 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/surface_dial.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tumuyan/Kontroller/e7a29024f59842e55474a0be1dc2c95fea1c3e09/app/src/main/res/drawable/surface_dial.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/view_border.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/layout-land/layout.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
10 |
16 |
17 |
30 |
31 |
38 |
39 |
40 |
46 |
47 |
52 |
53 |
58 |
59 |
64 |
65 |
70 |
71 |
76 |
77 |
82 |
83 |
88 |
89 |
94 |
95 |
96 |
97 |
107 |
108 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/keyboard_num.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
13 |
14 |
22 |
23 |
24 |
25 |
29 |
30 |
37 |
38 |
45 |
46 |
53 |
54 |
61 |
62 |
63 |
67 |
68 |
75 |
76 |
83 |
84 |
91 |
92 |
99 |
100 |
101 |
106 |
107 |
114 |
115 |
122 |
123 |
130 |
131 |
138 |
139 |
140 |
145 |
146 |
153 |
154 |
161 |
162 |
170 |
171 |
180 |
181 |
182 |
183 |
188 |
189 |
206 |
207 |
223 |
224 |
242 |
243 |
244 |
245 |
246 |
247 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/keyboard_num2.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
12 |
13 |
16 |
17 |
26 |
27 |
36 |
37 |
46 |
47 |
56 |
57 |
58 |
61 |
62 |
70 |
71 |
79 |
80 |
88 |
89 |
97 |
98 |
99 |
102 |
103 |
111 |
112 |
120 |
121 |
129 |
130 |
138 |
139 |
140 |
143 |
144 |
152 |
153 |
161 |
162 |
170 |
171 |
179 |
180 |
181 |
184 |
185 |
193 |
194 |
202 |
203 |
211 |
212 |
222 |
223 |
224 |
227 |
228 |
236 |
237 |
245 |
246 |
254 |
255 |
256 |
257 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/layout.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
10 |
15 |
16 |
29 |
30 |
37 |
38 |
39 |
44 |
45 |
50 |
51 |
56 |
57 |
62 |
63 |
68 |
69 |
74 |
75 |
80 |
81 |
86 |
87 |
92 |
93 |
94 |
95 |
105 |
106 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/select_device_activity_menu.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
14 |
21 |
22 |
23 |
29 |
30 |
39 |
40 |
46 |
47 |
48 |
49 |
50 |
51 |
55 |
56 |
61 |
66 |
67 |
72 |
73 |
78 |
79 |
83 |
89 |
90 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher_ble.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher_ble_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/values-zh/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | AB Dial
4 | 设置
5 | 键盘开关
6 | Ctrl/Alt/Win按键状态保持
7 | 断开链接
8 | (N)
9 | 自动配对
10 | autopairfl
11 | 屏幕常亮
12 | 应用没有通过蓝牙连接设备
13 | screenonfl
14 | ⇩
15 | ⌨
16 | (句)
17 | 隐藏键盘
18 | 手机键盘相当于蓝牙键盘
19 | 在文本框内输入后,一次性发送框内全部内容
20 |
21 | 实时发送键盘上的字符
22 | 以你选择的编码发送文本
23 | 以Unicode编码发送文本
24 |
25 | 发送文本
26 | 释放按键
27 | 选择编码
28 |
29 | 转盘振动间隔时间
30 | 设置触发旋转触振动的最小间隔时间(ms). 数值设置过低会造成马达持续转动甚至完全不转动.合理的数值在80-250之间. 默认值为0,表示旋转时不振动。
31 |
32 | 转盘振动时间
33 | 设置每次触发振动的振动持续时间. 目前这个参数不对应具体时间,提供的选项是系统提供的触觉反馈的模式。
34 |
35 | 转盘振动衰减
36 | 每转动多少个count触发一次振动. 当前定义1count=0.2度. 默认值30
37 |
38 | 振动开关
39 | 跟随系统设置
40 | 强制振动
41 | 关闭振动
42 | 触感设置
43 | 显示文本框
44 |
45 | 文本设置
46 | 字符延时(ms)
47 | APP发送字符串的本质是按下Alt+数字键,从而直接发送Unicode或者其他编码的字符到设备上。
48 | 经测试验证,按键操作速度太高会造成发送错误(并且目前还不能侦测到何时发生了错误),而在每次按按键之间设置的延时可以提高稳定性。默认延时13
49 |
50 | 换行转换
51 | Unix系统里,每行结尾只有“\\n”;Windows系统里面,每行结尾是“\\r\\n”;Mac系统里,每行结尾是“\\r”。
52 | Android属于Unix系统,输入的回车只生成了“\\n”。如果链接设备的系统并非Unix,没有对换行进行转换,并且没有打开快速模式,手机字符串中的换行在设备上会变成空格。
53 |
54 |
55 | 快速模式
56 | 发送字符串时,对标准键盘上的数字/英语字母/符号不进行编码转换,从而提高发送速度。如果打开快速模式,请务必确认设备的输入法切换到了英语键盘模式。
57 |
58 | 编码说明
59 | AB dial的键盘事件有三种,在APP顶部分别用HID/UI/ANSI表示。
60 | \nHID表示把键盘上的按键实时发送给设备。
61 | \nUI表示内容先输入到文本框内,点发送按钮时APP再把文本框内的字符串以Unicode发送到设备。
62 | \nANSI表示内容先输入到文本框内,点发送按钮时APP再把文本框内的字符串以你选择的编码发送到设备。
63 | \n如果选择了错误的编码,发送字符串时会形成乱码。在Windows系统中,不太老的软件一般支持Unicode编码。
64 | 如果输入Unicode失败,可以选择字符集GBK(中文简体)或者BIG5(中文繁体)并把图标点到ANSI上。
65 |
66 |
67 | 焕彩圆环
68 | Windows转盘设备由两个部分构成:可以按压的按钮(在AB dail中就是屏幕中央圆环内的可点击区域),可以旋转的外壳(在AB dial中就是圆环外的可滑动区域)。
69 | 开启“焕彩圆环”后,没有连接蓝牙时圆环为灰色,蓝牙连接成功变为绿色,此后每次触发按钮或者旋转动作,圆环的色彩都会变化,构成视觉反馈。
70 |
71 |
72 | 界面设置
73 |
74 | m
75 | \nu
76 | \nl
77 | \nt
78 | \n
79 | \nl
80 | \ni
81 | \nn
82 | \ne
83 | \n
84 | \ntext
85 |
--------------------------------------------------------------------------------
/app/src/main/res/values/attrs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | #F00
5 | #0000
6 | #000
7 | #3378FF
8 |
9 | #000
10 |
--------------------------------------------------------------------------------
/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 124dp
5 | 28dp
6 | 60dp
7 | 54dp
8 | 20dp
9 | 8dp
10 |
11 |
12 | 4dp
13 | 240dp
14 | 6dp
15 | 14dp
16 |
--------------------------------------------------------------------------------
/app/src/main/res/values/ids.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | AB Dial
3 | Settings
4 | Keyboard Toggle
5 | (N)
6 | Toggle to switch(Ctrl/Alt/Wn Keys) from click_each_time(Normal) state to key_pressed(Hold) state
7 | Disconnect
8 | Enable AutoPair
9 | autopairfl
10 | Screen Always On
11 | App not connected via bluetooth
12 |
13 | screenonfl
14 |
15 | ⇩
16 | ⌨
17 | (W)
18 | Hidden keyboard
19 | Your Android drive works like a bluetooth keyboard
20 | You can edit text first,finally click the send button
21 |
22 | Send keycode directly
23 | Send words with selected charset
24 | Send words with Unicode encode
25 |
26 | Send
27 | Release
28 | Select Charset
29 |
30 | Time between twice wheel haptic
31 | Setting the minimum time between twice wheel haptic(ms). setting=0,the wheel will not provide haptic
32 |
33 | wheel haptic time
34 | Set the mode of wheel haptic (provide by the operate system)
35 |
36 | wheel haptic skip count
37 | how many count could send once haptic event. 1count=0.2degree
38 |
39 | haptic mode
40 | Use system setting
41 | always
42 | never
43 |
44 | Haptic Setting
45 | Show EditText
46 |
47 | String Setting
48 | Delay(ms)
49 | Sending keys too fast mightly make some error. Set delay and it will be fine. Default delay is 13ms
50 |
51 | Replace Return
52 | In Unix,"return" is “\\n”;In Windows, "return" is “\\r\\n”;In Mac, "return" is “\\r”.\n
53 | Android is a Unix system,it is different from windows. If your PC is not Unix, and you also not turn “fast mode” ON,the “return” you inputed will be show as a space in your PC.
54 |
55 |
56 | Fast Mode
57 | Send string without converting the numbers,English letters and some symbols on the standard keyboard, it could increasing the speed.
58 | If it is turned on, make sure that the input method is switched to English keyboard mode.
59 | Encode
60 | AB Dial has three mode for send keyboard events, toggled HID/UI/ANSI icon at the top of the app.
61 | \nHID means that the keys on the keyboard are sent to the device in real time.
62 | \nUI means that the content is first entered into the text box, and then the app sends the string from the text box to the device in Unicode when you tap the send button.
63 | \nANSI means that the content is input into the text box first, and when you tap the send button, the app sends the string in the text box to the device with the code you choose.
64 | \nIf the wrong encoding is selected, sending the string will form a garbled code. On Windows, less old software generally supports Unicode encoding.
65 | If entering Unicode fails, you can select the character and dot the icon to ANSI.
66 |
67 | Wheel Color
68 | Windows radial controller devices have a primary button, and a rotational affordance.AB Dial use a wheel split the screen to two part:
69 | Click the area inside the wheel as click primary button, Move the area outside the wheel as rotation.
70 | When you turn the wheel color on, each operation could change the color of the wheel.
71 |
72 | UI Setting
73 | m
74 | \nu
75 | \nl
76 | \nt
77 | \n
78 | \nl
79 | \ni
80 | \nn
81 | \ne
82 | \n
83 | \ntext
84 |
85 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
8 |
13 |
14 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 | ext.kotlin_version = '1.5.10'
5 | ext.anko_version='0.10.8'
6 |
7 | repositories {
8 | maven{ url 'https://maven.aliyun.com/repository/google'}
9 | maven{ url 'https://maven.aliyun.com/repository/public'}
10 | google()
11 | // jcenter()
12 | }
13 | dependencies {
14 | classpath 'com.android.tools.build:gradle:4.2.2'
15 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
16 |
17 | // NOTE: Do not place your application dependencies here; they belong
18 | // in the individual module build.gradle files
19 | }
20 | }
21 |
22 | allprojects {
23 | repositories {
24 | maven{ url 'https://maven.aliyun.com/repository/public'}
25 | maven{ url 'https://maven.aliyun.com/repository/google'}
26 | google()
27 | // jcenter()
28 |
29 | }
30 | }
31 |
32 | task clean(type: Delete) {
33 | delete rootProject.buildDir
34 | }
35 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx1536m
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. More details, visit
12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
13 | # org.gradle.parallel=true
14 | # Kotlin code style for this project: "official" or "obsolete":
15 | kotlin.code.style=official
16 |
17 |
18 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tumuyan/Kontroller/e7a29024f59842e55474a0be1dc2c95fea1c3e09/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Sun Jul 19 17:12:46 CST 2020
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-bin.zip
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Attempt to set APP_HOME
10 | # Resolve links: $0 may be a link
11 | PRG="$0"
12 | # Need this for relative symlinks.
13 | while [ -h "$PRG" ] ; do
14 | ls=`ls -ld "$PRG"`
15 | link=`expr "$ls" : '.*-> \(.*\)$'`
16 | if expr "$link" : '/.*' > /dev/null; then
17 | PRG="$link"
18 | else
19 | PRG=`dirname "$PRG"`"/$link"
20 | fi
21 | done
22 | SAVED="`pwd`"
23 | cd "`dirname \"$PRG\"`/" >/dev/null
24 | APP_HOME="`pwd -P`"
25 | cd "$SAVED" >/dev/null
26 |
27 | APP_NAME="Gradle"
28 | APP_BASE_NAME=`basename "$0"`
29 |
30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
31 | DEFAULT_JVM_OPTS=""
32 |
33 | # Use the maximum available, or set MAX_FD != -1 to use that value.
34 | MAX_FD="maximum"
35 |
36 | warn () {
37 | echo "$*"
38 | }
39 |
40 | die () {
41 | echo
42 | echo "$*"
43 | echo
44 | exit 1
45 | }
46 |
47 | # OS specific support (must be 'true' or 'false').
48 | cygwin=false
49 | msys=false
50 | darwin=false
51 | nonstop=false
52 | case "`uname`" in
53 | CYGWIN* )
54 | cygwin=true
55 | ;;
56 | Darwin* )
57 | darwin=true
58 | ;;
59 | MINGW* )
60 | msys=true
61 | ;;
62 | NONSTOP* )
63 | nonstop=true
64 | ;;
65 | esac
66 |
67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
68 |
69 | # Determine the Java command to use to start the JVM.
70 | if [ -n "$JAVA_HOME" ] ; then
71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
72 | # IBM's JDK on AIX uses strange locations for the executables
73 | JAVACMD="$JAVA_HOME/jre/sh/java"
74 | else
75 | JAVACMD="$JAVA_HOME/bin/java"
76 | fi
77 | if [ ! -x "$JAVACMD" ] ; then
78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
79 |
80 | Please set the JAVA_HOME variable in your environment to match the
81 | location of your Java installation."
82 | fi
83 | else
84 | JAVACMD="java"
85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
86 |
87 | Please set the JAVA_HOME variable in your environment to match the
88 | location of your Java installation."
89 | fi
90 |
91 | # Increase the maximum file descriptors if we can.
92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
93 | MAX_FD_LIMIT=`ulimit -H -n`
94 | if [ $? -eq 0 ] ; then
95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
96 | MAX_FD="$MAX_FD_LIMIT"
97 | fi
98 | ulimit -n $MAX_FD
99 | if [ $? -ne 0 ] ; then
100 | warn "Could not set maximum file descriptor limit: $MAX_FD"
101 | fi
102 | else
103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
104 | fi
105 | fi
106 |
107 | # For Darwin, add options to specify how the application appears in the dock
108 | if $darwin; then
109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
110 | fi
111 |
112 | # For Cygwin, switch paths to Windows format before running java
113 | if $cygwin ; then
114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
116 | JAVACMD=`cygpath --unix "$JAVACMD"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Escape application args
158 | save () {
159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
160 | echo " "
161 | }
162 | APP_ARGS=$(save "$@")
163 |
164 | # Collect all arguments for the java command, following the shell quoting and substitution rules
165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
166 |
167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
169 | cd "$(dirname "$0")"
170 | fi
171 |
172 | exec "$JAVACMD" "$@"
173 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | set DIRNAME=%~dp0
12 | if "%DIRNAME%" == "" set DIRNAME=.
13 | set APP_BASE_NAME=%~n0
14 | set APP_HOME=%DIRNAME%
15 |
16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
17 | set DEFAULT_JVM_OPTS=
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windows variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 |
53 | :win9xME_args
54 | @rem Slurp the command line arguments.
55 | set CMD_LINE_ARGS=
56 | set _SKIP=2
57 |
58 | :win9xME_args_slurp
59 | if "x%~1" == "x" goto execute
60 |
61 | set CMD_LINE_ARGS=%*
62 |
63 | :execute
64 | @rem Setup the command line
65 |
66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
67 |
68 | @rem Execute Gradle
69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
70 |
71 | :end
72 | @rem End local scope for the variables with windows NT shell
73 | if "%ERRORLEVEL%"=="0" goto mainEnd
74 |
75 | :fail
76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
77 | rem the _cmd.exe /c_ return code!
78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
79 | exit /b 1
80 |
81 | :mainEnd
82 | if "%OS%"=="Windows_NT" endlocal
83 |
84 | :omega
85 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 |
--------------------------------------------------------------------------------