109 Commits

Author SHA1 Message Date
László Monda
1d4bb6113c Bump version to 1.1.5 in package.json and update the changelog. 2018-04-10 11:42:34 +02:00
Róbert Kiss
136120b831 feat: not allow run multiple Agent (#603) 2018-04-09 21:09:09 +02:00
Róbert Kiss
cd299c06d6 feat: not allow run multiple Agent (#604) 2018-04-09 20:48:22 +02:00
László Monda
e152a36ad7 Add agent-app-icon.png which I forgot to add. 2018-04-09 14:16:09 +02:00
László Monda
e90544db33 Bump Agent version to 1.1.4 in package.json and update the changelog. 2018-04-09 13:59:38 +02:00
László Monda
7ceca202b4 Reposition the ISO key in the scancode list. 2018-04-09 12:48:44 +02:00
László Monda
ddc65aa54b Replace application icon with a diagonal gradient based icon that should look better on the desktop. 2018-04-09 12:20:46 +02:00
Róbert Kiss
13ec617d58 Make saving the configuration more robust (#594)
* feat: Make saving the configuration more robust

* parse backup user config before return

* fix some bug

* Add write-userconfig.js and invalid-config.bin

* throw exception if failed user config parsing

* Merge branch 'master' into feat-467-make-save-more-robust

* hide keymaps and macros if agent in restore mode

* fix Device name settings
2018-04-09 10:11:26 +02:00
Róbert Kiss
00c5b69129 fix: display agent icon when user use ALT + TAB (#600) 2018-04-07 23:17:50 +02:00
Róbert Kiss
6ccf005750 feat: Handle privilege escalation gracefully even without PolicyKit (#599)
* feat: Handle privilege escalation gracefully even without PolicyKit

* build: upgrade tslint => 5.9.1

* build: add uhk-agent/package-lock.json

* feat: add error animation

* fix: display agent icon when user use ALT + TAB
2018-04-07 23:09:47 +02:00
László Monda
6e1f0ded9e Bump Agent version to 1.1.3 and update changelog. 2018-04-06 16:26:21 +02:00
László Monda
d58386ef4b Reference firmware 8.1.5 2018-04-04 15:51:04 +02:00
László Monda
179c982bfb Make the Fn+Backspace shortcut of the QWERTY for PC keymap switch to the TES keymap for testing purposes. 2018-04-03 23:25:30 +02:00
László Monda
a7d07dbf4c Make factory-update.js allow the layout to be set. 2018-04-03 21:04:47 +02:00
László Monda
0ca922d24a Sleep 1s between sending reset and idle kboot commands. Hopefully this will always make the left half resume after firmware updates. 2018-04-03 00:49:51 +02:00
László Monda
a6f1aa15a5 Supply the correct configBufferId values to launchEepromTransfer() and get rid of the obsoleted eepromTransfer mapping. 2018-04-02 23:54:48 +02:00
László Monda
fc2d025cc4 Don't display the buffer related USB transfers of writeConfig() because they generate too much noise. 2018-04-02 23:32:13 +02:00
László Monda
8b5ae106bd Display kboot command names instead of numberic ids. 2018-04-02 23:20:07 +02:00
László Monda
44639bbf53 Make apply-config.js and get-module-state.js use read() instead of readSync() 2018-04-02 22:56:28 +02:00
László Monda
9fcce9234a Make getDeviceState await. Dump transfer descriptions before the actual transfers when the debug mode is on. 2018-04-02 21:35:47 +02:00
László Monda
b26fecfc7a Make factory-update.js switch keymap. 2018-04-02 18:28:12 +02:00
László Monda
1b15911783 Extract code to uhk.writeUca() 2018-04-02 17:31:27 +02:00
László Monda
148dd8d361 Rewrite writeHca() using the JavaScript USB API instead of TypeScript because the latter couldn't reopen the USB device. 2018-04-02 17:25:35 +02:00
László Monda
0d9ac50999 Move writeHca() to uhk.js 2018-04-02 14:47:48 +02:00
László Monda
e19e4bc5a4 Move the gist of write-hca.js into writeHca() 2018-04-02 14:43:11 +02:00
László Monda
bdd79a5a9a Clean up writeConfig() a bit. 2018-04-02 14:32:45 +02:00
László Monda
fb4e05fdc4 Dump USB reads and writes via writeDevice() 2018-04-02 00:30:03 +02:00
László Monda
01fcf9053a Strip down factory-update.js to its essentials. 2018-04-02 00:11:35 +02:00
László Monda
533c2f13d2 Copy update-firmwares.js as factory-update.js 2018-04-01 23:58:13 +02:00
László Monda
b9c32b46a9 Extract uhk.applyConfig() and uhk.launchEepromTransfer() 2018-04-01 23:55:40 +02:00
László Monda
f9b7260be6 Extract uhk.writeUserConfig() 2018-04-01 01:23:50 +02:00
László Monda
847694d590 Rewrite write-config.js using async/await. 2018-04-01 01:13:17 +02:00
László Monda
58178a5c7b Extract uhk.updateFirmwares() 2018-03-31 23:50:02 +02:00
László Monda
9b93b4dac5 Rename update-all-firmwares.js to update-firmwares.js 2018-03-31 23:24:34 +02:00
Róbert Kiss
478dac0621 build: extract electron dependencies to the root package.json (#593) 2018-03-31 22:11:41 +02:00
László Monda
f196fcdaa2 Make switch-keymap.js accept a keymap abbreviation. 2018-03-30 19:17:42 +02:00
László Monda
0b420ff516 Extract uhk.switchKeymap() 2018-03-30 18:59:50 +02:00
László Monda
7656af76e4 Add switch-keymap.js 2018-03-30 12:48:28 +02:00
László Monda
beed546ae4 Remove switch keymap actions that point to the TES keymap. 2018-03-29 19:09:00 +02:00
László Monda
bf94370f2f Add the "Export device configuration" button instead of the export link. Make the button export JSON by default and BIN when pressed with Shift. 2018-03-29 18:44:09 +02:00
László Monda
2476049681 Out of play, play/pause, stop, and pause only leave play/pause in the default configuration. 2018-03-29 18:12:08 +02:00
László Monda
f8d8b6d213 Remove Launch Browser, Launch Calculator, and Eject Disk key actions from the default configurations because they don't work reliably across OSes and very rarely used. 2018-03-29 14:51:06 +02:00
László Monda
05bbce1d50 Remove redundant stop media playback actions. 2018-03-29 13:27:13 +02:00
László Monda
32494fa228 Remove relative switch keymap actions. 2018-03-29 13:12:27 +02:00
László Monda
e0ce38988e Remove shut down key actions, only keep sleep key actions, and bind them to the \ key. 2018-03-29 12:54:51 +02:00
László Monda
5c660c549d Update the test keymap. This one should be the final version. 2018-03-28 20:56:56 +02:00
László Monda
510b914e26 Change terminology from download / upload to export / import for greater clarity. 2018-03-28 18:27:27 +02:00
László Monda
cf64fc0c08 Make the tooltip text regarding non-US characters easier to understand. 2018-03-25 22:24:33 +02:00
Róbert Kiss
b25bc9d81d feat: show firmware version of the device/modules on firmware page (#589) 2018-03-23 07:10:02 +01:00
Róbert Kiss
2f00a5eaf4 feat: enhance device firmware page (#588)
* feat: enhance device firmware page

* remove confirmation dialog from firmware upgrade buttons
2018-03-15 12:20:35 +01:00
László Monda
e8fe0f8d3e Fix menu scancode. (#586)
* Fix menu scancode.

* Change the old menu key scancode 118 to 101.

* validate scancodes
2018-03-11 22:56:12 +01:00
László Monda
e84dbf2c15 Bump Agent version to 1.1.2. Bump firmware version to 8.1.4. Update changelog. 2018-03-09 19:02:46 +01:00
Róbert Kiss
990ff8e980 ci: mac code sign (#585)
* ci: register certificate into the mac keychain

* try to not use yarn electron-builder installer on mac

* use -P

* debug electron-osx-sign

* use cscLink

* use xcode xcode9.3beta

* increase package.json version

* revert version to 1.1.1

* delete unused files

* format release.js file

* format release.js file

* format release.js file
2018-03-09 18:31:48 +01:00
Róbert Kiss
1ca8e67e52 try to use xcode 9.2 (#584) 2018-03-08 18:09:17 +01:00
Róbert Kiss
23cb583bf7 chore: upgrade lerna => 2.9.0 (#583) 2018-03-06 00:34:06 +01:00
Róbert Kiss
d5cc735b85 build: Windows code sign (#581)
* build: Add windows certificate

* temporary bump version

* change CSC_LINK settings

* rdp connection

* remove appveyor RDP block
2018-03-01 17:29:55 +01:00
László Monda
1981311136 Merge branch 'master' of github.com:UltimateHackingKeyboard/agent 2018-02-28 00:31:48 +01:00
László Monda
bbb5d4a35b Add a notice regarding the lack of robustness of the firmware update process. 2018-02-28 00:31:06 +01:00
serge-rosov
58ef40fb02 year correction (#582) 2018-02-27 18:11:46 +01:00
Róbert Kiss
b8f35df155 build: switch off mac codesign 2018-02-24 23:33:22 +01:00
Róbert Kiss
c3e712851c build: Sign mac installer 2018-02-24 23:12:01 +01:00
László Monda
2eaa1e0634 Update package-lock.json 2018-02-19 17:11:16 +01:00
László Monda
6ee21bcd7a Fix get-debug-info.js 2018-02-18 03:44:17 +01:00
Róbert Kiss
10ae68ad4b fix(keymap): Fix undefined keymap serialization (#573) 2018-02-17 18:07:33 +01:00
László Monda
02044ae1d0 Bump version to 1.1.1 and update changelog. 2018-02-13 03:54:28 +01:00
László Monda
3f99d47bba Use firmware 8.1.2 2018-02-13 03:40:52 +01:00
László Monda
9beadb4aac Add keymap descriptions. 2018-02-13 03:24:06 +01:00
László Monda
d9fb7a4b42 Merge branch 'master' of github.com:UltimateHackingKeyboard/agent 2018-02-13 03:05:18 +01:00
László Monda
83912ec21f Assign "switch to test keymap" action on all keymaps. 2018-02-13 03:04:36 +01:00
Róbert Kiss
6c7232a5ba feat(device): Make Agent able to unbrick bricked modules (#577) 2018-02-13 02:11:27 +01:00
László Monda
65fc8b5efb Tweak the scancode related help text. 2018-02-12 15:36:39 +01:00
Róbert Kiss
7a64191955 fix(config): Save reset user configuration in web module (#574) 2018-02-11 20:44:08 +01:00
Róbert Kiss
1a413c824e Fix 560 delete bind play macro action when macro delete (#576)
* fix(config): delete KeyAction binding of deleted macro

* refactor: use sorter import

* fix(macro): read the macro id from route params

* fix(keyAction): use NoneAction in keyAction mapping
2018-02-11 20:12:12 +01:00
Róbert Kiss
e545c9d67b feat(popover): Add Non-US help tooltip (#578) 2018-02-11 19:47:41 +01:00
László Monda
8650fef7ae Make reenumerate() more reliable. 2018-01-31 04:26:49 +01:00
László Monda
5c618869a2 Use updateDeviceFirmware(), reenumerate(), and updateModuleFirmware() in update-all-firmwares.js instead of forking processes. 2018-01-31 03:40:46 +01:00
László Monda
1b8d6949e0 Extract updateModuleFirmware() to uhk.js 2018-01-31 03:03:34 +01:00
László Monda
aabc0a8746 Use waitForKbootIdle() in update-module-firmware.js 2018-01-31 02:28:29 +01:00
László Monda
9589398834 Move waitForKbootIdle() to uhk.js 2018-01-31 02:25:37 +01:00
László Monda
933a715ea5 Extract waitForKbootIdle() 2018-01-31 02:22:41 +01:00
László Monda
df14e2d569 Extract uhk.jumpToModuleBootloader() and use it. 2018-01-31 01:17:28 +01:00
László Monda
4f8a0247d3 Call uhk.sendKbootCommandToModule() instead of forking send-kboot-command-to-module.js 2018-01-31 00:57:53 +01:00
László Monda
85ec5f6b6a Make update-module-firmware.js use reenumerate() and sendKbootCommandToModule() instead of forking more processes. 2018-01-31 00:17:27 +01:00
László Monda
739b830f47 Use uhk.writeDevice() in jump-to-module-bootloader.js 2018-01-31 00:09:16 +01:00
László Monda
482cff3d3b Extract sendKbootCommandToModule() from send-kboot-command-to-module.js to uhk.js 2018-01-30 23:44:25 +01:00
László Monda
9284ae5032 Use reenumerate() instead of forking reenumerate.js 2018-01-30 23:32:37 +01:00
László Monda
cac6fdc190 Move updateDeviceFirmware() from update-device-firmware.js to uhk.js 2018-01-30 21:48:42 +01:00
László Monda
ca9bf60a1b Extract updateDeviceFirmware() 2018-01-30 21:46:39 +01:00
László Monda
bb7edb8e4d Remove shared.js 2018-01-30 21:39:05 +01:00
László Monda
0d9c976eb8 Move execRetry() from shared.js to uhk.js 2018-01-30 21:37:27 +01:00
László Monda
288d4f75b6 Move getBlhostCmd() from shared.js to uhk.js 2018-01-30 21:35:35 +01:00
László Monda
73e07eae2d Move checkFirmwareImage() from shared.js to uhk.js 2018-01-30 21:28:00 +01:00
László Monda
8e620caac5 Use reenumerate() in update-device-firmware.js instead of forking reenumerate.js 2018-01-30 20:39:48 +01:00
László Monda
0d4e1acf76 Extract reenumerate() from reenumerate.js to uhk.js 2018-01-30 19:34:29 +01:00
László Monda
88c42d58b1 Extract reenumerate() as an async function in reenumerate.js 2018-01-30 18:36:16 +01:00
László Monda
5099e904fc Use uhk.writeDevice() in set-i2c-baud-rate.js 2018-01-30 17:30:12 +01:00
László Monda
5476f7c3a5 Extract I2C0_F. 2018-01-30 17:25:29 +01:00
László Monda
e0bb0bcca3 Add the async uhk.writeDevice() and use it in get-i2c-baud-rate.js 2018-01-30 17:21:50 +01:00
László Monda
124c3ec29b Simplify reenumerate.js mainly by using uint32ToArray() 2018-01-30 06:09:09 +01:00
László Monda
2310320b8a Replace pushUint32() with uint32ToArray() and don't use it where not needed. 2018-01-30 05:30:32 +01:00
László Monda
67346b4cda Simplify getTransferData() 2018-01-30 05:10:29 +01:00
Róbert Kiss
662ca0152f fix(keymap): Don't show keymap description in the key action popover (#572) 2018-01-29 23:40:41 +01:00
Róbert Kiss
6358528438 ci: use node 8.9.4 (#571)
Node 8.9.4 contain a few security update and use npm 5.6.0 so not need to
install npm in the build process. The build time will a few sec sorter
2018-01-29 23:20:07 +01:00
József Farkas
02f1053d46 feat(popover): sort keymaps and macros alphabetically (#534)
* feat(popover): sort keymaps and macros alphabetically

Closes #512

* small performance refactoring
2018-01-29 23:15:21 +01:00
Róbert Kiss
a44a7dc5f8 chore: use lodash-es version (#569)
lodash-es support tree shaking and the bundle size will near 2MB less
2018-01-29 23:02:17 +01:00
Róbert Kiss
02d57fdabf feat(keymap): add description to keymap (#559)
* feat(keymap): add description to keymaps

* add new feature request

* preserve new lines
2018-01-29 22:54:29 +01:00
Róbert Kiss
38f6688930 chore: upgrade electron => 1.7.11 (#568) 2018-01-29 21:26:32 +01:00
Róbert Kiss
6ca12d0ccd build: set Apple appId => com.ultimategadgetlabs.agent (#566) 2018-01-23 19:19:15 +01:00
László Monda
acd17ac657 Fix the background color of the toplevel device node. Fixes #552 2018-01-22 04:41:43 +01:00
142 changed files with 10664 additions and 10214 deletions

2
.nvmrc
View File

@@ -1 +1 @@
8.9.1 8.9.4

View File

@@ -11,7 +11,7 @@ matrix:
fast_finish: true fast_finish: true
include: include:
- os: osx - os: osx
osx_image: xcode8.3 osx_image: xcode9.3beta
- os: linux - os: linux
env: CC=clang CXX=clang++ npm_config_clang=1 env: CC=clang CXX=clang++ npm_config_clang=1
compiler: clang compiler: clang
@@ -45,7 +45,6 @@ addons:
install: install:
- nvm install - nvm install
- npm i -g npm@5.6.0
- npm install - npm install
before_script: before_script:

View File

@@ -4,7 +4,57 @@ 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/). The format is loosely based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/).
## [1.1.0] - 2017-01-15 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 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

View File

@@ -1,8 +1,11 @@
os: unstable os: unstable
clone_folder: c:\projects\uhk-agent
environment: environment:
GH_TOKEN: GH_TOKEN:
secure: 3IebpEKmC39codi1wT6dXx8mql4/mCL1JzZ7lir7GQ5MWRnCxlED2OXbiKHHigDV secure: 3IebpEKmC39codi1wT6dXx8mql4/mCL1JzZ7lir7GQ5MWRnCxlED2OXbiKHHigDV
CSC_LINK: c:\projects\uhk-agent\scripts\certs\windows-cert.p12
matrix: matrix:
- nodejs_version: "8" - nodejs_version: "8"
@@ -18,7 +21,6 @@ shallow_clone: true
install: install:
- ps: Install-Product node $env:nodejs_version - ps: Install-Product node $env:nodejs_version
- npm i -g npm@5.6.0
- choco install chromium - choco install chromium
- set CI=true - set CI=true
- set PATH=%APPDATA%\npm;%PATH% - set PATH=%APPDATA%\npm;%PATH%

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 392 B

After

Width:  |  Height:  |  Size: 499 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 546 B

After

Width:  |  Height:  |  Size: 735 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 KiB

After

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 660 B

After

Width:  |  Height:  |  Size: 913 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 966 B

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

2948
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -3,8 +3,8 @@
"private": true, "private": true,
"author": "Ultimate Gadget Laboratories", "author": "Ultimate Gadget Laboratories",
"main": "electron/dist/electron-main.js", "main": "electron/dist/electron-main.js",
"version": "1.1.0", "version": "1.1.5",
"firmwareVersion": "8.1.0", "firmwareVersion": "8.1.5",
"deviceProtocolVersion": "4.2.0", "deviceProtocolVersion": "4.2.0",
"userConfigVersion": "4.0.0", "userConfigVersion": "4.0.0",
"hardwareConfigVersion": "1.0.0", "hardwareConfigVersion": "1.0.0",
@@ -21,7 +21,7 @@
"devDependencies": { "devDependencies": {
"@types/electron-devtools-installer": "2.0.2", "@types/electron-devtools-installer": "2.0.2",
"@types/electron-settings": "3.0.0", "@types/electron-settings": "3.0.0",
"@types/fs-extra": "4.0.5", "@types/fs-extra": "5.0.1",
"@types/jasmine": "2.6.0", "@types/jasmine": "2.6.0",
"@types/jsonfile": "4.0.1", "@types/jsonfile": "4.0.1",
"@types/node": "8.0.53", "@types/node": "8.0.53",
@@ -30,25 +30,28 @@
"@types/usb": "1.1.3", "@types/usb": "1.1.3",
"autoprefixer": "6.5.3", "autoprefixer": "6.5.3",
"buffer": "5.0.6", "buffer": "5.0.6",
"copyfiles": "^2.0.0",
"copy-webpack-plugin": "4.0.1", "copy-webpack-plugin": "4.0.1",
"core-js": "2.4.1", "core-js": "2.4.1",
"cross-env": "5.0.5", "cross-env": "5.0.5",
"decompress": "4.2.0", "decompress": "4.2.0",
"decompress-tarbz2": "^4.1.1", "decompress-tarbz2": "^4.1.1",
"devtron": "1.4.0", "devtron": "1.4.0",
"electron": "1.7.5", "electron": "1.8.4",
"electron-builder": "19.45.5", "electron-builder": "20.8.1",
"electron-debug": "1.4.0", "electron-debug": "1.5.0",
"electron-devtools-installer": "2.2.0", "electron-devtools-installer": "2.2.3",
"electron-log": "2.2.9", "electron-log": "2.2.14",
"electron-rebuild": "1.6.0", "electron-rebuild": "1.7.3",
"electron-settings": "3.1.2", "electron-settings": "3.1.4",
"electron-updater": "2.21.4",
"exports-loader": "0.6.3", "exports-loader": "0.6.3",
"file-loader": "0.10.0", "file-loader": "0.10.0",
"fs-extra": "4.0.2", "fs-extra": "5.0.0",
"jsonfile": "4.0.0", "jsonfile": "4.0.0",
"lerna": "2.0.0", "lerna": "2.9.0",
"mkdirp": "0.5.1", "mkdirp": "0.5.1",
"node-hid": "0.5.7",
"npm-run-all": "4.0.2", "npm-run-all": "4.0.2",
"pre-commit": "1.2.2", "pre-commit": "1.2.2",
"request": "2.83.0", "request": "2.83.0",
@@ -58,7 +61,7 @@
"svg-sprite": "1.3.7", "svg-sprite": "1.3.7",
"ts-loader": "2.3.1", "ts-loader": "2.3.1",
"ts-node": "3.0.4", "ts-node": "3.0.4",
"tslint": "5.5.0", "tslint": "5.9.1",
"typescript": "2.5.2", "typescript": "2.5.2",
"webpack": "2.4.1" "webpack": "2.4.1"
}, },
@@ -74,11 +77,11 @@
"test:uhk-web": "lerna exec --scope uhk-web npm test", "test:uhk-web": "lerna exec --scope uhk-web npm test",
"lint": "run-s -scn lint:ts lint:style", "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": "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-main": "tslint --project ./packages/uhk-agent/tsconfig.json",
"lint:ts:electron-renderer": "tslint --type-check --project ./packages/uhk-web/src/tsconfig.renderer.json", "lint:ts:electron-renderer": "tslint --project ./packages/uhk-web/src/tsconfig.renderer.json",
"lint:ts:web": "tslint --type-check --project ./packages/uhk-web/src/tsconfig.app.json", "lint:ts:web": "tslint --project ./packages/uhk-web/src/tsconfig.app.json",
"lint:ts:test-serializer": "tslint --type-check --project ./packages/test-serializer/tsconfig.json", "lint:ts:test-serializer": "tslint --project ./packages/test-serializer/tsconfig.json",
"lint:ts:uhk-usb": "tslint --type-check --project ./packages/uhk-usb/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", "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": "run-s build:common build:usb build:web build:electron",
"build:web": "lerna exec --scope uhk-web npm run build", "build:web": "lerna exec --scope uhk-web npm run build",
@@ -90,6 +93,7 @@
"server:web": "lerna exec --scope uhk-web npm start", "server:web": "lerna exec --scope uhk-web npm start",
"server:electron": "lerna exec --scope uhk-web npm run server:renderer", "server:electron": "lerna exec --scope uhk-web npm run server:renderer",
"electron": "lerna exec --scope uhk-agent npm start", "electron": "lerna exec --scope uhk-agent npm start",
"electron:spe": "lerna exec --scope uhk-agent npm run electron:spe",
"standard-version": "standard-version", "standard-version": "standard-version",
"pack": "node ./scripts/release.js", "pack": "node ./scripts/release.js",
"sprites": "node ./scripts/generate-svg-sprites", "sprites": "node ./scripts/generate-svg-sprites",

File diff suppressed because it is too large Load Diff

View File

@@ -17,13 +17,7 @@
"command-line-args": "4.0.7", "command-line-args": "4.0.7",
"decompress": "4.2.0", "decompress": "4.2.0",
"decompress-bzip2": "4.0.0", "decompress-bzip2": "4.0.0",
"electron": "1.7.9", "node-hid": "0.5.7",
"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",
"sudo-prompt": "7.0.0", "sudo-prompt": "7.0.0",
"tmp": "0.0.33", "tmp": "0.0.33",
"uhk-common": "^1.0.0", "uhk-common": "^1.0.0",
@@ -36,6 +30,7 @@
}, },
"scripts": { "scripts": {
"start": "electron ./dist/electron-main.js", "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": "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", "build:usb": "electron-rebuild -w node-hid -p -m ./dist",
"install:build-deps": "cd ./dist && npm i", "install:build-deps": "cd ./dist && npm i",

View File

@@ -2,7 +2,7 @@
/// <reference path="./custom_types/command-line-args.d.ts"/> /// <reference path="./custom_types/command-line-args.d.ts"/>
import './polyfills'; import './polyfills';
import { app, BrowserWindow, ipcMain } from 'electron'; import { app, BrowserWindow } from 'electron';
import { autoUpdater } from 'electron-updater'; import { autoUpdater } from 'electron-updater';
import * as path from 'path'; import * as path from 'path';
@@ -10,7 +10,7 @@ import * as url from 'url';
import * as commandLineArgs from 'command-line-args'; import * as commandLineArgs from 'command-line-args';
import { UhkHidDevice, UhkOperations } from 'uhk-usb'; import { UhkHidDevice, UhkOperations } from 'uhk-usb';
// import { ElectronDataStorageRepositoryService } from './services/electron-datastorage-repository.service'; // import { ElectronDataStorageRepositoryService } from './services/electron-datastorage-repository.service';
import { LogRegExps } from 'uhk-common'; import { CommandLineArgs, LogRegExps } from 'uhk-common';
import { DeviceService } from './services/device.service'; import { DeviceService } from './services/device.service';
import { logger } from './services/logger.service'; import { logger } from './services/logger.service';
import { AppUpdateService } from './services/app-update.service'; import { AppUpdateService } from './services/app-update.service';
@@ -18,13 +18,13 @@ import { AppService } from './services/app.service';
import { SudoService } from './services/sudo.service'; import { SudoService } from './services/sudo.service';
import { UhkBlhost } from '../../uhk-usb/src'; import { UhkBlhost } from '../../uhk-usb/src';
import * as isDev from 'electron-is-dev'; import * as isDev from 'electron-is-dev';
import { CommandLineInputs } from './models/command-line-inputs';
const optionDefinitions = [ const optionDefinitions = [
{name: 'addons', type: Boolean} {name: 'addons', type: Boolean},
{name: 'spe', type: Boolean} // simulate privilege escalation error
]; ];
const options: CommandLineInputs = commandLineArgs(optionDefinitions); const options: CommandLineArgs = commandLineArgs(optionDefinitions);
// import './dev-extension'; // import './dev-extension';
// require('electron-debug')({ showDevTools: true, enabled: true }); // require('electron-debug')({ showDevTools: true, enabled: true });
@@ -60,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() { function createWindow() {
if (isSecondInstance) {
return;
}
logger.info('[Electron Main] Create new window.'); logger.info('[Electron Main] Create new window.');
let packagesDir; let packagesDir;
if (isDev) { if (isDev) {
@@ -79,17 +97,17 @@ function createWindow() {
webPreferences: { webPreferences: {
nodeIntegration: true nodeIntegration: true
}, },
icon: 'assets/images/agent-icon.png' icon: path.join(__dirname, 'renderer/assets/images/agent-app-icon.png')
}); });
win.setMenuBarVisibility(false); win.setMenuBarVisibility(false);
win.maximize(); win.maximize();
uhkHidDeviceService = new UhkHidDevice(logger); uhkHidDeviceService = new UhkHidDevice(logger, options);
uhkBlhost = new UhkBlhost(logger, packagesDir); uhkBlhost = new UhkBlhost(logger, packagesDir);
uhkOperations = new UhkOperations(logger, uhkBlhost, uhkHidDeviceService, packagesDir); uhkOperations = new UhkOperations(logger, uhkBlhost, uhkHidDeviceService, packagesDir);
deviceService = new DeviceService(logger, win, uhkHidDeviceService, uhkOperations); deviceService = new DeviceService(logger, win, uhkHidDeviceService, uhkOperations);
appUpdateService = new AppUpdateService(logger, win, app); appUpdateService = new AppUpdateService(logger, win, app);
appService = new AppService(logger, win, deviceService, options, uhkHidDeviceService); 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. // and load the index.html of the app.
win.loadURL(url.format({ win.loadURL(url.format({
@@ -133,13 +151,13 @@ app.on('ready', createWindow);
// Quit when all windows are closed. // Quit when all windows are closed.
app.on('window-all-closed', () => { app.on('window-all-closed', () => {
app.quit();
});
app.on('will-quit', () => {
if (appUpdateService) { if (appUpdateService) {
appUpdateService.saveFirtsRun(); appUpdateService.saveFirtsRun();
} }
app.exit();
});
app.on('will-quit', () => {
}); });
app.on('activate', () => { app.on('activate', () => {

View File

@@ -1,3 +1,10 @@
export interface CommandLineInputs { export interface CommandLineInputs {
/**
* addons menu visible or not
*/
addons?: boolean; addons?: boolean;
/**
* simulate privilege escalation error
*/
spe?: boolean;
} }

View File

@@ -1,5 +1,15 @@
import { ipcMain } from 'electron'; import { ipcMain } from 'electron';
import { ConfigurationReply, DeviceConnectionState, IpcEvents, IpcResponse, LogService } from 'uhk-common'; import {
ConfigurationReply,
DeviceConnectionState,
getHardwareConfigFromDeviceResponse,
HardwareModules,
IpcEvents,
IpcResponse,
LogService,
mapObjectToUserConfigBinaryBuffer,
SaveUserConfigurationData
} from 'uhk-common';
import { snooze, UhkHidDevice, UhkOperations } from 'uhk-usb'; import { snooze, UhkHidDevice, UhkOperations } from 'uhk-usb';
import { Observable } from 'rxjs/Observable'; import { Observable } from 'rxjs/Observable';
import { Subscription } from 'rxjs/Subscription'; import { Subscription } from 'rxjs/Subscription';
@@ -14,6 +24,7 @@ import 'rxjs/add/operator/distinctUntilChanged';
import { saveTmpFirmware } from '../util/save-extract-firmware'; import { saveTmpFirmware } from '../util/save-extract-firmware';
import { TmpFirmware } from '../models/tmp-firmware'; import { TmpFirmware } from '../models/tmp-firmware';
import { QueueManager } from './queue-manager'; import { QueueManager } from './queue-manager';
import { backupUserConfiguration, getBackupUserConfigurationContent } from '../util/backup-user-confoguration';
/** /**
* IpcMain pair of the UHK Communication * IpcMain pair of the UHK Communication
@@ -73,10 +84,19 @@ export class DeviceService {
try { try {
await this.device.waitUntilKeyboardBusy(); await this.device.waitUntilKeyboardBusy();
const result = await this.operations.loadConfigurations(); 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 = { response = {
success: true, success: true,
...result ...result,
modules,
backupConfiguration: await getBackupUserConfigurationContent(this.logService, uniqueId)
}; };
} catch (error) { } catch (error) {
response = { response = {
@@ -157,10 +177,13 @@ export class DeviceService {
private async saveUserConfiguration(event: Electron.Event, args: Array<string>): Promise<void> { private async saveUserConfiguration(event: Electron.Event, args: Array<string>): Promise<void> {
const response = new IpcResponse(); const response = new IpcResponse();
const json = args[0]; const data: SaveUserConfigurationData = JSON.parse(args[0]);
try { try {
await this.operations.saveUserConfiguration(json); await backupUserConfiguration(data);
const buffer = mapObjectToUserConfigBinaryBuffer(data.configuration);
await this.operations.saveUserConfiguration(buffer);
response.success = true; response.success = true;
} }

View File

@@ -5,12 +5,13 @@ import * as sudo from 'sudo-prompt';
import { dirSync } from 'tmp'; import { dirSync } from 'tmp';
import { emptyDir, copy } from 'fs-extra'; import { emptyDir, copy } from 'fs-extra';
import { IpcEvents, LogService, IpcResponse } from 'uhk-common'; import { CommandLineArgs, IpcEvents, LogService, IpcResponse } from 'uhk-common';
export class SudoService { export class SudoService {
private rootDir: string; private rootDir: string;
constructor(private logService: LogService) { constructor(private logService: LogService,
private options: CommandLineArgs) {
if (isDev) { 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 { } else {
@@ -21,6 +22,19 @@ export class SudoService {
} }
private async 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) { switch (process.platform) {
case 'linux': case 'linux':
await this.setPrivilegeOnLinux(event); await this.setPrivilegeOnLinux(event);
@@ -28,7 +42,7 @@ export class SudoService {
default: default:
const response: IpcResponse = { const response: IpcResponse = {
success: false, 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); event.sender.send(IpcEvents.device.setPrivilegeOnLinuxReply, response);
@@ -39,7 +53,7 @@ export class SudoService {
private async setPrivilegeOnLinux(event: Electron.Event) { private async setPrivilegeOnLinux(event: Electron.Event) {
const tmpDirectory = dirSync(); const tmpDirectory = dirSync();
const rulesDir = path.join(this.rootDir, 'rules'); const rulesDir = path.join(this.rootDir, 'rules');
this.logService.debug('[SudoService] Copy rules dir', { src: rulesDir, dst: tmpDirectory.name }); this.logService.debug('[SudoService] Copy rules dir', {src: rulesDir, dst: tmpDirectory.name});
await copy(rulesDir, tmpDirectory.name); await copy(rulesDir, tmpDirectory.name);
const scriptPath = path.join(tmpDirectory.name, 'setup-rules.sh'); const scriptPath = path.join(tmpDirectory.name, 'setup-rules.sh');
@@ -55,7 +69,7 @@ export class SudoService {
if (error) { if (error) {
this.logService.error('[SudoService] Error when set privilege: ', error); this.logService.error('[SudoService] Error when set privilege: ', error);
response.success = false; response.success = false;
response.error = error; response.error = {message: error.message};
} else { } else {
response.success = true; response.success = true;
} }

View 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});
}
};

View File

@@ -10,7 +10,10 @@
"url": "git@github.com:UltimateHackingKeyboard/agent.git" "url": "git@github.com:UltimateHackingKeyboard/agent.git"
}, },
"scripts": { "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", "test": "jasmine-ts --config=jasmine.json",
"coverage": "nyc jasmine-ts --config=jasmine.json" "coverage": "nyc jasmine-ts --config=jasmine.json"
}, },

View File

@@ -9,3 +9,6 @@ export * from './macro';
export * from './module'; export * from './module';
export * from './module-configuration'; export * from './module-configuration';
export * from './user-configuration'; export * from './user-configuration';
export const SCANCODES = require('./scancodes.json');
export const SECONDARY_ROLES = require('./secondaryRole.json');

View File

@@ -7,6 +7,8 @@ import { SwitchLayerAction } from './switch-layer-action';
import { SwitchKeymapAction, UnresolvedSwitchKeymapAction } from './switch-keymap-action'; import { SwitchKeymapAction, UnresolvedSwitchKeymapAction } from './switch-keymap-action';
import { MouseAction } from './mouse-action'; import { MouseAction } from './mouse-action';
import { PlayMacroAction } from './play-macro-action'; import { PlayMacroAction } from './play-macro-action';
import { NoneAction } from './none-action';
import { isScancodeExists } from '../scancode-checker';
export class Helper { export class Helper {
@@ -25,7 +27,12 @@ export class Helper {
buffer.backtrack(); buffer.backtrack();
if (keyActionFirstByte >= KeyActionId.KeystrokeAction && keyActionFirstByte < KeyActionId.LastKeystrokeAction) { 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) { switch (keyActionFirstByte) {
@@ -67,8 +74,14 @@ export class Helper {
} }
switch (keyAction.keyActionType) { switch (keyAction.keyActionType) {
case keyActionType.KeystrokeAction: case keyActionType.KeystrokeAction: {
return new KeystrokeAction().fromJsonObject(keyAction); const keystrokeAction = new KeystrokeAction().fromJsonObject(keyAction);
if (isValidKeystrokeAction(keystrokeAction)) {
return keystrokeAction;
}
return new NoneAction();
}
case keyActionType.SwitchLayerAction: case keyActionType.SwitchLayerAction:
return new SwitchLayerAction().fromJsonObject(keyAction); return new SwitchLayerAction().fromJsonObject(keyAction);
case keyActionType.SwitchKeymapAction: case keyActionType.SwitchKeymapAction:
@@ -77,8 +90,16 @@ export class Helper {
return new MouseAction().fromJsonObject(keyAction); return new MouseAction().fromJsonObject(keyAction);
case keyActionType.PlayMacroAction: case keyActionType.PlayMacroAction:
return new PlayMacroAction().fromJsonObject(keyAction, macros); return new PlayMacroAction().fromJsonObject(keyAction, macros);
case keyActionType.NoneAction:
return new NoneAction();
default: default:
throw `Invalid KeyAction.keyActionType: "${keyAction.keyActionType}"`; throw `Invalid KeyAction.keyActionType: "${keyAction.keyActionType}"`;
} }
} }
} }
function isValidKeystrokeAction(keystrokeAction: KeystrokeAction): boolean {
return keystrokeAction.hasSecondaryRoleAction() ||
keystrokeAction.hasActiveModifier() ||
keystrokeAction.hasScancode() && isScancodeExists(keystrokeAction.scancode);
}

View File

@@ -54,15 +54,12 @@ export class Module {
const noneAction = new NoneAction(); const noneAction = new NoneAction();
const keyActions: KeyAction[] = this.keyActions.map(keyAction => { buffer.writeArray(this.keyActions, (uhkBuffer: UhkBuffer, keyAction: KeyAction) => {
if (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);
}); });
} }

View File

@@ -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);
}
}
}
}

View File

@@ -105,6 +105,10 @@
{ {
"id": "29", "id": "29",
"text": "Z" "text": "Z"
},
{
"id": "100",
"text": "| ISO"
} }
] ]
}, },
@@ -242,7 +246,7 @@
"text": "Delete" "text": "Delete"
}, },
{ {
"id": "118", "id": "101",
"text": "Menu" "text": "Menu"
}, },
{ {
@@ -314,10 +318,6 @@
"id": "69", "id": "69",
"text": "F12" "text": "F12"
}, },
{
"id": "100",
"text": "| ISO"
},
{ {
"id": "104", "id": "104",
"text": "F13" "text": "F13"

View File

@@ -1,3 +1,10 @@
export interface CommandLineArgs { export interface CommandLineArgs {
addons: boolean; /**
* addons menu visible or not
*/
addons?: boolean;
/**
* simulate privilege escalation error
*/
spe?: boolean;
} }

View File

@@ -1,6 +1,11 @@
import { HardwareModules } from './hardware-modules';
import { UserConfiguration } from '../config-serializer/config-items';
export interface ConfigurationReply { export interface ConfigurationReply {
success: boolean; success: boolean;
userConfiguration?: string; userConfiguration?: string;
hardwareConfiguration?: string; hardwareConfiguration?: string;
modules?: HardwareModules;
error?: string; error?: string;
backupConfiguration?: UserConfiguration;
} }

View File

@@ -0,0 +1,4 @@
export interface HardwareModuleInfo {
firmwareVersion?: string;
moduleProtocolVersion?: string;
}

View File

@@ -0,0 +1,6 @@
import { HardwareModuleInfo } from './hardware-module-info';
export interface HardwareModules {
leftModuleInfo?: HardwareModuleInfo;
rightModuleInfo?: HardwareModuleInfo;
}

View File

@@ -5,3 +5,6 @@ export * from './app-start-info';
export * from './configuration-reply'; export * from './configuration-reply';
export * from './version-information'; export * from './version-information';
export * from './device-connection-state'; export * from './device-connection-state';
export * from './hardware-modules';
export * from './hardware-module-info';
export * from './save-user-configuration-data';

View File

@@ -0,0 +1,4 @@
export interface SaveUserConfigurationData {
uniqueId: number;
configuration: string;
}

View 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();
};

View File

@@ -1,6 +1,7 @@
export { IpcEvents } from './ipcEvents'; export { IpcEvents } from './ipcEvents';
export * from './log'; export * from './log';
export * from './constants'; export * from './constants';
export * from './helpers';
// Source: http://stackoverflow.com/questions/13720256/javascript-regex-camelcase-to-sentence // Source: http://stackoverflow.com/questions/13720256/javascript-regex-camelcase-to-sentence
export function camelCaseToSentence(camelCasedText: string): string { export function camelCaseToSentence(camelCasedText: string): string {

View File

@@ -23,7 +23,7 @@
"integrity": "sha1-u13KOCu5TwXhUZQ3PRb9O6HKEQ0=", "integrity": "sha1-u13KOCu5TwXhUZQ3PRb9O6HKEQ0=",
"requires": { "requires": {
"delegates": "1.0.0", "delegates": "1.0.0",
"readable-stream": "2.3.3" "readable-stream": "2.3.6"
} }
}, },
"bindings": { "bindings": {
@@ -32,11 +32,12 @@
"integrity": "sha512-DpLh5EzMR2kzvX1KIlVC0VkC3iZtHKTgdtZ0a3pglBZdaQFjt5S9g9xd1lE+YvXyfd6mtCeRnrUfOLYiTMlNSw==" "integrity": "sha512-DpLh5EzMR2kzvX1KIlVC0VkC3iZtHKTgdtZ0a3pglBZdaQFjt5S9g9xd1lE+YvXyfd6mtCeRnrUfOLYiTMlNSw=="
}, },
"bl": { "bl": {
"version": "1.2.1", "version": "1.2.2",
"resolved": "https://registry.npmjs.org/bl/-/bl-1.2.1.tgz", "resolved": "https://registry.npmjs.org/bl/-/bl-1.2.2.tgz",
"integrity": "sha1-ysMo977kVzDUBLaSID/LWQ4XLV4=", "integrity": "sha512-e8tQYnZodmebYDWGH7KMRvtzKXaJHx3BbilrgZCfvyLUYdKpK1t5PSPmpkny/SgiTSCnjfLW7v5rlONXVFkQEA==",
"requires": { "requires": {
"readable-stream": "2.3.3" "readable-stream": "2.3.6",
"safe-buffer": "5.1.1"
} }
}, },
"chownr": { "chownr": {
@@ -59,6 +60,14 @@
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
"integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" "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": { "deep-extend": {
"version": "0.4.2", "version": "0.4.2",
"resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.4.2.tgz", "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", "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
"integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=" "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": { "end-of-stream": {
"version": "1.4.0", "version": "1.4.1",
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.0.tgz", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz",
"integrity": "sha1-epDYM+/abPpurA9JSduw+tOmMgY=", "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==",
"requires": { "requires": {
"once": "1.4.0" "once": "1.4.0"
} }
@@ -113,9 +127,9 @@
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
}, },
"ini": { "ini": {
"version": "1.3.4", "version": "1.3.5",
"resolved": "https://registry.npmjs.org/ini/-/ini-1.3.4.tgz", "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz",
"integrity": "sha1-BTfLedr1m1mhpRff9wbIbsA5Fi4=" "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw=="
}, },
"is-fullwidth-code-point": { "is-fullwidth-code-point": {
"version": "1.0.0", "version": "1.0.0",
@@ -130,6 +144,11 @@
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
"integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" "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": { "minimist": {
"version": "1.2.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
@@ -151,14 +170,17 @@
} }
}, },
"nan": { "nan": {
"version": "2.7.0", "version": "2.10.0",
"resolved": "https://registry.npmjs.org/nan/-/nan-2.7.0.tgz", "resolved": "https://registry.npmjs.org/nan/-/nan-2.10.0.tgz",
"integrity": "sha1-2Vv3IeyHfgjbJ27T/G63j5CDrUY=" "integrity": "sha512-bAdJv7fBLhWC+/Bls0Oza+mvTaNQtP+1RyhhhvD95pgUJz6XM5IzgmxOkItJ9tkoCiplvAnXI1tNmmUD/eScyA=="
}, },
"node-abi": { "node-abi": {
"version": "2.1.1", "version": "2.3.0",
"resolved": "https://registry.npmjs.org/node-abi/-/node-abi-2.1.1.tgz", "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-2.3.0.tgz",
"integrity": "sha512-6oxV13poCOv7TfGvhsSz6XZWpXeKkdGVh72++cs33OfMh3KAX8lN84dCvmqSETyDXAFcUHtV7eJrgFBoOqZbNQ==" "integrity": "sha512-zwm6vU3SsVgw3e9fu48JBaRBCJGIvAgysDsqtf5+vEexFE71bEOtaMWb5zr/zODZNzTPtQlqUUpC79k68Hspow==",
"requires": {
"semver": "5.5.0"
}
}, },
"node-hid": { "node-hid": {
"version": "0.5.7", "version": "0.5.7",
@@ -166,8 +188,8 @@
"integrity": "sha512-dwwpOetL2+MGYgivbO22ML+45ieCGbueWv1rYxRgBoEc2QMp6UF6ZucEkYts1IA3YPWJNkmpGh6dqQ85n19szw==", "integrity": "sha512-dwwpOetL2+MGYgivbO22ML+45ieCGbueWv1rYxRgBoEc2QMp6UF6ZucEkYts1IA3YPWJNkmpGh6dqQ85n19szw==",
"requires": { "requires": {
"bindings": "1.3.0", "bindings": "1.3.0",
"nan": "2.7.0", "nan": "2.10.0",
"prebuild-install": "2.3.0" "prebuild-install": "2.5.1"
} }
}, },
"noop-logger": { "noop-logger": {
@@ -210,62 +232,63 @@
"integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=" "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M="
}, },
"prebuild-install": { "prebuild-install": {
"version": "2.3.0", "version": "2.5.1",
"resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-2.3.0.tgz", "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-2.5.1.tgz",
"integrity": "sha512-gzjq2oHB8oMbzJSsSh9MQ64zrXZGt092/uT4TLZlz2qnrPxpWqp4vYB7LZrDxnlxf5RfbCjkgDI/z0EIVuYzAw==", "integrity": "sha512-3DX9L6pzwc1m1ksMkW3Ky2WLgPQUBiySOfXVl3WZyAeJSyJb4wtoH9OmeRGcubAWsMlLiL8BTHbwfm/jPQE9Ag==",
"requires": { "requires": {
"detect-libc": "1.0.3",
"expand-template": "1.1.0", "expand-template": "1.1.0",
"github-from-package": "0.0.0", "github-from-package": "0.0.0",
"minimist": "1.2.0", "minimist": "1.2.0",
"mkdirp": "0.5.1", "mkdirp": "0.5.1",
"node-abi": "2.1.1", "node-abi": "2.3.0",
"noop-logger": "0.1.1", "noop-logger": "0.1.1",
"npmlog": "4.1.2", "npmlog": "4.1.2",
"os-homedir": "1.0.2", "os-homedir": "1.0.2",
"pump": "1.0.2", "pump": "2.0.1",
"rc": "1.2.2", "rc": "1.2.6",
"simple-get": "1.4.3", "simple-get": "2.7.0",
"tar-fs": "1.16.0", "tar-fs": "1.16.0",
"tunnel-agent": "0.6.0", "tunnel-agent": "0.6.0",
"xtend": "4.0.1" "which-pm-runs": "1.0.0"
} }
}, },
"process-nextick-args": { "process-nextick-args": {
"version": "1.0.7", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz",
"integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=" "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw=="
}, },
"pump": { "pump": {
"version": "1.0.2", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/pump/-/pump-1.0.2.tgz", "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz",
"integrity": "sha1-Oz7mUS+U8OV1U4wXmV+fFpkKXVE=", "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==",
"requires": { "requires": {
"end-of-stream": "1.4.0", "end-of-stream": "1.4.1",
"once": "1.4.0" "once": "1.4.0"
} }
}, },
"rc": { "rc": {
"version": "1.2.2", "version": "1.2.6",
"resolved": "https://registry.npmjs.org/rc/-/rc-1.2.2.tgz", "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.6.tgz",
"integrity": "sha1-2M6ctX6NZNnHut2YdsfDTL48cHc=", "integrity": "sha1-6xiYnG1PTxYsOZ953dKfODVWgJI=",
"requires": { "requires": {
"deep-extend": "0.4.2", "deep-extend": "0.4.2",
"ini": "1.3.4", "ini": "1.3.5",
"minimist": "1.2.0", "minimist": "1.2.0",
"strip-json-comments": "2.0.1" "strip-json-comments": "2.0.1"
} }
}, },
"readable-stream": { "readable-stream": {
"version": "2.3.3", "version": "2.3.6",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
"integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
"requires": { "requires": {
"core-util-is": "1.0.2", "core-util-is": "1.0.2",
"inherits": "2.0.3", "inherits": "2.0.3",
"isarray": "1.0.0", "isarray": "1.0.0",
"process-nextick-args": "1.0.7", "process-nextick-args": "2.0.0",
"safe-buffer": "5.1.1", "safe-buffer": "5.1.1",
"string_decoder": "1.0.3", "string_decoder": "1.1.1",
"util-deprecate": "1.0.2" "util-deprecate": "1.0.2"
} }
}, },
@@ -274,6 +297,11 @@
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz",
"integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==" "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": { "set-blocking": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", "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", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz",
"integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=" "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": { "simple-get": {
"version": "1.4.3", "version": "2.7.0",
"resolved": "https://registry.npmjs.org/simple-get/-/simple-get-1.4.3.tgz", "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-2.7.0.tgz",
"integrity": "sha1-6XVe2kB+ltpAxeUVjJ6jezO+y+s=", "integrity": "sha512-RkE9rGPHcxYZ/baYmgJtOSM63vH0Vyq+ma5TijBcLla41SWlh8t6XYIGMR/oeZcmr+/G8k+zrClkkVrtnQ0esg==",
"requires": { "requires": {
"decompress-response": "3.3.0",
"once": "1.4.0", "once": "1.4.0",
"unzip-response": "1.0.2", "simple-concat": "1.0.0"
"xtend": "4.0.1"
} }
}, },
"string-width": { "string-width": {
@@ -305,9 +338,9 @@
} }
}, },
"string_decoder": { "string_decoder": {
"version": "1.0.3", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
"integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
"requires": { "requires": {
"safe-buffer": "5.1.1" "safe-buffer": "5.1.1"
} }
@@ -332,18 +365,29 @@
"requires": { "requires": {
"chownr": "1.0.1", "chownr": "1.0.1",
"mkdirp": "0.5.1", "mkdirp": "0.5.1",
"pump": "1.0.2", "pump": "1.0.3",
"tar-stream": "1.5.4" "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": { "tar-stream": {
"version": "1.5.4", "version": "1.5.5",
"resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-1.5.4.tgz", "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-1.5.5.tgz",
"integrity": "sha1-NlSc8E7RrumyowwBQyUiONr5QBY=", "integrity": "sha512-mQdgLPc/Vjfr3VWqWbfxW8yQNiJCbAZ+Gf6GDu1Cy0bdb33ofyiNGBtAY96jHFhDuivCwgW1H9DgTON+INiXgg==",
"requires": { "requires": {
"bl": "1.2.1", "bl": "1.2.2",
"end-of-stream": "1.4.0", "end-of-stream": "1.4.1",
"readable-stream": "2.3.3", "readable-stream": "2.3.6",
"xtend": "4.0.1" "xtend": "4.0.1"
} }
}, },
@@ -355,16 +399,16 @@
"safe-buffer": "5.1.1" "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": { "util-deprecate": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
"integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" "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": { "wide-align": {
"version": "1.1.2", "version": "1.1.2",
"resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.2.tgz", "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.2.tgz",

View File

@@ -22,7 +22,7 @@ export enum UsbCommand {
GetDebugBuffer = 0x0b, GetDebugBuffer = 0x0b,
GetAdcValue = 0x0c, GetAdcValue = 0x0c,
SetLedPwmBrightness = 0x0d, SetLedPwmBrightness = 0x0d,
GetModuleProperties = 0x0e GetModuleProperty = 0x0e
} }
export enum EepromOperation { export enum EepromOperation {
@@ -39,7 +39,8 @@ export enum ConfigBufferId {
export enum DevicePropertyIds { export enum DevicePropertyIds {
DeviceProtocolVersion = 0, DeviceProtocolVersion = 0,
ProtocolVersions = 1, ProtocolVersions = 1,
ConfigSizes = 2 ConfigSizes = 2,
CurrentKbootCommand = 3
} }
export enum EnumerationModes { export enum EnumerationModes {
@@ -80,3 +81,7 @@ export enum KbootCommands {
ping = 1, ping = 1,
reset = 2 reset = 2
} }
export enum ModulePropertyId {
protocolVersions = 0
}

View File

@@ -1,5 +1,5 @@
import { Device, devices, HID } from 'node-hid'; import { Device, devices, HID } from 'node-hid';
import { LogService } from 'uhk-common'; import { CommandLineArgs, LogService } from 'uhk-common';
import { import {
ConfigBufferId, ConfigBufferId,
@@ -27,7 +27,8 @@ export class UhkHidDevice {
private _device: HID; private _device: HID;
private _hasPermission = false; private _hasPermission = false;
constructor(private logService: LogService) { constructor(private logService: LogService,
private options: CommandLineArgs) {
} }
/** /**
@@ -38,6 +39,10 @@ export class UhkHidDevice {
* @returns {boolean} * @returns {boolean}
*/ */
public hasPermission(): boolean { public hasPermission(): boolean {
if (this.options.spe) {
return false;
}
try { try {
if (this._hasPermission) { if (this._hasPermission) {
return true; return true;

View File

@@ -1,12 +1,24 @@
import { LogService } from 'uhk-common'; import { HardwareModuleInfo, LogService, UhkBuffer } from 'uhk-common';
import { EnumerationModes, EnumerationNameToProductId, KbootCommands, ModuleSlotToI2cAddress, ModuleSlotToId } from './constants'; import {
EnumerationModes,
EnumerationNameToProductId,
KbootCommands,
ModulePropertyId,
ModuleSlotToI2cAddress,
ModuleSlotToId
} from './constants';
import * as path from 'path'; import * as path from 'path';
import * as fs from 'fs'; import * as fs from 'fs';
import { UhkBlhost } from './uhk-blhost'; import { UhkBlhost } from './uhk-blhost';
import { UhkHidDevice } from './uhk-hid-device'; import { UhkHidDevice } from './uhk-hid-device';
import { snooze } from './util'; import { snooze } from './util';
import { convertBufferToIntArray, getTransferBuffers, DevicePropertyIds, UsbCommand, ConfigBufferId import {
} from '../index'; convertBufferToIntArray,
getTransferBuffers,
DevicePropertyIds,
UsbCommand,
ConfigBufferId
} from '../index';
import { LoadConfigurationsResult } from './models/load-configurations-result'; import { LoadConfigurationsResult } from './models/load-configurations-result';
export class UhkOperations { export class UhkOperations {
@@ -42,6 +54,13 @@ export class UhkOperations {
await snooze(1000); await snooze(1000);
await this.device.jumpToBootloaderModule(ModuleSlotToId.leftHalf); await this.device.jumpToBootloaderModule(ModuleSlotToId.leftHalf);
this.device.close(); 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); await this.device.reenumerate(EnumerationModes.Buspal);
this.device.close(); this.device.close();
await this.blhost.runBlhostCommandRetry([...buspalPrefix, 'get-property', '1']); await this.blhost.runBlhostCommandRetry([...buspalPrefix, 'get-property', '1']);
@@ -86,8 +105,6 @@ export class UhkOperations {
* @returns {Promise<Buffer>} * @returns {Promise<Buffer>}
*/ */
public async loadConfiguration(configBufferId: ConfigBufferId): Promise<string> { public async loadConfiguration(configBufferId: ConfigBufferId): Promise<string> {
let response = [];
const configBufferIdToName = ['HardwareConfig', 'StagingUserConfig', 'ValidatedUserConfig']; const configBufferIdToName = ['HardwareConfig', 'StagingUserConfig', 'ValidatedUserConfig'];
const configName = configBufferIdToName[configBufferId]; const configName = configBufferIdToName[configBufferId];
@@ -121,7 +138,8 @@ export class UhkOperations {
} }
} }
} }
response = convertBufferToIntArray(configBuffer); const response = convertBufferToIntArray(configBuffer);
return Promise.resolve(JSON.stringify(response)); return Promise.resolve(JSON.stringify(response));
} catch (error) { } catch (error) {
const errMsg = `[DeviceOperation] ${configName} from eeprom error`; const errMsg = `[DeviceOperation] ${configName} from eeprom error`;
@@ -146,10 +164,10 @@ export class UhkOperations {
return configSize; return configSize;
} }
public async saveUserConfiguration(json: string): Promise<void> { public async saveUserConfiguration(buffer: Buffer): Promise<void> {
try { try {
this.logService.debug('[DeviceOperation] USB[T]: Write user configuration to keyboard'); 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'); this.logService.debug('[DeviceOperation] USB[T]: Write user configuration to EEPROM');
await this.device.writeConfigToEeprom(ConfigBufferId.validatedUserConfig); await this.device.writeConfigToEeprom(ConfigBufferId.validatedUserConfig);
} }
@@ -161,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. * 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>} * @returns {Promise<void>}
* @private * @private
*/ */
private async sendUserConfigToKeyboard(json: string): Promise<void> { private async sendUserConfigToKeyboard(buffer: Buffer): Promise<void> {
const buffer: Buffer = new Buffer(JSON.parse(json).data);
const fragments = getTransferBuffers(UsbCommand.WriteStagingUserConfig, buffer); const fragments = getTransferBuffers(UsbCommand.WriteStagingUserConfig, buffer);
for (const fragment of fragments) { for (const fragment of fragments) {
await this.device.write(fragment); await this.device.write(fragment);

File diff suppressed because it is too large Load Diff

View File

@@ -37,7 +37,7 @@
"@types/jasmine": "2.5.53", "@types/jasmine": "2.5.53",
"@types/jasminewd2": "2.0.2", "@types/jasminewd2": "2.0.2",
"@types/jquery": "3.2.9", "@types/jquery": "3.2.9",
"@types/node-hid": "0.5.2", "@types/lodash-es": "4.17.0",
"@types/usb": "1.1.3", "@types/usb": "1.1.3",
"angular-confirmation-popover": "3.2.0", "angular-confirmation-popover": "3.2.0",
"angular-notifier": "2.0.0", "angular-notifier": "2.0.0",
@@ -67,12 +67,12 @@
"karma-jasmine": "1.1.0", "karma-jasmine": "1.1.0",
"karma-jasmine-html-reporter": "0.2.2", "karma-jasmine-html-reporter": "0.2.2",
"less-loader": "4.0.5", "less-loader": "4.0.5",
"lodash": "4.17.4", "lodash-es": "4.17.4",
"ng2-dragula": "1.5.0", "ng2-dragula": "1.5.0",
"ng2-nouislider": "^1.7.6", "ng2-nouislider": "^1.7.6",
"ng2-select2": "1.0.0-beta.10", "ng2-select2": "1.0.0-beta.10",
"ngx-clipboard": "8.0.0",
"ngrx-store-freeze": "0.1.9", "ngrx-store-freeze": "0.1.9",
"node-hid": "0.5.4",
"nouislider": "^10.1.0", "nouislider": "^10.1.0",
"postcss-loader": "1.3.3", "postcss-loader": "1.3.3",
"postcss-url": "5.1.2", "postcss-url": "5.1.2",

View File

@@ -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 { animate, style, transition, trigger } from '@angular/animations';
import { Observable } from 'rxjs/Observable'; import { Observable } from 'rxjs/Observable';
import { Action, Store } from '@ngrx/store'; import { Action, Store } from '@ngrx/store';
@@ -14,7 +14,6 @@ import {
saveToKeyboardState saveToKeyboardState
} from './store'; } from './store';
import { ProgressButtonState } from './store/reducers/progress-button-state'; import { ProgressButtonState } from './store/reducers/progress-button-state';
import { SaveUserConfigInBinaryFileAction, SaveUserConfigInJsonFileAction } from './store/actions/user-config';
@Component({ @Component({
selector: 'main-app', selector: 'main-app',

View File

@@ -9,13 +9,13 @@
<ul class="list-unstyled btn-list"> <ul class="list-unstyled btn-list">
<li> <li>
Download device configuration in <button class="btn btn-default"
<span role="button" class="btn-link" (click)="saveConfigurationInJSONFormat()">JSON</span> or (click)="exportUserConfiguration($event)">Export device configuration
<span role="button" class="btn-link" (click)="saveConfigurationInBINFormat()">binary</span> format. </button>
</li> </li>
<li> <li>
<label class="btn btn-default btn-file"> <label class="btn btn-default btn-file">
Upload device configuration Import device configuration
<input type="file" <input type="file"
(change)="changeFile($event)"> (change)="changeFile($event)">
</label> </label>

View File

@@ -34,6 +34,14 @@ export class DeviceConfigurationComponent {
this.store.dispatch(new SaveUserConfigInBinaryFileAction()); this.store.dispatch(new SaveUserConfigInBinaryFileAction());
} }
exportUserConfiguration(event: MouseEvent) {
if (event.shiftKey) {
this.saveConfigurationInBINFormat();
} else {
this.saveConfigurationInJSONFormat();
}
}
changeFile(event): void { changeFile(event): void {
const files = event.srcElement.files; const files = event.srcElement.files;
const fileReader = new FileReader(); const fileReader = new FileReader();

View File

@@ -4,6 +4,7 @@ import { DeviceConfigurationComponent } from './configuration/device-configurati
import { DeviceFirmwareComponent } from './firmware/device-firmware.component'; import { DeviceFirmwareComponent } from './firmware/device-firmware.component';
import { MouseSpeedComponent } from './mouse-speed/mouse-speed.component'; import { MouseSpeedComponent } from './mouse-speed/mouse-speed.component';
import { LEDBrightnessComponent } from './led-brightness/led-brightness.component'; import { LEDBrightnessComponent } from './led-brightness/led-brightness.component';
import { RestoreConfigurationComponent } from './restore-configuration/restore-configuration.component';
export const deviceRoutes: Routes = [ export const deviceRoutes: Routes = [
{ {
@@ -29,6 +30,10 @@ export const deviceRoutes: Routes = [
{ {
path: 'firmware', path: 'firmware',
component: DeviceFirmwareComponent component: DeviceFirmwareComponent
},
{
path: 'restore-user-configuration',
component: RestoreConfigurationComponent
} }
] ]
} }

View File

@@ -6,23 +6,36 @@
<i class="fa fa-sliders"></i> <i class="fa fa-sliders"></i>
<span>Firmware</span> <span>Firmware</span>
</h1> </h1>
<p> <p>
Flash firmware {{ (getAgentVersionInfo$ | async).firmwareVersion }} (bundled with Agent) Firmware {{ hardwareModules.leftModuleInfo.firmwareVersion }} is running on the left keyboard half.<br>
<button class="btn btn-primary" Firmware {{ hardwareModules.rightModuleInfo.firmwareVersion }} is running on the right keyboard half.
[disabled]="flashFirmwareButtonDisbabled$ | async" </p>
(click)="onUpdateFirmware()">Flash firmware
</button> <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>
<p> <p>
Flash firmware file <input id="firmware-file-select"
type="file"
[disabled]="flashFirmwareButtonDisbabled$ | async"
(change)="changeFile($event)">
<button class="btn btn-primary" <button class="btn btn-primary"
[disabled]="flashFirmwareButtonDisbabled$ | async" [disabled]="flashFirmwareButtonDisbabled$ | async"
(click)="onUpdateFirmwareWithFile()">Flash firmware (click)="onUpdateFirmware()">
Flash firmware {{ (getAgentVersionInfo$ | async).firmwareVersion }} (bundled with Agent)
</button> </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> </p>
</div> </div>

View File

@@ -2,9 +2,16 @@ import { Component, ElementRef, OnDestroy, ViewChild } from '@angular/core';
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
import { Observable } from 'rxjs/Observable'; import { Observable } from 'rxjs/Observable';
import { Subscription } from 'rxjs/Subscription'; 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 { UpdateFirmwareAction, UpdateFirmwareOkButtonAction, UpdateFirmwareWithAction } from '../../../store/actions/device';
import { XtermLog } from '../../../models/xterm-log'; import { XtermLog } from '../../../models/xterm-log';
@@ -22,8 +29,9 @@ export class DeviceFirmwareComponent implements OnDestroy {
xtermLogSubscription: Subscription; xtermLogSubscription: Subscription;
getAgentVersionInfo$: Observable<VersionInformation>; getAgentVersionInfo$: Observable<VersionInformation>;
firmwareOkButtonDisabled$: Observable<boolean>; firmwareOkButtonDisabled$: Observable<boolean>;
hardwareModulesSubscription: Subscription;
hardwareModules: HardwareModules;
arrayBuffer: Uint8Array;
@ViewChild('scrollMe') divElement: ElementRef; @ViewChild('scrollMe') divElement: ElementRef;
constructor(private store: Store<AppState>) { constructor(private store: Store<AppState>) {
@@ -38,24 +46,20 @@ export class DeviceFirmwareComponent implements OnDestroy {
}); });
this.getAgentVersionInfo$ = store.select(getAgentVersionInfo); this.getAgentVersionInfo$ = store.select(getAgentVersionInfo);
this.firmwareOkButtonDisabled$ = store.select(firmwareOkButtonDisabled); this.firmwareOkButtonDisabled$ = store.select(firmwareOkButtonDisabled);
this.hardwareModulesSubscription = store.select(getHardwareModules).subscribe(data => {
this.hardwareModules = data;
});
} }
ngOnDestroy(): void { ngOnDestroy(): void {
this.xtermLogSubscription.unsubscribe(); this.xtermLogSubscription.unsubscribe();
this.hardwareModulesSubscription.unsubscribe();
} }
onUpdateFirmware(): void { onUpdateFirmware(): void {
this.store.dispatch(new UpdateFirmwareAction()); this.store.dispatch(new UpdateFirmwareAction());
} }
onUpdateFirmwareWithFile(): void {
if (!this.arrayBuffer) {
return;
}
this.store.dispatch(new UpdateFirmwareWithAction(Array.prototype.slice.call(this.arrayBuffer)));
}
onOkButtonClick(): void { onOkButtonClick(): void {
this.store.dispatch(new UpdateFirmwareOkButtonAction()); this.store.dispatch(new UpdateFirmwareOkButtonAction());
} }
@@ -64,14 +68,13 @@ export class DeviceFirmwareComponent implements OnDestroy {
const files = event.srcElement.files; const files = event.srcElement.files;
if (files.length === 0) { if (files.length === 0) {
this.arrayBuffer = null;
return; return;
} }
const fileReader = new FileReader(); const fileReader = new FileReader();
fileReader.onloadend = function () { 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); }.bind(this);
fileReader.readAsArrayBuffer(files[0]); fileReader.readAsArrayBuffer(files[0]);
} }

View File

@@ -2,4 +2,5 @@ export * from './configuration/device-configuration.component';
export * from './firmware/device-firmware.component'; export * from './firmware/device-firmware.component';
export * from './mouse-speed/mouse-speed.component'; export * from './mouse-speed/mouse-speed.component';
export * from './led-brightness/led-brightness.component'; export * from './led-brightness/led-brightness.component';
export * from './restore-configuration/restore-configuration.component';
export * from './device.routes'; export * from './device.routes';

View File

@@ -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>

View File

@@ -0,0 +1,10 @@
:host {
overflow-y: auto;
display: block;
height: 100%;
width: 100%;
p {
margin: 1.5rem 0;
}
}

View File

@@ -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());
}
}

View File

@@ -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>

View File

@@ -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;
}
}

View File

@@ -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 = () => {
}
}

View File

@@ -7,8 +7,11 @@
[selectedKey]="selectedKey" [selectedKey]="selectedKey"
[selected]="selectedKey?.layerId === index" [selected]="selectedKey?.layerId === index"
[keyboardLayout]="keyboardLayout" [keyboardLayout]="keyboardLayout"
[description]="description"
[showDescription]="true"
(keyClick)="keyClick.emit($event)" (keyClick)="keyClick.emit($event)"
(keyHover)="keyHover.emit($event)" (keyHover)="keyHover.emit($event)"
(capture)="capture.emit($event)" (capture)="capture.emit($event)"
(descriptionChanged)="descriptionChanged.emit($event)"
> >
</svg-keyboard> </svg-keyboard>

Before

Width:  |  Height:  |  Size: 659 B

After

Width:  |  Height:  |  Size: 809 B

View File

@@ -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 { animate, keyframes, state, style, transition, trigger } from '@angular/animations';
import { Layer } from 'uhk-common'; import { Layer } from 'uhk-common';
@@ -81,11 +81,14 @@ export class KeyboardSliderComponent implements OnChanges {
@Input() halvesSplit: boolean; @Input() halvesSplit: boolean;
@Input() selectedKey: { layerId: number, moduleId: number, keyId: number }; @Input() selectedKey: { layerId: number, moduleId: number, keyId: number };
@Input() keyboardLayout = KeyboardLayout.ANSI; @Input() keyboardLayout = KeyboardLayout.ANSI;
@Input() description: string;
@Output() keyClick = new EventEmitter(); @Output() keyClick = new EventEmitter();
@Output() keyHover = new EventEmitter(); @Output() keyHover = new EventEmitter();
@Output() capture = new EventEmitter(); @Output() capture = new EventEmitter();
@Output() descriptionChanged = new EventEmitter<string>();
layerAnimationState: AnimationKeyboard[]; layerAnimationState: AnimationKeyboard[];
ngOnChanges(changes: SimpleChanges) { ngOnChanges(changes: SimpleChanges) {
if (changes['layers']) { if (changes['layers']) {
this.layerAnimationState = this.layers.map<AnimationKeyboard>(() => 'initOut'); this.layerAnimationState = this.layers.map<AnimationKeyboard>(() => 'initOut');

View File

@@ -4,7 +4,8 @@
(downloadClick)="downloadKeymap()"></keymap-header> (downloadClick)="downloadKeymap()"></keymap-header>
<svg-keyboard-wrap [keymap]="keymap$ | async" <svg-keyboard-wrap [keymap]="keymap$ | async"
[halvesSplit]="keyboardSplit" [halvesSplit]="keyboardSplit"
[keyboardLayout]="keyboardLayout$ | async"></svg-keyboard-wrap> [keyboardLayout]="keyboardLayout$ | async"
(descriptionChanged)="descriptionChanged($event)"></svg-keyboard-wrap>
</ng-template> </ng-template>
<div *ngIf="!(keymap$ | async)" class="not-found"> <div *ngIf="!(keymap$ | async)" class="not-found">

View File

@@ -18,6 +18,8 @@ import { AppState, getKeyboardLayout } from '../../../store';
import { getKeymap, getKeymaps, getUserConfiguration } from '../../../store/reducers/user-configuration'; import { getKeymap, getKeymaps, getUserConfiguration } from '../../../store/reducers/user-configuration';
import { SvgKeyboardWrapComponent } from '../../svg/wrap'; import { SvgKeyboardWrapComponent } from '../../svg/wrap';
import { KeyboardLayout } from '../../../keyboard/keyboard-layout.enum'; import { KeyboardLayout } from '../../../keyboard/keyboard-layout.enum';
import { KeymapActions } from '../../../store/actions';
import { ChangeKeymapDescription } from '../../../models/ChangeKeymapDescription';
@Component({ @Component({
selector: 'keymap-edit', selector: 'keymap-edit',
@@ -64,7 +66,7 @@ export class KeymapEditComponent {
const keymap = latest[0]; const keymap = latest[0];
const exportableJSON = latest[1]; const exportableJSON = latest[1];
const fileName = keymap.name + '_keymap.json'; 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; this.keyboardSplit = !this.keyboardSplit;
} }
descriptionChanged(event: ChangeKeymapDescription): void {
this.store.dispatch(new KeymapActions.EditDescriptionAction(event));
}
private toExportableJSON(keymap: Keymap): Observable<any> { private toExportableJSON(keymap: Keymap): Observable<any> {
return this.store return this.store
.let(getUserConfiguration()) .let(getUserConfiguration())

View File

@@ -13,5 +13,5 @@
</ng-template> </ng-template>
<div *ngIf="!macro" class="not-found"> <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> </div>

View File

@@ -7,7 +7,7 @@ import { Subscription } from 'rxjs/Subscription';
import 'rxjs/add/operator/pluck'; import 'rxjs/add/operator/pluck';
import { MacroActions } from '../../../store/actions'; import { MacroActions } from '../../../store/actions';
import { AppState } from '../../../store/index'; import { AppState } from '../../../store';
import { getMacro } from '../../../store/reducers/user-configuration'; import { getMacro } from '../../../store/reducers/user-configuration';
@Component({ @Component({
@@ -21,13 +21,17 @@ import { getMacro } from '../../../store/reducers/user-configuration';
export class MacroEditComponent implements OnDestroy { export class MacroEditComponent implements OnDestroy {
macro: Macro; macro: Macro;
isNew: boolean; isNew: boolean;
macroId: number;
private subscription: Subscription; private subscription: Subscription;
constructor(private store: Store<AppState>, public route: ActivatedRoute) { constructor(private store: Store<AppState>, public route: ActivatedRoute) {
this.subscription = route this.subscription = route
.params .params
.pluck<{}, string>('id') .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) => { .subscribe((macro: Macro) => {
this.macro = macro; this.macro = macro;
}); });

View File

@@ -7,6 +7,10 @@
[width]="200" [width]="200"
[options]="options" [options]="options"
></select2> ></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> <capture-keystroke-button (capture)="onKeysCapture($event)" tabindex="0"></capture-keystroke-button>
</div> </div>
<div class="modifier-options"> <div class="modifier-options">

View File

@@ -10,6 +10,10 @@
position: relative; position: relative;
top: 2px; top: 2px;
} }
icon {
display: inline-block;
};
} }
.modifier-options { .modifier-options {

View File

@@ -1,6 +1,6 @@
import { Component, Input, OnChanges } from '@angular/core'; import { Component, Input, OnChanges } from '@angular/core';
import { Select2OptionData, Select2TemplateFunction } from 'ng2-select2'; 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 { Tab } from '../tab';
import { MapperService } from '../../../../services/mapper.service'; import { MapperService } from '../../../../services/mapper.service';
@@ -35,8 +35,8 @@ export class KeypressTabComponent extends Tab implements OnChanges {
id: '0', id: '0',
text: 'None' text: 'None'
}]; }];
this.scanCodeGroups = this.scanCodeGroups.concat(require('./scancodes.json')); this.scanCodeGroups = this.scanCodeGroups.concat(SCANCODES);
this.secondaryRoleGroups = require('./secondaryRole.json'); this.secondaryRoleGroups = SECONDARY_ROLES;
this.leftModifierSelects = Array(this.leftModifiers.length).fill(false); this.leftModifierSelects = Array(this.leftModifiers.length).fill(false);
this.rightModifierSelects = Array(this.rightModifiers.length).fill(false); this.rightModifierSelects = Array(this.rightModifiers.length).fill(false);
this.selectedScancodeOption = this.scanCodeGroups[0]; this.selectedScancodeOption = this.scanCodeGroups[0];

View File

@@ -1,4 +1,39 @@
<span class="privilege-checker-wrapper"> <div class="privilege-checker-wrapper">
<uhk-message header="Cannot talk to your UHK" subtitle="Your UHK has been detected, but its permissions are not set up yet, so Agent can't talk to it."></uhk-message> <uhk-message header="Cannot talk to your UHK"
<button class="btn btn-default btn-lg btn-primary" (click)="setUpPermissions()"> Set up permissions </button> subtitle="Your UHK has been detected, but its permissions are not set up yet, so Agent can't talk to it."></uhk-message>
</span>
<button class="btn btn-default btn-lg btn-primary"
(click)="setUpPermissions()"> Set up permissions
</button>
<div class="mt-10">
<a class="link-inline"
*ngIf="state.showWhatWillThisDo"
(click)="whatWillThisDo()">What will this do?
</a>
<div>
<p class="privilege-error"
#privilegeError
*ngIf="state.permissionSetupFailed">
Agent wasn't able to set up permissions via PolicyKit. This is most likely because the
<code>polkit</code> package is not installed on your system.
</p>
<div *ngIf="state.showWhatWillThisDoContent">
Agent uses the following script to set up permissions. You can run it manually as root, then
<a class="link-inline"
(click)="retry()">retry</a>.
<div class="copy-container">
<span class="fa fa-2x fa-copy"
ngxClipboard
[cbContent]="command"
title="Copy to clipboard"
data-toggle="tooltip"
data-placement="top"></span>
<pre><code>{{ command }}</code></pre>
</div>
</div>
</div>
</div>
</div>

View File

@@ -9,3 +9,19 @@
uhk-message { uhk-message {
max-width: 50%; max-width: 50%;
} }
.privilege-error {
animation: error-fade-in 2s;
}
@keyframes error-fade-in {
0% {
color: white;
background-color: red;
}
100% {
color: inherit;
background-color: inherit;
}
}

View File

@@ -1,26 +1,61 @@
import { Component } from '@angular/core'; import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core';
import { Router } from '@angular/router';
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
import 'rxjs/add/observable/of'; import { Subscription } from 'rxjs/Subscription';
import 'rxjs/add/observable/throw';
import 'rxjs/add/operator/distinctUntilChanged';
import 'rxjs/add/operator/ignoreElements';
import 'rxjs/add/operator/takeWhile';
import { AppState } from '../../store/index'; import { AppState, getPrivilegePageState } from '../../store';
import { SetPrivilegeOnLinuxAction } from '../../store/actions/device'; import { SetPrivilegeOnLinuxAction } from '../../store/actions/device';
import { LoadAppStartInfoAction, PrivilegeWhatWillThisDoAction } from '../../store/actions/app';
import { PrivilagePageSate } from '../../models/privilage-page-sate';
@Component({ @Component({
selector: 'privilege-checker', selector: 'privilege-checker',
changeDetection: ChangeDetectionStrategy.OnPush,
templateUrl: './privilege-checker.component.html', templateUrl: './privilege-checker.component.html',
styleUrls: ['./privilege-checker.component.scss'] styleUrls: ['./privilege-checker.component.scss']
}) })
export class PrivilegeCheckerComponent {
constructor(protected store: Store<AppState>) { export class PrivilegeCheckerComponent implements OnInit, OnDestroy {
state: PrivilagePageSate;
command = `cat <<EOF >/etc/udev/rules.d/50-uhk60.rules
# Ultimate Hacking Keyboard rules
# These are the udev rules for accessing the USB interfaces of the UHK as non-root users.
# Copy this file to /etc/udev/rules.d and physically reconnect the UHK afterwards.
SUBSYSTEMS=="usb", ATTRS{idVendor}=="1d50", ATTRS{idProduct}=="612[0-7]", MODE:="0666"
EOF
udevadm trigger
udevadm settle`;
private stateSubscription: Subscription;
constructor(private store: Store<AppState>,
private cdRef: ChangeDetectorRef) {
}
ngOnInit(): void {
this.stateSubscription = this.store.select(getPrivilegePageState)
.subscribe(state => {
this.state = state;
this.cdRef.markForCheck();
});
}
ngOnDestroy(): void {
if (this.stateSubscription) {
this.stateSubscription.unsubscribe();
}
} }
setUpPermissions(): void { setUpPermissions(): void {
this.store.dispatch(new SetPrivilegeOnLinuxAction()); this.store.dispatch(new SetPrivilegeOnLinuxAction());
} }
whatWillThisDo(): void {
this.store.dispatch(new PrivilegeWhatWillThisDoAction());
}
retry(): void {
this.store.dispatch(new LoadAppStartInfoAction());
}
} }

View File

@@ -5,6 +5,7 @@
<input #deviceName cancelable <input #deviceName cancelable
class="pane-title__name" class="pane-title__name"
type="text" type="text"
[readonly]="state.restoreUserConfiguration"
(change)="editDeviceName($event.target.value)" (change)="editDeviceName($event.target.value)"
(keyup.enter)="deviceName.blur()" (keyup.enter)="deviceName.blur()"
(keyup)="calculateHeaderTextWidth($event.target.value)"> (keyup)="calculateHeaderTextWidth($event.target.value)">
@@ -17,33 +18,43 @@
<i class="fa fa-chevron-up pull-right" (click)="toggleHide($event, 'configuration')"></i> <i class="fa fa-chevron-up pull-right" (click)="toggleHide($event, 'configuration')"></i>
</div> </div>
<ul [@toggler]="animation['configuration']"> <ul [@toggler]="animation['configuration']">
<li class="sidebar__level-2--item"> <li class="sidebar__level-2--item"
*ngIf="!state.restoreUserConfiguration">
<div class="sidebar__level-2" [routerLinkActive]="['active']"> <div class="sidebar__level-2" [routerLinkActive]="['active']">
<a [routerLink]="['/device/mouse-speed']" <a [routerLink]="['/device/mouse-speed']"
[class.disabled]="updatingFirmware$ | async">Mouse speed</a> [class.disabled]="state.updatingFirmware">Mouse speed</a>
</div> </div>
</li> </li>
<li class="sidebar__level-2--item"> <li class="sidebar__level-2--item"
*ngIf="!state.restoreUserConfiguration">
<div class="sidebar__level-2" [routerLinkActive]="['active']"> <div class="sidebar__level-2" [routerLinkActive]="['active']">
<a [routerLink]="['/device/led-brightness']" <a [routerLink]="['/device/led-brightness']"
[class.disabled]="updatingFirmware$ | async">LED brightness</a> [class.disabled]="state.updatingFirmware">LED brightness</a>
</div> </div>
</li> </li>
<li class="sidebar__level-2--item"> <li class="sidebar__level-2--item"
*ngIf="!state.restoreUserConfiguration">
<div class="sidebar__level-2" [routerLinkActive]="['active']"> <div class="sidebar__level-2" [routerLinkActive]="['active']">
<a [routerLink]="['/device/configuration']" <a [routerLink]="['/device/configuration']"
[class.disabled]="updatingFirmware$ | async">Configuration</a> [class.disabled]="state.updatingFirmware">Configuration</a>
</div>
</li>
<li class="sidebar__level-2--item"
*ngIf="state.restoreUserConfiguration">
<div class="sidebar__level-2" [routerLinkActive]="['active']">
<a [routerLink]="['/device/restore-user-configuration']">Fix configuration</a>
</div> </div>
</li> </li>
<li class="sidebar__level-2--item"> <li class="sidebar__level-2--item">
<div class="sidebar__level-2" [routerLinkActive]="['active']"> <div class="sidebar__level-2" [routerLinkActive]="['active']">
<a [routerLink]="['/device/firmware']" <a [routerLink]="['/device/firmware']"
[class.disabled]="updatingFirmware$ | async">Firmware</a> [class.disabled]="state.updatingFirmware">Firmware</a>
</div> </div>
</li> </li>
</ul> </ul>
</li> </li>
<li class="sidebar__level-1--item"> <li class="sidebar__level-1--item"
*ngIf="!state.restoreUserConfiguration">
<div class="sidebar__level-1"> <div class="sidebar__level-1">
<i class="fa fa-keyboard-o"></i> Keymaps <i class="fa fa-keyboard-o"></i> Keymaps
<!--a [routerLink]="['/keymap/add']" <!--a [routerLink]="['/keymap/add']"
@@ -55,10 +66,10 @@
(click)="toggleHide($event, 'keymap')"></i> (click)="toggleHide($event, 'keymap')"></i>
</div> </div>
<ul [@toggler]="animation['keymap']"> <ul [@toggler]="animation['keymap']">
<li *ngFor="let keymap of keymaps$ | async" class="sidebar__level-2--item"> <li *ngFor="let keymap of state.keymaps" class="sidebar__level-2--item">
<div class="sidebar__level-2" [routerLinkActive]="['active']"> <div class="sidebar__level-2" [routerLinkActive]="['active']">
<a [routerLink]="['/keymap', keymap.abbreviation]" <a [routerLink]="['/keymap', keymap.abbreviation]"
[class.disabled]="updatingFirmware$ | async">{{keymap.name}}</a> [class.disabled]="state.updatingFirmware">{{keymap.name}}</a>
<i *ngIf="keymap.isDefault" class="fa fa-star sidebar__fav" <i *ngIf="keymap.isDefault" class="fa fa-star sidebar__fav"
title="This is the default keymap which gets activated when powering the keyboard." title="This is the default keymap which gets activated when powering the keyboard."
data-toggle="tooltip" data-placement="bottom"></i> data-toggle="tooltip" data-placement="bottom"></i>
@@ -66,26 +77,27 @@
</li> </li>
</ul> </ul>
</li> </li>
<li class="sidebar__level-1--item"> <li class="sidebar__level-1--item"
*ngIf="!state.restoreUserConfiguration">
<div class="sidebar__level-1"> <div class="sidebar__level-1">
<i class="fa fa-play"></i> Macros <i class="fa fa-play"></i> Macros
<a (click)="addMacro()" <a (click)="addMacro()"
class="btn btn-default pull-right btn-sm" class="btn btn-default pull-right btn-sm"
[class.disabled]="updatingFirmware$ | async"> [class.disabled]="state.updatingFirmware">
<i class="fa fa-plus"></i> <i class="fa fa-plus"></i>
</a> </a>
<i class="fa fa-chevron-up pull-right" (click)="toggleHide($event, 'macro')"></i> <i class="fa fa-chevron-up pull-right" (click)="toggleHide($event, 'macro')"></i>
</div> </div>
<ul [@toggler]="animation['macro']"> <ul [@toggler]="animation['macro']">
<li *ngFor="let macro of macros$ | async" class="sidebar__level-2--item"> <li *ngFor="let macro of state.macros" class="sidebar__level-2--item">
<div class="sidebar__level-2" [routerLinkActive]="['active']"> <div class="sidebar__level-2" [routerLinkActive]="['active']">
<a [routerLink]="['/macro', macro.id]" <a [routerLink]="['/macro', macro.id]"
[class.disabled]="updatingFirmware$ | async">{{macro.name}}</a> [class.disabled]="state.updatingFirmware">{{macro.name}}</a>
</div> </div>
</li> </li>
</ul> </ul>
</li> </li>
<li class="sidebar__level-1--item" *ngIf="showAddonMenu$ | async"> <li class="sidebar__level-1--item" *ngIf="state.showAddonMenu">
<div class="sidebar__level-1"> <div class="sidebar__level-1">
<i class="fa fa-puzzle-piece"></i> Add-on modules <i class="fa fa-puzzle-piece"></i> Add-on modules
<i class="fa fa-chevron-up pull-right" (click)="toggleHide($event, 'addon')"></i> <i class="fa fa-chevron-up pull-right" (click)="toggleHide($event, 'addon')"></i>
@@ -94,25 +106,25 @@
<li class="sidebar__level-2--item" data-name="Key cluster" data-abbrev=""> <li class="sidebar__level-2--item" data-name="Key cluster" data-abbrev="">
<div class="sidebar__level-2" [routerLinkActive]="['active']"> <div class="sidebar__level-2" [routerLinkActive]="['active']">
<a [routerLink]="['/add-on', 'Key cluster']" <a [routerLink]="['/add-on', 'Key cluster']"
[class.disabled]="updatingFirmware$ | async">Key cluster</a> [class.disabled]="state.updatingFirmware">Key cluster</a>
</div> </div>
</li> </li>
<li class="sidebar__level-2--item" data-name="Trackball" data-abbrev=""> <li class="sidebar__level-2--item" data-name="Trackball" data-abbrev="">
<div class="sidebar__level-2" [routerLinkActive]="['active']"> <div class="sidebar__level-2" [routerLinkActive]="['active']">
<a [routerLink]="['/add-on', 'Trackball']" <a [routerLink]="['/add-on', 'Trackball']"
[class.disabled]="updatingFirmware$ | async">Trackball</a> [class.disabled]="state.updatingFirmware">Trackball</a>
</div> </div>
</li> </li>
<li class="sidebar__level-2--item" data-name="Toucpad" data-abbrev=""> <li class="sidebar__level-2--item" data-name="Toucpad" data-abbrev="">
<div class="sidebar__level-2" [routerLinkActive]="['active']"> <div class="sidebar__level-2" [routerLinkActive]="['active']">
<a [routerLink]="['/add-on', 'Touchpad']" <a [routerLink]="['/add-on', 'Touchpad']"
[class.disabled]="updatingFirmware$ | async">Touchpad</a> [class.disabled]="state.updatingFirmware">Touchpad</a>
</div> </div>
</li> </li>
<li class="sidebar__level-2--item" data-name="Trackpoint" data-abbrev=""> <li class="sidebar__level-2--item" data-name="Trackpoint" data-abbrev="">
<div class="sidebar__level-2" [routerLinkActive]="['active']"> <div class="sidebar__level-2" [routerLinkActive]="['active']">
<a [routerLink]="['/add-on', 'Trackpoint']" <a [routerLink]="['/add-on', 'Trackpoint']"
[class.disabled]="updatingFirmware$ | async">Trackpoint</a> [class.disabled]="state.updatingFirmware">Trackpoint</a>
</div> </div>
</li> </li>
</ul> </ul>
@@ -123,17 +135,19 @@
<div class="sidebar__level-0"> <div class="sidebar__level-0">
<i class="uhk-icon uhk-icon-agent-icon"></i> Agent <i class="uhk-icon uhk-icon-agent-icon"></i> Agent
<i class="fa fa-chevron-up pull-right" <i class="fa fa-chevron-up pull-right"
(click)="toggleHide($event, 'agent')"></i> (click)="toggleHide($event, 'agent')"></i>
</div> </div>
<ul [@toggler]="animation['agent']"> <ul [@toggler]="animation['agent']">
<li class="sidebar__level-2--item"> <li class="sidebar__level-2--item">
<div class="sidebar__level-2" [routerLinkActive]="['active']"> <div class="sidebar__level-2" [routerLinkActive]="['active']">
<a [routerLink]="['/settings']">Settings</a> <a [routerLink]="['/settings']"
[class.disabled]="state.updatingFirmware">Settings</a>
</div> </div>
</li> </li>
<li class="sidebar__level-2--item"> <li class="sidebar__level-2--item">
<div class="sidebar__level-2" [routerLinkActive]="['active']"> <div class="sidebar__level-2" [routerLinkActive]="['active']">
<a [routerLink]="['/about']">About</a> <a [routerLink]="['/about']"
[class.disabled]="state.updatingFirmware">About</a>
</div> </div>
</li> </li>
</ul> </ul>

View File

@@ -9,6 +9,10 @@
a { a {
color: #333; color: #333;
&.disabled {
opacity: 0.65;
}
} }
// General list styles for the sidebar-menu. // General list styles for the sidebar-menu.
@@ -112,6 +116,10 @@ ul {
&:focus { &:focus {
text-decoration: none; text-decoration: none;
} }
&.disabled {
opacity: 0.65;
}
} }
} }
} }
@@ -164,12 +172,12 @@ ul {
padding: 0; padding: 0;
margin: 0 0.25rem; margin: 0 0.25rem;
text-overflow: ellipsis; text-overflow: ellipsis;
background-color: inherit; background-color: transparent;
&:focus { &:focus {
box-shadow: 0 0 0 1px #ccc, 0 0 5px 0 #ccc; box-shadow: 0 0 0 1px #ccc, 0 0 5px 0 #ccc;
border-color: transparent; border-color: transparent;
background-color: inherit; background-color: transparent;
} }
} }
} }

View File

@@ -1,20 +1,27 @@
import { AfterContentInit, Component, ElementRef, OnDestroy, Renderer2, ViewChild } from '@angular/core'; import {
AfterContentInit,
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
ElementRef,
OnDestroy, OnInit,
Renderer2,
ViewChild
} from '@angular/core';
import { animate, state, style, transition, trigger } from '@angular/animations'; import { animate, state, style, transition, trigger } from '@angular/animations';
import { Keymap, Macro } from 'uhk-common';
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
import { Observable } from 'rxjs/Observable';
import { Subscription } from 'rxjs/Subscription'; import { Subscription } from 'rxjs/Subscription';
import 'rxjs/add/operator/do'; import 'rxjs/add/operator/do';
import 'rxjs/add/operator/map'; import 'rxjs/add/operator/map';
import 'rxjs/add/operator/let'; import 'rxjs/add/operator/let';
import { AppState, getDeviceName, runningInElectron, showAddonMenu, updatingFirmware } from '../../store'; import { AppState, getSideMenuPageState } from '../../store';
import { MacroActions } from '../../store/actions'; import { MacroActions } from '../../store/actions';
import { getKeymaps, getMacros } from '../../store/reducers/user-configuration';
import * as util from '../../util'; import * as util from '../../util';
import { RenameUserConfigurationAction } from '../../store/actions/user-config'; import { RenameUserConfigurationAction } from '../../store/actions/user-config';
import { SideMenuPageState } from '../../models/side-menu-page-state';
@Component({ @Component({
animations: [ animations: [
@@ -30,24 +37,19 @@ import { RenameUserConfigurationAction } from '../../store/actions/user-config';
], ],
selector: 'side-menu', selector: 'side-menu',
templateUrl: './side-menu.component.html', templateUrl: './side-menu.component.html',
styleUrls: ['./side-menu.component.scss'] styleUrls: ['./side-menu.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
}) })
export class SideMenuComponent implements AfterContentInit, OnDestroy { export class SideMenuComponent implements AfterContentInit, OnInit, OnDestroy {
showAddonMenu$: Observable<boolean>; state: SideMenuPageState;
runInElectron$: Observable<boolean>;
updatingFirmware$: Observable<boolean>;
deviceName$: Observable<string>;
deviceNameSubscription: Subscription;
keymaps$: Observable<Keymap[]>;
macros$: Observable<Macro[]>;
animation: { [key: string]: 'active' | 'inactive' }; animation: { [key: string]: 'active' | 'inactive' };
deviceNameValue: string;
updatingFirmware = false;
updatingFirmwareSubscription: Subscription;
@ViewChild('deviceName') deviceName: ElementRef; @ViewChild('deviceName') deviceName: ElementRef;
constructor(private store: Store<AppState>, private renderer: Renderer2) { private stateSubscription: Subscription;
constructor(private store: Store<AppState>,
private renderer: Renderer2,
private cdRef: ChangeDetectorRef) {
this.animation = { this.animation = {
device: 'active', device: 'active',
configuration: 'active', configuration: 'active',
@@ -55,29 +57,13 @@ export class SideMenuComponent implements AfterContentInit, OnDestroy {
macro: 'active', macro: 'active',
addon: 'active' addon: 'active'
}; };
}
this.keymaps$ = store.let(getKeymaps()) ngOnInit(): void {
.map(keymaps => keymaps.slice()) // Creating a new array reference, because the sort is working in place this.stateSubscription = this.store.select(getSideMenuPageState).subscribe(data => {
.do((keymaps: Keymap[]) => { this.state = data;
keymaps.sort((first: Keymap, second: Keymap) => first.name.localeCompare(second.name));
});
this.macros$ = store.let(getMacros())
.map(macros => macros.slice()) // Creating a new array reference, because the sort is working in place
.do((macros: Macro[]) => {
macros.sort((first: Macro, second: Macro) => first.name.localeCompare(second.name));
});
this.showAddonMenu$ = this.store.select(showAddonMenu);
this.runInElectron$ = this.store.select(runningInElectron);
this.deviceName$ = store.select(getDeviceName);
this.deviceNameSubscription = this.deviceName$.subscribe(name => {
this.deviceNameValue = name;
this.setDeviceName(); this.setDeviceName();
}); this.cdRef.markForCheck();
this.updatingFirmware$ = store.select(updatingFirmware);
this.updatingFirmwareSubscription = this.updatingFirmware$.subscribe(updating => {
this.updatingFirmware = updating;
}); });
} }
@@ -86,12 +72,13 @@ export class SideMenuComponent implements AfterContentInit, OnDestroy {
} }
ngOnDestroy(): void { ngOnDestroy(): void {
this.deviceNameSubscription.unsubscribe(); if (this.stateSubscription) {
this.updatingFirmwareSubscription.unsubscribe(); this.stateSubscription.unsubscribe();
}
} }
toggleHide(event: Event, type: string) { toggleHide(event: Event, type: string) {
if (this.updatingFirmware) { if (this.state.updatingFirmware) {
return; return;
} }
@@ -119,7 +106,7 @@ export class SideMenuComponent implements AfterContentInit, OnDestroy {
} }
editDeviceName(name: string): void { editDeviceName(name: string): void {
if (!util.isValidName(name) || name.trim() === this.deviceNameValue) { if (!util.isValidName(name) || name.trim() === this.state.deviceName) {
this.setDeviceName(); this.setDeviceName();
return; return;
} }
@@ -135,7 +122,7 @@ export class SideMenuComponent implements AfterContentInit, OnDestroy {
private setDeviceName(): void { private setDeviceName(): void {
if (this.deviceName) { if (this.deviceName) {
this.renderer.setProperty(this.deviceName.nativeElement, 'value', this.deviceNameValue); this.renderer.setProperty(this.deviceName.nativeElement, 'value', this.state.deviceName);
this.calculateHeaderTextWidth(this.deviceName.nativeElement.value); this.calculateHeaderTextWidth(this.deviceName.nativeElement.value);
} }
} }

View File

@@ -1,16 +1,20 @@
<svg xmlns="http://www.w3.org/2000/svg" [attr.viewBox]="viewBox" height="100%" width="100%"> <svg xmlns="http://www.w3.org/2000/svg" [attr.viewBox]="viewBox" height="100%" width="100%">
<svg:g svg-module *ngFor="let module of modules; let i = index" <svg:g svg-module *ngFor="let module of modules; let i = index"
[coverages]="module.coverages" [coverages]="module.coverages"
[keyboardKeys]="module.keyboardKeys" [keyboardKeys]="module.keyboardKeys"
[keybindAnimationEnabled]="keybindAnimationEnabled" [keybindAnimationEnabled]="keybindAnimationEnabled"
[capturingEnabled]="capturingEnabled" [capturingEnabled]="capturingEnabled"
[attr.transform]="module.attributes.transform" [attr.transform]="module.attributes.transform"
[keyActions]="moduleConfig[i].keyActions" [keyActions]="moduleConfig[i].keyActions"
[selectedKey]="selectedKey" [selectedKey]="selectedKey"
[@split]="moduleAnimationStates[i]" [@split]="moduleAnimationStates[i]"
[selected]="selectedKey?.moduleId === i" [selected]="selectedKey?.moduleId === i"
(keyClick)="onKeyClick(i, $event.index, $event.keyTarget)" (keyClick)="onKeyClick(i, $event.index, $event.keyTarget)"
(keyHover)="onKeyHover($event.index, $event.event, $event.over, i)" (keyHover)="onKeyHover($event.index, $event.event, $event.over, i)"
(capture)="onCapture(i, $event.index, $event.captured)" (capture)="onCapture(i, $event.index, $event.captured)"
/> />
</svg> </svg>
<editable-text *ngIf="showDescription"
[ngModel]="description"
(ngModelChange)="descriptionChanged.emit($event)"
placeholder="No description provided for this keymap."></editable-text>

Before

Width:  |  Height:  |  Size: 854 B

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@@ -1,5 +1,10 @@
:host { :host {
display: flex; display: block;
width: 100%; width: 100%;
position: relative; position: relative;
} }
editable-text {
padding-left: 2em;
padding-right: 2em;
display: block;
}

View File

@@ -31,9 +31,12 @@ export class SvgKeyboardComponent implements OnInit {
@Input() selected: boolean; @Input() selected: boolean;
@Input() halvesSplit: boolean; @Input() halvesSplit: boolean;
@Input() keyboardLayout = KeyboardLayout.ANSI; @Input() keyboardLayout = KeyboardLayout.ANSI;
@Input() description: string;
@Input() showDescription = false;
@Output() keyClick = new EventEmitter(); @Output() keyClick = new EventEmitter();
@Output() keyHover = new EventEmitter(); @Output() keyHover = new EventEmitter();
@Output() capture = new EventEmitter(); @Output() capture = new EventEmitter();
@Output() descriptionChanged = new EventEmitter<string>();
modules: SvgModule[]; modules: SvgModule[];
viewBox: string; viewBox: string;

View File

@@ -7,9 +7,11 @@
[selectedKey]="selectedKey" [selectedKey]="selectedKey"
[halvesSplit]="halvesSplit" [halvesSplit]="halvesSplit"
[keyboardLayout]="keyboardLayout" [keyboardLayout]="keyboardLayout"
[description]="keymap.description"
(keyClick)="onKeyClick($event.moduleId, $event.keyId, $event.keyTarget)" (keyClick)="onKeyClick($event.moduleId, $event.keyId, $event.keyTarget)"
(keyHover)="onKeyHover($event.moduleId, $event.event, $event.over, $event.keyId)" (keyHover)="onKeyHover($event.moduleId, $event.event, $event.over, $event.keyId)"
(capture)="onCapture($event.moduleId, $event.keyId, $event.captured)" (capture)="onCapture($event.moduleId, $event.keyId, $event.captured)"
(descriptionChanged)="onDescriptionChanged($event)"
></keyboard-slider> ></keyboard-slider>
<popover tabindex="0" [visible]="popoverShown" [keyPosition]="keyPosition" [wrapPosition]="wrapPosition" [defaultKeyAction]="popoverInitKeyAction" <popover tabindex="0" [visible]="popoverShown" [keyPosition]="keyPosition" [wrapPosition]="wrapPosition" [defaultKeyAction]="popoverInitKeyAction"
[currentKeymap]="keymap" [currentLayer]="currentLayer" (cancel)="hidePopover()" (remap)="onRemap($event)"></popover> [currentKeymap]="keymap" [currentLayer]="currentLayer" (cancel)="hidePopover()" (remap)="onRemap($event)"></popover>

View File

@@ -2,14 +2,16 @@ import {
ChangeDetectionStrategy, ChangeDetectionStrategy,
Component, Component,
ElementRef, ElementRef,
Renderer, EventEmitter,
HostBinding, HostBinding,
HostListener, HostListener,
Input, Input,
OnChanges, OnChanges,
OnInit, OnInit,
ViewChild, Output,
SimpleChanges Renderer,
SimpleChanges,
ViewChild
} from '@angular/core'; } from '@angular/core';
import { Observable } from 'rxjs/Observable'; import { Observable } from 'rxjs/Observable';
@@ -25,10 +27,10 @@ import {
KeystrokeAction, KeystrokeAction,
Layer, Layer,
LayerName, LayerName,
SecondaryRoleAction,
MouseAction, MouseAction,
MouseActionParam, MouseActionParam,
PlayMacroAction, PlayMacroAction,
SecondaryRoleAction,
SwitchKeymapAction, SwitchKeymapAction,
SwitchLayerAction SwitchLayerAction
} from 'uhk-common'; } from 'uhk-common';
@@ -38,6 +40,7 @@ import { AppState } from '../../../store';
import { KeymapActions } from '../../../store/actions'; import { KeymapActions } from '../../../store/actions';
import { PopoverComponent } from '../../popover'; import { PopoverComponent } from '../../popover';
import { KeyboardLayout } from '../../../keyboard/keyboard-layout.enum'; import { KeyboardLayout } from '../../../keyboard/keyboard-layout.enum';
import { ChangeKeymapDescription } from '../../../models/ChangeKeymapDescription';
interface NameValuePair { interface NameValuePair {
name: string; name: string;
@@ -56,6 +59,7 @@ export class SvgKeyboardWrapComponent implements OnInit, OnChanges {
@Input() tooltipEnabled: boolean = false; @Input() tooltipEnabled: boolean = false;
@Input() halvesSplit: boolean; @Input() halvesSplit: boolean;
@Input() keyboardLayout: KeyboardLayout.ANSI; @Input() keyboardLayout: KeyboardLayout.ANSI;
@Output() descriptionChanged = new EventEmitter<ChangeKeymapDescription>();
@ViewChild(PopoverComponent, { read: ElementRef }) popover: ElementRef; @ViewChild(PopoverComponent, { read: ElementRef }) popover: ElementRef;
@@ -237,6 +241,13 @@ export class SvgKeyboardWrapComponent implements OnInit, OnChanges {
return this.currentLayer; return this.currentLayer;
} }
onDescriptionChanged(description: string): void {
this.descriptionChanged.emit({
description,
abbr: this.keymap.abbreviation
});
}
private getKeyActionContent(keyAction: KeyAction): Observable<NameValuePair[]> { private getKeyActionContent(keyAction: KeyAction): Observable<NameValuePair[]> {
if (keyAction instanceof KeystrokeAction) { if (keyAction instanceof KeystrokeAction) {
const keystrokeAction: KeystrokeAction = keyAction; const keystrokeAction: KeystrokeAction = keyAction;

View File

@@ -3,7 +3,7 @@ import { Store } from '@ngrx/store';
import { Observable } from 'rxjs/Observable'; import { Observable } from 'rxjs/Observable';
import { Notification } from 'uhk-common'; import { Notification } from 'uhk-common';
import { AppState, getUndoableNotification } from '../../store/index'; import { AppState, getUndoableNotification } from '../../store';
import { DismissUndoNotificationAction, UndoLastAction } from '../../store/actions/app'; import { DismissUndoNotificationAction, UndoLastAction } from '../../store/actions/app';
@Component({ @Component({

View File

@@ -0,0 +1,16 @@
import { AfterViewInit, Directive, ElementRef } from '@angular/core';
@Directive({
selector: '[autofocus]'
})
export class Autofocus implements AfterViewInit {
constructor(private el: ElementRef) {
}
ngOnInit() {
}
ngAfterViewInit() {
this.el.nativeElement.focus();
}
}

View File

@@ -0,0 +1,4 @@
export interface ChangeKeymapDescription {
abbr: string;
description: string;
}

View File

@@ -0,0 +1,5 @@
export interface PrivilagePageSate {
showWhatWillThisDo: boolean;
showWhatWillThisDoContent: boolean;
permissionSetupFailed: boolean;
}

View File

@@ -0,0 +1,4 @@
export interface RestoreConfigurationState {
restoringUserConfiguration: boolean;
hasBackupUserConfiguration: boolean;
}

View File

@@ -0,0 +1,11 @@
import { Keymap, Macro } from 'uhk-common';
export interface SideMenuPageState {
showAddonMenu: boolean;
runInElectron: boolean;
updatingFirmware: boolean;
deviceName: string;
keymaps: Keymap[];
macros: Macro[];
restoreUserConfiguration: boolean;
}

View File

@@ -94,7 +94,7 @@ export class CaptureService {
this.mapping.set(88, 27); // X this.mapping.set(88, 27); // X
this.mapping.set(89, 28); // Y this.mapping.set(89, 28); // Y
this.mapping.set(90, 29); // Z this.mapping.set(90, 29); // Z
this.mapping.set(93, 118); // Menu this.mapping.set(93, 101); // Menu
this.mapping.set(96, 98); // Num pad 0 this.mapping.set(96, 98); // Num pad 0
this.mapping.set(97, 89); // Num pad 1 this.mapping.set(97, 89); // Num pad 1
this.mapping.set(98, 90); // Num pad 2 this.mapping.set(98, 90); // Num pad 2

View File

@@ -1,7 +1,7 @@
import { Injectable, NgZone } from '@angular/core'; import { Injectable, NgZone } from '@angular/core';
import { Action, Store } from '@ngrx/store'; import { Action, Store } from '@ngrx/store';
import { DeviceConnectionState, IpcEvents, IpcResponse, LogService } from 'uhk-common'; import { DeviceConnectionState, IpcEvents, IpcResponse, LogService, SaveUserConfigurationData } from 'uhk-common';
import { AppState } from '../store'; import { AppState } from '../store';
import { IpcCommonRenderer } from './ipc-common-renderer'; import { IpcCommonRenderer } from './ipc-common-renderer';
import { import {
@@ -26,8 +26,8 @@ export class DeviceRendererService {
this.ipcRenderer.send(IpcEvents.device.setPrivilegeOnLinux); this.ipcRenderer.send(IpcEvents.device.setPrivilegeOnLinux);
} }
saveUserConfiguration(buffer: Buffer): void { saveUserConfiguration(data: SaveUserConfigurationData): void {
this.ipcRenderer.send(IpcEvents.device.saveUserConfiguration, JSON.stringify(buffer)); this.ipcRenderer.send(IpcEvents.device.saveUserConfiguration, JSON.stringify(data));
} }
loadConfigurationFromKeyboard(): void { loadConfigurationFromKeyboard(): void {

View File

@@ -190,6 +190,7 @@ export class MapperService {
this.basicScanCodeTextMap.set(98, ['Insert', '0']); this.basicScanCodeTextMap.set(98, ['Insert', '0']);
this.basicScanCodeTextMap.set(99, ['Del', '.']); this.basicScanCodeTextMap.set(99, ['Del', '.']);
this.basicScanCodeTextMap.set(100, ['ISO key', '|']); this.basicScanCodeTextMap.set(100, ['ISO key', '|']);
this.basicScanCodeTextMap.set(101, ['Menu']);
this.basicScanCodeTextMap.set(104, ['F13']); this.basicScanCodeTextMap.set(104, ['F13']);
this.basicScanCodeTextMap.set(105, ['F14']); this.basicScanCodeTextMap.set(105, ['F14']);
this.basicScanCodeTextMap.set(106, ['F15']); this.basicScanCodeTextMap.set(106, ['F15']);
@@ -202,7 +203,6 @@ export class MapperService {
this.basicScanCodeTextMap.set(113, ['F22']); this.basicScanCodeTextMap.set(113, ['F22']);
this.basicScanCodeTextMap.set(114, ['F23']); this.basicScanCodeTextMap.set(114, ['F23']);
this.basicScanCodeTextMap.set(115, ['F24']); this.basicScanCodeTextMap.set(115, ['F24']);
this.basicScanCodeTextMap.set(118, ['Menu']);
this.basicScanCodeTextMap.set(176, ['00']); this.basicScanCodeTextMap.set(176, ['00']);
this.basicScanCodeTextMap.set(177, ['000']); this.basicScanCodeTextMap.set(177, ['000']);
@@ -236,7 +236,7 @@ export class MapperService {
this.basicScancodeIcons.set(80, 'icon-kbd__mod--arrow-left'); this.basicScancodeIcons.set(80, 'icon-kbd__mod--arrow-left');
this.basicScancodeIcons.set(81, 'icon-kbd__mod--arrow-down'); this.basicScancodeIcons.set(81, 'icon-kbd__mod--arrow-down');
this.basicScancodeIcons.set(82, 'icon-kbd__mod--arrow-up'); this.basicScancodeIcons.set(82, 'icon-kbd__mod--arrow-up');
this.basicScancodeIcons.set(118, 'icon-kbd__mod--menu'); this.basicScancodeIcons.set(101, 'icon-kbd__mod--menu');
this.mediaScancodeIcons = new Map<number, string>(); this.mediaScancodeIcons = new Map<number, string>();
this.mediaScancodeIcons.set(138, 'icon-kbd__fn--browser'); this.mediaScancodeIcons.set(138, 'icon-kbd__fn--browser');

File diff suppressed because it is too large Load Diff

View File

@@ -8,6 +8,7 @@ import { ConfirmationPopoverModule } from 'angular-confirmation-popover';
import { DragulaModule } from 'ng2-dragula/ng2-dragula'; import { DragulaModule } from 'ng2-dragula/ng2-dragula';
import { Select2Module } from 'ng2-select2/ng2-select2'; import { Select2Module } from 'ng2-select2/ng2-select2';
import { NouisliderModule } from 'ng2-nouislider'; import { NouisliderModule } from 'ng2-nouislider';
import { ClipboardModule } from 'ngx-clipboard';
import { AddOnComponent } from './components/add-on'; import { AddOnComponent } from './components/add-on';
import { KeyboardSliderComponent } from './components/keyboard/slider'; import { KeyboardSliderComponent } from './components/keyboard/slider';
@@ -15,7 +16,8 @@ import {
DeviceConfigurationComponent, DeviceConfigurationComponent,
DeviceFirmwareComponent, DeviceFirmwareComponent,
MouseSpeedComponent, MouseSpeedComponent,
LEDBrightnessComponent LEDBrightnessComponent,
RestoreConfigurationComponent
} from './components/device'; } from './components/device';
import { KeymapAddComponent, KeymapEditComponent, KeymapHeaderComponent } from './components/keymap'; import { KeymapAddComponent, KeymapEditComponent, KeymapHeaderComponent } from './components/keymap';
import { LayersComponent } from './components/layers'; import { LayersComponent } from './components/layers';
@@ -101,6 +103,8 @@ import { UhkDeviceLoadingGuard } from './services/uhk-device-loading.guard';
import { UhkDeviceLoadedGuard } from './services/uhk-device-loaded.guard'; import { UhkDeviceLoadedGuard } from './services/uhk-device-loaded.guard';
import { XtermComponent } from './components/xterm/xterm.component'; import { XtermComponent } from './components/xterm/xterm.component';
import { SliderWrapperComponent } from './components/slider-wrapper/slider-wrapper.component'; import { SliderWrapperComponent } from './components/slider-wrapper/slider-wrapper.component';
import { EditableTextComponent } from './components/editable-text/editable-text.component';
import { Autofocus } from './directives/autofocus/autofocus.directive';
@NgModule({ @NgModule({
declarations: [ declarations: [
@@ -169,7 +173,10 @@ import { SliderWrapperComponent } from './components/slider-wrapper/slider-wrapp
ProgressButtonComponent, ProgressButtonComponent,
LoadingDevicePageComponent, LoadingDevicePageComponent,
XtermComponent, XtermComponent,
SliderWrapperComponent SliderWrapperComponent,
EditableTextComponent,
Autofocus,
RestoreConfigurationComponent
], ],
imports: [ imports: [
CommonModule, CommonModule,
@@ -182,7 +189,8 @@ import { SliderWrapperComponent } from './components/slider-wrapper/slider-wrapp
NotifierModule.withConfig(angularNotifierConfig), NotifierModule.withConfig(angularNotifierConfig),
ConfirmationPopoverModule.forRoot({ ConfirmationPopoverModule.forRoot({
confirmButtonType: 'danger' // set defaults here confirmButtonType: 'danger' // set defaults here
}) }),
ClipboardModule
], ],
providers: [ providers: [
SvgModuleProviderService, SvgModuleProviderService,

View File

@@ -17,7 +17,10 @@ export const ActionTypes = {
DISMISS_UNDO_NOTIFICATION: type(PREFIX + 'dismiss notification action'), DISMISS_UNDO_NOTIFICATION: type(PREFIX + 'dismiss notification action'),
LOAD_HARDWARE_CONFIGURATION_SUCCESS: type(PREFIX + 'load hardware configuration success'), LOAD_HARDWARE_CONFIGURATION_SUCCESS: type(PREFIX + 'load hardware configuration success'),
ELECTRON_MAIN_LOG_RECEIVED: type(PREFIX + 'Electron main log received'), ELECTRON_MAIN_LOG_RECEIVED: type(PREFIX + 'Electron main log received'),
OPEN_URL_IN_NEW_WINDOW: type(PREFIX + 'Open URL in new Window') OPEN_URL_IN_NEW_WINDOW: type(PREFIX + 'Open URL in new Window'),
PRIVILEGE_WHAT_WILL_THIS_DO: type(PREFIX + 'What will this do clicked'),
SETUP_PERMISSION_ERROR: type(PREFIX + 'Setup permission error'),
LOAD_APP_START_INFO: type(PREFIX + 'Load app start info')
}; };
export class AppBootsrappedAction implements Action { export class AppBootsrappedAction implements Action {
@@ -31,25 +34,29 @@ export class AppStartedAction implements Action {
export class ShowNotificationAction implements Action { export class ShowNotificationAction implements Action {
type = ActionTypes.APP_SHOW_NOTIFICATION; type = ActionTypes.APP_SHOW_NOTIFICATION;
constructor(public payload: Notification) { } constructor(public payload: Notification) {
}
} }
export class ApplyCommandLineArgsAction implements Action { export class ApplyCommandLineArgsAction implements Action {
type = ActionTypes.APPLY_COMMAND_LINE_ARGS; type = ActionTypes.APPLY_COMMAND_LINE_ARGS;
constructor(public payload: CommandLineArgs) { } constructor(public payload: CommandLineArgs) {
}
} }
export class ProcessAppStartInfoAction implements Action { export class ProcessAppStartInfoAction implements Action {
type = ActionTypes.APP_PROCESS_START_INFO; type = ActionTypes.APP_PROCESS_START_INFO;
constructor(public payload: AppStartInfo) { } constructor(public payload: AppStartInfo) {
}
} }
export class UndoLastAction implements Action { export class UndoLastAction implements Action {
type = ActionTypes.UNDO_LAST; type = ActionTypes.UNDO_LAST;
constructor(public payload: any) {} constructor(public payload: any) {
}
} }
export class UndoLastSuccessAction implements Action { export class UndoLastSuccessAction implements Action {
@@ -63,19 +70,37 @@ export class DismissUndoNotificationAction implements Action {
export class LoadHardwareConfigurationSuccessAction implements Action { export class LoadHardwareConfigurationSuccessAction implements Action {
type = ActionTypes.LOAD_HARDWARE_CONFIGURATION_SUCCESS; type = ActionTypes.LOAD_HARDWARE_CONFIGURATION_SUCCESS;
constructor(public payload: HardwareConfiguration) {} constructor(public payload: HardwareConfiguration) {
}
} }
export class ElectronMainLogReceivedAction implements Action { export class ElectronMainLogReceivedAction implements Action {
type = ActionTypes.ELECTRON_MAIN_LOG_RECEIVED; type = ActionTypes.ELECTRON_MAIN_LOG_RECEIVED;
constructor(public payload: ElectronLogEntry) {} constructor(public payload: ElectronLogEntry) {
}
} }
export class OpenUrlInNewWindowAction implements Action { export class OpenUrlInNewWindowAction implements Action {
type = ActionTypes.OPEN_URL_IN_NEW_WINDOW; type = ActionTypes.OPEN_URL_IN_NEW_WINDOW;
constructor(public payload: string) {} constructor(public payload: string) {
}
}
export class PrivilegeWhatWillThisDoAction implements Action {
type = ActionTypes.PRIVILEGE_WHAT_WILL_THIS_DO;
}
export class SetupPermissionErrorAction implements Action {
type = ActionTypes.SETUP_PERMISSION_ERROR;
constructor(public payload: string) {
}
}
export class LoadAppStartInfoAction implements Action {
type = ActionTypes.LOAD_APP_START_INFO;
} }
export type Actions export type Actions
@@ -90,4 +115,7 @@ export type Actions
| LoadHardwareConfigurationSuccessAction | LoadHardwareConfigurationSuccessAction
| ElectronMainLogReceivedAction | ElectronMainLogReceivedAction
| OpenUrlInNewWindowAction | OpenUrlInNewWindowAction
| PrivilegeWhatWillThisDoAction
| SetupPermissionErrorAction
| LoadAppStartInfoAction
; ;

View File

@@ -1,5 +1,5 @@
import { Action } from '@ngrx/store'; import { Action } from '@ngrx/store';
import { DeviceConnectionState, IpcResponse, type } from 'uhk-common'; import { DeviceConnectionState, HardwareModules, IpcResponse, type } from 'uhk-common';
const PREFIX = '[device] '; const PREFIX = '[device] ';
@@ -22,7 +22,11 @@ export const ActionTypes = {
UPDATE_FIRMWARE_REPLY: type(PREFIX + 'update firmware reply'), UPDATE_FIRMWARE_REPLY: type(PREFIX + 'update firmware reply'),
UPDATE_FIRMWARE_SUCCESS: type(PREFIX + 'update firmware success'), UPDATE_FIRMWARE_SUCCESS: type(PREFIX + 'update firmware success'),
UPDATE_FIRMWARE_FAILED: type(PREFIX + 'update firmware failed'), UPDATE_FIRMWARE_FAILED: type(PREFIX + 'update firmware failed'),
UPDATE_FIRMWARE_OK_BUTTON: type(PREFIX + 'update firmware ok button click') UPDATE_FIRMWARE_OK_BUTTON: type(PREFIX + 'update firmware ok button click'),
MODULES_INFO_LOADED: type(PREFIX + 'module info loaded'),
HAS_BACKUP_USER_CONFIGURATION: type(PREFIX + 'Store backup user configuration'),
RESTORE_CONFIGURATION_FROM_BACKUP: type(PREFIX + 'Restore configuration from backup'),
RESTORE_CONFIGURATION_FROM_BACKUP_SUCCESS: type(PREFIX + 'Restore configuration from backup success')
}; };
export class SetPrivilegeOnLinuxAction implements Action { export class SetPrivilegeOnLinuxAction implements Action {
@@ -114,6 +118,28 @@ export class ResetMouseSpeedSettingsAction implements Action {
type = ActionTypes.RESET_MOUSE_SPEED_SETTINGS; type = ActionTypes.RESET_MOUSE_SPEED_SETTINGS;
} }
export class HardwareModulesLoadedAction implements Action {
type = ActionTypes.MODULES_INFO_LOADED;
constructor(public payload: HardwareModules) {
}
}
export class RestoreUserConfigurationFromBackupAction implements Action {
type = ActionTypes.RESTORE_CONFIGURATION_FROM_BACKUP;
}
export class HasBackupUserConfigurationAction implements Action {
type = ActionTypes.HAS_BACKUP_USER_CONFIGURATION;
constructor(public payload: boolean) {
}
}
export class RestoreUserConfigurationFromBackupSuccessAction implements Action {
type = ActionTypes.RESTORE_CONFIGURATION_FROM_BACKUP_SUCCESS;
}
export type Actions export type Actions
= SetPrivilegeOnLinuxAction = SetPrivilegeOnLinuxAction
| SetPrivilegeOnLinuxReplyAction | SetPrivilegeOnLinuxReplyAction
@@ -132,4 +158,8 @@ export type Actions
| UpdateFirmwareSuccessAction | UpdateFirmwareSuccessAction
| UpdateFirmwareFailedAction | UpdateFirmwareFailedAction
| UpdateFirmwareOkButtonAction | UpdateFirmwareOkButtonAction
| HardwareModulesLoadedAction
| RestoreUserConfigurationFromBackupAction
| HasBackupUserConfigurationAction
| RestoreUserConfigurationFromBackupSuccessAction
; ;

View File

@@ -1,6 +1,7 @@
import { Action } from '@ngrx/store'; import { Action } from '@ngrx/store';
import { KeyAction, Keymap, Macro } from 'uhk-common'; import { KeyAction, Keymap, Macro } from 'uhk-common';
import { UndoUserConfigData } from '../../models/undo-user-config-data'; import { UndoUserConfigData } from '../../models/undo-user-config-data';
import { ChangeKeymapDescription } from '../../models/ChangeKeymapDescription';
export type KeymapAction = export type KeymapAction =
KeymapActions.AddKeymapAction | KeymapActions.AddKeymapAction |
@@ -11,7 +12,8 @@ export type KeymapAction =
KeymapActions.SetDefaultAction | KeymapActions.SetDefaultAction |
KeymapActions.RemoveKeymapAction | KeymapActions.RemoveKeymapAction |
KeymapActions.SaveKeyAction | KeymapActions.SaveKeyAction |
KeymapActions.CheckMacroAction; KeymapActions.CheckMacroAction |
KeymapActions.EditDescriptionAction;
export namespace KeymapActions { export namespace KeymapActions {
export const ADD = '[Keymap] Add keymap'; export const ADD = '[Keymap] Add keymap';
@@ -98,6 +100,16 @@ export namespace KeymapActions {
payload: UndoUserConfigData payload: UndoUserConfigData
}; };
export const EDIT_DESCRIPTION = '[Keymap] Edit description';
export class EditDescriptionAction {
type = EDIT_DESCRIPTION;
constructor(public payload: ChangeKeymapDescription) {
}
}
export function loadKeymaps(): Action { export function loadKeymaps(): Action {
return { return {
type: KeymapActions.LOAD_KEYMAPS type: KeymapActions.LOAD_KEYMAPS

View File

@@ -40,6 +40,13 @@ export class ApplicationEffects {
this.logService.info('Renderer appStart effect end'); this.logService.info('Renderer appStart effect end');
}); });
@Effect({dispatch: false})
appStartInfo$: Observable<Action> = this.actions$
.ofType(ActionTypes.LOAD_APP_START_INFO)
.do(() => {
this.appRendererService.getAppStartInfo();
});
@Effect({dispatch: false}) @Effect({dispatch: false})
showNotification$: Observable<Action> = this.actions$ showNotification$: Observable<Action> = this.actions$
.ofType<ShowNotificationAction>(ActionTypes.APP_SHOW_NOTIFICATION) .ofType<ShowNotificationAction>(ActionTypes.APP_SHOW_NOTIFICATION)

View File

@@ -13,11 +13,19 @@ import 'rxjs/add/operator/mergeMap';
import 'rxjs/add/operator/withLatestFrom'; import 'rxjs/add/operator/withLatestFrom';
import 'rxjs/add/operator/switchMap'; import 'rxjs/add/operator/switchMap';
import { DeviceConnectionState, IpcResponse, NotificationType, UhkBuffer, UserConfiguration } from 'uhk-common'; import {
DeviceConnectionState,
HardwareConfiguration,
IpcResponse,
NotificationType,
UserConfiguration
} from 'uhk-common';
import { import {
ActionTypes, ActionTypes,
ConnectionStateChangedAction, ConnectionStateChangedAction,
HideSaveToKeyboardButton, HideSaveToKeyboardButton,
ResetUserConfigurationAction,
RestoreUserConfigurationFromBackupSuccessAction,
SaveConfigurationAction, SaveConfigurationAction,
SaveConfigurationReplyAction, SaveConfigurationReplyAction,
SaveToKeyboardSuccessAction, SaveToKeyboardSuccessAction,
@@ -31,14 +39,16 @@ import {
UpdateFirmwareWithAction UpdateFirmwareWithAction
} from '../actions/device'; } from '../actions/device';
import { DeviceRendererService } from '../../services/device-renderer.service'; import { DeviceRendererService } from '../../services/device-renderer.service';
import { ShowNotificationAction } from '../actions/app'; import { SetupPermissionErrorAction, ShowNotificationAction } from '../actions/app';
import { AppState } from '../index'; import { AppState } from '../index';
import { import {
ActionTypes as UserConfigActions, ActionTypes as UserConfigActions,
ApplyUserConfigurationFromFileAction,
LoadConfigFromDeviceAction, LoadConfigFromDeviceAction,
LoadResetUserConfigurationAction LoadResetUserConfigurationAction
} from '../actions/user-config'; } from '../actions/user-config';
import { DefaultUserConfigurationService } from '../../services/default-user-configuration.service'; import { DefaultUserConfigurationService } from '../../services/default-user-configuration.service';
import { DataStorageRepositoryService } from '../../services/datastorage-repository.service';
@Injectable() @Injectable()
export class DeviceEffects { export class DeviceEffects {
@@ -76,30 +86,23 @@ export class DeviceEffects {
setPrivilegeOnLinuxReply$: Observable<Action> = this.actions$ setPrivilegeOnLinuxReply$: Observable<Action> = this.actions$
.ofType<SetPrivilegeOnLinuxReplyAction>(ActionTypes.SET_PRIVILEGE_ON_LINUX_REPLY) .ofType<SetPrivilegeOnLinuxReplyAction>(ActionTypes.SET_PRIVILEGE_ON_LINUX_REPLY)
.map(action => action.payload) .map(action => action.payload)
.mergeMap((response: any) => { .map((response: any): any => {
if (response.success) { if (response.success) {
return [ return new ConnectionStateChangedAction({
new ConnectionStateChangedAction({ connected: true,
connected: true, hasPermission: true
hasPermission: true });
})
];
} }
return [
<any>new ShowNotificationAction({ return new SetupPermissionErrorAction(response.error);
type: NotificationType.Error,
message: response.error.message || response.error
})
];
}); });
@Effect({dispatch: false}) @Effect({dispatch: false})
saveConfiguration$: Observable<Action> = this.actions$ saveConfiguration$: Observable<Action> = this.actions$
.ofType(ActionTypes.SAVE_CONFIGURATION) .ofType(ActionTypes.SAVE_CONFIGURATION)
.withLatestFrom(this.store) .withLatestFrom(this.store)
.map(([action, state]) => state.userConfiguration) .do(([action, state]) => {
.do((userConfiguration: UserConfiguration) => { setTimeout(() => this.sendUserConfigToKeyboard(state.userConfiguration, state.app.hardwareConfig), 100);
setTimeout(() => this.sendUserConfigToKeyboard(userConfiguration), 100);
}) })
.switchMap(() => Observable.empty()); .switchMap(() => Observable.empty());
@@ -126,8 +129,18 @@ export class DeviceEffects {
@Effect() @Effect()
autoHideSaveToKeyboardButton$: Observable<Action> = this.actions$ autoHideSaveToKeyboardButton$: Observable<Action> = this.actions$
.ofType(ActionTypes.SAVE_TO_KEYBOARD_SUCCESS) .ofType(ActionTypes.SAVE_TO_KEYBOARD_SUCCESS)
.switchMap(() => Observable.timer(1000) .withLatestFrom(this.store)
.switchMap(() => Observable.of(new HideSaveToKeyboardButton())) .switchMap(([action, state]) => Observable.timer(1000)
.mergeMap(() => {
const actions = [new HideSaveToKeyboardButton()];
if (state.device.hasBackupUserConfiguration) {
actions.push(new RestoreUserConfigurationFromBackupSuccessAction());
this.router.navigate(['/']);
}
return actions;
})
); );
@Effect() @Effect()
@@ -162,8 +175,16 @@ export class DeviceEffects {
}); });
@Effect() saveResetUserConfigurationToDevice$ = this.actions$ @Effect() saveResetUserConfigurationToDevice$ = this.actions$
.ofType(UserConfigActions.LOAD_RESET_USER_CONFIGURATION, UserConfigActions.APPLY_USER_CONFIGURATION_FROM_FILE) .ofType<ApplyUserConfigurationFromFileAction
.switchMap(() => Observable.of(new SaveConfigurationAction())); | LoadResetUserConfigurationAction>(
UserConfigActions.LOAD_RESET_USER_CONFIGURATION,
UserConfigActions.APPLY_USER_CONFIGURATION_FROM_FILE)
.map(action => action.payload)
.switchMap((config: UserConfiguration) => {
this.dataStorageRepository.saveConfig(config);
return Observable.of(new SaveConfigurationAction());
});
@Effect({dispatch: false}) updateFirmware$ = this.actions$ @Effect({dispatch: false}) updateFirmware$ = this.actions$
.ofType<UpdateFirmwareAction>(ActionTypes.UPDATE_FIRMWARE) .ofType<UpdateFirmwareAction>(ActionTypes.UPDATE_FIRMWARE)
@@ -189,16 +210,22 @@ export class DeviceEffects {
.ofType<UpdateFirmwareOkButtonAction>(ActionTypes.UPDATE_FIRMWARE_OK_BUTTON) .ofType<UpdateFirmwareOkButtonAction>(ActionTypes.UPDATE_FIRMWARE_OK_BUTTON)
.do(() => this.deviceRendererService.startConnectionPoller()); .do(() => this.deviceRendererService.startConnectionPoller());
@Effect() restoreUserConfiguration$ = this.actions$
.ofType<ResetUserConfigurationAction>(ActionTypes.RESTORE_CONFIGURATION_FROM_BACKUP)
.map(() => new SaveConfigurationAction());
constructor(private actions$: Actions, constructor(private actions$: Actions,
private router: Router, private router: Router,
private deviceRendererService: DeviceRendererService, private deviceRendererService: DeviceRendererService,
private store: Store<AppState>, private store: Store<AppState>,
private dataStorageRepository: DataStorageRepositoryService,
private defaultUserConfigurationService: DefaultUserConfigurationService) { private defaultUserConfigurationService: DefaultUserConfigurationService) {
} }
private sendUserConfigToKeyboard(userConfiguration: UserConfiguration): void { private sendUserConfigToKeyboard(userConfiguration: UserConfiguration, hardwareConfig: HardwareConfiguration): void {
const uhkBuffer = new UhkBuffer(); this.deviceRendererService.saveUserConfiguration({
userConfiguration.toBinary(uhkBuffer); uniqueId: hardwareConfig && hardwareConfig.uniqueId,
this.deviceRendererService.saveUserConfiguration(uhkBuffer.getBufferContent()); configuration: userConfiguration.toJsonObject()
});
} }
} }

View File

@@ -7,14 +7,17 @@ import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/do'; import 'rxjs/add/operator/do';
import 'rxjs/add/operator/map'; import 'rxjs/add/operator/map';
import 'rxjs/add/operator/pairwise';
import 'rxjs/add/operator/startWith'; import 'rxjs/add/operator/startWith';
import 'rxjs/add/operator/switchMap'; import 'rxjs/add/operator/switchMap';
import 'rxjs/add/operator/withLatestFrom'; import 'rxjs/add/operator/withLatestFrom';
import 'rxjs/add/observable/of'; import 'rxjs/add/observable/of';
import { Keymap } from 'uhk-common'; import { Keymap } from 'uhk-common';
import { findNewItem } from '../../util';
import { KeymapActions } from '../actions'; import { KeymapActions } from '../actions';
import { AppState } from '../index'; import { AppState } from '../index';
import { getKeymaps } from '../reducers/user-configuration';
@Injectable() @Injectable()
export class KeymapEffects { export class KeymapEffects {
@@ -32,10 +35,10 @@ export class KeymapEffects {
@Effect({ dispatch: false }) addOrDuplicate$: any = this.actions$ @Effect({ dispatch: false }) addOrDuplicate$: any = this.actions$
.ofType(KeymapActions.ADD, KeymapActions.DUPLICATE) .ofType(KeymapActions.ADD, KeymapActions.DUPLICATE)
.withLatestFrom(this.store) .withLatestFrom(this.store.let(getKeymaps()).pairwise(), (action, latest) => latest)
.map(latest => latest[1].userConfiguration.keymaps) .do(([prevKeymaps, newKeymaps]) => {
.do(keymaps => { const newKeymap = findNewItem(prevKeymaps, newKeymaps);
this.router.navigate(['/keymap', keymaps[keymaps.length - 1].abbreviation]); this.router.navigate(['/keymap', newKeymap.abbreviation]);
}); });
@Effect({ dispatch: false }) remove$: any = this.actions$ @Effect({ dispatch: false }) remove$: any = this.actions$

View File

@@ -2,14 +2,18 @@ import { Injectable } from '@angular/core';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import { Actions, Effect } from '@ngrx/effects'; import { Actions, Effect } from '@ngrx/effects';
import { Store } from '@ngrx/store'; import { Store, Action } from '@ngrx/store';
import 'rxjs/add/operator/do'; import 'rxjs/add/operator/do';
import 'rxjs/add/operator/map'; import 'rxjs/add/operator/map';
import 'rxjs/add/operator/pairwise';
import 'rxjs/add/operator/withLatestFrom'; import 'rxjs/add/operator/withLatestFrom';
import { Macro } from 'uhk-common';
import { KeymapActions, MacroActions } from '../actions'; import { KeymapActions, MacroActions } from '../actions';
import { AppState } from '../index'; import { AppState } from '../index';
import { getMacros } from '../reducers/user-configuration';
import { findNewItem } from '../../util';
@Injectable() @Injectable()
export class MacroEffects { export class MacroEffects {
@@ -27,23 +31,17 @@ export class MacroEffects {
} }
}); });
@Effect({ dispatch: false }) add$: any = this.actions$ @Effect({ dispatch: false }) addOrDuplicate$: any = this.actions$
.ofType(MacroActions.ADD) .ofType(MacroActions.ADD, MacroActions.DUPLICATE)
.withLatestFrom(this.store) .withLatestFrom(this.store.let(getMacros()).pairwise(), (action, latest) => ([action, latest[0], latest[1]]))
.map(([action, state]) => state.userConfiguration.macros) .do(([action, prevMacros, newMacros]: [Action, Macro[], Macro[]]) => {
.map(macros => macros[macros.length - 1]) const newMacro = findNewItem(prevMacros, newMacros);
.do(lastMacro => { const commands = ['/macro', newMacro.id];
this.router.navigate(['/macro', lastMacro.id, 'new']); if (action.type === MacroActions.ADD) {
commands.push('new');
}
this.router.navigate(commands);
}); });
@Effect({ dispatch: false }) duplicate: any = this.actions$ constructor(private actions$: Actions, private router: Router, private store: Store<AppState>) { }
.ofType(MacroActions.DUPLICATE)
.withLatestFrom(this.store)
.map(([action, state]) => state.userConfiguration.macros)
.map(macros => macros[macros.length - 1])
.do(lastMacro => {
this.router.navigate(['/macro', lastMacro.id]);
});
constructor(private actions$: Actions, private router: Router, private store: Store<AppState>) {}
} }

View File

@@ -15,8 +15,9 @@ import 'rxjs/add/observable/of';
import 'rxjs/add/observable/empty'; import 'rxjs/add/observable/empty';
import { import {
getHardwareConfigFromDeviceResponse,
getUserConfigFromDeviceResponse,
ConfigurationReply, ConfigurationReply,
HardwareConfiguration,
LogService, LogService,
NotificationType, NotificationType,
UhkBuffer, UhkBuffer,
@@ -43,7 +44,11 @@ import {
ShowNotificationAction, ShowNotificationAction,
UndoLastAction UndoLastAction
} from '../actions/app'; } from '../actions/app';
import { ShowSaveToKeyboardButtonAction } from '../actions/device'; import {
HardwareModulesLoadedAction,
ShowSaveToKeyboardButtonAction,
HasBackupUserConfigurationAction
} from '../actions/device';
import { DeviceRendererService } from '../../services/device-renderer.service'; import { DeviceRendererService } from '../../services/device-renderer.service';
import { UndoUserConfigData } from '../../models/undo-user-config-data'; import { UndoUserConfigData } from '../../models/undo-user-config-data';
import { UploadFileData } from '../../models/upload-file-data'; import { UploadFileData } from '../../models/upload-file-data';
@@ -51,29 +56,6 @@ import { UploadFileData } from '../../models/upload-file-data';
@Injectable() @Injectable()
export class UserConfigEffects { export class UserConfigEffects {
private static getUserConfigFromDeviceResponse(json: string): UserConfiguration {
const data = JSON.parse(json);
const userConfig = new UserConfiguration();
userConfig.fromBinary(UhkBuffer.fromArray(data));
if (userConfig.userConfigMajorVersion > 0) {
return userConfig;
}
return null;
}
private static 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;
}
@Effect() loadUserConfig$: Observable<Action> = defer(() => { @Effect() loadUserConfig$: Observable<Action> = defer(() => {
return Observable.of(new LoadUserConfigSuccessAction(this.getUserConfiguration())); return Observable.of(new LoadUserConfigSuccessAction(this.getUserConfiguration()));
}); });
@@ -81,7 +63,7 @@ export class UserConfigEffects {
@Effect() saveUserConfig$: Observable<Action> = (this.actions$ @Effect() saveUserConfig$: Observable<Action> = (this.actions$
.ofType( .ofType(
KeymapActions.ADD, KeymapActions.DUPLICATE, KeymapActions.EDIT_NAME, KeymapActions.EDIT_ABBR, KeymapActions.ADD, KeymapActions.DUPLICATE, KeymapActions.EDIT_NAME, KeymapActions.EDIT_ABBR,
KeymapActions.SET_DEFAULT, KeymapActions.REMOVE, KeymapActions.SAVE_KEY, KeymapActions.SET_DEFAULT, KeymapActions.REMOVE, KeymapActions.SAVE_KEY, KeymapActions.EDIT_DESCRIPTION,
MacroActions.ADD, MacroActions.DUPLICATE, MacroActions.EDIT_NAME, MacroActions.REMOVE, MacroActions.ADD_ACTION, MacroActions.ADD, MacroActions.DUPLICATE, MacroActions.EDIT_NAME, MacroActions.REMOVE, MacroActions.ADD_ACTION,
MacroActions.SAVE_ACTION, MacroActions.DELETE_ACTION, MacroActions.REORDER_ACTION, MacroActions.SAVE_ACTION, MacroActions.DELETE_ACTION, MacroActions.REORDER_ACTION,
ActionTypes.RENAME_USER_CONFIGURATION, ActionTypes.SET_USER_CONFIGURATION_VALUE) as ActionTypes.RENAME_USER_CONFIGURATION, ActionTypes.SET_USER_CONFIGURATION_VALUE) as
@@ -146,23 +128,24 @@ export class UserConfigEffects {
} }
const result = []; const result = [];
let newPageDestination = ['/'];
try { try {
const userConfig = UserConfigEffects.getUserConfigFromDeviceResponse(data.userConfiguration); const userConfig = getUserConfigFromDeviceResponse(data.userConfiguration);
result.push(new LoadUserConfigSuccessAction(userConfig)); result.push(new LoadUserConfigSuccessAction(userConfig));
} catch (err) { } catch (err) {
this.logService.error('Eeprom user-config parse error:', err); this.logService.error('Eeprom user-config parse error:', err);
result.push( const userConfig = new UserConfiguration().fromJsonObject(data.backupConfiguration);
new ShowNotificationAction({
type: NotificationType.Error,
message: err
}));
result.push(new LoadUserConfigSuccessAction(this.getUserConfiguration())); result.push(new HasBackupUserConfigurationAction(!!data.backupConfiguration));
result.push(new LoadUserConfigSuccessAction(userConfig));
newPageDestination = ['/device/restore-user-configuration'];
} }
try { try {
const hardwareConfig = UserConfigEffects.getHardwareConfigFromDeviceResponse(data.hardwareConfiguration); const hardwareConfig = getHardwareConfigFromDeviceResponse(data.hardwareConfiguration);
result.push(new LoadHardwareConfigurationSuccessAction(hardwareConfig)); result.push(new LoadHardwareConfigurationSuccessAction(hardwareConfig));
} catch (err) { } catch (err) {
this.logService.error('Eeprom hardware-config parse error:', err); this.logService.error('Eeprom hardware-config parse error:', err);
@@ -173,7 +156,9 @@ export class UserConfigEffects {
})); }));
} }
this.router.navigate(['/']); result.push(new HardwareModulesLoadedAction(data.modules));
this.router.navigate(newPageDestination);
return result; return result;
}); });

View File

@@ -39,7 +39,6 @@ export const metaReducers: MetaReducer<AppState>[] = environment.production
: [storeFreeze]; : [storeFreeze];
export const getUserConfiguration = (state: AppState) => state.userConfiguration; export const getUserConfiguration = (state: AppState) => state.userConfiguration;
export const getDeviceName = createSelector(getUserConfiguration, fromUserConfig.getDeviceName);
export const appState = (state: AppState) => state.app; export const appState = (state: AppState) => state.app;
@@ -47,10 +46,10 @@ export const showAddonMenu = createSelector(appState, fromApp.showAddonMenu);
export const getUndoableNotification = createSelector(appState, fromApp.getUndoableNotification); export const getUndoableNotification = createSelector(appState, fromApp.getUndoableNotification);
export const getPrevUserConfiguration = createSelector(appState, fromApp.getPrevUserConfiguration); export const getPrevUserConfiguration = createSelector(appState, fromApp.getPrevUserConfiguration);
export const runningInElectron = createSelector(appState, fromApp.runningInElectron); export const runningInElectron = createSelector(appState, fromApp.runningInElectron);
export const getHardwareConfiguration = createSelector(appState, fromApp.getHardwareConfiguration);
export const getKeyboardLayout = createSelector(appState, fromApp.getKeyboardLayout); export const getKeyboardLayout = createSelector(appState, fromApp.getKeyboardLayout);
export const deviceConfigurationLoaded = createSelector(appState, fromApp.deviceConfigurationLoaded); export const deviceConfigurationLoaded = createSelector(appState, fromApp.deviceConfigurationLoaded);
export const getAgentVersionInfo = createSelector(appState, fromApp.getAgentVersionInfo); export const getAgentVersionInfo = createSelector(appState, fromApp.getAgentVersionInfo);
export const getPrivilegePageState = createSelector(appState, fromApp.getPrivilagePageState);
export const appUpdateState = (state: AppState) => state.appUpdate; export const appUpdateState = (state: AppState) => state.appUpdate;
export const getShowAppUpdateAvailable = createSelector(appUpdateState, fromAppUpdate.getShowAppUpdateAvailable); export const getShowAppUpdateAvailable = createSelector(appUpdateState, fromAppUpdate.getShowAppUpdateAvailable);
@@ -77,3 +76,29 @@ export const xtermLog = createSelector(deviceState, fromDevice.xtermLog);
export const firmwareOkButtonDisabled = createSelector(deviceState, fromDevice.firmwareOkButtonDisabled); export const firmwareOkButtonDisabled = createSelector(deviceState, fromDevice.firmwareOkButtonDisabled);
// tslint:disable-next-line: max-line-length // tslint:disable-next-line: max-line-length
export const flashFirmwareButtonDisbabled = createSelector(runningInElectron, deviceState, (electron, state: fromDevice.State) => !electron || state.updatingFirmware); export const flashFirmwareButtonDisbabled = createSelector(runningInElectron, deviceState, (electron, state: fromDevice.State) => !electron || state.updatingFirmware);
export const getHardwareModules = createSelector(deviceState, fromDevice.getHardwareModules);
export const getBackupUserConfigurationState = createSelector(deviceState, fromDevice.getBackupUserConfigurationState);
export const getRestoreUserConfiguration = createSelector(deviceState, fromDevice.getHasBackupUserConfiguration);
export const getSideMenuPageState = createSelector(
showAddonMenu,
runningInElectron,
updatingFirmware,
getUserConfiguration,
getRestoreUserConfiguration,
(showAddonMenuValue: boolean,
runningInElectronValue: boolean,
updatingFirmwareValue: boolean,
userConfiguration: UserConfiguration,
restoreUserConfiguration: boolean) => {
return {
showAddonMenu: showAddonMenuValue,
runInElectron: runningInElectronValue,
updatingFirmware: updatingFirmwareValue,
deviceName: userConfiguration.deviceName,
keymaps: userConfiguration.keymaps,
macros: userConfiguration.macros,
restoreUserConfiguration
};
}
);

View File

@@ -1,13 +1,20 @@
import { ROUTER_NAVIGATION } from '@ngrx/router-store'; import { ROUTER_NAVIGATION } from '@ngrx/router-store';
import { Action } from '@ngrx/store'; import { Action } from '@ngrx/store';
import { VersionInformation } from 'uhk-common'; import {
HardwareConfiguration,
Notification,
NotificationType,
runInElectron,
UserConfiguration,
VersionInformation
} from 'uhk-common';
import { HardwareConfiguration, Notification, NotificationType, runInElectron, UserConfiguration } from 'uhk-common';
import { ActionTypes, ShowNotificationAction } from '../actions/app'; import { ActionTypes, ShowNotificationAction } from '../actions/app';
import { ActionTypes as UserConfigActionTypes } from '../actions/user-config'; import { ActionTypes as UserConfigActionTypes } from '../actions/user-config';
import { ActionTypes as DeviceActionTypes } from '../actions/device'; import { ActionTypes as DeviceActionTypes } from '../actions/device';
import { KeyboardLayout } from '../../keyboard/keyboard-layout.enum'; import { KeyboardLayout } from '../../keyboard/keyboard-layout.enum';
import { getVersions } from '../../util'; import { getVersions } from '../../util';
import { PrivilagePageSate } from '../../models/privilage-page-sate';
export interface State { export interface State {
started: boolean; started: boolean;
@@ -19,6 +26,8 @@ export interface State {
configLoading: boolean; configLoading: boolean;
hardwareConfig?: HardwareConfiguration; hardwareConfig?: HardwareConfiguration;
agentVersionInfo?: VersionInformation; agentVersionInfo?: VersionInformation;
privilegeWhatWillThisDoClicked: boolean;
permissionError?: any;
} }
export const initialState: State = { export const initialState: State = {
@@ -27,7 +36,8 @@ export const initialState: State = {
navigationCountAfterNotification: 0, navigationCountAfterNotification: 0,
runningInElectron: runInElectron(), runningInElectron: runInElectron(),
configLoading: true, configLoading: true,
agentVersionInfo: getVersions() agentVersionInfo: getVersions(),
privilegeWhatWillThisDoClicked: false
}; };
export function reducer(state = initialState, action: Action & { payload: any }) { export function reducer(state = initialState, action: Action & { payload: any }) {
@@ -115,6 +125,24 @@ export function reducer(state = initialState, action: Action & { payload: any })
}; };
} }
case ActionTypes.PRIVILEGE_WHAT_WILL_THIS_DO:
return {
...state,
privilegeWhatWillThisDoClicked: true
};
case ActionTypes.SETUP_PERMISSION_ERROR:
return {
...state,
permissionError: action.payload
};
case DeviceActionTypes.SET_PRIVILEGE_ON_LINUX:
return {
...state,
permissionError: null
};
default: default:
return state; return state;
} }
@@ -124,7 +152,6 @@ export const showAddonMenu = (state: State) => state.showAddonMenu;
export const getUndoableNotification = (state: State) => state.undoableNotification; export const getUndoableNotification = (state: State) => state.undoableNotification;
export const getPrevUserConfiguration = (state: State) => state.prevUserConfig; export const getPrevUserConfiguration = (state: State) => state.prevUserConfig;
export const runningInElectron = (state: State) => state.runningInElectron; export const runningInElectron = (state: State) => state.runningInElectron;
export const getHardwareConfiguration = (state: State) => state.hardwareConfig;
export const getKeyboardLayout = (state: State): KeyboardLayout => { export const getKeyboardLayout = (state: State): KeyboardLayout => {
if (state.hardwareConfig && state.hardwareConfig.isIso) { if (state.hardwareConfig && state.hardwareConfig.isIso) {
return KeyboardLayout.ISO; return KeyboardLayout.ISO;
@@ -134,3 +161,12 @@ export const getKeyboardLayout = (state: State): KeyboardLayout => {
}; };
export const deviceConfigurationLoaded = (state: State) => !state.runningInElectron ? true : !!state.hardwareConfig; export const deviceConfigurationLoaded = (state: State) => !state.runningInElectron ? true : !!state.hardwareConfig;
export const getAgentVersionInfo = (state: State) => state.agentVersionInfo || {} as VersionInformation; export const getAgentVersionInfo = (state: State) => state.agentVersionInfo || {} as VersionInformation;
export const getPrivilagePageState = (state: State): PrivilagePageSate => {
const permissionSetupFailed = !!state.permissionError;
return {
permissionSetupFailed,
showWhatWillThisDo: !state.privilegeWhatWillThisDoClicked && !permissionSetupFailed,
showWhatWillThisDoContent: state.privilegeWhatWillThisDoClicked || permissionSetupFailed
};
};

Some files were not shown because too many files have changed in this diff Show More