Compare commits
184 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1d4bb6113c | ||
|
|
136120b831 | ||
|
|
cd299c06d6 | ||
|
|
e152a36ad7 | ||
|
|
e90544db33 | ||
|
|
7ceca202b4 | ||
|
|
ddc65aa54b | ||
|
|
13ec617d58 | ||
|
|
00c5b69129 | ||
|
|
6ccf005750 | ||
|
|
6e1f0ded9e | ||
|
|
d58386ef4b | ||
|
|
179c982bfb | ||
|
|
a7d07dbf4c | ||
|
|
0ca922d24a | ||
|
|
a6f1aa15a5 | ||
|
|
fc2d025cc4 | ||
|
|
8b5ae106bd | ||
|
|
44639bbf53 | ||
|
|
9fcce9234a | ||
|
|
b26fecfc7a | ||
|
|
1b15911783 | ||
|
|
148dd8d361 | ||
|
|
0d9ac50999 | ||
|
|
e19e4bc5a4 | ||
|
|
bdd79a5a9a | ||
|
|
fb4e05fdc4 | ||
|
|
01fcf9053a | ||
|
|
533c2f13d2 | ||
|
|
b9c32b46a9 | ||
|
|
f9b7260be6 | ||
|
|
847694d590 | ||
|
|
58178a5c7b | ||
|
|
9b93b4dac5 | ||
|
|
478dac0621 | ||
|
|
f196fcdaa2 | ||
|
|
0b420ff516 | ||
|
|
7656af76e4 | ||
|
|
beed546ae4 | ||
|
|
bf94370f2f | ||
|
|
2476049681 | ||
|
|
f8d8b6d213 | ||
|
|
05bbce1d50 | ||
|
|
32494fa228 | ||
|
|
e0ce38988e | ||
|
|
5c660c549d | ||
|
|
510b914e26 | ||
|
|
cf64fc0c08 | ||
|
|
b25bc9d81d | ||
|
|
2f00a5eaf4 | ||
|
|
e8fe0f8d3e | ||
|
|
e84dbf2c15 | ||
|
|
990ff8e980 | ||
|
|
1ca8e67e52 | ||
|
|
23cb583bf7 | ||
|
|
d5cc735b85 | ||
|
|
1981311136 | ||
|
|
bbb5d4a35b | ||
|
|
58ef40fb02 | ||
|
|
b8f35df155 | ||
|
|
c3e712851c | ||
|
|
2eaa1e0634 | ||
|
|
6ee21bcd7a | ||
|
|
10ae68ad4b | ||
|
|
02044ae1d0 | ||
|
|
3f99d47bba | ||
|
|
9beadb4aac | ||
|
|
d9fb7a4b42 | ||
|
|
83912ec21f | ||
|
|
6c7232a5ba | ||
|
|
65fc8b5efb | ||
|
|
7a64191955 | ||
|
|
1a413c824e | ||
|
|
e545c9d67b | ||
|
|
8650fef7ae | ||
|
|
5c618869a2 | ||
|
|
1b8d6949e0 | ||
|
|
aabc0a8746 | ||
|
|
9589398834 | ||
|
|
933a715ea5 | ||
|
|
df14e2d569 | ||
|
|
4f8a0247d3 | ||
|
|
85ec5f6b6a | ||
|
|
739b830f47 | ||
|
|
482cff3d3b | ||
|
|
9284ae5032 | ||
|
|
cac6fdc190 | ||
|
|
ca9bf60a1b | ||
|
|
bb7edb8e4d | ||
|
|
0d9c976eb8 | ||
|
|
288d4f75b6 | ||
|
|
73e07eae2d | ||
|
|
8e620caac5 | ||
|
|
0d4e1acf76 | ||
|
|
88c42d58b1 | ||
|
|
5099e904fc | ||
|
|
5476f7c3a5 | ||
|
|
e0bb0bcca3 | ||
|
|
124c3ec29b | ||
|
|
2310320b8a | ||
|
|
67346b4cda | ||
|
|
662ca0152f | ||
|
|
6358528438 | ||
|
|
02f1053d46 | ||
|
|
a44a7dc5f8 | ||
|
|
02d57fdabf | ||
|
|
38f6688930 | ||
|
|
6ca12d0ccd | ||
|
|
acd17ac657 | ||
|
|
5393501f68 | ||
|
|
99e020d66f | ||
|
|
2c74ce8d3e | ||
|
|
3cd2d208b9 | ||
|
|
d0cd30f915 | ||
|
|
010a23aaeb | ||
|
|
c723fe2651 | ||
|
|
95caa58624 | ||
|
|
9089f088b6 | ||
|
|
1aeb4e8326 | ||
|
|
96b9226adb | ||
|
|
7c065f4368 | ||
|
|
a8108b9abf | ||
|
|
c7baa00720 | ||
|
|
5cdf2282f8 | ||
|
|
89221faf60 | ||
|
|
3b70c84c61 | ||
|
|
5b1f4cb584 | ||
|
|
3ee6c680a1 | ||
|
|
fdcf64d5c6 | ||
|
|
6c327ee414 | ||
|
|
b6bdd1486c | ||
|
|
bd5be98d99 | ||
|
|
802e6a4649 | ||
|
|
ae11c01725 | ||
|
|
f0139c55ee | ||
|
|
b3f2e3451e | ||
|
|
906beaac0e | ||
|
|
46f855d1db | ||
|
|
5341d953ff | ||
|
|
bd9a2a0eeb | ||
|
|
4c10954721 | ||
|
|
bbce1e0e0f | ||
|
|
13f064229f | ||
|
|
d3295c5666 | ||
|
|
216793bbb8 | ||
|
|
558c8b0dbf | ||
|
|
e3c65f77df | ||
|
|
227f8f0d2c | ||
|
|
7e0bc39de1 | ||
|
|
c4d3648f73 | ||
|
|
547ab738c2 | ||
|
|
3de9e9aa84 | ||
|
|
01ac4c1e8b | ||
|
|
a0c8849f13 | ||
|
|
2a3dfcb0d0 | ||
|
|
2b3462c33f | ||
|
|
a0b838b2e9 | ||
|
|
90f56c350e | ||
|
|
5ceca41e0f | ||
|
|
721a4dc6e7 | ||
|
|
c9f03b4e57 | ||
|
|
bbb6839f7e | ||
|
|
dd973c80ea | ||
|
|
48574a121a | ||
|
|
8bc2462589 | ||
|
|
975ab8a5e9 | ||
|
|
f0a54768d4 | ||
|
|
0b69d01a0d | ||
|
|
838a92836c | ||
|
|
500ccc296b | ||
|
|
8bb9f7f839 | ||
|
|
5d1cc9202b | ||
|
|
c11658f1fc | ||
|
|
969c36561b | ||
|
|
04904aca5d | ||
|
|
ef0b0aa4ba | ||
|
|
3967593c9c | ||
|
|
b8be1c965b | ||
|
|
b32c93f0f8 | ||
|
|
711d3c0690 | ||
|
|
42b4465230 | ||
|
|
2bf7d545a2 | ||
|
|
d09c46af42 | ||
|
|
d57ba81d10 |
@@ -11,7 +11,7 @@ matrix:
|
||||
fast_finish: true
|
||||
include:
|
||||
- os: osx
|
||||
osx_image: xcode8.3
|
||||
osx_image: xcode9.3beta
|
||||
- os: linux
|
||||
env: CC=clang CXX=clang++ npm_config_clang=1
|
||||
compiler: clang
|
||||
@@ -45,7 +45,6 @@ addons:
|
||||
|
||||
install:
|
||||
- nvm install
|
||||
- npm i -g npm@5.6.0
|
||||
- npm install
|
||||
|
||||
before_script:
|
||||
|
||||
94
CHANGELOG.md
@@ -4,8 +4,100 @@ All notable changes to this project will be documented in this file.
|
||||
|
||||
The format is loosely based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/).
|
||||
|
||||
Every Agent version includes the most recent firmware version. See the [firmware changelog](https://github.com/UltimateHackingKeyboard/firmware/blob/master/CHANGELOG.md).
|
||||
|
||||
## [1.1.5] - 2018-04-10
|
||||
|
||||
Firmware: 8.1.5 [[release](https://github.com/UltimateHackingKeyboard/firmware/releases/tag/v8.1.5)] | Device Protocol: 4.2.0 | User Config: 4.0.0 | Hardware Config: 1.0.0
|
||||
|
||||
- Don't allow to run multiple instances of Agent at the same time, but rather focus the already existing Agent window.
|
||||
|
||||
## [1.1.4] - 2018-04-09
|
||||
|
||||
Firmware: 8.1.5 [[release](https://github.com/UltimateHackingKeyboard/firmware/releases/tag/v8.1.5)] | Device Protocol: 4.2.0 | User Config: 4.0.0 | Hardware Config: 1.0.0
|
||||
|
||||
- Handle privilege escalation gracefully on Linux even without PolicyKit.
|
||||
- Fix application icon path.
|
||||
- Replace application icon with a diagonal gradient based icon that should look better on desktop.
|
||||
- Make saving the configuration more robust, and add a configuration recovery screen.
|
||||
- Reposition the ISO key in the scancode list.
|
||||
|
||||
## [1.1.3] - 2018-04-06
|
||||
|
||||
Firmware: 8.1.**5** [[release](https://github.com/UltimateHackingKeyboard/firmware/releases/tag/v8.1.5)] | Device Protocol: 4.2.0 | User Config: 4.0.0 | Hardware Config: 1.0.0
|
||||
|
||||
- Show the firmware versions of the left and right keyboard halves on the firmware page.
|
||||
- Fix menu scancode.
|
||||
- Make the tooltip text regarding non-US characters easier to understand.
|
||||
- On the Device Configuration page change terminology from download/upload to export/import for greater clarity.
|
||||
|
||||
## [1.1.2] - 2018-03-09
|
||||
|
||||
Firmware: 8.1.**4** [[release](https://github.com/UltimateHackingKeyboard/firmware/releases/tag/v8.1.4)] | Device Protocol: 4.2.0 | User Config: 4.0.0 | Hardware Config: 1.0.0
|
||||
|
||||
- Fix the configuration serializer so that the correct key actions get serialized, and the save button always appears when needed.
|
||||
- Add instructions to the firmware page to aid users.
|
||||
- Fix code signing on OSX.
|
||||
- Sign Agent on Windows.
|
||||
|
||||
## [1.1.1] - 2018-02-13
|
||||
|
||||
Firmware: 8.1.**2** [[release](https://github.com/UltimateHackingKeyboard/firmware/releases/tag/v8.1.2)] | Device Protocol: 4.2.0 | User Config: 4.0.0 | Hardware Config: 1.0.0
|
||||
|
||||
- Sign Agent on OSX resulting in easier installation.
|
||||
- Add per-keymap description field.
|
||||
- Sort keymaps and macros alphabetically within the key action popover.
|
||||
- Add tooltip regarding non-US scancodes.
|
||||
- When deleting a macro, also delete the relevant play macro actions.
|
||||
- Make the reset configuration button persist the reset configuration in Agent-web.
|
||||
- Make Agent able to unbrick bricked modules.
|
||||
- Assign "switch to test keymap" action on all keymaps in the default configuration.
|
||||
- Add keymap descriptions in the default configuration.
|
||||
|
||||
## [1.1.0] - 2018-01-15
|
||||
|
||||
Firmware: 8.**1**.0 [[release](https://github.com/UltimateHackingKeyboard/firmware/releases/tag/v8.1.0)] | Device Protocol: 4.2.0 | User Config: 4.0.0 | Hardware Config: 1.0.0
|
||||
|
||||
- Only accept device, keymap, and macro names upon editing if their trimmed length is non-zero.
|
||||
- Add diagnostics USB scripts, most notably /packages/usb/{get-i2c-health,set-i2c-baud-rate}.js, some utilizing new device protocol commands and properties. `DEVICEPROTOCOL:MINOR`
|
||||
- Implement the Device -> Upload device configuration feature.
|
||||
- Make update-module-firmware.js more robust and able to recover bricked modules (including the left half) by utilizing the newly added wait-for-kboot-idle.js. `DEVICEPROTOCOL:MINOR`
|
||||
- Add the Agent -> About page containing the version number of Agent.
|
||||
- On the mouse speed section of the key action popover, remove the now incorrect bottom sentence and slightly rephrase the top sentence.
|
||||
- Remove --buspal speed specification argument because it gets disrespected by the firmware anyways.
|
||||
- Fix get-left-firmware-version.js to display the correct firmware version.
|
||||
|
||||
## [1.0.4] - 2017-12-30
|
||||
|
||||
Firmware: 8.0.0 [[release](https://github.com/UltimateHackingKeyboard/firmware/releases/tag/v8.0.0)] | Device Protocol: 4.0.0 | User Config: 4.0.0 | Hardware Config: 1.0.0
|
||||
|
||||
- Add mouse speed settings.
|
||||
|
||||
## [1.0.3] - 2017-12-28
|
||||
|
||||
Firmware: 8.0.**0** [[release](https://github.com/UltimateHackingKeyboard/firmware/releases/tag/v8.0.0)] | Device Protocol: 4.0.0 | User Config: 4.0.0 | Hardware Config: 1.0.0
|
||||
|
||||
- Add LED brightness settings.
|
||||
- Some key actions, for example Left Arrow were displayed as text with modifiers and as icon without modifires. Now, they're always displayed as icons.
|
||||
- Clean up firmware update console messages a bit.
|
||||
- Remove the add keymap button because this feature is not only useless but confusing until it gets reimplemented.
|
||||
- Explicitly mention on the macro tab of the key action popover that macro playback is not implemented yet.
|
||||
- Downgrade to firmware 8.0.0 because the left I2C watchdog of firmware 8.0.1 is not proven yet.
|
||||
|
||||
## [1.0.2] - 2017-12-25
|
||||
|
||||
Firmware: **8.0.1**[[release](https://github.com/UltimateHackingKeyboard/firmware/releases/tag/v8.0.1)] | Device Protocol: 4.0.0 | User Config: 4.0.0 | Hardware Config: 1.0.0
|
||||
|
||||
- Fix firmware upgrade on Linux.
|
||||
|
||||
## [1.0.1] - 2017-12-22
|
||||
|
||||
Firmware: 7.0.0[[release](https://github.com/UltimateHackingKeyboard/firmware/releases/tag/v7.0.0)] | Device Protocol: 4.0.0 | User Config: 4.0.0 | Hardware Config: 1.0.0
|
||||
|
||||
- Fix Linux privilege escalation when udev rules aren't set up.
|
||||
|
||||
## [1.0.0] - 2017-12-14
|
||||
|
||||
Firmware: [**7**.0.0](https://github.com/UltimateHackingKeyboard/firmware/releases/tag/7.0.0) | Device Protocol: 4.0.0 | User Config: 4.0.0 | Hardware Config: 1.0.0
|
||||
Firmware: **7**.0.0[[release](https://github.com/UltimateHackingKeyboard/firmware/releases/tag/v7.0.0)] | Device Protocol: 4.0.0 | User Config: 4.0.0 | Hardware Config: 1.0.0
|
||||
|
||||
- First release
|
||||
|
||||
39
README.md
@@ -7,23 +7,46 @@ Agent is the configuration application of the [Ultimate Hacking Keyboard](https:
|
||||
|
||||
[Give it a whirl!](http://ultimatehackingkeyboard.github.io/agent/)
|
||||
|
||||
## Set up instructions
|
||||
## Two builds to rule them all
|
||||
|
||||
First up, make sure that node >=8.1.x and npm >=5.1.x are installed on your system. Next up:
|
||||
It's worth mentioning that Agent has two builds.
|
||||
|
||||
The **electron build** is the desktop application which is meant to be used if you have an actual UHK at hand. It starts with an opening screen which detects your UHK. You cannot get past this screen without connecting a UHK via USB.
|
||||
|
||||
The **web build** is meant to be used for demonstration purposes, so people who don't yet own a UHK can get a feel of Agent and its capabilities in their browser. Eventually, WebUSB support will be added to the web build, making it able to communicate with the UHK. Given the sandboxed nature of browsers, the web build will always lack features that the electron build offers, so this won't make the electron build obsolete.
|
||||
|
||||
The two builds share code as much as possible.
|
||||
|
||||
## Building the electron application
|
||||
|
||||
### Step 1: Build Dependencies
|
||||
|
||||
You'll need Node.js LTS. Use your OS package manager to install it. [Check the NodeJS site for more info.](https://nodejs.org/en/download/package-manager/ "Installing Node.js via package manager") Mac OS users can simply `brew install node` to get both. Should you need multiple Node.js versions on the same computer, use Node Version Manager for [Mac/Linux](https://github.com/creationix/nvm) or for [Windows](https://github.com/coreybutler/nvm-windows)
|
||||
|
||||
You'll also need `libusb`.
|
||||
On debian-based linux distros, `apt-get install libusb-dev libudev-dev g++` is sufficient.
|
||||
On Mac OS, use `brew install libusb libusb-compat`.
|
||||
For everyone else, use the appropriate package manager for your OS.
|
||||
|
||||
### Step 2: Build Environment
|
||||
|
||||
```
|
||||
# Execute the following line on Linux. Use relevant package manager and package names on non-Debian based distros.
|
||||
apt-get install libusb-dev libudev-dev g++
|
||||
|
||||
git clone git@github.com:UltimateHackingKeyboard/agent.git
|
||||
cd agent
|
||||
npm install
|
||||
npm run build:electron
|
||||
npm run electron
|
||||
npm install # to install Node dependencies
|
||||
npm run build:electron # to build the agent
|
||||
npm run electron # to run the newly built agent
|
||||
```
|
||||
|
||||
At this point, Agent should be running on your machine.
|
||||
|
||||
## Developing the web application
|
||||
|
||||
- The frontend code is located in `packages/uhk-web/`
|
||||
- Run the project locally with `npm run server:web`
|
||||
- View the app at `http://localhost:8080`
|
||||
- The app will automatically reload when you make changes
|
||||
|
||||
## Contributing
|
||||
|
||||
Wanna contribute? Please let us show you [how](CONTRIBUTING.md).
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
os: unstable
|
||||
|
||||
clone_folder: c:\projects\uhk-agent
|
||||
|
||||
environment:
|
||||
GH_TOKEN:
|
||||
secure: 3IebpEKmC39codi1wT6dXx8mql4/mCL1JzZ7lir7GQ5MWRnCxlED2OXbiKHHigDV
|
||||
CSC_LINK: c:\projects\uhk-agent\scripts\certs\windows-cert.p12
|
||||
matrix:
|
||||
- nodejs_version: "8"
|
||||
|
||||
@@ -18,7 +21,6 @@ shallow_clone: true
|
||||
|
||||
install:
|
||||
- ps: Install-Product node $env:nodejs_version
|
||||
- npm i -g npm@5.6.0
|
||||
- choco install chromium
|
||||
- set CI=true
|
||||
- set PATH=%APPDATA%\npm;%PATH%
|
||||
|
||||
BIN
build/icon.icns
BIN
build/icon.ico
|
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 8.4 KiB |
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 127 KiB |
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 392 B After Width: | Height: | Size: 499 B |
|
Before Width: | Height: | Size: 546 B After Width: | Height: | Size: 735 B |
|
Before Width: | Height: | Size: 5.0 KiB After Width: | Height: | Size: 9.9 KiB |
|
Before Width: | Height: | Size: 660 B After Width: | Height: | Size: 913 B |
|
Before Width: | Height: | Size: 966 B After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 2.7 KiB |
2951
package-lock.json
generated
42
package.json
@@ -3,7 +3,11 @@
|
||||
"private": true,
|
||||
"author": "Ultimate Gadget Laboratories",
|
||||
"main": "electron/dist/electron-main.js",
|
||||
"version": "1.0.0",
|
||||
"version": "1.1.5",
|
||||
"firmwareVersion": "8.1.5",
|
||||
"deviceProtocolVersion": "4.2.0",
|
||||
"userConfigVersion": "4.0.0",
|
||||
"hardwareConfigVersion": "1.0.0",
|
||||
"description": "Agent is the configuration application of the Ultimate Hacking Keyboard.",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -17,7 +21,7 @@
|
||||
"devDependencies": {
|
||||
"@types/electron-devtools-installer": "2.0.2",
|
||||
"@types/electron-settings": "3.0.0",
|
||||
"@types/fs-extra": "4.0.5",
|
||||
"@types/fs-extra": "5.0.1",
|
||||
"@types/jasmine": "2.6.0",
|
||||
"@types/jsonfile": "4.0.1",
|
||||
"@types/node": "8.0.53",
|
||||
@@ -26,25 +30,28 @@
|
||||
"@types/usb": "1.1.3",
|
||||
"autoprefixer": "6.5.3",
|
||||
"buffer": "5.0.6",
|
||||
"copyfiles": "^2.0.0",
|
||||
"copy-webpack-plugin": "4.0.1",
|
||||
"core-js": "2.4.1",
|
||||
"cross-env": "5.0.5",
|
||||
"decompress": "4.2.0",
|
||||
"decompress-tarbz2": "^4.1.1",
|
||||
"devtron": "1.4.0",
|
||||
"electron": "1.7.5",
|
||||
"electron-builder": "19.45.5",
|
||||
"electron-debug": "1.4.0",
|
||||
"electron-devtools-installer": "2.2.0",
|
||||
"electron-log": "2.2.9",
|
||||
"electron-rebuild": "1.6.0",
|
||||
"electron-settings": "3.1.2",
|
||||
"electron": "1.8.4",
|
||||
"electron-builder": "20.8.1",
|
||||
"electron-debug": "1.5.0",
|
||||
"electron-devtools-installer": "2.2.3",
|
||||
"electron-log": "2.2.14",
|
||||
"electron-rebuild": "1.7.3",
|
||||
"electron-settings": "3.1.4",
|
||||
"electron-updater": "2.21.4",
|
||||
"exports-loader": "0.6.3",
|
||||
"file-loader": "0.10.0",
|
||||
"fs-extra": "4.0.2",
|
||||
"fs-extra": "5.0.0",
|
||||
"jsonfile": "4.0.0",
|
||||
"lerna": "2.0.0",
|
||||
"lerna": "2.9.0",
|
||||
"mkdirp": "0.5.1",
|
||||
"node-hid": "0.5.7",
|
||||
"npm-run-all": "4.0.2",
|
||||
"pre-commit": "1.2.2",
|
||||
"request": "2.83.0",
|
||||
@@ -54,7 +61,7 @@
|
||||
"svg-sprite": "1.3.7",
|
||||
"ts-loader": "2.3.1",
|
||||
"ts-node": "3.0.4",
|
||||
"tslint": "5.5.0",
|
||||
"tslint": "5.9.1",
|
||||
"typescript": "2.5.2",
|
||||
"webpack": "2.4.1"
|
||||
},
|
||||
@@ -70,11 +77,11 @@
|
||||
"test:uhk-web": "lerna exec --scope uhk-web npm test",
|
||||
"lint": "run-s -scn lint:ts lint:style",
|
||||
"lint:ts": "run-p -sn lint:ts:electron-main lint:ts:electron-renderer lint:ts:web lint:ts:test-serializer lint:ts:uhk-usb",
|
||||
"lint:ts:electron-main": "tslint --type-check --project ./packages/uhk-agent/tsconfig.json",
|
||||
"lint:ts:electron-renderer": "tslint --type-check --project ./packages/uhk-web/src/tsconfig.renderer.json",
|
||||
"lint:ts:web": "tslint --type-check --project ./packages/uhk-web/src/tsconfig.app.json",
|
||||
"lint:ts:test-serializer": "tslint --type-check --project ./packages/test-serializer/tsconfig.json",
|
||||
"lint:ts:uhk-usb": "tslint --type-check --project ./packages/uhk-usb/tsconfig.json",
|
||||
"lint:ts:electron-main": "tslint --project ./packages/uhk-agent/tsconfig.json",
|
||||
"lint:ts:electron-renderer": "tslint --project ./packages/uhk-web/src/tsconfig.renderer.json",
|
||||
"lint:ts:web": "tslint --project ./packages/uhk-web/src/tsconfig.app.json",
|
||||
"lint:ts:test-serializer": "tslint --project ./packages/test-serializer/tsconfig.json",
|
||||
"lint:ts:uhk-usb": "tslint --project ./packages/uhk-usb/tsconfig.json",
|
||||
"lint:style": "stylelint \"packages/uhk-agent/src/**/*.scss\" \"packages/uhk-web/src/**/*.scss\" --syntax scss",
|
||||
"build": "run-s build:common build:usb build:web build:electron",
|
||||
"build:web": "lerna exec --scope uhk-web npm run build",
|
||||
@@ -86,6 +93,7 @@
|
||||
"server:web": "lerna exec --scope uhk-web npm start",
|
||||
"server:electron": "lerna exec --scope uhk-web npm run server:renderer",
|
||||
"electron": "lerna exec --scope uhk-agent npm start",
|
||||
"electron:spe": "lerna exec --scope uhk-agent npm run electron:spe",
|
||||
"standard-version": "standard-version",
|
||||
"pack": "node ./scripts/release.js",
|
||||
"sprites": "node ./scripts/generate-svg-sprites",
|
||||
|
||||
2585
packages/uhk-agent/package-lock.json
generated
@@ -17,13 +17,7 @@
|
||||
"command-line-args": "4.0.7",
|
||||
"decompress": "4.2.0",
|
||||
"decompress-bzip2": "4.0.0",
|
||||
"electron": "1.7.9",
|
||||
"electron-is-dev": "0.1.2",
|
||||
"electron-log": "2.2.9",
|
||||
"electron-rebuild": "1.6.0",
|
||||
"electron-settings": "3.1.2",
|
||||
"electron-updater": "2.15.0",
|
||||
"node-hid": "0.5.4",
|
||||
"node-hid": "0.5.7",
|
||||
"sudo-prompt": "7.0.0",
|
||||
"tmp": "0.0.33",
|
||||
"uhk-common": "^1.0.0",
|
||||
@@ -36,6 +30,7 @@
|
||||
},
|
||||
"scripts": {
|
||||
"start": "electron ./dist/electron-main.js",
|
||||
"electron:spe": "electron ./dist/electron-main.js --spe",
|
||||
"build": "webpack && npm run install:build-deps && npm run build:usb && npm run download-firmware && npm run copy-blhost",
|
||||
"build:usb": "electron-rebuild -w node-hid -p -m ./dist",
|
||||
"install:build-deps": "cd ./dist && npm i",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
/// <reference path="./custom_types/command-line-args.d.ts"/>
|
||||
|
||||
import './polyfills';
|
||||
import { app, BrowserWindow, ipcMain } from 'electron';
|
||||
import { app, BrowserWindow } from 'electron';
|
||||
import { autoUpdater } from 'electron-updater';
|
||||
|
||||
import * as path from 'path';
|
||||
@@ -20,7 +20,8 @@ import { UhkBlhost } from '../../uhk-usb/src';
|
||||
import * as isDev from 'electron-is-dev';
|
||||
|
||||
const optionDefinitions = [
|
||||
{name: 'addons', type: Boolean, defaultOption: false}
|
||||
{name: 'addons', type: Boolean},
|
||||
{name: 'spe', type: Boolean} // simulate privilege escalation error
|
||||
];
|
||||
|
||||
const options: CommandLineArgs = commandLineArgs(optionDefinitions);
|
||||
@@ -59,7 +60,25 @@ if (console.debug) {
|
||||
};
|
||||
}
|
||||
|
||||
const isSecondInstance = app.makeSingleInstance(function (commandLine, workingDirectory) {
|
||||
// Someone tried to run a second instance, we should focus our window.
|
||||
if (win) {
|
||||
if (win.isMinimized()) {
|
||||
win.restore();
|
||||
}
|
||||
win.focus();
|
||||
}
|
||||
});
|
||||
|
||||
if (isSecondInstance) {
|
||||
app.quit();
|
||||
}
|
||||
|
||||
function createWindow() {
|
||||
if (isSecondInstance) {
|
||||
return;
|
||||
}
|
||||
|
||||
logger.info('[Electron Main] Create new window.');
|
||||
let packagesDir;
|
||||
if (isDev) {
|
||||
@@ -78,17 +97,17 @@ function createWindow() {
|
||||
webPreferences: {
|
||||
nodeIntegration: true
|
||||
},
|
||||
icon: 'assets/images/agent-icon.png'
|
||||
icon: path.join(__dirname, 'renderer/assets/images/agent-app-icon.png')
|
||||
});
|
||||
win.setMenuBarVisibility(false);
|
||||
win.maximize();
|
||||
uhkHidDeviceService = new UhkHidDevice(logger);
|
||||
uhkHidDeviceService = new UhkHidDevice(logger, options);
|
||||
uhkBlhost = new UhkBlhost(logger, packagesDir);
|
||||
uhkOperations = new UhkOperations(logger, uhkBlhost, uhkHidDeviceService, packagesDir);
|
||||
deviceService = new DeviceService(logger, win, uhkHidDeviceService, uhkOperations);
|
||||
appUpdateService = new AppUpdateService(logger, win, app);
|
||||
appService = new AppService(logger, win, deviceService, options, uhkHidDeviceService);
|
||||
sudoService = new SudoService(logger);
|
||||
sudoService = new SudoService(logger, options);
|
||||
// and load the index.html of the app.
|
||||
|
||||
win.loadURL(url.format({
|
||||
@@ -132,13 +151,13 @@ app.on('ready', createWindow);
|
||||
|
||||
// Quit when all windows are closed.
|
||||
app.on('window-all-closed', () => {
|
||||
app.quit();
|
||||
});
|
||||
|
||||
app.on('will-quit', () => {
|
||||
if (appUpdateService) {
|
||||
appUpdateService.saveFirtsRun();
|
||||
}
|
||||
app.exit();
|
||||
});
|
||||
|
||||
app.on('will-quit', () => {
|
||||
});
|
||||
|
||||
app.on('activate', () => {
|
||||
|
||||
10
packages/uhk-agent/src/models/command-line-inputs.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
export interface CommandLineInputs {
|
||||
/**
|
||||
* addons menu visible or not
|
||||
*/
|
||||
addons?: boolean;
|
||||
/**
|
||||
* simulate privilege escalation error
|
||||
*/
|
||||
spe?: boolean;
|
||||
}
|
||||
@@ -15,10 +15,5 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"node-hid": "0.5.7"
|
||||
},
|
||||
"firmwareVersion": "7.0.0",
|
||||
"deviceProtocolVersion": "4.0.0",
|
||||
"moduleProtocolVersion": "3.0.0",
|
||||
"userConfigVersion": "4.0.0",
|
||||
"hardwareConfigVersion": "1.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,8 +37,13 @@ export class AppUpdateService extends MainServiceBase {
|
||||
});
|
||||
|
||||
autoUpdater.on('error', (ev: any, err: string) => {
|
||||
console.error('[AppUpdateService] error', err);
|
||||
this.sendIpcToWindow(IpcEvents.autoUpdater.autoUpdateError, err.substr(0, 100));
|
||||
this.logService.error('[AppUpdateService] error', err);
|
||||
let msg = 'Electron updater error';
|
||||
if (err) {
|
||||
msg = err.substr(0, 100);
|
||||
}
|
||||
|
||||
this.sendIpcToWindow(IpcEvents.autoUpdater.autoUpdateError, msg);
|
||||
});
|
||||
|
||||
autoUpdater.on('download-progress', (progressObj: ProgressInfo) => {
|
||||
|
||||
@@ -1,60 +1,45 @@
|
||||
import { BrowserWindow, ipcMain } from 'electron';
|
||||
import { BrowserWindow, ipcMain, shell } from 'electron';
|
||||
import { UhkHidDevice } from 'uhk-usb';
|
||||
import { readFile } from 'fs';
|
||||
import { join } from 'path';
|
||||
|
||||
import { AppStartInfo, CommandLineArgs, IpcEvents, LogService } from 'uhk-common';
|
||||
import { AppStartInfo, IpcEvents, LogService } from 'uhk-common';
|
||||
import { MainServiceBase } from './main-service-base';
|
||||
import { DeviceService } from './device.service';
|
||||
import { CommandLineInputs } from '../models/command-line-inputs';
|
||||
|
||||
export class AppService extends MainServiceBase {
|
||||
constructor(protected logService: LogService,
|
||||
protected win: Electron.BrowserWindow,
|
||||
private deviceService: DeviceService,
|
||||
private options: CommandLineArgs,
|
||||
private options: CommandLineInputs,
|
||||
private uhkHidDeviceService: UhkHidDevice) {
|
||||
super(logService, win);
|
||||
|
||||
ipcMain.on(IpcEvents.app.getAppStartInfo, this.handleAppStartInfo.bind(this));
|
||||
ipcMain.on(IpcEvents.app.exit, this.exit.bind(this));
|
||||
ipcMain.on(IpcEvents.app.openUrl, this.openUrl.bind(this));
|
||||
logService.info('[AppService] init success');
|
||||
}
|
||||
|
||||
private async handleAppStartInfo(event: Electron.Event) {
|
||||
this.logService.info('[AppService] getAppStartInfo');
|
||||
|
||||
const packageJson = await this.getPackageJson();
|
||||
|
||||
const response: AppStartInfo = {
|
||||
commandLineArgs: this.options,
|
||||
deviceConnected: this.deviceService.isConnected,
|
||||
hasPermission: this.uhkHidDeviceService.hasPermission(),
|
||||
agentVersionInfo: {
|
||||
version: packageJson.version,
|
||||
firmwareVersion: packageJson.firmwareVersion,
|
||||
deviceProtocolVersion: packageJson.deviceProtocolVersion,
|
||||
moduleProtocolVersion: packageJson.moduleProtocolVersion,
|
||||
userConfigVersion: packageJson.userConfigVersion,
|
||||
hardwareConfigVersion: packageJson.hardwareConfigVersion
|
||||
}
|
||||
commandLineArgs: {
|
||||
addons: this.options.addons || false
|
||||
},
|
||||
deviceConnected: this.uhkHidDeviceService.deviceConnected(),
|
||||
hasPermission: this.uhkHidDeviceService.hasPermission()
|
||||
};
|
||||
this.logService.info('[AppService] getAppStartInfo response:', response);
|
||||
return event.sender.send(IpcEvents.app.getAppStartInfoReply, response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the package.json that delivered with the bundle. Do not use require('package.json')
|
||||
* because the deploy process change the package.json after the build
|
||||
* @returns {Promise<any>}
|
||||
*/
|
||||
private async getPackageJson(): Promise<any> {
|
||||
return new Promise((resolve, reject) => {
|
||||
readFile(join(__dirname, 'package.json'), {encoding: 'utf-8'}, (err, data) => {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
private exit() {
|
||||
this.logService.info('[AppService] exit');
|
||||
this.win.close();
|
||||
}
|
||||
|
||||
resolve(JSON.parse(data));
|
||||
});
|
||||
});
|
||||
private openUrl(event: Electron.Event, urls: Array<string>) {
|
||||
shell.openExternal(urls[0]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,18 @@
|
||||
import { ipcMain } from 'electron';
|
||||
import { ConfigurationReply, IpcEvents, IpcResponse, LogService } from 'uhk-common';
|
||||
import { Constants, snooze, UhkHidDevice, UhkOperations } from 'uhk-usb';
|
||||
import {
|
||||
ConfigurationReply,
|
||||
DeviceConnectionState,
|
||||
getHardwareConfigFromDeviceResponse,
|
||||
HardwareModules,
|
||||
IpcEvents,
|
||||
IpcResponse,
|
||||
LogService,
|
||||
mapObjectToUserConfigBinaryBuffer,
|
||||
SaveUserConfigurationData
|
||||
} from 'uhk-common';
|
||||
import { snooze, UhkHidDevice, UhkOperations } from 'uhk-usb';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { Subscription } from 'rxjs/Subscription';
|
||||
import { Device, devices } from 'node-hid';
|
||||
import { emptyDir } from 'fs-extra';
|
||||
|
||||
import 'rxjs/add/observable/interval';
|
||||
@@ -15,17 +24,16 @@ import 'rxjs/add/operator/distinctUntilChanged';
|
||||
import { saveTmpFirmware } from '../util/save-extract-firmware';
|
||||
import { TmpFirmware } from '../models/tmp-firmware';
|
||||
import { QueueManager } from './queue-manager';
|
||||
import { backupUserConfiguration, getBackupUserConfigurationContent } from '../util/backup-user-confoguration';
|
||||
|
||||
/**
|
||||
* IpcMain pair of the UHK Communication
|
||||
* Functionality:
|
||||
* - Detect device is connected or not
|
||||
* - Send UserConfiguration to the UHK Device
|
||||
* - Read UserConfiguration from the UHK Device
|
||||
*/
|
||||
export class DeviceService {
|
||||
private pollTimer$: Subscription;
|
||||
private connected = false;
|
||||
private queueManager = new QueueManager();
|
||||
|
||||
constructor(private logService: LogService,
|
||||
@@ -66,14 +74,6 @@ export class DeviceService {
|
||||
logService.debug('[DeviceService] init success');
|
||||
}
|
||||
|
||||
/**
|
||||
* Return with true is an UHK Device is connected to the computer.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
public get isConnected(): boolean {
|
||||
return this.connected;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return with the actual UserConfiguration from UHK Device
|
||||
* @returns {Promise<Buffer>}
|
||||
@@ -84,12 +84,20 @@ export class DeviceService {
|
||||
try {
|
||||
await this.device.waitUntilKeyboardBusy();
|
||||
const result = await this.operations.loadConfigurations();
|
||||
const modules: HardwareModules = {
|
||||
leftModuleInfo: await this.operations.getLeftModuleVersionInfo(),
|
||||
rightModuleInfo: await this.operations.getRightModuleVersionInfo()
|
||||
};
|
||||
|
||||
const hardwareConfig = getHardwareConfigFromDeviceResponse(result.hardwareConfiguration);
|
||||
const uniqueId = hardwareConfig.uniqueId;
|
||||
|
||||
response = {
|
||||
success: true,
|
||||
...result
|
||||
...result,
|
||||
modules,
|
||||
backupConfiguration: await getBackupUserConfigurationContent(this.logService, uniqueId)
|
||||
};
|
||||
event.sender.send(IpcEvents.device.loadConfigurationReply, JSON.stringify(response));
|
||||
} catch (error) {
|
||||
response = {
|
||||
success: false,
|
||||
@@ -103,7 +111,6 @@ export class DeviceService {
|
||||
}
|
||||
|
||||
public close(): void {
|
||||
this.connected = false;
|
||||
this.stopPollTimer();
|
||||
this.logService.info('[DeviceService] Device connection checker stopped.');
|
||||
}
|
||||
@@ -154,25 +161,29 @@ export class DeviceService {
|
||||
|
||||
this.pollTimer$ = Observable.interval(1000)
|
||||
.startWith(0)
|
||||
.map(() => {
|
||||
return devices().some((dev: Device) => dev.vendorId === Constants.VENDOR_ID &&
|
||||
dev.productId === Constants.PRODUCT_ID);
|
||||
})
|
||||
.map(() => this.device.deviceConnected())
|
||||
.distinctUntilChanged()
|
||||
.do((connected: boolean) => {
|
||||
this.connected = connected;
|
||||
this.win.webContents.send(IpcEvents.device.deviceConnectionStateChanged, connected);
|
||||
this.logService.info(`[DeviceService] Device connection state changed to: ${connected}`);
|
||||
const response: DeviceConnectionState = {
|
||||
connected,
|
||||
hasPermission: this.device.hasPermission()
|
||||
};
|
||||
|
||||
this.win.webContents.send(IpcEvents.device.deviceConnectionStateChanged, response);
|
||||
this.logService.info('[DeviceService] Device connection state changed to:', response);
|
||||
})
|
||||
.subscribe();
|
||||
}
|
||||
|
||||
private async saveUserConfiguration(event: Electron.Event, args: Array<string>): Promise<void> {
|
||||
const response = new IpcResponse();
|
||||
const json = args[0];
|
||||
const data: SaveUserConfigurationData = JSON.parse(args[0]);
|
||||
|
||||
try {
|
||||
await this.operations.saveUserConfiguration(json);
|
||||
await backupUserConfiguration(data);
|
||||
|
||||
const buffer = mapObjectToUserConfigBinaryBuffer(data.configuration);
|
||||
await this.operations.saveUserConfiguration(buffer);
|
||||
|
||||
response.success = true;
|
||||
}
|
||||
|
||||
@@ -2,15 +2,18 @@ import { ipcMain, app } from 'electron';
|
||||
import * as isDev from 'electron-is-dev';
|
||||
import * as path from 'path';
|
||||
import * as sudo from 'sudo-prompt';
|
||||
import { dirSync } from 'tmp';
|
||||
import { emptyDir, copy } from 'fs-extra';
|
||||
|
||||
import { IpcEvents, LogService, IpcResponse } from 'uhk-common';
|
||||
import { CommandLineArgs, IpcEvents, LogService, IpcResponse } from 'uhk-common';
|
||||
|
||||
export class SudoService {
|
||||
private rootDir: string;
|
||||
|
||||
constructor(private logService: LogService) {
|
||||
constructor(private logService: LogService,
|
||||
private options: CommandLineArgs) {
|
||||
if (isDev) {
|
||||
this.rootDir = path.join(path.join(process.cwd(), process.argv[1]), '..');
|
||||
this.rootDir = path.join(path.join(process.cwd(), process.argv[1]), '../../../../');
|
||||
} else {
|
||||
this.rootDir = path.dirname(app.getAppPath());
|
||||
}
|
||||
@@ -18,15 +21,28 @@ export class SudoService {
|
||||
ipcMain.on(IpcEvents.device.setPrivilegeOnLinux, this.setPrivilege.bind(this));
|
||||
}
|
||||
|
||||
private setPrivilege(event: Electron.Event) {
|
||||
private async setPrivilege(event: Electron.Event) {
|
||||
if (this.options.spe) {
|
||||
const error = new Error('No polkit authentication agent found.');
|
||||
this.logService.error('[SudoService] Simulate privilege escalation error ', error);
|
||||
|
||||
const response = new IpcResponse();
|
||||
response.success = false;
|
||||
response.error = {message: error.message};
|
||||
|
||||
event.sender.send(IpcEvents.device.setPrivilegeOnLinuxReply, response);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
switch (process.platform) {
|
||||
case 'linux':
|
||||
this.setPrivilegeOnLinux(event);
|
||||
await this.setPrivilegeOnLinux(event);
|
||||
break;
|
||||
default:
|
||||
const response: IpcResponse = {
|
||||
success: false,
|
||||
error: { message: 'Permissions couldn\'t be set. Invalid platform: ' + process.platform }
|
||||
error: {message: 'Permissions couldn\'t be set. Invalid platform: ' + process.platform}
|
||||
};
|
||||
|
||||
event.sender.send(IpcEvents.device.setPrivilegeOnLinuxReply, response);
|
||||
@@ -34,23 +50,31 @@ export class SudoService {
|
||||
}
|
||||
}
|
||||
|
||||
private setPrivilegeOnLinux(event: Electron.Event) {
|
||||
const scriptPath = path.join(this.rootDir, 'rules/setup-rules.sh');
|
||||
private async setPrivilegeOnLinux(event: Electron.Event) {
|
||||
const tmpDirectory = dirSync();
|
||||
const rulesDir = path.join(this.rootDir, 'rules');
|
||||
this.logService.debug('[SudoService] Copy rules dir', {src: rulesDir, dst: tmpDirectory.name});
|
||||
await copy(rulesDir, tmpDirectory.name);
|
||||
|
||||
const scriptPath = path.join(tmpDirectory.name, 'setup-rules.sh');
|
||||
|
||||
const options = {
|
||||
name: 'Setting UHK access rules'
|
||||
};
|
||||
const command = `sh ${scriptPath}`;
|
||||
console.log(command);
|
||||
sudo.exec(command, options, (error: any) => {
|
||||
this.logService.debug('[SudoService] Set privilege command: ', command);
|
||||
sudo.exec(command, options, async (error: any) => {
|
||||
const response = new IpcResponse();
|
||||
|
||||
if (error) {
|
||||
this.logService.error('[SudoService] Error when set privilege: ', error);
|
||||
response.success = false;
|
||||
response.error = error;
|
||||
response.error = {message: error.message};
|
||||
} else {
|
||||
response.success = true;
|
||||
}
|
||||
|
||||
await emptyDir(tmpDirectory.name);
|
||||
event.sender.send(IpcEvents.device.setPrivilegeOnLinuxReply, response);
|
||||
});
|
||||
}
|
||||
|
||||
32
packages/uhk-agent/src/util/backup-user-confoguration.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import { app } from 'electron';
|
||||
import { LogService, UserConfiguration, SaveUserConfigurationData } from 'uhk-common';
|
||||
import * as path from 'path';
|
||||
import * as fs from 'fs-extra';
|
||||
|
||||
export const getBackupUserConfigurationPath = (uniqueId: number): string => {
|
||||
const appDataDir = app.getPath('userData');
|
||||
|
||||
return path.join(appDataDir, `${uniqueId}.json`);
|
||||
};
|
||||
|
||||
export const backupUserConfiguration = (data: SaveUserConfigurationData): Promise<void> => {
|
||||
const backupFilePath = getBackupUserConfigurationPath(data.uniqueId);
|
||||
return fs.writeJSON(backupFilePath, data.configuration, {spaces: 2});
|
||||
};
|
||||
|
||||
export const getBackupUserConfigurationContent = async (logService: LogService, uniqueId: number): Promise<UserConfiguration> => {
|
||||
try {
|
||||
const backupFilePath = getBackupUserConfigurationPath(uniqueId);
|
||||
|
||||
if (await fs.pathExists(backupFilePath)) {
|
||||
const json = await fs.readJSON(backupFilePath);
|
||||
new UserConfiguration().fromJsonObject(json);
|
||||
|
||||
return json;
|
||||
}
|
||||
|
||||
return null;
|
||||
} catch (error) {
|
||||
logService.error('Can not load backup user configuration for device', {uniqueId, error});
|
||||
}
|
||||
};
|
||||
@@ -10,7 +10,10 @@
|
||||
"url": "git@github.com:UltimateHackingKeyboard/agent.git"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"build": "run-s tsc copy:*",
|
||||
"tsc": "tsc",
|
||||
"copy:scancodes": "copyfiles ./src/config-serializer/config-items/scancodes.json dist",
|
||||
"copy:secondary-roles": "copyfiles ./src/config-serializer/config-items/secondaryRole.json dist",
|
||||
"test": "jasmine-ts --config=jasmine.json",
|
||||
"coverage": "nyc jasmine-ts --config=jasmine.json"
|
||||
},
|
||||
|
||||
@@ -9,3 +9,6 @@ export * from './macro';
|
||||
export * from './module';
|
||||
export * from './module-configuration';
|
||||
export * from './user-configuration';
|
||||
|
||||
export const SCANCODES = require('./scancodes.json');
|
||||
export const SECONDARY_ROLES = require('./secondaryRole.json');
|
||||
|
||||
@@ -7,6 +7,8 @@ import { SwitchLayerAction } from './switch-layer-action';
|
||||
import { SwitchKeymapAction, UnresolvedSwitchKeymapAction } from './switch-keymap-action';
|
||||
import { MouseAction } from './mouse-action';
|
||||
import { PlayMacroAction } from './play-macro-action';
|
||||
import { NoneAction } from './none-action';
|
||||
import { isScancodeExists } from '../scancode-checker';
|
||||
|
||||
export class Helper {
|
||||
|
||||
@@ -25,7 +27,12 @@ export class Helper {
|
||||
buffer.backtrack();
|
||||
|
||||
if (keyActionFirstByte >= KeyActionId.KeystrokeAction && keyActionFirstByte < KeyActionId.LastKeystrokeAction) {
|
||||
return new KeystrokeAction().fromBinary(buffer);
|
||||
const keystrokeAction = new KeystrokeAction().fromBinary(buffer);
|
||||
if (isValidKeystrokeAction(keystrokeAction)) {
|
||||
return keystrokeAction;
|
||||
}
|
||||
|
||||
return new NoneAction();
|
||||
}
|
||||
|
||||
switch (keyActionFirstByte) {
|
||||
@@ -67,8 +74,14 @@ export class Helper {
|
||||
}
|
||||
|
||||
switch (keyAction.keyActionType) {
|
||||
case keyActionType.KeystrokeAction:
|
||||
return new KeystrokeAction().fromJsonObject(keyAction);
|
||||
case keyActionType.KeystrokeAction: {
|
||||
const keystrokeAction = new KeystrokeAction().fromJsonObject(keyAction);
|
||||
if (isValidKeystrokeAction(keystrokeAction)) {
|
||||
return keystrokeAction;
|
||||
}
|
||||
|
||||
return new NoneAction();
|
||||
}
|
||||
case keyActionType.SwitchLayerAction:
|
||||
return new SwitchLayerAction().fromJsonObject(keyAction);
|
||||
case keyActionType.SwitchKeymapAction:
|
||||
@@ -77,8 +90,16 @@ export class Helper {
|
||||
return new MouseAction().fromJsonObject(keyAction);
|
||||
case keyActionType.PlayMacroAction:
|
||||
return new PlayMacroAction().fromJsonObject(keyAction, macros);
|
||||
case keyActionType.NoneAction:
|
||||
return new NoneAction();
|
||||
default:
|
||||
throw `Invalid KeyAction.keyActionType: "${keyAction.keyActionType}"`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function isValidKeystrokeAction(keystrokeAction: KeystrokeAction): boolean {
|
||||
return keystrokeAction.hasSecondaryRoleAction() ||
|
||||
keystrokeAction.hasActiveModifier() ||
|
||||
keystrokeAction.hasScancode() && isScancodeExists(keystrokeAction.scancode);
|
||||
}
|
||||
|
||||
@@ -54,15 +54,12 @@ export class Module {
|
||||
|
||||
const noneAction = new NoneAction();
|
||||
|
||||
const keyActions: KeyAction[] = this.keyActions.map(keyAction => {
|
||||
buffer.writeArray(this.keyActions, (uhkBuffer: UhkBuffer, keyAction: KeyAction) => {
|
||||
if (keyAction) {
|
||||
return keyAction;
|
||||
keyAction.toBinary(uhkBuffer, userConfiguration);
|
||||
} else {
|
||||
noneAction.toBinary(uhkBuffer);
|
||||
}
|
||||
return noneAction;
|
||||
});
|
||||
|
||||
buffer.writeArray(keyActions, (uhkBuffer: UhkBuffer, keyAction: KeyAction) => {
|
||||
keyAction.toBinary(uhkBuffer, userConfiguration);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
import { SCANCODES } from './';
|
||||
|
||||
let scancodeMap: Map<number, any>;
|
||||
|
||||
export function isScancodeExists(scancode: number): boolean {
|
||||
if (!scancodeMap) {
|
||||
fillScancodeMap();
|
||||
}
|
||||
|
||||
return scancodeMap.has(scancode);
|
||||
}
|
||||
|
||||
function fillScancodeMap(): void {
|
||||
scancodeMap = new Map<number, any>();
|
||||
|
||||
for (const scanGroup of SCANCODES) {
|
||||
for (const child of scanGroup.children) {
|
||||
if (child.additional && child.additional.scancode) {
|
||||
scancodeMap.set(child.additional.scancode, child);
|
||||
}
|
||||
else {
|
||||
scancodeMap.set(Number.parseInt(child.id), child);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -105,6 +105,10 @@
|
||||
{
|
||||
"id": "29",
|
||||
"text": "Z"
|
||||
},
|
||||
{
|
||||
"id": "100",
|
||||
"text": "| ISO"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -242,7 +246,7 @@
|
||||
"text": "Delete"
|
||||
},
|
||||
{
|
||||
"id": "118",
|
||||
"id": "101",
|
||||
"text": "Menu"
|
||||
},
|
||||
{
|
||||
@@ -314,10 +318,6 @@
|
||||
"id": "69",
|
||||
"text": "F12"
|
||||
},
|
||||
{
|
||||
"id": "100",
|
||||
"text": "| ISO"
|
||||
},
|
||||
{
|
||||
"id": "104",
|
||||
"text": "F13"
|
||||
@@ -1,12 +1,7 @@
|
||||
import { CommandLineArgs } from './command-line-args';
|
||||
import { VersionInformation } from './version-information';
|
||||
|
||||
export interface AppStartInfo {
|
||||
commandLineArgs: CommandLineArgs;
|
||||
deviceConnected: boolean;
|
||||
hasPermission: boolean;
|
||||
/**
|
||||
* This property contains the version information of the deployed agent components
|
||||
*/
|
||||
agentVersionInfo: VersionInformation;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,10 @@
|
||||
export interface CommandLineArgs {
|
||||
addons: boolean;
|
||||
/**
|
||||
* addons menu visible or not
|
||||
*/
|
||||
addons?: boolean;
|
||||
/**
|
||||
* simulate privilege escalation error
|
||||
*/
|
||||
spe?: boolean;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
import { HardwareModules } from './hardware-modules';
|
||||
import { UserConfiguration } from '../config-serializer/config-items';
|
||||
|
||||
export interface ConfigurationReply {
|
||||
success: boolean;
|
||||
userConfiguration?: string;
|
||||
hardwareConfiguration?: string;
|
||||
modules?: HardwareModules;
|
||||
error?: string;
|
||||
backupConfiguration?: UserConfiguration;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
export interface DeviceConnectionState {
|
||||
connected: boolean;
|
||||
hasPermission: boolean;
|
||||
}
|
||||
4
packages/uhk-common/src/models/hardware-module-info.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export interface HardwareModuleInfo {
|
||||
firmwareVersion?: string;
|
||||
moduleProtocolVersion?: string;
|
||||
}
|
||||
6
packages/uhk-common/src/models/hardware-modules.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { HardwareModuleInfo } from './hardware-module-info';
|
||||
|
||||
export interface HardwareModules {
|
||||
leftModuleInfo?: HardwareModuleInfo;
|
||||
rightModuleInfo?: HardwareModuleInfo;
|
||||
}
|
||||
@@ -4,3 +4,7 @@ export * from './ipc-response';
|
||||
export * from './app-start-info';
|
||||
export * from './configuration-reply';
|
||||
export * from './version-information';
|
||||
export * from './device-connection-state';
|
||||
export * from './hardware-modules';
|
||||
export * from './hardware-module-info';
|
||||
export * from './save-user-configuration-data';
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
export interface SaveUserConfigurationData {
|
||||
uniqueId: number;
|
||||
configuration: string;
|
||||
}
|
||||
@@ -1,5 +1,3 @@
|
||||
export namespace Constants {
|
||||
export const VENDOR_ID = 0x1D50;
|
||||
export const PRODUCT_ID = 0x6122;
|
||||
export const MAX_PAYLOAD_SIZE = 64;
|
||||
export const AGENT_GITHUB_URL = 'https://github.com/UltimateHackingKeyboard/agent';
|
||||
}
|
||||
|
||||
33
packages/uhk-common/src/util/helpers.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import { HardwareConfiguration, UhkBuffer, UserConfiguration } from '../../index';
|
||||
|
||||
export const getHardwareConfigFromDeviceResponse = (json: string): HardwareConfiguration => {
|
||||
const data = JSON.parse(json);
|
||||
const hardwareConfig = new HardwareConfiguration();
|
||||
hardwareConfig.fromBinary(UhkBuffer.fromArray(data));
|
||||
|
||||
if (hardwareConfig.uniqueId > 0) {
|
||||
return hardwareConfig;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
export const getUserConfigFromDeviceResponse = (json: string): UserConfiguration => {
|
||||
const data = JSON.parse(json);
|
||||
const userConfig = new UserConfiguration();
|
||||
userConfig.fromBinary(UhkBuffer.fromArray(data));
|
||||
|
||||
if (userConfig.userConfigMajorVersion > 0) {
|
||||
return userConfig;
|
||||
}
|
||||
|
||||
throw Error('Invalid user configuration');
|
||||
};
|
||||
|
||||
export const mapObjectToUserConfigBinaryBuffer = (obj: any): Buffer => {
|
||||
const configuration = new UserConfiguration();
|
||||
configuration.fromJsonObject(obj);
|
||||
const buffer = new UhkBuffer();
|
||||
configuration.toBinary(buffer);
|
||||
|
||||
return buffer.getBufferContent();
|
||||
};
|
||||
@@ -1,5 +1,7 @@
|
||||
export { IpcEvents } from './ipcEvents';
|
||||
export * from './log';
|
||||
export * from './constants';
|
||||
export * from './helpers';
|
||||
|
||||
// Source: http://stackoverflow.com/questions/13720256/javascript-regex-camelcase-to-sentence
|
||||
export function camelCaseToSentence(camelCasedText: string): string {
|
||||
|
||||
@@ -2,6 +2,8 @@ class App {
|
||||
public static readonly appStarted = 'app-started';
|
||||
public static readonly getAppStartInfo = 'app-get-start-info';
|
||||
public static readonly getAppStartInfoReply = 'app-get-start-info-reply';
|
||||
public static readonly exit = 'app-exit';
|
||||
public static readonly openUrl = 'open-url';
|
||||
}
|
||||
|
||||
class AutoUpdate {
|
||||
|
||||
172
packages/uhk-usb/package-lock.json
generated
@@ -23,7 +23,7 @@
|
||||
"integrity": "sha1-u13KOCu5TwXhUZQ3PRb9O6HKEQ0=",
|
||||
"requires": {
|
||||
"delegates": "1.0.0",
|
||||
"readable-stream": "2.3.3"
|
||||
"readable-stream": "2.3.6"
|
||||
}
|
||||
},
|
||||
"bindings": {
|
||||
@@ -32,11 +32,12 @@
|
||||
"integrity": "sha512-DpLh5EzMR2kzvX1KIlVC0VkC3iZtHKTgdtZ0a3pglBZdaQFjt5S9g9xd1lE+YvXyfd6mtCeRnrUfOLYiTMlNSw=="
|
||||
},
|
||||
"bl": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/bl/-/bl-1.2.1.tgz",
|
||||
"integrity": "sha1-ysMo977kVzDUBLaSID/LWQ4XLV4=",
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/bl/-/bl-1.2.2.tgz",
|
||||
"integrity": "sha512-e8tQYnZodmebYDWGH7KMRvtzKXaJHx3BbilrgZCfvyLUYdKpK1t5PSPmpkny/SgiTSCnjfLW7v5rlONXVFkQEA==",
|
||||
"requires": {
|
||||
"readable-stream": "2.3.3"
|
||||
"readable-stream": "2.3.6",
|
||||
"safe-buffer": "5.1.1"
|
||||
}
|
||||
},
|
||||
"chownr": {
|
||||
@@ -59,6 +60,14 @@
|
||||
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
|
||||
"integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
|
||||
},
|
||||
"decompress-response": {
|
||||
"version": "3.3.0",
|
||||
"resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz",
|
||||
"integrity": "sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M=",
|
||||
"requires": {
|
||||
"mimic-response": "1.0.0"
|
||||
}
|
||||
},
|
||||
"deep-extend": {
|
||||
"version": "0.4.2",
|
||||
"resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.4.2.tgz",
|
||||
@@ -69,10 +78,15 @@
|
||||
"resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
|
||||
"integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o="
|
||||
},
|
||||
"detect-libc": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz",
|
||||
"integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups="
|
||||
},
|
||||
"end-of-stream": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.0.tgz",
|
||||
"integrity": "sha1-epDYM+/abPpurA9JSduw+tOmMgY=",
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz",
|
||||
"integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==",
|
||||
"requires": {
|
||||
"once": "1.4.0"
|
||||
}
|
||||
@@ -113,9 +127,9 @@
|
||||
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
|
||||
},
|
||||
"ini": {
|
||||
"version": "1.3.4",
|
||||
"resolved": "https://registry.npmjs.org/ini/-/ini-1.3.4.tgz",
|
||||
"integrity": "sha1-BTfLedr1m1mhpRff9wbIbsA5Fi4="
|
||||
"version": "1.3.5",
|
||||
"resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz",
|
||||
"integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw=="
|
||||
},
|
||||
"is-fullwidth-code-point": {
|
||||
"version": "1.0.0",
|
||||
@@ -130,6 +144,11 @@
|
||||
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
|
||||
"integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
|
||||
},
|
||||
"mimic-response": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.0.tgz",
|
||||
"integrity": "sha1-3z02Uqc/3ta5sLJBRub9BSNTRY4="
|
||||
},
|
||||
"minimist": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
|
||||
@@ -151,14 +170,17 @@
|
||||
}
|
||||
},
|
||||
"nan": {
|
||||
"version": "2.7.0",
|
||||
"resolved": "https://registry.npmjs.org/nan/-/nan-2.7.0.tgz",
|
||||
"integrity": "sha1-2Vv3IeyHfgjbJ27T/G63j5CDrUY="
|
||||
"version": "2.10.0",
|
||||
"resolved": "https://registry.npmjs.org/nan/-/nan-2.10.0.tgz",
|
||||
"integrity": "sha512-bAdJv7fBLhWC+/Bls0Oza+mvTaNQtP+1RyhhhvD95pgUJz6XM5IzgmxOkItJ9tkoCiplvAnXI1tNmmUD/eScyA=="
|
||||
},
|
||||
"node-abi": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/node-abi/-/node-abi-2.1.1.tgz",
|
||||
"integrity": "sha512-6oxV13poCOv7TfGvhsSz6XZWpXeKkdGVh72++cs33OfMh3KAX8lN84dCvmqSETyDXAFcUHtV7eJrgFBoOqZbNQ=="
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/node-abi/-/node-abi-2.3.0.tgz",
|
||||
"integrity": "sha512-zwm6vU3SsVgw3e9fu48JBaRBCJGIvAgysDsqtf5+vEexFE71bEOtaMWb5zr/zODZNzTPtQlqUUpC79k68Hspow==",
|
||||
"requires": {
|
||||
"semver": "5.5.0"
|
||||
}
|
||||
},
|
||||
"node-hid": {
|
||||
"version": "0.5.7",
|
||||
@@ -166,8 +188,8 @@
|
||||
"integrity": "sha512-dwwpOetL2+MGYgivbO22ML+45ieCGbueWv1rYxRgBoEc2QMp6UF6ZucEkYts1IA3YPWJNkmpGh6dqQ85n19szw==",
|
||||
"requires": {
|
||||
"bindings": "1.3.0",
|
||||
"nan": "2.7.0",
|
||||
"prebuild-install": "2.3.0"
|
||||
"nan": "2.10.0",
|
||||
"prebuild-install": "2.5.1"
|
||||
}
|
||||
},
|
||||
"noop-logger": {
|
||||
@@ -210,62 +232,63 @@
|
||||
"integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M="
|
||||
},
|
||||
"prebuild-install": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-2.3.0.tgz",
|
||||
"integrity": "sha512-gzjq2oHB8oMbzJSsSh9MQ64zrXZGt092/uT4TLZlz2qnrPxpWqp4vYB7LZrDxnlxf5RfbCjkgDI/z0EIVuYzAw==",
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-2.5.1.tgz",
|
||||
"integrity": "sha512-3DX9L6pzwc1m1ksMkW3Ky2WLgPQUBiySOfXVl3WZyAeJSyJb4wtoH9OmeRGcubAWsMlLiL8BTHbwfm/jPQE9Ag==",
|
||||
"requires": {
|
||||
"detect-libc": "1.0.3",
|
||||
"expand-template": "1.1.0",
|
||||
"github-from-package": "0.0.0",
|
||||
"minimist": "1.2.0",
|
||||
"mkdirp": "0.5.1",
|
||||
"node-abi": "2.1.1",
|
||||
"node-abi": "2.3.0",
|
||||
"noop-logger": "0.1.1",
|
||||
"npmlog": "4.1.2",
|
||||
"os-homedir": "1.0.2",
|
||||
"pump": "1.0.2",
|
||||
"rc": "1.2.2",
|
||||
"simple-get": "1.4.3",
|
||||
"pump": "2.0.1",
|
||||
"rc": "1.2.6",
|
||||
"simple-get": "2.7.0",
|
||||
"tar-fs": "1.16.0",
|
||||
"tunnel-agent": "0.6.0",
|
||||
"xtend": "4.0.1"
|
||||
"which-pm-runs": "1.0.0"
|
||||
}
|
||||
},
|
||||
"process-nextick-args": {
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz",
|
||||
"integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M="
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz",
|
||||
"integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw=="
|
||||
},
|
||||
"pump": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/pump/-/pump-1.0.2.tgz",
|
||||
"integrity": "sha1-Oz7mUS+U8OV1U4wXmV+fFpkKXVE=",
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz",
|
||||
"integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==",
|
||||
"requires": {
|
||||
"end-of-stream": "1.4.0",
|
||||
"end-of-stream": "1.4.1",
|
||||
"once": "1.4.0"
|
||||
}
|
||||
},
|
||||
"rc": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/rc/-/rc-1.2.2.tgz",
|
||||
"integrity": "sha1-2M6ctX6NZNnHut2YdsfDTL48cHc=",
|
||||
"version": "1.2.6",
|
||||
"resolved": "https://registry.npmjs.org/rc/-/rc-1.2.6.tgz",
|
||||
"integrity": "sha1-6xiYnG1PTxYsOZ953dKfODVWgJI=",
|
||||
"requires": {
|
||||
"deep-extend": "0.4.2",
|
||||
"ini": "1.3.4",
|
||||
"ini": "1.3.5",
|
||||
"minimist": "1.2.0",
|
||||
"strip-json-comments": "2.0.1"
|
||||
}
|
||||
},
|
||||
"readable-stream": {
|
||||
"version": "2.3.3",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz",
|
||||
"integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==",
|
||||
"version": "2.3.6",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
|
||||
"integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
|
||||
"requires": {
|
||||
"core-util-is": "1.0.2",
|
||||
"inherits": "2.0.3",
|
||||
"isarray": "1.0.0",
|
||||
"process-nextick-args": "1.0.7",
|
||||
"process-nextick-args": "2.0.0",
|
||||
"safe-buffer": "5.1.1",
|
||||
"string_decoder": "1.0.3",
|
||||
"string_decoder": "1.1.1",
|
||||
"util-deprecate": "1.0.2"
|
||||
}
|
||||
},
|
||||
@@ -274,6 +297,11 @@
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz",
|
||||
"integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg=="
|
||||
},
|
||||
"semver": {
|
||||
"version": "5.5.0",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz",
|
||||
"integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA=="
|
||||
},
|
||||
"set-blocking": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
|
||||
@@ -284,14 +312,19 @@
|
||||
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz",
|
||||
"integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0="
|
||||
},
|
||||
"simple-concat": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.0.tgz",
|
||||
"integrity": "sha1-c0TLuLbib7J9ZrL8hvn21Zl1IcY="
|
||||
},
|
||||
"simple-get": {
|
||||
"version": "1.4.3",
|
||||
"resolved": "https://registry.npmjs.org/simple-get/-/simple-get-1.4.3.tgz",
|
||||
"integrity": "sha1-6XVe2kB+ltpAxeUVjJ6jezO+y+s=",
|
||||
"version": "2.7.0",
|
||||
"resolved": "https://registry.npmjs.org/simple-get/-/simple-get-2.7.0.tgz",
|
||||
"integrity": "sha512-RkE9rGPHcxYZ/baYmgJtOSM63vH0Vyq+ma5TijBcLla41SWlh8t6XYIGMR/oeZcmr+/G8k+zrClkkVrtnQ0esg==",
|
||||
"requires": {
|
||||
"decompress-response": "3.3.0",
|
||||
"once": "1.4.0",
|
||||
"unzip-response": "1.0.2",
|
||||
"xtend": "4.0.1"
|
||||
"simple-concat": "1.0.0"
|
||||
}
|
||||
},
|
||||
"string-width": {
|
||||
@@ -305,9 +338,9 @@
|
||||
}
|
||||
},
|
||||
"string_decoder": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz",
|
||||
"integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==",
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
|
||||
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
|
||||
"requires": {
|
||||
"safe-buffer": "5.1.1"
|
||||
}
|
||||
@@ -332,18 +365,29 @@
|
||||
"requires": {
|
||||
"chownr": "1.0.1",
|
||||
"mkdirp": "0.5.1",
|
||||
"pump": "1.0.2",
|
||||
"tar-stream": "1.5.4"
|
||||
"pump": "1.0.3",
|
||||
"tar-stream": "1.5.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"pump": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/pump/-/pump-1.0.3.tgz",
|
||||
"integrity": "sha512-8k0JupWme55+9tCVE+FS5ULT3K6AbgqrGa58lTT49RpyfwwcGedHqaC5LlQNdEAumn/wFsu6aPwkuPMioy8kqw==",
|
||||
"requires": {
|
||||
"end-of-stream": "1.4.1",
|
||||
"once": "1.4.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"tar-stream": {
|
||||
"version": "1.5.4",
|
||||
"resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-1.5.4.tgz",
|
||||
"integrity": "sha1-NlSc8E7RrumyowwBQyUiONr5QBY=",
|
||||
"version": "1.5.5",
|
||||
"resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-1.5.5.tgz",
|
||||
"integrity": "sha512-mQdgLPc/Vjfr3VWqWbfxW8yQNiJCbAZ+Gf6GDu1Cy0bdb33ofyiNGBtAY96jHFhDuivCwgW1H9DgTON+INiXgg==",
|
||||
"requires": {
|
||||
"bl": "1.2.1",
|
||||
"end-of-stream": "1.4.0",
|
||||
"readable-stream": "2.3.3",
|
||||
"bl": "1.2.2",
|
||||
"end-of-stream": "1.4.1",
|
||||
"readable-stream": "2.3.6",
|
||||
"xtend": "4.0.1"
|
||||
}
|
||||
},
|
||||
@@ -355,16 +399,16 @@
|
||||
"safe-buffer": "5.1.1"
|
||||
}
|
||||
},
|
||||
"unzip-response": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/unzip-response/-/unzip-response-1.0.2.tgz",
|
||||
"integrity": "sha1-uYTwh3/AqJwsdzzB73tbIytbBv4="
|
||||
},
|
||||
"util-deprecate": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||
"integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
|
||||
},
|
||||
"which-pm-runs": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/which-pm-runs/-/which-pm-runs-1.0.0.tgz",
|
||||
"integrity": "sha1-Zws6+8VS4LVd9rd4DKdGFfI60cs="
|
||||
},
|
||||
"wide-align": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.2.tgz",
|
||||
|
||||
@@ -21,7 +21,8 @@ export enum UsbCommand {
|
||||
SetTestLed = 0x0a,
|
||||
GetDebugBuffer = 0x0b,
|
||||
GetAdcValue = 0x0c,
|
||||
SetLedPwmBrightness = 0x0d
|
||||
SetLedPwmBrightness = 0x0d,
|
||||
GetModuleProperty = 0x0e
|
||||
}
|
||||
|
||||
export enum EepromOperation {
|
||||
@@ -38,7 +39,8 @@ export enum ConfigBufferId {
|
||||
export enum DevicePropertyIds {
|
||||
DeviceProtocolVersion = 0,
|
||||
ProtocolVersions = 1,
|
||||
ConfigSizes = 2
|
||||
ConfigSizes = 2,
|
||||
CurrentKbootCommand = 3
|
||||
}
|
||||
|
||||
export enum EnumerationModes {
|
||||
@@ -79,3 +81,7 @@ export enum KbootCommands {
|
||||
ping = 1,
|
||||
reset = 2
|
||||
}
|
||||
|
||||
export enum ModulePropertyId {
|
||||
protocolVersions = 0
|
||||
}
|
||||
|
||||
@@ -70,7 +70,7 @@ export class UhkBlhost {
|
||||
let blhostPath;
|
||||
switch (process.platform) {
|
||||
case 'linux':
|
||||
blhostPath = 'linux/amd64/blhost';
|
||||
blhostPath = 'linux/x86_64/blhost';
|
||||
break;
|
||||
case 'darwin':
|
||||
blhostPath = 'mac/blhost';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Device, devices, HID } from 'node-hid';
|
||||
import { LogService } from 'uhk-common';
|
||||
import { CommandLineArgs, LogService } from 'uhk-common';
|
||||
|
||||
import {
|
||||
ConfigBufferId,
|
||||
@@ -25,8 +25,10 @@ export class UhkHidDevice {
|
||||
* @private
|
||||
*/
|
||||
private _device: HID;
|
||||
private _hasPermission = false;
|
||||
|
||||
constructor(private logService: LogService) {
|
||||
constructor(private logService: LogService,
|
||||
private options: CommandLineArgs) {
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -37,9 +39,23 @@ export class UhkHidDevice {
|
||||
* @returns {boolean}
|
||||
*/
|
||||
public hasPermission(): boolean {
|
||||
if (this.options.spe) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
devices();
|
||||
return true;
|
||||
if (this._hasPermission) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!this.deviceConnected()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
this._hasPermission = this.getDevice() !== null;
|
||||
this.close();
|
||||
|
||||
return this._hasPermission;
|
||||
} catch (err) {
|
||||
this.logService.error('[UhkHidDevice] hasPermission', err);
|
||||
}
|
||||
@@ -47,6 +63,21 @@ export class UhkHidDevice {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return with true is an UHK Device is connected to the computer.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
public deviceConnected(): boolean {
|
||||
const connected = devices().some((dev: Device) => dev.vendorId === Constants.VENDOR_ID &&
|
||||
dev.productId === Constants.PRODUCT_ID);
|
||||
|
||||
if (!connected) {
|
||||
this._hasPermission = false;
|
||||
}
|
||||
|
||||
return connected;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send data to the UHK device and wait for the response.
|
||||
* Throw an error when 1st byte of the response is not 0
|
||||
@@ -225,7 +256,7 @@ export class UhkHidDevice {
|
||||
}
|
||||
}
|
||||
|
||||
function kbootKommandName(module: ModuleSlotToI2cAddress): string {
|
||||
function kbootKommandName(module: ModuleSlotToI2cAddress): string {
|
||||
switch (module) {
|
||||
case ModuleSlotToI2cAddress.leftHalf:
|
||||
return 'leftHalf';
|
||||
|
||||
@@ -1,12 +1,24 @@
|
||||
import { LogService } from 'uhk-common';
|
||||
import { EnumerationModes, EnumerationNameToProductId, KbootCommands, ModuleSlotToI2cAddress, ModuleSlotToId } from './constants';
|
||||
import { HardwareModuleInfo, LogService, UhkBuffer } from 'uhk-common';
|
||||
import {
|
||||
EnumerationModes,
|
||||
EnumerationNameToProductId,
|
||||
KbootCommands,
|
||||
ModulePropertyId,
|
||||
ModuleSlotToI2cAddress,
|
||||
ModuleSlotToId
|
||||
} from './constants';
|
||||
import * as path from 'path';
|
||||
import * as fs from 'fs';
|
||||
import { UhkBlhost } from './uhk-blhost';
|
||||
import { UhkHidDevice } from './uhk-hid-device';
|
||||
import { snooze } from './util';
|
||||
import { convertBufferToIntArray, getTransferBuffers, DevicePropertyIds, UsbCommand, ConfigBufferId
|
||||
} from '../index';
|
||||
import {
|
||||
convertBufferToIntArray,
|
||||
getTransferBuffers,
|
||||
DevicePropertyIds,
|
||||
UsbCommand,
|
||||
ConfigBufferId
|
||||
} from '../index';
|
||||
import { LoadConfigurationsResult } from './models/load-configurations-result';
|
||||
|
||||
export class UhkOperations {
|
||||
@@ -26,14 +38,14 @@ export class UhkOperations {
|
||||
await this.blhost.runBlhostCommand([...prefix, 'flash-erase-region', '0xc000', '475136']);
|
||||
await this.blhost.runBlhostCommand([...prefix, 'flash-image', `"${firmwarePath}"`]);
|
||||
await this.blhost.runBlhostCommand([...prefix, 'reset']);
|
||||
this.logService.debug('[UhkOperations] End flashing right firmware');
|
||||
this.logService.debug('[UhkOperations] Right firmware successfully flashed');
|
||||
}
|
||||
|
||||
public async updateLeftModule(firmwarePath = this.getLeftModuleFirmwarePath()) {
|
||||
this.logService.debug('[UhkOperations] Start flashing left module firmware');
|
||||
|
||||
const prefix = [`--usb 0x1d50,0x${EnumerationNameToProductId.buspal.toString(16)}`];
|
||||
const buspalPrefix = [...prefix, `--buspal i2c,${ModuleSlotToI2cAddress.leftHalf},100k`];
|
||||
const buspalPrefix = [...prefix, `--buspal i2c,${ModuleSlotToI2cAddress.leftHalf}`];
|
||||
|
||||
await this.device.reenumerate(EnumerationModes.NormalKeyboard);
|
||||
this.device.close();
|
||||
@@ -42,6 +54,13 @@ export class UhkOperations {
|
||||
await snooze(1000);
|
||||
await this.device.jumpToBootloaderModule(ModuleSlotToId.leftHalf);
|
||||
this.device.close();
|
||||
|
||||
const leftModuleBricked = await this.waitForKbootIdle();
|
||||
if (!leftModuleBricked) {
|
||||
this.logService.error('[UhkOperations] Couldn\'t connect to the left keyboard half.');
|
||||
return;
|
||||
}
|
||||
|
||||
await this.device.reenumerate(EnumerationModes.Buspal);
|
||||
this.device.close();
|
||||
await this.blhost.runBlhostCommandRetry([...buspalPrefix, 'get-property', '1']);
|
||||
@@ -58,7 +77,8 @@ export class UhkOperations {
|
||||
await this.device.sendKbootCommandToModule(ModuleSlotToI2cAddress.leftHalf, KbootCommands.idle);
|
||||
this.device.close();
|
||||
|
||||
this.logService.debug('[UhkOperations] End flashing left module firmware');
|
||||
this.logService.debug('[UhkOperations] Left firmware successfully flashed');
|
||||
this.logService.debug('[UhkOperations] Both left and right firmwares successfully flashed');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -85,8 +105,6 @@ export class UhkOperations {
|
||||
* @returns {Promise<Buffer>}
|
||||
*/
|
||||
public async loadConfiguration(configBufferId: ConfigBufferId): Promise<string> {
|
||||
let response = [];
|
||||
|
||||
const configBufferIdToName = ['HardwareConfig', 'StagingUserConfig', 'ValidatedUserConfig'];
|
||||
const configName = configBufferIdToName[configBufferId];
|
||||
|
||||
@@ -120,7 +138,8 @@ export class UhkOperations {
|
||||
}
|
||||
}
|
||||
}
|
||||
response = convertBufferToIntArray(configBuffer);
|
||||
const response = convertBufferToIntArray(configBuffer);
|
||||
|
||||
return Promise.resolve(JSON.stringify(response));
|
||||
} catch (error) {
|
||||
const errMsg = `[DeviceOperation] ${configName} from eeprom error`;
|
||||
@@ -145,10 +164,10 @@ export class UhkOperations {
|
||||
return configSize;
|
||||
}
|
||||
|
||||
public async saveUserConfiguration(json: string): Promise<void> {
|
||||
public async saveUserConfiguration(buffer: Buffer): Promise<void> {
|
||||
try {
|
||||
this.logService.debug('[DeviceOperation] USB[T]: Write user configuration to keyboard');
|
||||
await this.sendUserConfigToKeyboard(json);
|
||||
await this.sendUserConfigToKeyboard(buffer);
|
||||
this.logService.debug('[DeviceOperation] USB[T]: Write user configuration to EEPROM');
|
||||
await this.device.writeConfigToEeprom(ConfigBufferId.validatedUserConfig);
|
||||
}
|
||||
@@ -160,14 +179,77 @@ export class UhkOperations {
|
||||
}
|
||||
}
|
||||
|
||||
public async waitForKbootIdle(): Promise<boolean> {
|
||||
const timeoutTime = new Date(new Date().getTime() + 30000);
|
||||
|
||||
while (new Date() < timeoutTime) {
|
||||
const buffer = await this.device.write(new Buffer([UsbCommand.GetProperty, DevicePropertyIds.CurrentKbootCommand]));
|
||||
this.device.close();
|
||||
|
||||
if (buffer[1] === 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// tslint:disable-next-line: max-line-length
|
||||
this.logService.info('[DeviceOperation] Cannot ping the bootloader. Please reconnect the left keyboard half. It probably needs several tries, so keep reconnecting until you see this message.');
|
||||
|
||||
await snooze(1000);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public async getLeftModuleVersionInfo(): Promise<HardwareModuleInfo> {
|
||||
try {
|
||||
this.logService.debug('[DeviceOperation] USB[T]: Read left module version information');
|
||||
|
||||
const command = new Buffer([
|
||||
UsbCommand.GetModuleProperty,
|
||||
ModuleSlotToId.leftHalf,
|
||||
ModulePropertyId.protocolVersions
|
||||
]);
|
||||
|
||||
const buffer = await this.device.write(command);
|
||||
const uhkBuffer = UhkBuffer.fromArray(convertBufferToIntArray(buffer));
|
||||
// skip the first 2 byte
|
||||
uhkBuffer.readUInt16();
|
||||
|
||||
return {
|
||||
moduleProtocolVersion: `${uhkBuffer.readUInt16()}.${uhkBuffer.readUInt16()}.${uhkBuffer.readUInt16()}`,
|
||||
firmwareVersion: `${uhkBuffer.readUInt16()}.${uhkBuffer.readUInt16()}.${uhkBuffer.readUInt16()}`
|
||||
};
|
||||
}
|
||||
catch (error) {
|
||||
this.logService.error('[DeviceOperation] Could not read left module version information', error);
|
||||
}
|
||||
|
||||
return {
|
||||
moduleProtocolVersion: '',
|
||||
firmwareVersion: ''
|
||||
};
|
||||
}
|
||||
|
||||
public async getRightModuleVersionInfo(): Promise<HardwareModuleInfo> {
|
||||
this.logService.debug('[DeviceOperation] USB[T]: Read right module version information');
|
||||
|
||||
const command = new Buffer([UsbCommand.GetProperty, DevicePropertyIds.ProtocolVersions]);
|
||||
const buffer = await this.device.write(command);
|
||||
const uhkBuffer = UhkBuffer.fromArray(convertBufferToIntArray(buffer));
|
||||
// skip the first byte
|
||||
uhkBuffer.readUInt8();
|
||||
|
||||
return {
|
||||
firmwareVersion: `${uhkBuffer.readUInt16()}.${uhkBuffer.readUInt16()}.${uhkBuffer.readUInt16()}`
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* IpcMain handler. Send the UserConfiguration to the UHK Device and send a response with the result.
|
||||
* @param {string} json - UserConfiguration in JSON format
|
||||
* @param {Buffer} buffer - UserConfiguration buffer
|
||||
* @returns {Promise<void>}
|
||||
* @private
|
||||
*/
|
||||
private async sendUserConfigToKeyboard(json: string): Promise<void> {
|
||||
const buffer: Buffer = new Buffer(JSON.parse(json).data);
|
||||
private async sendUserConfigToKeyboard(buffer: Buffer): Promise<void> {
|
||||
const fragments = getTransferBuffers(UsbCommand.WriteStagingUserConfig, buffer);
|
||||
for (const fragment of fragments) {
|
||||
await this.device.write(fragment);
|
||||
|
||||
@@ -89,7 +89,7 @@ export async function retry(command: Function, maxTry = 3, logService?: LogServi
|
||||
throw err;
|
||||
} else {
|
||||
if (logService) {
|
||||
logService.error(`[retry] failed, but try run FUNCTION:\n ${command}, \n retry: ${retryCount}`);
|
||||
logService.info(`[retry] failed, but try run FUNCTION:\n ${command}, \n retry: ${retryCount}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,11 +21,13 @@
|
||||
"prefix": "app",
|
||||
"styles": [
|
||||
"../node_modules/bootstrap/dist/css/bootstrap.min.css",
|
||||
"../node_modules/nouislider/distribute/nouislider.min.css",
|
||||
"styles.scss"
|
||||
],
|
||||
"scripts": [
|
||||
"../node_modules/bootstrap/dist/js/bootstrap.js",
|
||||
"../node_modules/select2/dist/js/select2.full.js"
|
||||
"../node_modules/select2/dist/js/select2.full.js",
|
||||
"../node_modules/nouislider/distribute/nouislider.js"
|
||||
],
|
||||
"environmentSource": "environments/environment.ts",
|
||||
"environments": {
|
||||
|
||||
3533
packages/uhk-web/package-lock.json
generated
@@ -37,7 +37,7 @@
|
||||
"@types/jasmine": "2.5.53",
|
||||
"@types/jasminewd2": "2.0.2",
|
||||
"@types/jquery": "3.2.9",
|
||||
"@types/node-hid": "0.5.2",
|
||||
"@types/lodash-es": "4.17.0",
|
||||
"@types/usb": "1.1.3",
|
||||
"angular-confirmation-popover": "3.2.0",
|
||||
"angular-notifier": "2.0.0",
|
||||
@@ -67,11 +67,13 @@
|
||||
"karma-jasmine": "1.1.0",
|
||||
"karma-jasmine-html-reporter": "0.2.2",
|
||||
"less-loader": "4.0.5",
|
||||
"lodash": "4.17.4",
|
||||
"lodash-es": "4.17.4",
|
||||
"ng2-dragula": "1.5.0",
|
||||
"ng2-nouislider": "^1.7.6",
|
||||
"ng2-select2": "1.0.0-beta.10",
|
||||
"ngx-clipboard": "8.0.0",
|
||||
"ngrx-store-freeze": "0.1.9",
|
||||
"node-hid": "0.5.4",
|
||||
"nouislider": "^10.1.0",
|
||||
"postcss-loader": "1.3.3",
|
||||
"postcss-url": "5.1.2",
|
||||
"protractor": "5.1.2",
|
||||
|
||||
@@ -7,9 +7,6 @@
|
||||
<div id="main-content" class="main-content">
|
||||
<router-outlet></router-outlet>
|
||||
</div>
|
||||
<div class="github-fork-ribbon" *ngIf="!(runningInElectron$ | async)">
|
||||
<a class="" href="https://github.com/UltimateHackingKeyboard/agent" title="Fork me on GitHub">Fork me on GitHub</a>
|
||||
</div>
|
||||
<notifier-container></notifier-container>
|
||||
<progress-button class="save-to-keyboard-button"
|
||||
*ngIf="(saveToKeyboardState$ | async).showButton"
|
||||
|
||||
@@ -1,36 +1,3 @@
|
||||
/* GitHub ribbon */
|
||||
.github-fork-ribbon {
|
||||
background-color: #a00;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
position: fixed;
|
||||
right: -50px;
|
||||
bottom: 40px;
|
||||
z-index: 2000;
|
||||
/* stylelint-disable indentation */
|
||||
-webkit-transform: rotate(-45deg);
|
||||
-moz-transform: rotate(-45deg);
|
||||
-ms-transform: rotate(-45deg);
|
||||
-o-transform: rotate(-45deg);
|
||||
transform: rotate(-45deg);
|
||||
-webkit-box-shadow: 0 0 10px #888;
|
||||
-moz-box-shadow: 0 0 10px #888;
|
||||
box-shadow: 0 0 10px #888;
|
||||
/* stylelint-enable indentation */
|
||||
|
||||
a {
|
||||
border: 1px solid #faa;
|
||||
color: #fff;
|
||||
display: block;
|
||||
font: bold 81.25% 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
||||
margin: 1px 0;
|
||||
padding: 10px 50px;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
text-shadow: 0 0 5px #444;
|
||||
}
|
||||
}
|
||||
|
||||
main-app {
|
||||
min-height: 100%;
|
||||
height: 100%;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Component, HostListener, ViewEncapsulation } from '@angular/core';
|
||||
import { Component, ViewEncapsulation } from '@angular/core';
|
||||
import { animate, style, transition, trigger } from '@angular/animations';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { Action, Store } from '@ngrx/store';
|
||||
@@ -14,7 +14,6 @@ import {
|
||||
saveToKeyboardState
|
||||
} from './store';
|
||||
import { ProgressButtonState } from './store/reducers/progress-button-state';
|
||||
import { SaveUserConfigInBinaryFileAction, SaveUserConfigInJsonFileAction } from './store/actions/user-config';
|
||||
|
||||
@Component({
|
||||
selector: 'main-app',
|
||||
|
||||
@@ -12,7 +12,7 @@ import { UhkDeviceConnectedGuard } from './services/uhk-device-connected.guard';
|
||||
import { UhkDeviceUninitializedGuard } from './services/uhk-device-uninitialized.guard';
|
||||
import { UhkDeviceInitializedGuard } from './services/uhk-device-initialized.guard';
|
||||
import { MainPage } from './pages/main-page/main.page';
|
||||
import { settingsRoutes } from './components/settings/settings.routes';
|
||||
import { agentRoutes } from './components/agent/agent.routes';
|
||||
import { LoadingDevicePageComponent } from './pages/loading-page/loading-device.page';
|
||||
import { UhkDeviceLoadingGuard } from './services/uhk-device-loading.guard';
|
||||
import { UhkDeviceLoadedGuard } from './services/uhk-device-loaded.guard';
|
||||
@@ -42,7 +42,7 @@ const appRoutes: Routes = [
|
||||
...keymapRoutes,
|
||||
...macroRoutes,
|
||||
...addOnRoutes,
|
||||
...settingsRoutes
|
||||
...agentRoutes
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
<div class="row">
|
||||
<h1 class="col-xs-12 pane-title">
|
||||
<i class="uhk-icon uhk-icon-agent-icon"></i>
|
||||
<span>About</span>
|
||||
</h1>
|
||||
<div class="col-xs-12">
|
||||
<div class="agent-version">Agent version: <span class="text-bold">{{version}}</span></div>
|
||||
<div><a class="link-github" (click)="openAgentGitHubPage($event)">Agent on GitHub</a></div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,20 @@
|
||||
:host {
|
||||
overflow-y: auto;
|
||||
display: block;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.agent {
|
||||
&-version {
|
||||
margin-bottom: 1rem;
|
||||
|
||||
span {
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.link-github {
|
||||
cursor: pointer;
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { Constants } from 'uhk-common';
|
||||
|
||||
import { AppState } from '../../../store';
|
||||
import { getVersions } from '../../../util';
|
||||
import { OpenUrlInNewWindowAction } from '../../../store/actions/app';
|
||||
|
||||
@Component({
|
||||
selector: 'about-page',
|
||||
templateUrl: './about.component.html',
|
||||
styleUrls: ['./about.component.scss'],
|
||||
host: {
|
||||
'class': 'container-fluid'
|
||||
}
|
||||
})
|
||||
export class AboutComponent {
|
||||
version: string = getVersions().version;
|
||||
|
||||
constructor(private store: Store<AppState>) {
|
||||
}
|
||||
|
||||
openAgentGitHubPage(event) {
|
||||
event.preventDefault();
|
||||
this.store.dispatch(new OpenUrlInNewWindowAction(Constants.AGENT_GITHUB_URL));
|
||||
}
|
||||
}
|
||||
15
packages/uhk-web/src/app/components/agent/agent.routes.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { Routes } from '@angular/router';
|
||||
|
||||
import { SettingsComponent } from './settings/settings.component';
|
||||
import { AboutComponent } from './about/about.component';
|
||||
|
||||
export const agentRoutes: Routes = [
|
||||
{
|
||||
path: 'settings',
|
||||
component: SettingsComponent
|
||||
},
|
||||
{
|
||||
path: 'about',
|
||||
component: AboutComponent
|
||||
}
|
||||
];
|
||||
3
packages/uhk-web/src/app/components/agent/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export * from './agent.routes';
|
||||
export * from './about/about.component';
|
||||
export * from './settings/settings.component';
|
||||
@@ -2,13 +2,14 @@ import { Component } from '@angular/core';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
|
||||
import { AppState, getAutoUpdateSettings, getCheckingForUpdate } from '../../store';
|
||||
import { AppState, getAutoUpdateSettings, getCheckingForUpdate } from '../../../store';
|
||||
import {
|
||||
CheckForUpdateNowAction,
|
||||
ToggleCheckForUpdateOnStartupAction,
|
||||
TogglePreReleaseFlagAction
|
||||
} from '../../store/actions/auto-update-settings';
|
||||
import { AutoUpdateSettings } from '../../models/auto-update-settings';
|
||||
} from '../../../store/actions/auto-update-settings';
|
||||
import { AutoUpdateSettings } from '../../../models/auto-update-settings';
|
||||
import { getVersions } from '../../../util';
|
||||
|
||||
@Component({
|
||||
selector: 'settings',
|
||||
@@ -19,8 +20,7 @@ import { AutoUpdateSettings } from '../../models/auto-update-settings';
|
||||
}
|
||||
})
|
||||
export class SettingsComponent {
|
||||
// TODO: From where do we get the version number? The electron gives back in main process, but the web...
|
||||
version = '1.0.0';
|
||||
version: string = getVersions().version;
|
||||
autoUpdateSettings$: Observable<AutoUpdateSettings>;
|
||||
checkingForUpdate$: Observable<boolean>;
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
<div class="form-group">
|
||||
<label class="col-sm-2 control-label">Version:</label>
|
||||
<div class="col-sm-10">
|
||||
<p class="form-control-static">{{version}}</p>
|
||||
<p>{{version}}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -9,14 +9,16 @@
|
||||
|
||||
<ul class="list-unstyled btn-list">
|
||||
<li>
|
||||
Download device configuration in
|
||||
<span role="button" class="btn-link" (click)="saveConfigurationInJSONFormat()">JSON</span> or
|
||||
<span role="button" class="btn-link" (click)="saveConfigurationInBINFormat()">binary</span> format.
|
||||
<button class="btn btn-default"
|
||||
(click)="exportUserConfiguration($event)">Export device configuration
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<button class="btn btn-default"
|
||||
>Upload device configuration
|
||||
</button>
|
||||
<label class="btn btn-default btn-file">
|
||||
Import device configuration
|
||||
<input type="file"
|
||||
(change)="changeFile($event)">
|
||||
</label>
|
||||
</li>
|
||||
<li>
|
||||
<button class="btn btn-danger"
|
||||
|
||||
@@ -3,7 +3,11 @@ import { Store } from '@ngrx/store';
|
||||
|
||||
import { AppState } from '../../../store';
|
||||
import { ResetUserConfigurationAction } from '../../../store/actions/device';
|
||||
import { SaveUserConfigInBinaryFileAction, SaveUserConfigInJsonFileAction } from '../../../store/actions/user-config';
|
||||
import {
|
||||
LoadUserConfigurationFromFileAction,
|
||||
SaveUserConfigInBinaryFileAction,
|
||||
SaveUserConfigInJsonFileAction
|
||||
} from '../../../store/actions/user-config';
|
||||
|
||||
@Component({
|
||||
selector: 'device-settings',
|
||||
@@ -29,4 +33,25 @@ export class DeviceConfigurationComponent {
|
||||
saveConfigurationInBINFormat() {
|
||||
this.store.dispatch(new SaveUserConfigInBinaryFileAction());
|
||||
}
|
||||
|
||||
exportUserConfiguration(event: MouseEvent) {
|
||||
if (event.shiftKey) {
|
||||
this.saveConfigurationInBINFormat();
|
||||
} else {
|
||||
this.saveConfigurationInJSONFormat();
|
||||
}
|
||||
}
|
||||
|
||||
changeFile(event): void {
|
||||
const files = event.srcElement.files;
|
||||
const fileReader = new FileReader();
|
||||
fileReader.onloadend = function () {
|
||||
const arrayBuffer = new Uint8Array(fileReader.result);
|
||||
this.store.dispatch(new LoadUserConfigurationFromFileAction({
|
||||
filename: event.srcElement.value,
|
||||
data: Array.from(arrayBuffer)
|
||||
}));
|
||||
}.bind(this);
|
||||
fileReader.readAsArrayBuffer(files[0]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@ import { Routes } from '@angular/router';
|
||||
import { DeviceConfigurationComponent } from './configuration/device-configuration.component';
|
||||
import { DeviceFirmwareComponent } from './firmware/device-firmware.component';
|
||||
import { MouseSpeedComponent } from './mouse-speed/mouse-speed.component';
|
||||
import { LEDBrightnessComponent } from './led-brightness/led-brightness.component';
|
||||
import { RestoreConfigurationComponent } from './restore-configuration/restore-configuration.component';
|
||||
|
||||
export const deviceRoutes: Routes = [
|
||||
{
|
||||
@@ -21,9 +23,17 @@ export const deviceRoutes: Routes = [
|
||||
path: 'mouse-speed',
|
||||
component: MouseSpeedComponent
|
||||
},
|
||||
{
|
||||
path: 'led-brightness',
|
||||
component: LEDBrightnessComponent
|
||||
},
|
||||
{
|
||||
path: 'firmware',
|
||||
component: DeviceFirmwareComponent
|
||||
},
|
||||
{
|
||||
path: 'restore-user-configuration',
|
||||
component: RestoreConfigurationComponent
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -6,23 +6,36 @@
|
||||
<i class="fa fa-sliders"></i>
|
||||
<span>Firmware</span>
|
||||
</h1>
|
||||
|
||||
<p>
|
||||
Flash firmware {{ (getAgentVersionInfo$ | async).firmwareVersion }} (bundled with Agent)
|
||||
<button class="btn btn-primary"
|
||||
[disabled]="flashFirmwareButtonDisbabled$ | async"
|
||||
(click)="onUpdateFirmware()">Flash firmware
|
||||
</button>
|
||||
Firmware {{ hardwareModules.leftModuleInfo.firmwareVersion }} is running on the left keyboard half.<br>
|
||||
Firmware {{ hardwareModules.rightModuleInfo.firmwareVersion }} is running on the right keyboard half.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<i>
|
||||
Please note that the firmware update process may sometimes fail. If if fails then
|
||||
simply retry until it succeeds. If the left half becomes unresponsive after a failed
|
||||
update then retry and follow the instructions displayed during the update to fix it.
|
||||
We'll make the firmware update process more robust.
|
||||
</i>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Flash firmware file <input id="firmware-file-select"
|
||||
type="file"
|
||||
[disabled]="flashFirmwareButtonDisbabled$ | async"
|
||||
(change)="changeFile($event)">
|
||||
<button class="btn btn-primary"
|
||||
[disabled]="flashFirmwareButtonDisbabled$ | async"
|
||||
(click)="onUpdateFirmwareWithFile()">Flash firmware
|
||||
(click)="onUpdateFirmware()">
|
||||
Flash firmware {{ (getAgentVersionInfo$ | async).firmwareVersion }} (bundled with Agent)
|
||||
</button>
|
||||
<label class="btn btn-primary btn-file"
|
||||
[class.disabled]="flashFirmwareButtonDisbabled$ | async">
|
||||
Choose firmware file and flash it
|
||||
<input id="firmware-file-select"
|
||||
type="file"
|
||||
accept=".tar.bz2"
|
||||
[disabled]="flashFirmwareButtonDisbabled$ | async"
|
||||
(change)="changeFile($event)">
|
||||
</label>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -2,9 +2,16 @@ import { Component, ElementRef, OnDestroy, ViewChild } from '@angular/core';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { Subscription } from 'rxjs/Subscription';
|
||||
import { VersionInformation } from 'uhk-common';
|
||||
import { HardwareModules, VersionInformation } from 'uhk-common';
|
||||
|
||||
import { AppState, firmwareOkButtonDisabled, flashFirmwareButtonDisbabled, getAgentVersionInfo, xtermLog } from '../../../store';
|
||||
import {
|
||||
AppState,
|
||||
firmwareOkButtonDisabled,
|
||||
flashFirmwareButtonDisbabled,
|
||||
getAgentVersionInfo,
|
||||
getHardwareModules,
|
||||
xtermLog
|
||||
} from '../../../store';
|
||||
import { UpdateFirmwareAction, UpdateFirmwareOkButtonAction, UpdateFirmwareWithAction } from '../../../store/actions/device';
|
||||
import { XtermLog } from '../../../models/xterm-log';
|
||||
|
||||
@@ -22,8 +29,9 @@ export class DeviceFirmwareComponent implements OnDestroy {
|
||||
xtermLogSubscription: Subscription;
|
||||
getAgentVersionInfo$: Observable<VersionInformation>;
|
||||
firmwareOkButtonDisabled$: Observable<boolean>;
|
||||
hardwareModulesSubscription: Subscription;
|
||||
hardwareModules: HardwareModules;
|
||||
|
||||
arrayBuffer: Uint8Array;
|
||||
@ViewChild('scrollMe') divElement: ElementRef;
|
||||
|
||||
constructor(private store: Store<AppState>) {
|
||||
@@ -38,24 +46,20 @@ export class DeviceFirmwareComponent implements OnDestroy {
|
||||
});
|
||||
this.getAgentVersionInfo$ = store.select(getAgentVersionInfo);
|
||||
this.firmwareOkButtonDisabled$ = store.select(firmwareOkButtonDisabled);
|
||||
this.hardwareModulesSubscription = store.select(getHardwareModules).subscribe(data => {
|
||||
this.hardwareModules = data;
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.xtermLogSubscription.unsubscribe();
|
||||
this.hardwareModulesSubscription.unsubscribe();
|
||||
}
|
||||
|
||||
onUpdateFirmware(): void {
|
||||
this.store.dispatch(new UpdateFirmwareAction());
|
||||
}
|
||||
|
||||
onUpdateFirmwareWithFile(): void {
|
||||
if (!this.arrayBuffer) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.store.dispatch(new UpdateFirmwareWithAction(Array.prototype.slice.call(this.arrayBuffer)));
|
||||
}
|
||||
|
||||
onOkButtonClick(): void {
|
||||
this.store.dispatch(new UpdateFirmwareOkButtonAction());
|
||||
}
|
||||
@@ -64,14 +68,13 @@ export class DeviceFirmwareComponent implements OnDestroy {
|
||||
const files = event.srcElement.files;
|
||||
|
||||
if (files.length === 0) {
|
||||
this.arrayBuffer = null;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const fileReader = new FileReader();
|
||||
fileReader.onloadend = function () {
|
||||
this.arrayBuffer = new Uint8Array(fileReader.result);
|
||||
const arrayBuffer = new Uint8Array(fileReader.result);
|
||||
this.store.dispatch(new UpdateFirmwareWithAction(Array.prototype.slice.call(arrayBuffer)));
|
||||
}.bind(this);
|
||||
fileReader.readAsArrayBuffer(files[0]);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
export * from './configuration/device-configuration.component';
|
||||
export * from './firmware/device-firmware.component';
|
||||
export * from './mouse-speed/mouse-speed.component';
|
||||
export * from './led-brightness/led-brightness.component';
|
||||
export * from './restore-configuration/restore-configuration.component';
|
||||
export * from './device.routes';
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
<h1>
|
||||
<i class="fa fa-sliders"></i>
|
||||
<span>LED brightness</span>
|
||||
</h1>
|
||||
<div class="row led-setting">
|
||||
<div class="col-xs-12 col-md-6">
|
||||
<slider-wrapper
|
||||
label="LED display icon and layer texts brightness"
|
||||
[min]="0"
|
||||
[max]="255"
|
||||
[step]="1"
|
||||
[pips]="sliderPips"
|
||||
[(ngModel)]="iconsAndLayerTextsBrightness"
|
||||
(ngModelChange)="onSetPropertyValue('iconsAndLayerTextsBrightness', $event)"></slider-wrapper>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row led-setting">
|
||||
<div class="col-xs-12 col-md-6">
|
||||
<slider-wrapper
|
||||
label="LED display alphanumeric segments brightness"
|
||||
[min]="0"
|
||||
[max]="255"
|
||||
[step]="1"
|
||||
[pips]="sliderPips"
|
||||
[(ngModel)]="alphanumericSegmentsBrightness"
|
||||
(ngModelChange)="onSetPropertyValue('alphanumericSegmentsBrightness', $event)"></slider-wrapper>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row led-setting">
|
||||
<div class="col-xs-12 col-md-6">
|
||||
<slider-wrapper
|
||||
label="Key backlight brightness"
|
||||
[min]="0"
|
||||
[max]="255"
|
||||
[step]="1"
|
||||
[pips]="sliderPips"
|
||||
[(ngModel)]="keyBacklightBrightness"
|
||||
(ngModelChange)="onSetPropertyValue('keyBacklightBrightness', $event)"></slider-wrapper>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,15 @@
|
||||
:host {
|
||||
overflow-y: auto;
|
||||
display: block;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
|
||||
label {
|
||||
display: block;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.led-setting {
|
||||
margin-bottom: 6rem;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
import { AfterViewInit, Component, OnInit, OnDestroy, ViewChildren, QueryList } from '@angular/core';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { AppState, getUserConfiguration } from '../../../store';
|
||||
import { SetUserConfigurationValueAction } from '../../../store/actions/user-config';
|
||||
import { SliderPips } from '../../slider-wrapper/slider-wrapper.component';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { Subscription } from 'rxjs/Subscription';
|
||||
import { UserConfiguration } from 'uhk-common';
|
||||
|
||||
@Component({
|
||||
selector: 'device-led-brightness',
|
||||
templateUrl: './led-brightness.component.html',
|
||||
styleUrls: ['./led-brightness.component.scss'],
|
||||
host: {
|
||||
'class': 'container-fluid'
|
||||
}
|
||||
})
|
||||
export class LEDBrightnessComponent implements OnInit, OnDestroy {
|
||||
public iconsAndLayerTextsBrightness: number = 0;
|
||||
public alphanumericSegmentsBrightness: number = 0;
|
||||
public keyBacklightBrightness: number = 0;
|
||||
public sliderPips: SliderPips = {
|
||||
mode: 'positions',
|
||||
values: [0, 50, 100],
|
||||
density: 6,
|
||||
stepped: true
|
||||
};
|
||||
|
||||
private userConfig$: Store<UserConfiguration>;
|
||||
private userConfigSubscription: Subscription;
|
||||
|
||||
constructor(private store: Store<AppState>) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.userConfig$ = this.store.select(getUserConfiguration);
|
||||
this.userConfigSubscription = this.userConfig$.subscribe(config => {
|
||||
this.iconsAndLayerTextsBrightness = config.iconsAndLayerTextsBrightness;
|
||||
this.alphanumericSegmentsBrightness = config.alphanumericSegmentsBrightness;
|
||||
this.keyBacklightBrightness = config.keyBacklightBrightness;
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.userConfigSubscription.unsubscribe();
|
||||
}
|
||||
|
||||
onSetPropertyValue(propertyName: string, value: number): void {
|
||||
this.store.dispatch(new SetUserConfigurationValueAction({
|
||||
propertyName,
|
||||
value
|
||||
}));
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,41 @@
|
||||
<i class="fa fa-sliders"></i>
|
||||
<span>Mouse speed</span>
|
||||
</h1>
|
||||
<p>
|
||||
Coming soon ...
|
||||
</p>
|
||||
<h3>Mouse pointer speed</h3>
|
||||
<div class="row mouse-speed-setting" *ngFor="let prop of moveProps">
|
||||
<div class="col-xs-12 col-md-6">
|
||||
<slider-wrapper
|
||||
[label]="prop.title"
|
||||
[tooltip]="prop.tooltip"
|
||||
[min]="moveSettings.min"
|
||||
[max]="moveSettings.max"
|
||||
[step]="moveSettings.step"
|
||||
[pips]="sliderPips"
|
||||
[valueUnit]="prop.valueUnit"
|
||||
[(ngModel)]="prop.value"
|
||||
(ngModelChange)="onSetPropertyValue(prop.prop, $event)"></slider-wrapper>
|
||||
</div>
|
||||
</div>
|
||||
<h3>Mouse scroll speed</h3>
|
||||
<div class="row mouse-speed-setting" *ngFor="let prop of scrollProps">
|
||||
<div class="col-xs-12 col-md-6">
|
||||
<slider-wrapper
|
||||
[label]="prop.title"
|
||||
[tooltip]="prop.tooltip"
|
||||
[min]="scrollSettings.min"
|
||||
[max]="scrollSettings.max"
|
||||
[step]="scrollSettings.step"
|
||||
[pips]="sliderPips"
|
||||
[valueUnit]="prop.valueUnit"
|
||||
[(ngModel)]="prop.value"
|
||||
(ngModelChange)="onSetPropertyValue(prop.prop, $event)"></slider-wrapper>
|
||||
</div>
|
||||
</div>
|
||||
<button class="btn btn-danger mouse-speed-reset-button"
|
||||
mwlConfirmationPopover
|
||||
title="Are you sure?"
|
||||
placement="top"
|
||||
confirmText="Yes"
|
||||
cancelText="No"
|
||||
(confirm)="resetToDefault()">Reset speeds to default
|
||||
</button>
|
||||
@@ -4,4 +4,25 @@
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
|
||||
label {
|
||||
display: block;
|
||||
font-weight: normal;
|
||||
|
||||
icon {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
|
||||
.mouse-speed-reset-button {
|
||||
display: block;
|
||||
margin-bottom: 4rem;
|
||||
}
|
||||
|
||||
.mouse-speed-setting {
|
||||
margin-bottom: 6rem;
|
||||
|
||||
+ h3 {
|
||||
margin-top: 2rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,14 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { Component, OnInit, OnDestroy } from '@angular/core';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { AppState, getUserConfiguration } from '../../../store';
|
||||
import { SetUserConfigurationValueAction } from '../../../store/actions/user-config';
|
||||
import { DefaultUserConfigurationService } from '../../../services/default-user-configuration.service';
|
||||
import { SliderPips, SliderProps } from '../../slider-wrapper/slider-wrapper.component';
|
||||
import { Subscription } from 'rxjs/Subscription';
|
||||
import { UserConfiguration } from 'uhk-common';
|
||||
import { ResetMouseSpeedSettingsAction } from '../../../store/actions/device';
|
||||
|
||||
const MOUSE_MOVE_VALUE_MULTIPLIER = 25;
|
||||
|
||||
@Component({
|
||||
selector: 'device-mouse-speed',
|
||||
@@ -8,5 +18,131 @@ import { Component } from '@angular/core';
|
||||
'class': 'container-fluid'
|
||||
}
|
||||
})
|
||||
export class MouseSpeedComponent {
|
||||
export class MouseSpeedComponent implements OnInit, OnDestroy {
|
||||
public moveProps = [
|
||||
{
|
||||
prop: 'mouseMoveInitialSpeed',
|
||||
title: 'Initial speed',
|
||||
tooltip: 'When mouse movement begins, this is the starting speed.',
|
||||
valueUnit: 'px/s',
|
||||
value: 0
|
||||
},
|
||||
{
|
||||
prop: 'mouseMoveBaseSpeed',
|
||||
title: 'Base speed',
|
||||
tooltip: 'This speed is reached after the initial moving speed sufficiently ramps up.',
|
||||
valueUnit: 'px/s',
|
||||
value: 0
|
||||
},
|
||||
{
|
||||
prop: 'mouseMoveAcceleration',
|
||||
title: 'Acceleration',
|
||||
tooltip: 'The rate of acceleration from the initial movement speed to the base speed.',
|
||||
valueUnit: 'px/s²',
|
||||
value: 0
|
||||
},
|
||||
{
|
||||
prop: 'mouseMoveDeceleratedSpeed',
|
||||
title: 'Decelerated speed',
|
||||
tooltip: 'This speed is used while moving with the <i>decelerate key</i> pressed.',
|
||||
valueUnit: 'px/s',
|
||||
value: 0
|
||||
},
|
||||
{
|
||||
prop: 'mouseMoveAcceleratedSpeed',
|
||||
title: 'Accelerated speed',
|
||||
tooltip: 'This speed is used while moving with the <i>accelerate key</i> pressed.',
|
||||
valueUnit: 'px/s',
|
||||
value: 0
|
||||
}
|
||||
];
|
||||
|
||||
public scrollProps = [
|
||||
{
|
||||
prop: 'mouseScrollInitialSpeed',
|
||||
title: 'Initial speed',
|
||||
tooltip: 'When mouse scrolling begins, this is the starting speed.',
|
||||
valueUnit: 'pulse/s',
|
||||
value: 0
|
||||
},
|
||||
{
|
||||
prop: 'mouseScrollBaseSpeed',
|
||||
title: 'Base speed',
|
||||
tooltip: 'This speed is reached after the initial scrolling speed sufficiently ramps up.',
|
||||
valueUnit: 'pulse/s',
|
||||
value: 0
|
||||
},
|
||||
{
|
||||
prop: 'mouseScrollAcceleration',
|
||||
title: 'Acceleration',
|
||||
tooltip: 'The rate of acceleration from the initial scrolling speed to the base speed.',
|
||||
valueUnit: 'pulse/s²',
|
||||
value: 0
|
||||
},
|
||||
{
|
||||
prop: 'mouseScrollDeceleratedSpeed',
|
||||
title: 'Decelerated speed',
|
||||
tooltip: 'This speed is used while scrolling with the <i>decelerate key</i> pressed.',
|
||||
valueUnit: 'pulse/s',
|
||||
value: 0
|
||||
},
|
||||
{
|
||||
prop: 'mouseScrollAcceleratedSpeed',
|
||||
title: 'Accelerated speed',
|
||||
tooltip: 'This speed is used while scrolling with the <i>accelerate key</i> pressed.',
|
||||
valueUnit: 'pulse/s',
|
||||
value: 0
|
||||
}
|
||||
];
|
||||
|
||||
public sliderPips: SliderPips = {
|
||||
mode: 'positions',
|
||||
values: [0, 50, 100],
|
||||
density: 6,
|
||||
stepped: true
|
||||
};
|
||||
|
||||
public moveSettings: SliderProps = {
|
||||
min: MOUSE_MOVE_VALUE_MULTIPLIER,
|
||||
max: 6375,
|
||||
step: MOUSE_MOVE_VALUE_MULTIPLIER
|
||||
};
|
||||
|
||||
public scrollSettings: SliderProps = {
|
||||
min: 1,
|
||||
max: 255,
|
||||
step: 1
|
||||
};
|
||||
|
||||
private userConfig$: Store<UserConfiguration>;
|
||||
private userConfigSubscription: Subscription;
|
||||
|
||||
constructor(private store: Store<AppState>, private defaultUserConfigurationService: DefaultUserConfigurationService) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.userConfig$ = this.store.select(getUserConfiguration);
|
||||
this.userConfigSubscription = this.userConfig$.subscribe(config => {
|
||||
this.moveProps.forEach(moveProp => {
|
||||
moveProp.value = config[moveProp.prop] * MOUSE_MOVE_VALUE_MULTIPLIER || 0;
|
||||
});
|
||||
this.scrollProps.forEach(scrollProp => {
|
||||
scrollProp.value = config[scrollProp.prop] || 0;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.userConfigSubscription.unsubscribe();
|
||||
}
|
||||
|
||||
onSetPropertyValue(propertyName: string, value: number): void {
|
||||
this.store.dispatch(new SetUserConfigurationValueAction({
|
||||
propertyName,
|
||||
value: propertyName.indexOf('mouseMove') !== -1 ? value / MOUSE_MOVE_VALUE_MULTIPLIER : value
|
||||
}));
|
||||
}
|
||||
|
||||
resetToDefault() {
|
||||
this.store.dispatch(new ResetMouseSpeedSettingsAction());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
<h1>
|
||||
<i class="fa fa-exclamation-circle"></i>
|
||||
<span>Fix configuration</span>
|
||||
</h1>
|
||||
|
||||
<p>
|
||||
Your on-board device configuration is invalid.
|
||||
</p>
|
||||
<button class="btn btn-primary"
|
||||
*ngIf="state.hasBackupUserConfiguration"
|
||||
[disabled]="state.restoringUserConfiguration"
|
||||
(click)="restoreUserConfiguration()"> Restore the last valid device configuration
|
||||
</button>
|
||||
|
||||
<button class="btn btn-danger"
|
||||
*ngIf="!state.hasBackupUserConfiguration"
|
||||
[disabled]="state.restoringUserConfiguration"
|
||||
(click)="resetUserConfiguration()">Reset device configuration
|
||||
</button>
|
||||
@@ -0,0 +1,10 @@
|
||||
:host {
|
||||
overflow-y: auto;
|
||||
display: block;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
|
||||
p {
|
||||
margin: 1.5rem 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { Subscription } from 'rxjs/Subscription';
|
||||
|
||||
import { AppState, getBackupUserConfigurationState } from '../../../store';
|
||||
import { ResetUserConfigurationAction, RestoreUserConfigurationFromBackupAction } from '../../../store/actions/device';
|
||||
import { RestoreConfigurationState } from '../../../models/restore-configuration-state';
|
||||
|
||||
@Component({
|
||||
selector: 'restore-configuration',
|
||||
templateUrl: './restore-configuration.component.html',
|
||||
styleUrls: ['./restore-configuration.component.scss'],
|
||||
host: {
|
||||
'class': 'container-fluid'
|
||||
}
|
||||
})
|
||||
export class RestoreConfigurationComponent implements OnInit, OnDestroy {
|
||||
state: RestoreConfigurationState;
|
||||
|
||||
private stateSubscription: Subscription;
|
||||
|
||||
constructor(private store: Store<AppState>,
|
||||
private cdRef: ChangeDetectorRef) {
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
if (this.stateSubscription) {
|
||||
this.stateSubscription.unsubscribe();
|
||||
}
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.stateSubscription = this.store
|
||||
.select(getBackupUserConfigurationState)
|
||||
.subscribe(data => {
|
||||
this.state = data;
|
||||
this.cdRef.markForCheck();
|
||||
});
|
||||
}
|
||||
|
||||
resetUserConfiguration() {
|
||||
this.store.dispatch(new ResetUserConfigurationAction());
|
||||
}
|
||||
|
||||
restoreUserConfiguration(): void {
|
||||
this.store.dispatch(new RestoreUserConfigurationFromBackupAction());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
<div class="text-center">
|
||||
<span *ngIf="showPlaceholder"
|
||||
class="placeholder">
|
||||
<span (click)="editText()">{{ placeholder }}</span>
|
||||
</span>
|
||||
|
||||
<span *ngIf="showText"
|
||||
class="editable">
|
||||
<span (click)="editText()"
|
||||
[innerHtml]="displayText"></span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div *ngIf="editing">
|
||||
<textarea class="text-editor"
|
||||
[(ngModel)]="text"
|
||||
autofocus
|
||||
(keydown.control.enter)="keydownEnter()"
|
||||
(keydown.alt.enter)="keydownEnter()"></textarea>
|
||||
<div class="pull-right buttons">
|
||||
<button class="btn btn-danger"
|
||||
(click)="cancelEditText()">
|
||||
Cancel
|
||||
</button>
|
||||
<button class="btn btn-primary"
|
||||
(click)="saveText()"
|
||||
[disabled]="isSaveDisabled">
|
||||
Update description
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,26 @@
|
||||
:host {
|
||||
margin-top: 0.5em;
|
||||
|
||||
span.placeholder {
|
||||
color: gray;
|
||||
display: inline-block;
|
||||
|
||||
.glyphicon {
|
||||
color: black;
|
||||
}
|
||||
}
|
||||
|
||||
span.editable,
|
||||
span.placeholder {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
textarea.text-editor {
|
||||
display: block;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.buttons {
|
||||
margin-top: 0.5em;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, forwardRef, Input } from '@angular/core';
|
||||
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
|
||||
|
||||
@Component({
|
||||
selector: 'editable-text',
|
||||
templateUrl: './editable-text.component.html',
|
||||
styleUrls: ['./editable-text.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
providers: [
|
||||
{provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => EditableTextComponent), multi: true}
|
||||
]
|
||||
})
|
||||
export class EditableTextComponent implements ControlValueAccessor {
|
||||
|
||||
@Input() placeholder = 'No editable content';
|
||||
text: string;
|
||||
originalText: string;
|
||||
editing = false;
|
||||
|
||||
get isSaveDisabled(): boolean {
|
||||
return !this.text || this.text.trim().length === 0;
|
||||
}
|
||||
|
||||
get displayText(): string {
|
||||
return this.text && this.text.replace(/\n/g, '<br>');
|
||||
}
|
||||
|
||||
constructor(private cdr: ChangeDetectorRef) {
|
||||
|
||||
}
|
||||
|
||||
writeValue(obj: any): void {
|
||||
if (this.text === obj) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.text = obj;
|
||||
this.cdr.markForCheck();
|
||||
}
|
||||
|
||||
registerOnChange(fn: any): void {
|
||||
this.textChange = fn;
|
||||
}
|
||||
|
||||
registerOnTouched(fn: any): void {
|
||||
}
|
||||
|
||||
saveText(): void {
|
||||
this.originalText = null;
|
||||
this.editing = false;
|
||||
this.textChange(this.text);
|
||||
}
|
||||
|
||||
editText(): void {
|
||||
this.originalText = this.text;
|
||||
this.editing = true;
|
||||
}
|
||||
|
||||
cancelEditText(): void {
|
||||
this.text = this.originalText;
|
||||
this.editing = false;
|
||||
}
|
||||
|
||||
keydownEnter(): void {
|
||||
if (this.isSaveDisabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.saveText();
|
||||
}
|
||||
|
||||
get showPlaceholder(): boolean {
|
||||
return !this.editing && !this.text;
|
||||
}
|
||||
|
||||
get showText(): boolean {
|
||||
return !this.editing && !!this.text;
|
||||
}
|
||||
|
||||
private textChange: any = () => {
|
||||
}
|
||||
}
|
||||
@@ -7,8 +7,11 @@
|
||||
[selectedKey]="selectedKey"
|
||||
[selected]="selectedKey?.layerId === index"
|
||||
[keyboardLayout]="keyboardLayout"
|
||||
[description]="description"
|
||||
[showDescription]="true"
|
||||
(keyClick)="keyClick.emit($event)"
|
||||
(keyHover)="keyHover.emit($event)"
|
||||
(capture)="capture.emit($event)"
|
||||
(descriptionChanged)="descriptionChanged.emit($event)"
|
||||
>
|
||||
</svg-keyboard>
|
||||
|
||||
|
Before Width: | Height: | Size: 659 B After Width: | Height: | Size: 809 B |
@@ -1,4 +1,4 @@
|
||||
import { Component, ChangeDetectionStrategy, Input, Output, EventEmitter, OnChanges, SimpleChanges } from '@angular/core';
|
||||
import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnChanges, Output, SimpleChanges } from '@angular/core';
|
||||
import { animate, keyframes, state, style, transition, trigger } from '@angular/animations';
|
||||
import { Layer } from 'uhk-common';
|
||||
|
||||
@@ -81,11 +81,14 @@ export class KeyboardSliderComponent implements OnChanges {
|
||||
@Input() halvesSplit: boolean;
|
||||
@Input() selectedKey: { layerId: number, moduleId: number, keyId: number };
|
||||
@Input() keyboardLayout = KeyboardLayout.ANSI;
|
||||
@Input() description: string;
|
||||
@Output() keyClick = new EventEmitter();
|
||||
@Output() keyHover = new EventEmitter();
|
||||
@Output() capture = new EventEmitter();
|
||||
@Output() descriptionChanged = new EventEmitter<string>();
|
||||
|
||||
layerAnimationState: AnimationKeyboard[];
|
||||
|
||||
ngOnChanges(changes: SimpleChanges) {
|
||||
if (changes['layers']) {
|
||||
this.layerAnimationState = this.layers.map<AnimationKeyboard>(() => 'initOut');
|
||||
|
||||
@@ -4,7 +4,8 @@
|
||||
(downloadClick)="downloadKeymap()"></keymap-header>
|
||||
<svg-keyboard-wrap [keymap]="keymap$ | async"
|
||||
[halvesSplit]="keyboardSplit"
|
||||
[keyboardLayout]="keyboardLayout$ | async"></svg-keyboard-wrap>
|
||||
[keyboardLayout]="keyboardLayout$ | async"
|
||||
(descriptionChanged)="descriptionChanged($event)"></svg-keyboard-wrap>
|
||||
</ng-template>
|
||||
|
||||
<div *ngIf="!(keymap$ | async)" class="not-found">
|
||||
|
||||
@@ -18,6 +18,8 @@ import { AppState, getKeyboardLayout } from '../../../store';
|
||||
import { getKeymap, getKeymaps, getUserConfiguration } from '../../../store/reducers/user-configuration';
|
||||
import { SvgKeyboardWrapComponent } from '../../svg/wrap';
|
||||
import { KeyboardLayout } from '../../../keyboard/keyboard-layout.enum';
|
||||
import { KeymapActions } from '../../../store/actions';
|
||||
import { ChangeKeymapDescription } from '../../../models/ChangeKeymapDescription';
|
||||
|
||||
@Component({
|
||||
selector: 'keymap-edit',
|
||||
@@ -64,7 +66,7 @@ export class KeymapEditComponent {
|
||||
const keymap = latest[0];
|
||||
const exportableJSON = latest[1];
|
||||
const fileName = keymap.name + '_keymap.json';
|
||||
saveAs(new Blob([exportableJSON], { type: 'application/json' }), fileName);
|
||||
saveAs(new Blob([exportableJSON], {type: 'application/json'}), fileName);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -73,6 +75,10 @@ export class KeymapEditComponent {
|
||||
this.keyboardSplit = !this.keyboardSplit;
|
||||
}
|
||||
|
||||
descriptionChanged(event: ChangeKeymapDescription): void {
|
||||
this.store.dispatch(new KeymapActions.EditDescriptionAction(event));
|
||||
}
|
||||
|
||||
private toExportableJSON(keymap: Keymap): Observable<any> {
|
||||
return this.store
|
||||
.let(getUserConfiguration())
|
||||
|
||||
@@ -74,7 +74,7 @@ export class KeymapHeaderComponent implements OnChanges {
|
||||
}
|
||||
|
||||
editKeymapName(name: string) {
|
||||
if (name.length === 0) {
|
||||
if (!util.isValidName(name)) {
|
||||
this.setName();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -13,5 +13,5 @@
|
||||
</ng-template>
|
||||
|
||||
<div *ngIf="!macro" class="not-found">
|
||||
There is no macro with id {{ route.params.select('id') | async }}.
|
||||
There is no macro with id {{ macroId }}.
|
||||
</div>
|
||||
|
||||
@@ -7,7 +7,7 @@ import { Subscription } from 'rxjs/Subscription';
|
||||
import 'rxjs/add/operator/pluck';
|
||||
|
||||
import { MacroActions } from '../../../store/actions';
|
||||
import { AppState } from '../../../store/index';
|
||||
import { AppState } from '../../../store';
|
||||
import { getMacro } from '../../../store/reducers/user-configuration';
|
||||
|
||||
@Component({
|
||||
@@ -21,13 +21,17 @@ import { getMacro } from '../../../store/reducers/user-configuration';
|
||||
export class MacroEditComponent implements OnDestroy {
|
||||
macro: Macro;
|
||||
isNew: boolean;
|
||||
macroId: number;
|
||||
|
||||
private subscription: Subscription;
|
||||
constructor(private store: Store<AppState>, public route: ActivatedRoute) {
|
||||
this.subscription = route
|
||||
.params
|
||||
.pluck<{}, string>('id')
|
||||
.switchMap((id: string) => store.let(getMacro(+id)))
|
||||
.switchMap((id: string) => {
|
||||
this.macroId = +id;
|
||||
return store.let(getMacro(this.macroId));
|
||||
})
|
||||
.subscribe((macro: Macro) => {
|
||||
this.macro = macro;
|
||||
});
|
||||
|
||||
@@ -59,7 +59,7 @@ export class MacroHeaderComponent implements AfterViewInit, OnChanges {
|
||||
}
|
||||
|
||||
editMacroName(name: string) {
|
||||
if (name.length === 0) {
|
||||
if (!util.isValidName(name)) {
|
||||
this.setName();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -7,6 +7,10 @@
|
||||
[width]="200"
|
||||
[options]="options"
|
||||
></select2>
|
||||
<icon name="question-circle"
|
||||
data-toggle="tooltip"
|
||||
title="Looking for a non-US character? Just pick the character of the desired key according to the US layout."
|
||||
data-placement="bottom"></icon>
|
||||
<capture-keystroke-button (capture)="onKeysCapture($event)" tabindex="0"></capture-keystroke-button>
|
||||
</div>
|
||||
<div class="modifier-options">
|
||||
|
||||
@@ -10,6 +10,10 @@
|
||||
position: relative;
|
||||
top: 2px;
|
||||
}
|
||||
|
||||
icon {
|
||||
display: inline-block;
|
||||
};
|
||||
}
|
||||
|
||||
.modifier-options {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Component, Input, OnChanges } from '@angular/core';
|
||||
import { Select2OptionData, Select2TemplateFunction } from 'ng2-select2';
|
||||
import { KeyAction, KeystrokeAction, KeystrokeType } from 'uhk-common';
|
||||
import { KeyAction, KeystrokeAction, KeystrokeType, SCANCODES, SECONDARY_ROLES } from 'uhk-common';
|
||||
|
||||
import { Tab } from '../tab';
|
||||
import { MapperService } from '../../../../services/mapper.service';
|
||||
@@ -35,8 +35,8 @@ export class KeypressTabComponent extends Tab implements OnChanges {
|
||||
id: '0',
|
||||
text: 'None'
|
||||
}];
|
||||
this.scanCodeGroups = this.scanCodeGroups.concat(require('./scancodes.json'));
|
||||
this.secondaryRoleGroups = require('./secondaryRole.json');
|
||||
this.scanCodeGroups = this.scanCodeGroups.concat(SCANCODES);
|
||||
this.secondaryRoleGroups = SECONDARY_ROLES;
|
||||
this.leftModifierSelects = Array(this.leftModifiers.length).fill(false);
|
||||
this.rightModifierSelects = Array(this.rightModifiers.length).fill(false);
|
||||
this.selectedScancodeOption = this.scanCodeGroups[0];
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
<span> No macros are available to choose from. Create a macro first! </span>
|
||||
</ng-template>
|
||||
<ng-template [ngIf]="macroOptions.length > 0">
|
||||
<p><i>Please note that macro playback is not implemented yet. You can bind macros, but they don't have any effect.</i></p>
|
||||
<div class="macro-selector">
|
||||
<b> Play macro: </b>
|
||||
<select2 [data]="macroOptions" [value]="macroOptions[selectedMacroIndex].id" (valueChanged)="onChange($event)" [width]="'100%'"></select2>
|
||||
|
||||