163 Commits

Author SHA1 Message Date
László Monda
8bb645125d Update changelog and re-release v1.2.2 2018-05-27 02:53:10 +02:00
László Monda
9471b31a5d Instruct the user to power cycle the keyboard when encountered with an invalid hardware configuration. 2018-05-27 02:43:12 +02:00
László Monda
ffa52757c9 Upgrade to firmware 8.2.5 2018-05-27 02:31:22 +02:00
Róbert Kiss
ee53a0df9b feat: set disable style of device name in side menu (#656)
* feat: visualise disabled state of the 'Device name' input control

* fix: original value handling
2018-05-27 02:30:41 +02:00
Róbert Kiss
8e20c85e07 feat: add file upload component (#655)
The component allow upload the same file multiple times
2018-05-26 22:46:41 +02:00
László Monda
65ea786358 Don't include os.platform into the firmware update log because os.type makes it practically redundant. 2018-05-26 17:13:10 +02:00
László Monda
1035837b3b Fix lint error. 2018-05-22 15:29:48 +02:00
Róbert Kiss
18fc2e6b3f fix: permission detection when device not plugged (#653) 2018-05-22 15:11:43 +02:00
László Monda
fc728697d7 Bump version to 1.2.2 and update changelog and package.json 2018-05-22 02:19:48 +02:00
László Monda
cdf3caee9e Display device list at the beginning of the firmware update process. 2018-05-22 01:53:14 +02:00
László Monda
0a4d3a002e Include operating system type to the firmware update log. Add a new tip to the firmware page. 2018-05-22 01:16:02 +02:00
László Monda
d11c532ea4 Add a lot of useful instructions to the firmware page. 2018-05-22 00:38:27 +02:00
László Monda
1ff51697b1 Upgrade from firmware 8.2.2 to 8.2.4 2018-05-21 14:34:42 +02:00
Róbert Kiss
ab8ae31324 fix: permission detection on linux (#651) 2018-05-21 11:46:19 +02:00
László Monda
daa0e723b1 Display more detailed "invalid hardware configuration" errors. 2018-05-21 11:03:05 +02:00
Róbert Kiss
609aba856a fix: permission detection on win and mac (#650) 2018-05-21 11:00:57 +02:00
Róbert Kiss
a6678bd537 feat: update firmware version after update (#649)
* feat: add clipboard copy icon to the x-term-component

* feat: start device poll after firmware upgrade

* feat: remove the OK button from the firmware upgrade page

* feat: read the firmware after firmware upgrade

* fix: scrolling of the x-term-component

* feat: refresh the firmware version after recovery device

* fix: remove the scrollbar styling

* fix: stay on device firmware upgrade screen
2018-05-21 10:57:34 +02:00
László Monda
6c4f580fc2 Check if the keyboard is in factory reset mode and if so, display a relevant instruction. 2018-05-20 01:40:45 +02:00
László Monda
ea41661c65 Add erase-user-config.js 2018-05-19 21:53:41 +02:00
László Monda
c553c7b63b Rename erase-hca.js to erase-hardware-config.js 2018-05-19 21:33:12 +02:00
László Monda
e5988aa800 Rename write-hca.js to write-hardware-config.js 2018-05-19 21:32:15 +02:00
László Monda
ae319c607f Rename write-userconfig.js to write-user-config.js 2018-05-19 21:14:48 +02:00
László Monda
5d23ad1c9e Fix write-hca.js and write-user.js, and remove write-config.js. Fixes #627. 2018-05-19 20:59:11 +02:00
László Monda
55eef50da7 Add erase-hca.js 2018-05-19 20:05:41 +02:00
Róbert Kiss
653465f0e0 feat: device recovery mode (#642)
* add new page and ipc processing

* refactor: remove unused references from uhk.js

* feat: add device recovery route

* refactor: device permission

* feat: write firmware update log to the screen

* fix: xterm height

* feat: add reload button to the recovery page

* refactor: deviceConnectionState.hasPermission in appStartInfo

* refactor: use correct imports

* refactor: move .ok-button css class to the main style.scss

* feat: add bootload active route guard

* style: move RecoveryDeviceAction into new line

* feat: delete reload button

* feat: start device polling after device recovery
2018-05-19 17:22:46 +02:00
László Monda
2cf8044987 Check the signature of the hardware configuration area instead of uniqueId. 2018-05-19 14:07:18 +02:00
László Monda
3c056a7255 Display "Invalid hardware configuration" when the hardware configuration area is uninitialized instead of a general error message. Improves #623. 2018-05-19 13:38:16 +02:00
Róbert Kiss
091796d13c feat: not allow non ascii character in macro action text (#645) 2018-05-17 23:56:36 +02:00
László Monda
eb97dd844f Make the bootloader timeout of the reenumerate script specifiable. 2018-05-16 23:19:36 +02:00
Róbert Kiss
17693ec8fe build: upgrade node.js => 8.11.2 (#641) 2018-05-16 22:08:11 +02:00
Róbert Kiss
7c7ce8f50f feat: only send auto update notification when user indicated the update (#640)
* feat: only send auto update notification when user indicated the update

* fix: add more logging to the auto update process
2018-05-16 22:05:13 +02:00
László Monda
e294727ac5 Bump Agent version to 1.2.1 and update the changelog. 2018-05-12 11:26:41 +02:00
László Monda
f29d64c803 Rename kbootKommandName to kbootCommandName. 2018-05-09 02:16:38 +02:00
László Monda
0385b0ce29 Simply write that the list of available devices is unchanged instead of always listing the devices. 2018-05-09 01:32:55 +02:00
László Monda
b526274cd7 Upgrade to firmware 8.2.2 2018-05-09 00:38:48 +02:00
László Monda
88c16af4a9 Merge branch 'master' of github.com:UltimateHackingKeyboard/agent 2018-05-08 17:17:36 +02:00
László Monda
05ac9a6832 Clarify the content of the tooltop regarding non-US characters. 2018-05-08 17:17:04 +02:00
Kristian Sloth Lauszus
04aa5236c2 The usage page and usage number was changed in 6f0b1adc14 (#630) 2018-05-06 21:38:40 +02:00
László Monda
ec98e4e1c6 Rephrase and add explanations about the macro engine not being ready yet. 2018-05-06 03:24:54 +02:00
László Monda
bb9ece494c Update README.md 2018-05-05 21:55:52 +02:00
László Monda
217e6776ac Add changelog entry that I forgot to add. 2018-05-03 01:38:40 +02:00
László Monda
2286218980 Make long media key macro actions work. (#621)
* Make long media key macro actions work.

* fix: macro key action mapping
2018-05-03 00:28:35 +02:00
Róbert Kiss
3d9c83f9f4 build: check node version before build (#625)
* build: check node version before build

* use package.json engine section
2018-05-01 22:50:28 +02:00
Róbert Kiss
14ed163238 chore: npm run clean delete root node_modules and dist folders (#624) 2018-05-01 22:46:23 +02:00
László Monda
c815de0718 Edit phrasing: "... layer by *tapping* this key" 2018-05-01 03:20:44 +02:00
László Monda
6a46556d9e Make get-right-firmware-version.js display not only firmware version but also device protocol version, module protocol version, user config version and hardware config version. 2018-05-01 02:31:17 +02:00
krokofant
f8f820529f Resolve #553 (#614) 2018-04-29 21:32:33 +02:00
László Monda
cac11155e7 Update firmware version to 8.2.0 2018-04-20 10:04:21 +02:00
László Monda
d20870f11e Update firmware version and default mouse speed. 2018-04-20 09:49:27 +02:00
László Monda
10ceb6c79d Merge branch 'master' of github.com:UltimateHackingKeyboard/agent 2018-04-20 09:19:58 +02:00
Róbert Kiss
b38b6fa294 build: fix nouislider conflict in webpack conflict (#611)
* build: fix nouislider conflict in webpack conflict and upgrade nouislider

* use package import instead of sub file
2018-04-20 09:19:10 +02:00
László Monda
94c1d35429 Merge branch 'master' of github.com:UltimateHackingKeyboard/agent 2018-04-19 22:31:25 +02:00
László Monda
e33cef4e89 Update README. Fix #609 2018-04-13 19:32:20 +02:00
Róbert Kiss
9b815ed9c1 chore: upgrade angular to 5.3.9 and typescript to 2.6.2 (#605)
* chore: upgrade angular to 5.3.9 and typescript to 2.6.2

* fix electron renderer build

* fix webpack config

* format webpack.config

* fix renderer build
2018-04-10 19:52:58 +02:00
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
190 changed files with 16277 additions and 13903 deletions

2
.nvmrc
View File

@@ -1 +1 @@
8.9.1 8.11.2

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,9 +4,90 @@ 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).
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 ## [1.2.2] - 2018-05-27
Firmware: 8.2.**5** [[release](https://github.com/UltimateHackingKeyboard/firmware/releases/tag/v8.2.5)] | Device Protocol: 4.3.**1** | User Config: 4.0.**1** | Hardware Config: 1.0.0
- Offer recovery for bricked right keyboard halfs.
- Detect when the hardware configuration of a device is invalid and display a notification.
- Check if the keyboard is in factory reset mode and if so, display a relevant instruction.
- Only allow ASCII characters in type text macro actions.
- Allow uploading the same file multiple times in a row.
- Only send auto update notification when the user initiates the update.
- Update the firmware versions on the firmware update page right after firmware updates.
- Add a lot of useful instructions to the firmware page to help users update the firmware.
- Add the operating system and initial device list to the firmware update log.
- Add copy to clipboard button to the top right corner of the firmware update terminal widget.
## [1.2.1] - 2018-05-12
Firmware: 8.2.**2** [[release](https://github.com/UltimateHackingKeyboard/firmware/releases/tag/v8.2.2)] | Device Protocol: 4.3.0 | User Config: 4.0.**1** | Hardware Config: 1.0.0
- Match for the new USB usage page and usage number. This is critical for UHKs flashed with firmware >=8.2.2 to be recognized by Agent on OSX.
- Make the config serializer handle long media macro actions. `USERCONFIG:PATCH`
- Add note on the macro page explaining that the macro engine of the firmware is not ready yet.
- Add an example to the scancode tooltip to better explain users how to invoke non-US characters.
## [1.2.0] - 2018-04-20
Firmware: 8.**2.0** [[release](https://github.com/UltimateHackingKeyboard/firmware/releases/tag/v8.2.0)] | Device Protocol: 4.**3.0** | User Config: 4.0.0 | Hardware Config: 1.0.0
- Tweak the default mouse speed. This was necessary because the last firmware version adjusted speed multipliers. The mouse speed can be reset via the "Reset speeds to default" button of the "Mouse speed" page.
- Make the newly added switch-keymap.js script utilize the new UsbCommandId_SwitchKeymap, allowing for programmatic keymap switching. `DEVICEPROTOCOL:MINOR`
## [1.1.5] - 2018-04-10
Firmware: 8.1.5 [[release](https://github.com/UltimateHackingKeyboard/firmware/releases/tag/v8.1.5)] | Device Protocol: 4.2.0 | User Config: 4.0.0 | Hardware Config: 1.0.0
- Don't allow to run multiple instances of Agent at the same time, but rather focus the already existing Agent window.
## [1.1.4] - 2018-04-09
Firmware: 8.1.5 [[release](https://github.com/UltimateHackingKeyboard/firmware/releases/tag/v8.1.5)] | Device Protocol: 4.2.0 | User Config: 4.0.0 | Hardware Config: 1.0.0
- Handle privilege escalation gracefully on Linux even without PolicyKit.
- Fix application icon path.
- Replace application icon with a diagonal gradient based icon that should look better on desktop.
- Make saving the configuration more robust, and add a configuration recovery screen.
- Reposition the ISO key in the scancode list.
## [1.1.3] - 2018-04-06
Firmware: 8.1.**5** [[release](https://github.com/UltimateHackingKeyboard/firmware/releases/tag/v8.1.5)] | Device Protocol: 4.2.0 | User Config: 4.0.0 | Hardware Config: 1.0.0
- Show the firmware versions of the left and right keyboard halves on the firmware page.
- Fix menu scancode.
- Make the tooltip text regarding non-US characters easier to understand.
- On the Device Configuration page change terminology from download/upload to export/import for greater clarity.
## [1.1.2] - 2018-03-09
Firmware: 8.1.**4** [[release](https://github.com/UltimateHackingKeyboard/firmware/releases/tag/v8.1.4)] | Device Protocol: 4.2.0 | User Config: 4.0.0 | Hardware Config: 1.0.0
- Fix the configuration serializer so that the correct key actions get serialized, and the save button always appears when needed.
- Add instructions to the firmware page to aid users.
- Fix code signing on OSX.
- Sign Agent on Windows.
## [1.1.1] - 2018-02-13
Firmware: 8.1.**2** [[release](https://github.com/UltimateHackingKeyboard/firmware/releases/tag/v8.1.2)] | Device Protocol: 4.2.0 | User Config: 4.0.0 | Hardware Config: 1.0.0
- Sign Agent on OSX resulting in easier installation.
- Add per-keymap description field.
- Sort keymaps and macros alphabetically within the key action popover.
- Add tooltip regarding non-US scancodes.
- When deleting a macro, also delete the relevant play macro actions.
- Make the reset configuration button persist the reset configuration in Agent-web.
- Make Agent able to unbrick bricked modules.
- Assign "switch to test keymap" action on all keymaps in the default configuration.
- Add keymap descriptions in the default configuration.
## [1.1.0] - 2018-01-15
Firmware: 8.**1**.0 [[release](https://github.com/UltimateHackingKeyboard/firmware/releases/tag/v8.1.0)] | Device Protocol: 4.**2.0** | User Config: 4.0.0 | Hardware Config: 1.0.0
- Only accept device, keymap, and macro names upon editing if their trimmed length is non-zero. - Only accept device, keymap, and macro names upon editing if their trimmed length is non-zero.
- Add diagnostics USB scripts, most notably /packages/usb/{get-i2c-health,set-i2c-baud-rate}.js, some utilizing new device protocol commands and properties. `DEVICEPROTOCOL:MINOR` - Add diagnostics USB scripts, most notably /packages/usb/{get-i2c-health,set-i2c-baud-rate}.js, some utilizing new device protocol commands and properties. `DEVICEPROTOCOL:MINOR`

View File

@@ -5,17 +5,8 @@
Agent is the configuration application of the [Ultimate Hacking Keyboard](https://ultimatehackingkeyboard.com/). Agent is the configuration application of the [Ultimate Hacking Keyboard](https://ultimatehackingkeyboard.com/).
[Give it a whirl!](http://ultimatehackingkeyboard.github.io/agent/) * Try out the [web build of Agent](http://ultimatehackingkeyboard.github.io/agent/) in your browser. This is meant to be used for demonstration purposes.
* Download the [desktop build of Agent](https://github.com/UltimateHackingKeyboard/agent/releases) from our releases page. Use this if you have an actual UHK at hand, or else you won't get past the opening screen!
## Two builds to rule them all
It's worth mentioning that Agent has two builds.
The **electron build** is the desktop application which is meant to be used if you have an actual UHK at hand. It starts with an opening screen which detects your UHK. You cannot get past this screen without connecting a UHK via USB.
The **web build** is meant to be used for demonstration purposes, so people who don't yet own a UHK can get a feel of Agent and its capabilities in their browser. Eventually, WebUSB support will be added to the web build, making it able to communicate with the UHK. Given the sandboxed nature of browsers, the web build will always lack features that the electron build offers, so this won't make the electron build obsolete.
The two builds share code as much as possible.
## Building the electron application ## Building the electron application
@@ -33,9 +24,9 @@ For everyone else, use the appropriate package manager for your OS.
``` ```
git clone git@github.com:UltimateHackingKeyboard/agent.git git clone git@github.com:UltimateHackingKeyboard/agent.git
cd agent cd agent
npm install # to install Node dependencies npm install
npm run build:electron # to build the agent npm run build
npm run electron # to run the newly built agent npm run electron
``` ```
At this point, Agent should be running on your machine. At this point, Agent should be running on your machine.

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

4678
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -3,10 +3,10 @@
"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.2.2",
"firmwareVersion": "8.1.0", "firmwareVersion": "8.2.5",
"deviceProtocolVersion": "4.2.0", "deviceProtocolVersion": "4.3.1",
"userConfigVersion": "4.0.0", "userConfigVersion": "4.0.1",
"hardwareConfigVersion": "1.0.0", "hardwareConfigVersion": "1.0.0",
"description": "Agent is the configuration application of the Ultimate Hacking Keyboard.", "description": "Agent is the configuration application of the Ultimate Hacking Keyboard.",
"repository": { "repository": {
@@ -21,8 +21,9 @@
"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/jquery": "3.3.1",
"@types/jsonfile": "4.0.1", "@types/jsonfile": "4.0.1",
"@types/node": "8.0.53", "@types/node": "8.0.53",
"@types/node-hid": "0.5.2", "@types/node-hid": "0.5.2",
@@ -30,25 +31,30 @@
"@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",
"check-node-version": "^3.2.0",
"copy-webpack-plugin": "4.0.1", "copy-webpack-plugin": "4.0.1",
"copyfiles": "^2.0.0",
"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",
"gh-pages": "1.1.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,9 +64,9 @@
"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.6.2",
"webpack": "2.4.1" "webpack": "3.10.0"
}, },
"pre-commit": [ "pre-commit": [
"precommit-msg" "precommit-msg"
@@ -74,12 +80,13 @@
"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",
"prebuild": "check-node-version --package",
"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",
"build:electron": "cross-env AOT_BUILD=true run-s -sn build:electron:renderer build:electron:main", "build:electron": "cross-env AOT_BUILD=true run-s -sn build:electron:renderer build:electron:main",
@@ -90,11 +97,14 @@
"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",
"release": "node ./scripts/release.js", "release": "node ./scripts/release.js",
"clean": "lerna exec rimraf ./node_modules ./dist" "clean": "lerna exec rimraf ./node_modules ./dist && rimraf ./node_modules ./dist",
"predeploy-gh-pages": "run-s build:web",
"deploy-gh-pages": "gh-pages -d packages/uhk-web/dist"
}, },
"dependencies": {} "dependencies": {}
} }

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

@@ -9,6 +9,9 @@ import { IpcEvents, LogService } from 'uhk-common';
import { MainServiceBase } from './main-service-base'; import { MainServiceBase } from './main-service-base';
export class AppUpdateService extends MainServiceBase { export class AppUpdateService extends MainServiceBase {
private sendAutoUpdateNotification = false;
constructor(protected logService: LogService, constructor(protected logService: LogService,
protected win: Electron.BrowserWindow, protected win: Electron.BrowserWindow,
private app: Electron.App) { private app: Electron.App) {
@@ -24,16 +27,21 @@ export class AppUpdateService extends MainServiceBase {
private initListeners() { private initListeners() {
autoUpdater.on('checking-for-update', () => { autoUpdater.on('checking-for-update', () => {
this.logService.debug('[AppUpdateService] checking for update');
this.sendIpcToWindow(IpcEvents.autoUpdater.checkingForUpdate); this.sendIpcToWindow(IpcEvents.autoUpdater.checkingForUpdate);
}); });
autoUpdater.on('update-available', async (ev: any, info: UpdateInfo) => { autoUpdater.on('update-available', async (ev: any, info: UpdateInfo) => {
this.logService.debug('[AppUpdateService] update available. Downloading started');
await autoUpdater.downloadUpdate(); await autoUpdater.downloadUpdate();
this.sendIpcToWindow(IpcEvents.autoUpdater.updateAvailable, info); this.sendIpcToWindow(IpcEvents.autoUpdater.updateAvailable, info);
}); });
autoUpdater.on('update-not-available', (ev: any, info: UpdateInfo) => { autoUpdater.on('update-not-available', (ev: any, info: UpdateInfo) => {
this.sendIpcToWindow(IpcEvents.autoUpdater.updateNotAvailable, info); if (this.sendAutoUpdateNotification) {
this.logService.debug('[AppUpdateService] update not available');
this.sendIpcToWindow(IpcEvents.autoUpdater.updateNotAvailable, info);
}
}); });
autoUpdater.on('error', (ev: any, err: string) => { autoUpdater.on('error', (ev: any, err: string) => {
@@ -51,6 +59,7 @@ export class AppUpdateService extends MainServiceBase {
}); });
autoUpdater.on('update-downloaded', (ev: any, info: UpdateInfo) => { autoUpdater.on('update-downloaded', (ev: any, info: UpdateInfo) => {
this.logService.debug('[AppUpdateService] update downloaded');
this.sendIpcToWindow(IpcEvents.autoUpdater.autoUpdateDownloaded, info); this.sendIpcToWindow(IpcEvents.autoUpdater.autoUpdateDownloaded, info);
}); });
@@ -61,12 +70,15 @@ export class AppUpdateService extends MainServiceBase {
ipcMain.on(IpcEvents.app.appStarted, () => { ipcMain.on(IpcEvents.app.appStarted, () => {
if (this.checkForUpdateAtStartup()) { if (this.checkForUpdateAtStartup()) {
this.sendAutoUpdateNotification = false;
this.logService.debug('[AppUpdateService] app started. Automatically check for update.');
this.checkForUpdate(); this.checkForUpdate();
} }
}); });
ipcMain.on(IpcEvents.autoUpdater.checkForUpdate, () => { ipcMain.on(IpcEvents.autoUpdater.checkForUpdate, () => {
this.logService.debug('[AppUpdateService] checkForUpdate request from renderer process'); this.logService.debug('[AppUpdateService] checkForUpdate request from renderer process');
this.sendAutoUpdateNotification = true;
this.checkForUpdate(); this.checkForUpdate();
}); });
} }
@@ -75,14 +87,22 @@ export class AppUpdateService extends MainServiceBase {
if (isDev) { if (isDev) {
const msg = '[AppUpdateService] Application update is not working in dev mode.'; const msg = '[AppUpdateService] Application update is not working in dev mode.';
this.logService.info(msg); this.logService.info(msg);
this.sendIpcToWindow(IpcEvents.autoUpdater.checkForUpdateNotAvailable, msg);
if (this.sendAutoUpdateNotification) {
this.sendIpcToWindow(IpcEvents.autoUpdater.checkForUpdateNotAvailable, msg);
}
return; return;
} }
if (this.isFirstRun()) { if (this.isFirstRun()) {
const msg = '[AppUpdateService] Application update is skipping at first run.'; const msg = '[AppUpdateService] Application update is skipping at first run.';
this.logService.info(msg); this.logService.info(msg);
this.sendIpcToWindow(IpcEvents.autoUpdater.checkForUpdateNotAvailable, msg);
if (this.sendAutoUpdateNotification) {
this.sendIpcToWindow(IpcEvents.autoUpdater.checkForUpdateNotAvailable, msg);
}
return; return;
} }

View File

@@ -22,13 +22,14 @@ export class AppService extends MainServiceBase {
private async handleAppStartInfo(event: Electron.Event) { private async handleAppStartInfo(event: Electron.Event) {
this.logService.info('[AppService] getAppStartInfo'); this.logService.info('[AppService] getAppStartInfo');
const deviceConnectionState = this.uhkHidDeviceService.getDeviceConnectionState();
const response: AppStartInfo = { const response: AppStartInfo = {
commandLineArgs: { commandLineArgs: {
addons: this.options.addons || false addons: this.options.addons || false
}, },
deviceConnected: this.uhkHidDeviceService.deviceConnected(), deviceConnected: deviceConnectionState.connected,
hasPermission: this.uhkHidDeviceService.hasPermission() hasPermission: deviceConnectionState.hasPermission,
bootloaderActive: deviceConnectionState.bootloaderActive
}; };
this.logService.info('[AppService] getAppStartInfo response:', response); this.logService.info('[AppService] getAppStartInfo response:', response);
return event.sender.send(IpcEvents.app.getAppStartInfoReply, response); return event.sender.send(IpcEvents.app.getAppStartInfoReply, response);

View File

@@ -1,6 +1,17 @@
import { ipcMain } from 'electron'; import { ipcMain } from 'electron';
import { ConfigurationReply, DeviceConnectionState, IpcEvents, IpcResponse, LogService } from 'uhk-common'; import {
import { snooze, UhkHidDevice, UhkOperations } from 'uhk-usb'; ConfigurationReply,
DeviceConnectionState,
FirmwareUpgradeIpcResponse,
getHardwareConfigFromDeviceResponse,
HardwareModules,
IpcEvents,
IpcResponse,
LogService,
mapObjectToUserConfigBinaryBuffer,
SaveUserConfigurationData
} from 'uhk-common';
import { deviceConnectionStateComparer, 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';
import { emptyDir } from 'fs-extra'; import { emptyDir } from 'fs-extra';
@@ -14,6 +25,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
@@ -60,6 +72,15 @@ export class DeviceService {
ipcMain.on(IpcEvents.device.startConnectionPoller, this.pollUhkDevice.bind(this)); ipcMain.on(IpcEvents.device.startConnectionPoller, this.pollUhkDevice.bind(this));
ipcMain.on(IpcEvents.device.recoveryDevice, (...args: any[]) => {
this.queueManager.add({
method: this.recoveryDevice,
bind: this,
params: args,
asynchronous: true
});
});
logService.debug('[DeviceService] init success'); logService.debug('[DeviceService] init success');
} }
@@ -73,10 +94,16 @@ 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 = await this.getHardwareModules(false);
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 = {
@@ -90,16 +117,36 @@ export class DeviceService {
event.sender.send(IpcEvents.device.loadConfigurationReply, JSON.stringify(response)); event.sender.send(IpcEvents.device.loadConfigurationReply, JSON.stringify(response));
} }
public async getHardwareModules(catchError: boolean): Promise<HardwareModules> {
try {
await this.device.waitUntilKeyboardBusy();
return {
leftModuleInfo: await this.operations.getLeftModuleVersionInfo(),
rightModuleInfo: await this.operations.getRightModuleVersionInfo()
};
}
catch (err) {
if (!catchError) {
return err;
}
this.logService.error('[DeviceService] Read hardware modules information failed', err);
}
}
public close(): void { public close(): void {
this.stopPollTimer(); this.stopPollTimer();
this.logService.info('[DeviceService] Device connection checker stopped.'); this.logService.info('[DeviceService] Device connection checker stopped.');
} }
public async updateFirmware(event: Electron.Event, args?: Array<string>): Promise<void> { public async updateFirmware(event: Electron.Event, args?: Array<string>): Promise<void> {
const response = new IpcResponse(); const response = new FirmwareUpgradeIpcResponse();
let firmwarePathData: TmpFirmware; let firmwarePathData: TmpFirmware;
try { try {
this.device.resetDeviceCache();
this.stopPollTimer(); this.stopPollTimer();
if (args && args.length > 0) { if (args && args.length > 0) {
@@ -113,10 +160,12 @@ export class DeviceService {
} }
response.success = true; response.success = true;
response.modules = await this.getHardwareModules(false);
} catch (error) { } catch (error) {
const err = {message: error.message, stack: error.stack}; const err = {message: error.message, stack: error.stack};
this.logService.error('[DeviceService] updateFirmware error', err); this.logService.error('[DeviceService] updateFirmware error', err);
response.modules = await this.getHardwareModules(true);
response.error = err; response.error = err;
} }
@@ -124,6 +173,35 @@ export class DeviceService {
await emptyDir(firmwarePathData.tmpDirectory.name); await emptyDir(firmwarePathData.tmpDirectory.name);
} }
await snooze(500);
this.pollUhkDevice();
event.sender.send(IpcEvents.device.updateFirmwareReply, response);
}
public async recoveryDevice(event: Electron.Event): Promise<void> {
const response = new FirmwareUpgradeIpcResponse();
try {
this.stopPollTimer();
await this.operations.updateRightFirmware();
await snooze(500);
this.pollUhkDevice();
response.modules = await this.getHardwareModules(false);
response.success = true;
} catch (error) {
const err = {message: error.message, stack: error.stack};
this.logService.error('[DeviceService] updateFirmware error', err);
response.modules = await this.getHardwareModules(true);
response.error = err;
}
await snooze(500); await snooze(500);
event.sender.send(IpcEvents.device.updateFirmwareReply, response); event.sender.send(IpcEvents.device.updateFirmwareReply, response);
} }
@@ -141,26 +219,24 @@ export class DeviceService {
this.pollTimer$ = Observable.interval(1000) this.pollTimer$ = Observable.interval(1000)
.startWith(0) .startWith(0)
.map(() => this.device.deviceConnected()) .map(() => this.device.getDeviceConnectionState())
.distinctUntilChanged() .distinctUntilChanged<DeviceConnectionState>(deviceConnectionStateComparer)
.do((connected: boolean) => { .do((state: DeviceConnectionState) => {
const response: DeviceConnectionState = { this.win.webContents.send(IpcEvents.device.deviceConnectionStateChanged, state);
connected, this.logService.info('[DeviceService] Device connection state changed to:', state);
hasPermission: this.device.hasPermission()
};
this.win.webContents.send(IpcEvents.device.deviceConnectionStateChanged, response);
this.logService.info('[DeviceService] Device connection state changed to:', response);
}) })
.subscribe(); .subscribe();
} }
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

@@ -18,11 +18,11 @@
"integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8="
}, },
"ansi-styles": { "ansi-styles": {
"version": "3.2.0", "version": "3.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.0.tgz", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
"integrity": "sha512-NnSOmMEYtVR2JVMIGTzynRkkaxtiq1xnFBcdQD/DnNCYPoEPsVJhM98BDyaoNOQIi7p4okdi3E27eN7GQbsUug==", "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
"requires": { "requires": {
"color-convert": "1.9.0" "color-convert": "1.9.1"
} }
}, },
"arrify": { "arrify": {
@@ -36,9 +36,9 @@
"integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c="
}, },
"brace-expansion": { "brace-expansion": {
"version": "1.1.8", "version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.8.tgz", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha1-wHshHHyVLsH479Uad+8NHTmQopI=", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"requires": { "requires": {
"balanced-match": "1.0.0", "balanced-match": "1.0.0",
"concat-map": "0.0.1" "concat-map": "0.0.1"
@@ -55,13 +55,13 @@
"integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=" "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0="
}, },
"chalk": { "chalk": {
"version": "2.1.0", "version": "2.3.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.1.0.tgz", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.2.tgz",
"integrity": "sha512-LUHGS/dge4ujbXMJrnihYMcL4AoOweGnw9Tp3kQuqy1Kx5c1qKjqvMJZ6nVJPMWJtKCTN72ZogH3oeSO9g9rXQ==", "integrity": "sha512-ZM4j2/ld/YZDc3Ma8PgN7gyAk+kHMMMyzLNryCPGhWrsfAuDVeuid5bpRFTDgMH9JBK2lA4dyyAkkZYF/WcqDQ==",
"requires": { "requires": {
"ansi-styles": "3.2.0", "ansi-styles": "3.2.1",
"escape-string-regexp": "1.0.5", "escape-string-regexp": "1.0.5",
"supports-color": "4.4.0" "supports-color": "5.3.0"
} }
}, },
"cliui": { "cliui": {
@@ -100,9 +100,9 @@
} }
}, },
"color-convert": { "color-convert": {
"version": "1.9.0", "version": "1.9.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.0.tgz", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.1.tgz",
"integrity": "sha1-Gsz5fdc5uYO/mU1W/sj5WFNkG3o=", "integrity": "sha512-mjGanIiwQJskCC18rPR6OmrZ6fm2Lc7PeGFYwCmy5J34wC6F1PzdGL6xeMfmgicfYcNLGuVFA3WzXtIDCQSZxQ==",
"requires": { "requires": {
"color-name": "1.1.3" "color-name": "1.1.3"
} }
@@ -122,15 +122,15 @@
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz",
"integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=",
"requires": { "requires": {
"lru-cache": "4.1.1", "lru-cache": "4.1.2",
"shebang-command": "1.2.0", "shebang-command": "1.2.0",
"which": "1.3.0" "which": "1.3.0"
}, },
"dependencies": { "dependencies": {
"lru-cache": { "lru-cache": {
"version": "4.1.1", "version": "4.1.2",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.1.tgz", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.2.tgz",
"integrity": "sha512-q4spe4KTfsAS1SUHLO0wz8Qiyf1+vMIAgpRYioFYDMNqKfHQbg+AVDH3i4fvpl71/P1L0dBl+fQi+P37UYf0ew==", "integrity": "sha512-wgeVXhrDwAWnIF/yZARsFnMBtdFXOg1b8RIrhilp+0iDYN4mdQcNZElDZ0e4B64BhaxeQ5zN7PMyvu7we1kPeQ==",
"requires": { "requires": {
"pseudomap": "1.0.2", "pseudomap": "1.0.2",
"yallist": "2.1.2" "yallist": "2.1.2"
@@ -144,9 +144,9 @@
"integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA="
}, },
"diff": { "diff": {
"version": "3.4.0", "version": "3.5.0",
"resolved": "https://registry.npmjs.org/diff/-/diff-3.4.0.tgz", "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz",
"integrity": "sha512-QpVuMTEoJMF7cKzi6bvWhRulU1fZqZnvyVQgNhPaxxuTYwyjn/j1v9falseQ/uXWwPnO56RBfwtg4h/EQXmucA==" "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA=="
}, },
"error-ex": { "error-ex": {
"version": "1.3.1", "version": "1.3.1",
@@ -271,9 +271,9 @@
"integrity": "sha1-3i1mE20ALhErpw8/EMMc98NQsto=" "integrity": "sha1-3i1mE20ALhErpw8/EMMc98NQsto="
}, },
"has-flag": { "has-flag": {
"version": "2.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
"integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=" "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0="
}, },
"homedir-polyfill": { "homedir-polyfill": {
"version": "1.0.1", "version": "1.0.1",
@@ -284,9 +284,9 @@
} }
}, },
"hosted-git-info": { "hosted-git-info": {
"version": "2.5.0", "version": "2.6.0",
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.5.0.tgz", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.6.0.tgz",
"integrity": "sha512-pNgbURSuab90KbTqvRPsseaTxOJCZBD0a7t+haSN33piP9cCM4l0CqdzAif2hUqm716UovKB2ROmiabGAKVXyg==" "integrity": "sha512-lIbgIIQA3lz5XaB6vxakj6sDHADJiZadYEJB+FgA+C4nubM1NwcuvUr9EJPmnH1skZqpqUzWborWo8EIUi0Sdw=="
}, },
"inflight": { "inflight": {
"version": "1.0.6", "version": "1.0.6",
@@ -382,7 +382,7 @@
"requires": { "requires": {
"jasmine": "2.8.0", "jasmine": "2.8.0",
"ts-node": "3.3.0", "ts-node": "3.3.0",
"typescript": "2.5.3", "typescript": "2.8.1",
"yargs": "8.0.2" "yargs": "8.0.2"
} }
}, },
@@ -432,29 +432,29 @@
"integrity": "sha1-bUUk6LlV+V1PW1iFHOId1y+06VI=" "integrity": "sha1-bUUk6LlV+V1PW1iFHOId1y+06VI="
}, },
"make-error": { "make-error": {
"version": "1.3.0", "version": "1.3.4",
"resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.0.tgz", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.4.tgz",
"integrity": "sha1-Uq06M5zPEM5itAQLcI/nByRLi5Y=" "integrity": "sha512-0Dab5btKVPhibSalc9QGXb559ED7G7iLjFXBaj9Wq8O3vorueR5K5jaE3hkG6ZQINyhA/JgG6Qk4qdFQjsYV6g=="
}, },
"mem": { "mem": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/mem/-/mem-1.1.0.tgz", "resolved": "https://registry.npmjs.org/mem/-/mem-1.1.0.tgz",
"integrity": "sha1-Xt1StIXKHZAP5kiVUFOZoN+kX3Y=", "integrity": "sha1-Xt1StIXKHZAP5kiVUFOZoN+kX3Y=",
"requires": { "requires": {
"mimic-fn": "1.1.0" "mimic-fn": "1.2.0"
} }
}, },
"mimic-fn": { "mimic-fn": {
"version": "1.1.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.1.0.tgz", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz",
"integrity": "sha1-5md4PZLonb00KBi1IwudYqZyrRg=" "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ=="
}, },
"minimatch": { "minimatch": {
"version": "3.0.4", "version": "3.0.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
"requires": { "requires": {
"brace-expansion": "1.1.8" "brace-expansion": "1.1.11"
} }
}, },
"minimist": { "minimist": {
@@ -472,10 +472,10 @@
"resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.4.0.tgz", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.4.0.tgz",
"integrity": "sha512-9jjUFbTPfEy3R/ad/2oNbKtW9Hgovl5O1FvFWKkKblNXoN/Oou6+9+KKohPK13Yc3/TyunyWhJp6gvRNR/PPAw==", "integrity": "sha512-9jjUFbTPfEy3R/ad/2oNbKtW9Hgovl5O1FvFWKkKblNXoN/Oou6+9+KKohPK13Yc3/TyunyWhJp6gvRNR/PPAw==",
"requires": { "requires": {
"hosted-git-info": "2.5.0", "hosted-git-info": "2.6.0",
"is-builtin-module": "1.0.0", "is-builtin-module": "1.0.0",
"semver": "5.4.1", "semver": "5.5.0",
"validate-npm-package-license": "3.0.1" "validate-npm-package-license": "3.0.3"
} }
}, },
"npm-run-path": { "npm-run-path": {
@@ -1942,18 +1942,26 @@
"integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=" "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4="
}, },
"p-limit": { "p-limit": {
"version": "1.1.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.1.0.tgz", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.2.0.tgz",
"integrity": "sha1-sH/y2aXYi+yAYDWJWiurZqJ5iLw=" "integrity": "sha512-Y/OtIaXtUPr4/YpMv1pCL5L5ed0rumAaAeBSj12F+bSlMdys7i8oQF/GUJmfpTS/QoaRrS/k6pma29haJpsMng==",
"requires": {
"p-try": "1.0.0"
}
}, },
"p-locate": { "p-locate": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz",
"integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=",
"requires": { "requires": {
"p-limit": "1.1.0" "p-limit": "1.2.0"
} }
}, },
"p-try": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz",
"integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M="
},
"parse-json": { "parse-json": {
"version": "2.2.0", "version": "2.2.0",
"resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz",
@@ -2030,9 +2038,9 @@
"integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=" "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE="
}, },
"semver": { "semver": {
"version": "5.4.1", "version": "5.5.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.4.1.tgz", "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz",
"integrity": "sha512-WfG/X9+oATh81XtllIo/I8gOiY9EXRdv1cQdyykeXK17YcUW3EXUAi2To4pcH6nZtJPr7ZOpM5OMyWJZm+8Rsg==" "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA=="
}, },
"set-blocking": { "set-blocking": {
"version": "2.0.0", "version": "2.0.0",
@@ -2076,22 +2084,32 @@
} }
}, },
"spdx-correct": { "spdx-correct": {
"version": "1.0.2", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-1.0.2.tgz", "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.0.0.tgz",
"integrity": "sha1-SzBz2TP/UfORLwOsVRlJikFQ20A=", "integrity": "sha512-N19o9z5cEyc8yQQPukRCZ9EUmb4HUpnrmaL/fxS2pBo2jbfcFRVuFZ/oFC+vZz0MNNk0h80iMn5/S6qGZOL5+g==",
"requires": { "requires": {
"spdx-license-ids": "1.2.2" "spdx-expression-parse": "3.0.0",
"spdx-license-ids": "3.0.0"
} }
}, },
"spdx-exceptions": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.1.0.tgz",
"integrity": "sha512-4K1NsmrlCU1JJgUrtgEeTVyfx8VaYea9J9LvARxhbHtVtohPs/gFGG5yy49beySjlIMhhXZ4QqujIZEfS4l6Cg=="
},
"spdx-expression-parse": { "spdx-expression-parse": {
"version": "1.0.4", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-1.0.4.tgz", "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz",
"integrity": "sha1-m98vIOH0DtRH++JzJmGR/O1RYmw=" "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==",
"requires": {
"spdx-exceptions": "2.1.0",
"spdx-license-ids": "3.0.0"
}
}, },
"spdx-license-ids": { "spdx-license-ids": {
"version": "1.2.2", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-1.2.2.tgz", "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.0.tgz",
"integrity": "sha1-yd96NCRZSt5r0RkA1ZZpbcBrrFc=" "integrity": "sha512-2+EPwgbnmOIl8HjGBXXMd9NAu02vLjOO1nWw4kmeRDFyHn+M/ETfHxQUK0oXg8ctgVnl9t3rosNVsZ1jG61nDA=="
}, },
"string-width": { "string-width": {
"version": "2.1.1", "version": "2.1.1",
@@ -2146,11 +2164,11 @@
"integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=" "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo="
}, },
"supports-color": { "supports-color": {
"version": "4.4.0", "version": "5.3.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.4.0.tgz", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.3.0.tgz",
"integrity": "sha512-rKC3+DyXWgK0ZLKwmRsrkyHVZAjNkfzeehuFWdGGcqGDTZFH73+RH6S/RDAAxl9GusSjZSUWYLmT9N5pzXFOXQ==", "integrity": "sha512-0aP01LLIskjKs3lq52EC0aGBAJhLq7B2Rd8HC/DR/PtNNpcLilNmHC12O+hu0usQpo7wtHNRqtrhBwtDb0+dNg==",
"requires": { "requires": {
"has-flag": "2.0.0" "has-flag": "3.0.0"
} }
}, },
"ts-node": { "ts-node": {
@@ -2159,14 +2177,14 @@
"integrity": "sha1-wTxqMCTjC+EYDdUwOPwgkonUv2k=", "integrity": "sha1-wTxqMCTjC+EYDdUwOPwgkonUv2k=",
"requires": { "requires": {
"arrify": "1.0.1", "arrify": "1.0.1",
"chalk": "2.1.0", "chalk": "2.3.2",
"diff": "3.4.0", "diff": "3.5.0",
"make-error": "1.3.0", "make-error": "1.3.4",
"minimist": "1.2.0", "minimist": "1.2.0",
"mkdirp": "0.5.1", "mkdirp": "0.5.1",
"source-map-support": "0.4.18", "source-map-support": "0.4.18",
"tsconfig": "6.0.0", "tsconfig": "6.0.0",
"v8flags": "3.0.1", "v8flags": "3.0.2",
"yn": "2.0.0" "yn": "2.0.0"
}, },
"dependencies": { "dependencies": {
@@ -2202,9 +2220,9 @@
} }
}, },
"typescript": { "typescript": {
"version": "2.5.3", "version": "2.8.1",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-2.5.3.tgz", "resolved": "https://registry.npmjs.org/typescript/-/typescript-2.8.1.tgz",
"integrity": "sha512-ptLSQs2S4QuS6/OD1eAKG+S5G8QQtrU5RT32JULdZQtM1L3WTi34Wsu48Yndzi8xsObRAB9RPt/KhA9wlpEF6w==" "integrity": "sha512-Ao/f6d/4EPLq0YwzsQz8iXflezpTkQzqAyenTiw4kCUGr1uPiFLC3+fZ+gMZz6eeI/qdRUqvC+HxIJzUAzEFdg=="
}, },
"underscore": { "underscore": {
"version": "1.6.0", "version": "1.6.0",
@@ -2212,20 +2230,20 @@
"integrity": "sha1-izixDKze9jM3uLJOT/htRa6lKag=" "integrity": "sha1-izixDKze9jM3uLJOT/htRa6lKag="
}, },
"v8flags": { "v8flags": {
"version": "3.0.1", "version": "3.0.2",
"resolved": "https://registry.npmjs.org/v8flags/-/v8flags-3.0.1.tgz", "resolved": "https://registry.npmjs.org/v8flags/-/v8flags-3.0.2.tgz",
"integrity": "sha1-3Oj8N5wX2fLJ6e142JzgAFKxt2s=", "integrity": "sha512-6sgSKoFw1UpUPd3cFdF7QGnrH6tDeBgW1F3v9gy8gLY0mlbiBXq8soy8aQpY6xeeCjH5K+JvC62Acp7gtl7wWA==",
"requires": { "requires": {
"homedir-polyfill": "1.0.1" "homedir-polyfill": "1.0.1"
} }
}, },
"validate-npm-package-license": { "validate-npm-package-license": {
"version": "3.0.1", "version": "3.0.3",
"resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.1.tgz", "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.3.tgz",
"integrity": "sha1-KAS6vnEq0zeUWaz74kdGqywwP7w=", "integrity": "sha512-63ZOUnL4SIXj4L0NixR3L1lcjO38crAbgrTpl28t8jjrfuiOBL5Iygm+60qPs/KsZGzPNg6Smnc/oY16QTjF0g==",
"requires": { "requires": {
"spdx-correct": "1.0.2", "spdx-correct": "3.0.0",
"spdx-expression-parse": "1.0.4" "spdx-expression-parse": "3.0.0"
} }
}, },
"walkdir": { "walkdir": {

View File

@@ -3,14 +3,18 @@
"private": true, "private": true,
"version": "1.0.0", "version": "1.0.0",
"description": "Common Library contains the common code for uhk-agent (electron-main) and web (electron-renderer) modules", "description": "Common Library contains the common code for uhk-agent (electron-main) and web (electron-renderer) modules",
"main": "dist/index.js", "main": "dist/src/index.js",
"types": "dist/src/index.d.ts",
"author": "Ultimate Gadget Laboratories", "author": "Ultimate Gadget Laboratories",
"repository": { "repository": {
"type": "git", "type": "git",
"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

@@ -41,16 +41,20 @@ export class HardwareConfiguration {
} }
fromBinary(buffer: UhkBuffer): HardwareConfiguration { fromBinary(buffer: UhkBuffer): HardwareConfiguration {
this.signature = buffer.readString(); try {
this.majorVersion = buffer.readUInt8(); this.signature = buffer.readString();
this.minorVersion = buffer.readUInt8(); this.majorVersion = buffer.readUInt8();
this.patchVersion = buffer.readUInt8(); this.minorVersion = buffer.readUInt8();
this.brandId = buffer.readUInt8(); this.patchVersion = buffer.readUInt8();
this.deviceId = buffer.readUInt8(); this.brandId = buffer.readUInt8();
this.uniqueId = buffer.readUInt32(); this.deviceId = buffer.readUInt8();
this.isVendorModeOn = buffer.readBoolean(); this.uniqueId = buffer.readUInt32();
this.isIso = buffer.readBoolean(); this.isVendorModeOn = buffer.readBoolean();
return this; this.isIso = buffer.readBoolean();
return this;
} catch (e) {
throw new Error('Please power cycle your keyboard (Invalid hardware configuration: Index out of bounds)');
}
} }
toJsonObject(): any { toJsonObject(): any {

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

@@ -13,7 +13,7 @@ export enum KeystrokeActionFlag {
const KEYSTROKE_ACTION_FLAG_LENGTH = 3; const KEYSTROKE_ACTION_FLAG_LENGTH = 3;
interface JsonObjectKeystrokeAction { export interface JsonObjectKeystrokeAction {
keyActionType: string; keyActionType: string;
scancode?: number; scancode?: number;
modifierMask?: number; modifierMask?: number;

View File

@@ -1,10 +1,10 @@
import { assertEnum, assertUInt8 } from '../../assert'; import { assertEnum, assertUInt8, assertUInt16 } from '../../assert';
import { UhkBuffer } from '../../uhk-buffer'; import { UhkBuffer } from '../../uhk-buffer';
import { KeyModifiers } from '../key-modifiers'; import { KeyModifiers } from '../key-modifiers';
import { MacroAction, MacroActionId, MacroKeySubAction, macroActionType } from './macro-action'; import { MacroAction, MacroActionId, MacroKeySubAction, macroActionType } from './macro-action';
import { KeystrokeType } from '../key-action'; import { KeystrokeType } from '../key-action';
interface JsObjectKeyMacroAction { export interface JsObjectKeyMacroAction {
macroActionType: string; macroActionType: string;
action: string; action: string;
type?: string; type?: string;
@@ -20,12 +20,24 @@ export class KeyMacroAction extends MacroAction {
@assertEnum(KeystrokeType) @assertEnum(KeystrokeType)
type: KeystrokeType; type: KeystrokeType;
@assertUInt8
scancode: number;
@assertUInt8 @assertUInt8
modifierMask: number; modifierMask: number;
@assertUInt16
private _scancode: number;
set scancode(scancode: number) {
this._scancode = scancode;
if (this.type !== KeystrokeType.shortMedia && this.type !== KeystrokeType.longMedia) {
return;
}
this.type = scancode < 256 ? KeystrokeType.shortMedia : KeystrokeType.longMedia;
}
get scancode() {
return this._scancode;
}
constructor(other?: KeyMacroAction) { constructor(other?: KeyMacroAction) {
super(); super();
if (!other) { if (!other) {
@@ -33,7 +45,7 @@ export class KeyMacroAction extends MacroAction {
} }
this.action = other.action; this.action = other.action;
this.type = other.type; this.type = other.type;
this.scancode = other.scancode; this._scancode = other._scancode;
this.modifierMask = other.modifierMask; this.modifierMask = other.modifierMask;
} }
@@ -45,7 +57,7 @@ export class KeyMacroAction extends MacroAction {
} else { } else {
this.type = KeystrokeType[jsObject.type]; this.type = KeystrokeType[jsObject.type];
} }
this.scancode = jsObject.scancode; this._scancode = jsObject.scancode;
this.modifierMask = jsObject.modifierMask; this.modifierMask = jsObject.modifierMask;
return this; return this;
} }
@@ -58,7 +70,7 @@ export class KeyMacroAction extends MacroAction {
this.type = keyMacroType & 0b11; this.type = keyMacroType & 0b11;
keyMacroType >>= 2; keyMacroType >>= 2;
if (keyMacroType & 0b10) { if (keyMacroType & 0b10) {
this.scancode = buffer.readUInt8(); this._scancode = this.type === KeystrokeType.longMedia ? buffer.readUInt16() : buffer.readUInt8();
} }
if (keyMacroType & 0b01) { if (keyMacroType & 0b01) {
this.modifierMask = buffer.readUInt8(); this.modifierMask = buffer.readUInt8();
@@ -78,7 +90,7 @@ export class KeyMacroAction extends MacroAction {
} else { } else {
jsObject.type = KeystrokeType[this.type]; jsObject.type = KeystrokeType[this.type];
} }
jsObject.scancode = this.scancode; jsObject.scancode = this._scancode;
} }
if (this.hasModifiers()) { if (this.hasModifiers()) {
@@ -98,7 +110,11 @@ export class KeyMacroAction extends MacroAction {
buffer.writeUInt8(keyMacroType); buffer.writeUInt8(keyMacroType);
if (this.hasScancode()) { if (this.hasScancode()) {
buffer.writeUInt8(this.scancode); if (this.type === KeystrokeType.longMedia) {
buffer.writeUInt16(this.scancode);
} else {
buffer.writeUInt8(this.scancode);
}
} }
if (this.hasModifiers()) { if (this.hasModifiers()) {
buffer.writeUInt8(this.modifierMask); buffer.writeUInt8(this.modifierMask);
@@ -106,7 +122,7 @@ export class KeyMacroAction extends MacroAction {
} }
toString(): string { toString(): string {
return `<KeyMacroAction action="${this.action}" scancode="${this.scancode}" modifierMask="${this.modifierMask}">`; return `<KeyMacroAction action="${this.action}" scancode="${this._scancode}" modifierMask="${this.modifierMask}">`;
} }
isModifierActive(modifier: KeyModifiers): boolean { isModifierActive(modifier: KeyModifiers): boolean {
@@ -114,7 +130,7 @@ export class KeyMacroAction extends MacroAction {
} }
hasScancode(): boolean { hasScancode(): boolean {
return !!this.scancode; return !!this._scancode;
} }
hasModifiers(): boolean { hasModifiers(): boolean {

View File

@@ -8,7 +8,7 @@ export enum MouseButtons {
Right = 1 << 2 Right = 1 << 2
} }
interface JsObjectMouseButtonMacroAction { export interface JsObjectMouseButtonMacroAction {
macroActionType: string; macroActionType: string;
action: string; action: string;
mouseButtonsMask?: number; mouseButtonsMask?: number;

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

@@ -4,4 +4,5 @@ export interface AppStartInfo {
commandLineArgs: CommandLineArgs; commandLineArgs: CommandLineArgs;
deviceConnected: boolean; deviceConnected: boolean;
hasPermission: boolean; hasPermission: boolean;
bootloaderActive: boolean;
} }

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

@@ -1,4 +1,5 @@
export interface DeviceConnectionState { export interface DeviceConnectionState {
connected: boolean; connected: boolean;
hasPermission: boolean; hasPermission: boolean;
bootloaderActive: boolean;
} }

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

@@ -1,4 +1,10 @@
import { HardwareModules } from './hardware-modules';
export class IpcResponse { export class IpcResponse {
success: boolean; success: boolean;
error?: { message: string }; error?: { message: string };
} }
export class FirmwareUpgradeIpcResponse extends IpcResponse {
modules?: HardwareModules;
}

View File

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

View File

@@ -1,3 +1,4 @@
export namespace Constants { export namespace Constants {
export const AGENT_GITHUB_URL = 'https://github.com/UltimateHackingKeyboard/agent'; export const AGENT_GITHUB_URL = 'https://github.com/UltimateHackingKeyboard/agent';
export const FIRMWARE_GITHUB_ISSUE_URL = 'https://github.com/UltimateHackingKeyboard/agent/issues/567';
} }

View File

@@ -0,0 +1,38 @@
import { HardwareConfiguration, UhkBuffer, UserConfiguration } from '../config-serializer';
export const getHardwareConfigFromDeviceResponse = (json: string): HardwareConfiguration => {
const data = JSON.parse(json);
const hardwareConfig = new HardwareConfiguration();
hardwareConfig.fromBinary(UhkBuffer.fromArray(data));
if (hardwareConfig.signature === 'FTY') {
throw Error('The device is in factory reset mode. Power-cycle the device to use it with Agent!');
}
if (hardwareConfig.signature !== 'UHK') {
throw Error('Please power cycle your keyboard (Invalid hardware configuration: Invalid signature)');
}
return hardwareConfig;
};
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

@@ -1,4 +1,4 @@
class App { export class App {
public static readonly appStarted = 'app-started'; public static readonly appStarted = 'app-started';
public static readonly getAppStartInfo = 'app-get-start-info'; public static readonly getAppStartInfo = 'app-get-start-info';
public static readonly getAppStartInfoReply = 'app-get-start-info-reply'; public static readonly getAppStartInfoReply = 'app-get-start-info-reply';
@@ -6,7 +6,7 @@ class App {
public static readonly openUrl = 'open-url'; public static readonly openUrl = 'open-url';
} }
class AutoUpdate { export class AutoUpdate {
public static readonly checkingForUpdate = 'checking-for-update'; public static readonly checkingForUpdate = 'checking-for-update';
public static readonly updateAvailable = 'update-available'; public static readonly updateAvailable = 'update-available';
public static readonly updateNotAvailable = 'update-not-available'; public static readonly updateNotAvailable = 'update-not-available';
@@ -18,7 +18,7 @@ class AutoUpdate {
public static readonly checkForUpdateNotAvailable = 'check-for-update-not-available'; public static readonly checkForUpdateNotAvailable = 'check-for-update-not-available';
} }
class Device { export class Device {
public static readonly setPrivilegeOnLinux = 'set-privilege-on-linux'; public static readonly setPrivilegeOnLinux = 'set-privilege-on-linux';
public static readonly setPrivilegeOnLinuxReply = 'set-privilege-on-linux-reply'; public static readonly setPrivilegeOnLinuxReply = 'set-privilege-on-linux-reply';
public static readonly deviceConnectionStateChanged = 'device-connection-state-changed'; public static readonly deviceConnectionStateChanged = 'device-connection-state-changed';
@@ -29,6 +29,7 @@ class Device {
public static readonly updateFirmware = 'device-update-firmware'; public static readonly updateFirmware = 'device-update-firmware';
public static readonly updateFirmwareReply = 'device-update-firmware-reply'; public static readonly updateFirmwareReply = 'device-update-firmware-reply';
public static readonly startConnectionPoller = 'device-start-connection-poller'; public static readonly startConnectionPoller = 'device-start-connection-poller';
public static readonly recoveryDevice = 'device-recovery';
} }
export class IpcEvents { export class IpcEvents {

View File

@@ -3,7 +3,7 @@
"compilerOptions": { "compilerOptions": {
"sourceMap": true, "sourceMap": true,
"outDir": "./dist", "outDir": "./dist",
"declaration": false, "declaration": true,
"module": "commonjs", "module": "commonjs",
"moduleResolution": "node", "moduleResolution": "node",
"emitDecoratorMetadata": true, "emitDecoratorMetadata": true,

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,16 @@
"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="
}, },
"lodash-es": {
"version": "4.17.10",
"resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.10.tgz",
"integrity": "sha512-iesFYPmxYYGTcmQK0sL8bX3TGHyM6b2qREaB4kamHfQyfPJP0xgoGxp19nsH16nsfquLdiyKyX3mQkfiSGV8Rg=="
},
"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 +175,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 +193,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 +237,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 +302,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 +317,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 +343,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 +370,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 +404,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

@@ -11,6 +11,7 @@
"@types/node": "8.0.28" "@types/node": "8.0.28"
}, },
"dependencies": { "dependencies": {
"lodash-es": "^4.17.10",
"node-hid": "0.5.7", "node-hid": "0.5.7",
"uhk-common": "1.0.0" "uhk-common": "1.0.0"
} }

View File

@@ -1,6 +1,7 @@
export namespace Constants { export namespace Constants {
export const VENDOR_ID = 0x1D50; export const VENDOR_ID = 0x1D50;
export const PRODUCT_ID = 0x6122; export const PRODUCT_ID = 0x6122;
export const BOOTLOADER_ID = 0x6120;
export const MAX_PAYLOAD_SIZE = 64; export const MAX_PAYLOAD_SIZE = 64;
} }
@@ -22,7 +23,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 +40,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 +82,7 @@ export enum KbootCommands {
ping = 1, ping = 1,
reset = 2 reset = 2
} }
export enum ModulePropertyId {
protocolVersions = 0
}

View File

@@ -1,5 +1,6 @@
import { isEqual } from 'lodash';
import { Device, devices, HID } from 'node-hid'; import { Device, devices, HID } from 'node-hid';
import { LogService } from 'uhk-common'; import { CommandLineArgs, DeviceConnectionState, LogService } from 'uhk-common';
import { import {
ConfigBufferId, ConfigBufferId,
@@ -12,7 +13,7 @@ import {
ModuleSlotToId, ModuleSlotToId,
UsbCommand UsbCommand
} from './constants'; } from './constants';
import { bufferToString, getTransferData, retry, snooze } from './util'; import { bufferToString, getTransferData, isUhkDevice, retry, snooze } from './util';
export const BOOTLOADER_TIMEOUT_MS = 5000; export const BOOTLOADER_TIMEOUT_MS = 5000;
@@ -24,10 +25,12 @@ export class UhkHidDevice {
* Internal variable that represent the USB UHK device * Internal variable that represent the USB UHK device
* @private * @private
*/ */
private _prevDevices = {};
private _device: HID; private _device: HID;
private _hasPermission = false; private _hasPermission = false;
constructor(private logService: LogService) { constructor(private logService: LogService,
private options: CommandLineArgs) {
} }
/** /**
@@ -38,17 +41,25 @@ 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;
} }
if (!this.deviceConnected()) { const dev = devices().find((x: Device) => isUhkDevice(x) || x.productId === Constants.BOOTLOADER_ID);
if (!dev) {
return true; return true;
} }
this._hasPermission = this.getDevice() !== null; const device = new HID(dev.path);
this.close(); device.close();
this._hasPermission = true;
return this._hasPermission; return this._hasPermission;
} catch (err) { } catch (err) {
@@ -62,15 +73,24 @@ export class UhkHidDevice {
* Return with true is an UHK Device is connected to the computer. * Return with true is an UHK Device is connected to the computer.
* @returns {boolean} * @returns {boolean}
*/ */
public deviceConnected(): boolean { public getDeviceConnectionState(): DeviceConnectionState {
const connected = devices().some((dev: Device) => dev.vendorId === Constants.VENDOR_ID && const devs = devices();
dev.productId === Constants.PRODUCT_ID); const result: DeviceConnectionState = {
bootloaderActive: false,
connected: false,
hasPermission: this.hasPermission()
};
if (!connected) { for (const dev of devs) {
this._hasPermission = false; if (isUhkDevice(dev)) {
result.connected = true;
} else if (dev.vendorId === Constants.VENDOR_ID &&
dev.productId === Constants.BOOTLOADER_ID) {
result.bootloaderActive = true;
}
} }
return connected; return result;
} }
/** /**
@@ -137,6 +157,10 @@ export class UhkHidDevice {
} }
} }
public resetDeviceCache(): void {
this._prevDevices = {};
}
async reenumerate(enumerationMode: EnumerationModes): Promise<void> { async reenumerate(enumerationMode: EnumerationModes): Promise<void> {
const reenumMode = EnumerationModes[enumerationMode].toString(); const reenumMode = EnumerationModes[enumerationMode].toString();
this.logService.debug(`[UhkHidDevice] Start reenumeration, mode: ${reenumMode}`); this.logService.debug(`[UhkHidDevice] Start reenumeration, mode: ${reenumMode}`);
@@ -192,7 +216,7 @@ export class UhkHidDevice {
async sendKbootCommandToModule(module: ModuleSlotToI2cAddress, command: KbootCommands, maxTry = 1): Promise<any> { async sendKbootCommandToModule(module: ModuleSlotToI2cAddress, command: KbootCommands, maxTry = 1): Promise<any> {
let transfer; let transfer;
const moduleName = kbootKommandName(module); const moduleName = kbootCommandName(module);
this.logService.debug(`[UhkHidDevice] USB[T]: Send KbootCommand ${moduleName} ${KbootCommands[command].toString()}`); this.logService.debug(`[UhkHidDevice] USB[T]: Send KbootCommand ${moduleName} ${KbootCommands[command].toString()}`);
if (command === KbootCommands.idle) { if (command === KbootCommands.idle) {
transfer = new Buffer([UsbCommand.SendKbootCommandToModule, command]); transfer = new Buffer([UsbCommand.SendKbootCommandToModule, command]);
@@ -228,12 +252,14 @@ export class UhkHidDevice {
private connectToDevice(): HID { private connectToDevice(): HID {
try { try {
const devs = devices(); const devs = devices();
this.logService.debug('[UhkHidDevice] Available devices:', devs); if (!isEqual(this._prevDevices, devs)) {
this.logService.debug('[UhkHidDevice] Available devices:', devs);
this._prevDevices = devs;
} else {
this.logService.debug('[UhkHidDevice] Available devices unchanged');
}
const dev = devs.find((x: Device) => const dev = devs.find(isUhkDevice);
x.vendorId === Constants.VENDOR_ID &&
x.productId === Constants.PRODUCT_ID &&
((x.usagePage === 128 && x.usage === 129) || x.interface === 0));
if (!dev) { if (!dev) {
this.logService.debug('[UhkHidDevice] UHK Device not found:'); this.logService.debug('[UhkHidDevice] UHK Device not found:');
@@ -251,7 +277,7 @@ export class UhkHidDevice {
} }
} }
function kbootKommandName(module: ModuleSlotToI2cAddress): string { function kbootCommandName(module: ModuleSlotToI2cAddress): string {
switch (module) { switch (module) {
case ModuleSlotToI2cAddress.leftHalf: case ModuleSlotToI2cAddress.leftHalf:
return 'leftHalf'; return 'leftHalf';

View File

@@ -1,12 +1,25 @@
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 * as os from 'os';
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 {
@@ -17,6 +30,7 @@ export class UhkOperations {
} }
public async updateRightFirmware(firmwarePath = this.getFirmwarePath()) { public async updateRightFirmware(firmwarePath = this.getFirmwarePath()) {
this.logService.debug(`[UhkOperations] Operating system: ${os.type()} ${os.release()} ${os.arch()}`);
this.logService.debug('[UhkOperations] Start flashing right firmware'); this.logService.debug('[UhkOperations] Start flashing right firmware');
const prefix = [`--usb 0x1d50,0x${EnumerationNameToProductId.bootloader.toString(16)}`]; const prefix = [`--usb 0x1d50,0x${EnumerationNameToProductId.bootloader.toString(16)}`];
@@ -42,6 +56,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 +107,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 +140,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 +166,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 +181,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);

View File

@@ -1,5 +1,7 @@
import { Device } from 'node-hid';
import { DeviceConnectionState, LogService } from 'uhk-common';
import { Constants, UsbCommand } from './constants'; import { Constants, UsbCommand } from './constants';
import { LogService } from 'uhk-common';
export const snooze = ms => new Promise(resolve => setTimeout(resolve, ms)); export const snooze = ms => new Promise(resolve => setTimeout(resolve, ms));
@@ -95,3 +97,18 @@ export async function retry(command: Function, maxTry = 3, logService?: LogServi
} }
} }
} }
export const deviceConnectionStateComparer = (a: DeviceConnectionState, b: DeviceConnectionState): boolean => {
return a.hasPermission === b.hasPermission
&& a.connected === b.connected
&& a.bootloaderActive === b.bootloaderActive;
};
export const isUhkDevice = (dev: Device): boolean => {
return dev.vendorId === Constants.VENDOR_ID &&
dev.productId === Constants.PRODUCT_ID &&
// hidapi can not read the interface number on Mac, so check the usage page and usage
((dev.usagePage === 128 && dev.usage === 129) || // Old firmware
(dev.usagePage === (0xFF00 | 0x00) && dev.usage === 0x01) || // New firmware
dev.interface === 0);
};

File diff suppressed because it is too large Load Diff

View File

@@ -10,22 +10,24 @@
"lint": "ng lint", "lint": "ng lint",
"e2e": "ng e2e", "e2e": "ng e2e",
"build:renderer": "webpack --config webpack.config.js", "build:renderer": "webpack --config webpack.config.js",
"server:renderer": "webpack --config webpack.config.js --watch" "server:renderer": "webpack --config webpack.config.js --watch",
"pree2e": "webdriver-manager update --standalone false --gecko false --quiet"
}, },
"private": true, "private": true,
"devDependencies": { "devDependencies": {
"@angular/animations": "4.4.5", "@angular/animations": "5.2.9",
"@angular/cli": "1.4.7", "@angular/cli": "1.7.4",
"@angular/common": "4.4.5", "@angular/common": "5.2.9",
"@angular/compiler": "4.4.5", "@angular/compiler": "5.2.9",
"@angular/compiler-cli": "4.4.5", "@angular/compiler-cli": "5.2.9",
"@angular/core": "4.4.5", "@angular/core": "5.2.9",
"@angular/forms": "4.4.5", "@angular-devkit/build-optimizer": "0.3.2",
"@angular/http": "4.4.5", "@angular/forms": "5.2.9",
"@angular/language-service": "4.4.5", "@angular/http": "5.2.9",
"@angular/platform-browser": "4.4.5", "@angular/language-service": "5.2.9",
"@angular/platform-browser-dynamic": "4.4.5", "@angular/platform-browser": "5.2.9",
"@angular/router": "4.4.5", "@angular/platform-browser-dynamic": "5.2.9",
"@angular/router": "5.2.9",
"@ngrx/effects": "4.0.5", "@ngrx/effects": "4.0.5",
"@ngrx/router-store": "4.0.4", "@ngrx/router-store": "4.0.4",
"@ngrx/store": "4.0.3", "@ngrx/store": "4.0.3",
@@ -37,64 +39,45 @@
"@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",
"autoprefixer": "6.5.3", "autoprefixer": "^7.2.3",
"bootstrap": "3.3.7", "bootstrap": "3.3.7",
"buffer": "5.0.6", "buffer": "5.0.6",
"circular-dependency-plugin": "3.0.0", "circular-dependency-plugin": "^4.2.1",
"codelyzer": "3.0.1", "codelyzer": "3.0.1",
"copy-webpack-plugin": "4.0.1",
"css-loader": "0.28.1",
"cssnano": "3.10.0",
"dragula": "3.7.2", "dragula": "3.7.2",
"exports-loader": "0.6.3",
"file-loader": "0.10.0",
"file-saver": "1.3.3",
"font-awesome": "4.7.0", "font-awesome": "4.7.0",
"html-webpack-plugin": "2.29.0", "html-webpack-plugin": "^2.29.0",
"istanbul-instrumenter-loader": "2.0.0",
"jasmine-core": "2.6.2", "jasmine-core": "2.6.2",
"jasmine-spec-reporter": "4.1.0", "jasmine-spec-reporter": "4.1.0",
"jquery": "3.2.1", "jquery": "3.2.1",
"jsonfile": "3.0.1", "jsonfile": "3.0.1",
"karma": "1.7.0", "karma": "1.7.0",
"karma-chrome-launcher": "2.1.1", "karma-chrome-launcher": "2.1.1",
"karma-cli": "1.0.1",
"karma-coverage-istanbul-reporter": "1.2.1", "karma-coverage-istanbul-reporter": "1.2.1",
"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", "lodash-es": "4.17.4",
"lodash": "4.17.4",
"ng2-dragula": "1.5.0", "ng2-dragula": "1.5.0",
"ng2-nouislider": "^1.7.6", "ng2-nouislider": "^1.7.7",
"ng2-select2": "1.0.0-beta.10", "ng2-select2": "1.0.0-beta.10",
"ngx-clipboard": "10.0.0",
"ngrx-store-freeze": "0.1.9", "ngrx-store-freeze": "0.1.9",
"node-hid": "0.5.4", "nouislider": "^11.1.0",
"nouislider": "^10.1.0", "postcss-url": "^7.1.2",
"postcss-loader": "1.3.3",
"postcss-url": "5.1.2",
"protractor": "5.1.2", "protractor": "5.1.2",
"raw-loader": "0.5.1",
"reselect": "3.0.1", "reselect": "3.0.1",
"sass-loader": "6.0.3", "rxjs": "5.5.8",
"script-loader": "0.7.0",
"select2": "4.0.3", "select2": "4.0.3",
"source-map-loader": "0.2.0", "typescript": "2.6.2",
"style-loader": "0.13.1",
"stylus-loader": "3.0.1",
"sudo-prompt": "7.1.1",
"ts-loader": "2.3.1",
"ts-node": "3.0.4",
"uhk-common": "1.0.0", "uhk-common": "1.0.0",
"url-loader": "0.5.7",
"webpack": "3.4.1",
"webpack-dev-server": "2.5.1",
"webpack-svgstore-plugin": "4.0.1",
"xml-loader": "1.2.1", "xml-loader": "1.2.1",
"zone.js": "0.8.14" "zone.js": "0.8.26",
"@angular-devkit/core": "0.3.2",
"@ngtools/webpack": "1.10.2"
}, },
"dependencies": { "dependencies": {
"classlist.js": "1.1.20150312", "classlist.js": "1.1.20150312",

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

@@ -5,17 +5,19 @@ import { deviceRoutes } from './components/device';
import { addOnRoutes } from './components/add-on'; import { addOnRoutes } from './components/add-on';
import { keymapRoutes } from './components/keymap'; import { keymapRoutes } from './components/keymap';
import { macroRoutes } from './components/macro'; import { macroRoutes } from './components/macro';
import { PrivilegeCheckerComponent } from './components/privilege-checker/privilege-checker.component'; import { PrivilegeCheckerComponent } from './components/privilege-checker';
import { MissingDeviceComponent } from './components/missing-device/missing-device.component'; import { MissingDeviceComponent } from './components/missing-device';
import { UhkDeviceDisconnectedGuard } from './services/uhk-device-disconnected.guard'; import { UhkDeviceDisconnectedGuard } from './services/uhk-device-disconnected.guard';
import { UhkDeviceConnectedGuard } from './services/uhk-device-connected.guard'; import { UhkDeviceConnectedGuard } from './services/uhk-device-connected.guard';
import { UhkDeviceUninitializedGuard } from './services/uhk-device-uninitialized.guard'; import { UhkDeviceUninitializedGuard } from './services/uhk-device-uninitialized.guard';
import { UhkDeviceInitializedGuard } from './services/uhk-device-initialized.guard'; import { UhkDeviceInitializedGuard } from './services/uhk-device-initialized.guard';
import { MainPage } from './pages/main-page/main.page'; import { MainPage } from './pages/main-page/main.page';
import { agentRoutes } from './components/agent/agent.routes'; import { agentRoutes } from './components/agent';
import { LoadingDevicePageComponent } from './pages/loading-page/loading-device.page'; import { LoadingDevicePageComponent } from './pages/loading-page/loading-device.page';
import { UhkDeviceLoadingGuard } from './services/uhk-device-loading.guard'; 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 { RecoveryModeComponent } from './components/device';
import { UhkDeviceBootloaderNotActiveGuard } from './services/uhk-device-bootloader-not-active.guard';
const appRoutes: Routes = [ const appRoutes: Routes = [
{ {
@@ -33,6 +35,11 @@ const appRoutes: Routes = [
component: LoadingDevicePageComponent, component: LoadingDevicePageComponent,
canActivate: [UhkDeviceLoadedGuard] canActivate: [UhkDeviceLoadedGuard]
}, },
{
path: 'recovery-device',
component: RecoveryModeComponent,
canActivate: [UhkDeviceBootloaderNotActiveGuard]
},
{ {
path: '', path: '',
component: MainPage, component: MainPage,

View File

@@ -0,0 +1,10 @@
<input #inputControl
cancelable
[class]="css"
type="text"
[disabled]="disabled"
[(ngModel)]="model"
(blur)="blur()"
(focus)="focus()"
(keyup.enter)="keyEnter($event)"
(keyup)="calculateTextWidth($event.target.value)">

View File

@@ -0,0 +1,118 @@
import {
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
ElementRef,
forwardRef, HostListener,
Input,
Renderer2,
ViewChild
} from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
import * as util from '../../util';
const noop = (_: any) => {
};
@Component({
selector: 'auto-grow-input',
templateUrl: './auto-grow-input.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => AutoGrowInputComponent),
multi: true
}
]
})
export class AutoGrowInputComponent implements ControlValueAccessor {
@Input() maxParentWidthPercent = 1;
@Input() css: string;
@ViewChild('inputControl') inputControl: ElementRef;
disabled: boolean;
get model(): string {
return this._model;
}
set model(value: string) {
if (this._model === value) {
return;
}
this._model = value;
}
private _model: string;
private _originalModel: string;
private _onChanged = noop;
private _onTouched = noop;
constructor(private _cdRef: ChangeDetectorRef,
private _renderer: Renderer2) {
}
registerOnChange(fn: any): void {
this._onChanged = fn;
}
registerOnTouched(fn: any): void {
this._onTouched = fn;
}
setDisabledState(isDisabled: boolean): void {
if (this.disabled === isDisabled) {
return;
}
this.disabled = isDisabled;
this._cdRef.markForCheck();
}
@HostListener('window:resize')
windowResize(): void {
this.calculateTextWidth(this._model);
}
writeValue(obj: any): void {
console.log('write', new Date());
if (this.model === obj) {
return;
}
this._model = obj;
this._originalModel = obj;
this.calculateTextWidth(this._model);
this._cdRef.markForCheck();
}
focus(): void {
this._onTouched(this);
}
blur(): void {
if (!util.isValidName(this._model) || this._model.trim() === this._originalModel) {
this._model = this._originalModel;
this.calculateTextWidth(this._model);
this._cdRef.markForCheck();
return;
}
this._originalModel = this._model;
this._onChanged(this._model);
}
keyEnter(event): void {
event.target.blur();
}
calculateTextWidth(text: string): void {
const htmlInput = this.inputControl.nativeElement as HTMLInputElement;
const maxWidth = htmlInput.parentElement.parentElement.offsetWidth * this.maxParentWidthPercent;
const textWidth = util.getContentWidth(window.getComputedStyle(htmlInput), text);
this._renderer.setStyle(htmlInput, 'width', Math.min(maxWidth, textWidth) + 'px');
}
}

View File

@@ -0,0 +1 @@
export * from './auto-grow-input.component';

View File

@@ -9,16 +9,14 @@
<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"> <file-upload (fileChanged)="changeFile($event)"
Upload device configuration label="Import device configuration">
<input type="file" </file-upload>
(change)="changeFile($event)">
</label>
</li> </li>
<li> <li>
<button class="btn btn-danger" <button class="btn btn-danger"

View File

@@ -8,6 +8,7 @@ import {
SaveUserConfigInBinaryFileAction, SaveUserConfigInBinaryFileAction,
SaveUserConfigInJsonFileAction SaveUserConfigInJsonFileAction
} from '../../../store/actions/user-config'; } from '../../../store/actions/user-config';
import { UploadFileData } from '../../../models/upload-file-data';
@Component({ @Component({
selector: 'device-settings', selector: 'device-settings',
@@ -34,16 +35,15 @@ export class DeviceConfigurationComponent {
this.store.dispatch(new SaveUserConfigInBinaryFileAction()); this.store.dispatch(new SaveUserConfigInBinaryFileAction());
} }
changeFile(event): void { exportUserConfiguration(event: MouseEvent) {
const files = event.srcElement.files; if (event.shiftKey) {
const fileReader = new FileReader(); this.saveConfigurationInBINFormat();
fileReader.onloadend = function () { } else {
const arrayBuffer = new Uint8Array(fileReader.result); this.saveConfigurationInJSONFormat();
this.store.dispatch(new LoadUserConfigurationFromFileAction({ }
filename: event.srcElement.value, }
data: Array.from(arrayBuffer)
})); changeFile(data: UploadFileData): void {
}.bind(this); this.store.dispatch(new LoadUserConfigurationFromFileAction(data));
fileReader.readAsArrayBuffer(files[0]);
} }
} }

View File

@@ -4,6 +4,8 @@ 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';
import { RecoveryModeComponent } from './recovery-mode/recovery-mode.component';
export const deviceRoutes: Routes = [ export const deviceRoutes: Routes = [
{ {
@@ -29,6 +31,14 @@ export const deviceRoutes: Routes = [
{ {
path: 'firmware', path: 'firmware',
component: DeviceFirmwareComponent component: DeviceFirmwareComponent
},
{
path: 'restore-user-configuration',
component: RestoreConfigurationComponent
},
{
path: 'recovery-mode',
component: RecoveryModeComponent
} }
] ]
} }

View File

@@ -6,35 +6,41 @@
<i class="fa fa-sliders"></i> <i class="fa fa-sliders"></i>
<span>Firmware</span> <span>Firmware</span>
</h1> </h1>
<p>
Flash firmware {{ (getAgentVersionInfo$ | async).firmwareVersion }} (bundled with Agent)
<button class="btn btn-primary"
[disabled]="flashFirmwareButtonDisbabled$ | async"
(click)="onUpdateFirmware()">Flash firmware
</button>
</p>
<p> <p>
Flash firmware file <input id="firmware-file-select" Firmware {{ hardwareModules.leftModuleInfo.firmwareVersion }} is running on the left keyboard half.<br>
type="file" Firmware {{ hardwareModules.rightModuleInfo.firmwareVersion }} is running on the right keyboard half.
[disabled]="flashFirmwareButtonDisbabled$ | async" </p>
(change)="changeFile($event)">
If the update process fails, consider the following points:
<ol>
<li>Windows 7, Windows Vista, and Windows XP are not supported. Use Linux, OSX, Windows 10, or Windows 8.</li>
<li>Connect your UHK directly to the host computer. Don't use USB hubs or KVM switches.</li>
<li>Run Agent directly on the host operating system. Don't use VirtualBox or VMware Workstation.</li>
<li>Give a try to every USB port of your computer.</li>
<li>Remove every other USB device from your computer.</li>
<li>If the left half becomes unresponsive after a failed update then retry and follow the instructions displayed during the update to fix it.</li>
<li>If the above fails, retry a couple of times.</li>
<li>If everything else fails, please add a new comment to <a class="link-github" (click)="openFirmwareGitHubIssuePage($event)">the GitHub issue</a>, and attach the update log.</li>
</ol>
<p>
<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>
<file-upload [disabled]="flashFirmwareButtonDisbabled$ | async"
(fileChanged)="changeFile($event)"
accept=".tar.bz2"
label="Choose firmware file and flash it"></file-upload>
</p> </p>
</div> </div>
<div class="flex-grow" #scrollMe> <div class="flex-grow">
<xterm [logs]="xtermLog$ | async"></xterm> <xterm [logs]="xtermLog$ | async"></xterm>
</div> </div>
<div class="footer"> <div class="flex-footer">
<button type="button"
class="btn btn-primary ok-button"
[disabled]="firmwareOkButtonDisabled$ | async"
(click)="onOkButtonClick()">OK
</button>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -6,24 +6,6 @@
width: 100%; width: 100%;
} }
.flex-container { .link-github {
height: 100%; cursor: pointer;
max-height: 100%;
display: flex;
flex-direction: column;
}
.flex-grow {
background-color: black;
overflow: auto;
flex: 1;
}
.footer {
margin-top: 0.5em;
margin-bottom: 0.5em;
}
.ok-button {
min-width: 100px;
} }

View File

@@ -1,12 +1,21 @@
import { Component, ElementRef, OnDestroy, ViewChild } from '@angular/core'; import { Component, OnDestroy } 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 { Constants } from 'uhk-common';
import { OpenUrlInNewWindowAction } from '../../../store/actions/app';
import { AppState, firmwareOkButtonDisabled, flashFirmwareButtonDisbabled, getAgentVersionInfo, xtermLog } from '../../../store'; import {
import { UpdateFirmwareAction, UpdateFirmwareOkButtonAction, UpdateFirmwareWithAction } from '../../../store/actions/device'; AppState,
flashFirmwareButtonDisbabled,
getAgentVersionInfo,
getHardwareModules,
xtermLog
} from '../../../store';
import { UpdateFirmwareAction, UpdateFirmwareWithAction } from '../../../store/actions/device';
import { XtermLog } from '../../../models/xterm-log'; import { XtermLog } from '../../../models/xterm-log';
import { UploadFileData } from '../../../models/upload-file-data';
@Component({ @Component({
selector: 'device-firmware', selector: 'device-firmware',
@@ -19,60 +28,33 @@ import { XtermLog } from '../../../models/xterm-log';
export class DeviceFirmwareComponent implements OnDestroy { export class DeviceFirmwareComponent implements OnDestroy {
flashFirmwareButtonDisbabled$: Observable<boolean>; flashFirmwareButtonDisbabled$: Observable<boolean>;
xtermLog$: Observable<Array<XtermLog>>; xtermLog$: Observable<Array<XtermLog>>;
xtermLogSubscription: Subscription;
getAgentVersionInfo$: Observable<VersionInformation>; getAgentVersionInfo$: Observable<VersionInformation>;
firmwareOkButtonDisabled$: Observable<boolean>; hardwareModulesSubscription: Subscription;
hardwareModules: HardwareModules;
arrayBuffer: Uint8Array;
@ViewChild('scrollMe') divElement: ElementRef;
constructor(private store: Store<AppState>) { constructor(private store: Store<AppState>) {
this.flashFirmwareButtonDisbabled$ = store.select(flashFirmwareButtonDisbabled); this.flashFirmwareButtonDisbabled$ = store.select(flashFirmwareButtonDisbabled);
this.xtermLog$ = store.select(xtermLog); this.xtermLog$ = store.select(xtermLog);
this.xtermLogSubscription = this.xtermLog$.subscribe(() => {
if (this.divElement && this.divElement.nativeElement) {
setTimeout(() => {
this.divElement.nativeElement.scrollTop = this.divElement.nativeElement.scrollHeight;
});
}
});
this.getAgentVersionInfo$ = store.select(getAgentVersionInfo); this.getAgentVersionInfo$ = store.select(getAgentVersionInfo);
this.firmwareOkButtonDisabled$ = store.select(firmwareOkButtonDisabled); this.hardwareModulesSubscription = store.select(getHardwareModules).subscribe(data => {
this.hardwareModules = data;
});
} }
ngOnDestroy(): void { ngOnDestroy(): void {
this.xtermLogSubscription.unsubscribe(); this.hardwareModulesSubscription.unsubscribe();
} }
onUpdateFirmware(): void { onUpdateFirmware(): void {
this.store.dispatch(new UpdateFirmwareAction()); this.store.dispatch(new UpdateFirmwareAction());
} }
onUpdateFirmwareWithFile(): void { changeFile(data: UploadFileData): void {
if (!this.arrayBuffer) { this.store.dispatch(new UpdateFirmwareWithAction(data.data));
return;
}
this.store.dispatch(new UpdateFirmwareWithAction(Array.prototype.slice.call(this.arrayBuffer)));
} }
onOkButtonClick(): void { openFirmwareGitHubIssuePage(event): void {
this.store.dispatch(new UpdateFirmwareOkButtonAction()); event.preventDefault();
} this.store.dispatch(new OpenUrlInNewWindowAction(Constants.FIRMWARE_GITHUB_ISSUE_URL));
changeFile(event): void {
const files = event.srcElement.files;
if (files.length === 0) {
this.arrayBuffer = null;
return;
}
const fileReader = new FileReader();
fileReader.onloadend = function () {
this.arrayBuffer = new Uint8Array(fileReader.result);
}.bind(this);
fileReader.readAsArrayBuffer(files[0]);
} }
} }

View File

@@ -2,4 +2,6 @@ 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 './recovery-mode/recovery-mode.component';
export * from './device.routes'; export * from './device.routes';

View File

@@ -0,0 +1,26 @@
<div class="full-height">
<div class="flex-container">
<div>
<h1>
<i class="fa fa-wrench"></i>
<span>Fix device</span>
</h1>
<p>
Your device seems to be broken. No worries, Agent can fix it.
</p>
<p>
<button class="btn btn-primary"
type="button"
[disabled]="flashFirmwareButtonDisbabled$ | async"
(click)="onRecoveryDevice()">Fix device
</button>
</p>
</div>
<div class="flex-grow">
<xterm [logs]="xtermLog$ | async"></xterm>
</div>
<div class="flex-footer">
</div>
</div>
</div>

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,34 @@
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
import { Store } from '@ngrx/store';
import { Observable } from 'rxjs/Observable';
import { XtermLog } from '../../../models/xterm-log';
import { AppState, flashFirmwareButtonDisbabled, xtermLog } from '../../../store';
import { RecoveryDeviceAction } from '../../../store/actions/device';
@Component({
selector: 'device-recovery-mode',
changeDetection: ChangeDetectionStrategy.OnPush,
templateUrl: './recovery-mode.component.html',
styleUrls: ['./recovery-mode.component.scss'],
host: {
'class': 'container-fluid'
}
})
export class RecoveryModeComponent implements OnInit {
flashFirmwareButtonDisbabled$: Observable<boolean>;
xtermLog$: Observable<Array<XtermLog>>;
constructor(private store: Store<AppState>) {
}
ngOnInit(): void {
this.flashFirmwareButtonDisbabled$ = this.store.select(flashFirmwareButtonDisbabled);
this.xtermLog$ = this.store.select(xtermLog);
}
onRecoveryDevice(): void {
this.store.dispatch(new RecoveryDeviceAction());
}
}

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

@@ -0,0 +1,9 @@
<label class="btn btn-primary btn-file"
[class.disabled]="disabled">
{{ label }}
<input #inputControl
type="file"
[accept]="accept"
[disabled]="disabled"
(change)="changeFile($event)">
</label>

View File

@@ -0,0 +1,36 @@
import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core';
import { UploadFileData } from '../../models/upload-file-data';
@Component({
selector: 'file-upload',
templateUrl: './file-upload.component.html',
changeDetection: ChangeDetectionStrategy.OnPush
})
export class FileUploadComponent {
@Input() label = 'Select file';
@Input() disabled: boolean;
@Input() accept: string;
@Output() fileChanged = new EventEmitter<UploadFileData>();
changeFile(event): void {
const files = event.srcElement.files;
if (files.length === 0) {
return;
}
const fileReader = new FileReader();
fileReader.onloadend = function () {
const arrayBuffer = new Uint8Array(fileReader.result);
const target = event.target || event.srcElement || event.currentTarget;
target.value = null;
this.fileChanged.emit({
filename: event.srcElement.value,
data: Array.from(arrayBuffer)
});
}.bind(this);
fileReader.readAsArrayBuffer(files[0]);
}
}

View File

@@ -0,0 +1 @@
export * from './file-upload.component';

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

@@ -67,7 +67,7 @@ export class MacroKeyTabComponent extends MacroBaseComponent implements OnInit {
} }
getKeyMacroAction(): KeyMacroAction { getKeyMacroAction(): KeyMacroAction {
const keyMacroAction = Object.assign(new KeyMacroAction(), this.keypressTab.toKeyAction()); const keyMacroAction = new KeyMacroAction(this.keypressTab.toKeyAction() as any);
keyMacroAction.action = this.getActionType(this.activeTab); keyMacroAction.action = this.getActionType(this.activeTab);
return keyMacroAction; return keyMacroAction;
} }

View File

@@ -1,5 +1,10 @@
<div> <div>
<h4>Type text</h4> <h4>Type text</h4>
<textarea #macroTextInput name="macro-text" (change)="onTextChange()" <textarea #macroTextInput
(keyup)="validate()" class="macro__text-input">{{ macroAction?.text }}</textarea> name="macro-text"
(keydown)="onKeydown($event)"
(change)="onTextChange()"
(keyup)="validate()"
(paste)="onPaste($event)"
class="macro__text-input">{{ macroAction?.text }}</textarea>
</div> </div>

View File

@@ -11,6 +11,8 @@ import { TextMacroAction } from 'uhk-common';
import { MacroBaseComponent } from '../macro-base.component'; import { MacroBaseComponent } from '../macro-base.component';
const NON_ASCII_REGEXP = /[^\x00-\x7F]/g;
@Component({ @Component({
selector: 'macro-text-tab', selector: 'macro-text-tab',
templateUrl: './macro-text.component.html', templateUrl: './macro-text.component.html',
@@ -36,6 +38,41 @@ export class MacroTextTabComponent extends MacroBaseComponent implements OnInit,
this.macroAction.text = this.input.nativeElement.value; this.macroAction.text = this.input.nativeElement.value;
} }
/**
* Not allow non ascii character
* @param $event
*/
onKeydown($event: KeyboardEvent): void {
if (new RegExp(NON_ASCII_REGEXP).test($event.key)) {
$event.preventDefault();
$event.stopPropagation();
}
}
/**
* Remove non ascii character from clipboard data
* @param $event
*/
onPaste($event: ClipboardEvent): void {
$event.preventDefault();
const textarea: HTMLTextAreaElement = this.input.nativeElement;
const data = $event.clipboardData.getData('text/plain');
const text = data && data.replace(NON_ASCII_REGEXP, '') || '';
if (text.length === 0) {
return;
}
const value = textarea.value || '';
const prefix = value.substr(0, textarea.selectionStart);
const end = textarea.selectionEnd;
const suffix = value.substr(textarea.selectionEnd);
textarea.value = prefix + text + suffix;
const correction = end === 0 ? 0 : 1;
textarea.selectionStart = textarea.selectionEnd = end + text.length - correction;
this.macroAction.text = textarea.value;
}
isMacroValid = () => !!this.input.nativeElement.value; isMacroValid = () => !!this.input.nativeElement.value;
private init = () => { private init = () => {

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

@@ -1,5 +1,6 @@
<div class="row list-container"> <div class="row list-container">
<div class="col-xs-10 col-xs-offset-1 list-group"> <div class="col-xs-10 col-xs-offset-1 list-group">
<p><i>Please note that macro playback is not implemented yet. You can create macros, but they won't have any effect until firmware support is implemented. We're working on this.</i></p>
<div class="macro-actions-container" [dragula]="'macroActions'" [dragulaModel]="macro.macroActions"> <div class="macro-actions-container" [dragula]="'macroActions'" [dragulaModel]="macro.macroActions">
<macro-item *ngFor="let macroAction of macro.macroActions; let macroActionIndex = index" <macro-item *ngFor="let macroAction of macro.macroActions; let macroActionIndex = index"
[macroAction]="macroAction" [macroAction]="macroAction"

View File

@@ -79,8 +79,8 @@ export class PopoverComponent implements OnChanges {
@Input() defaultKeyAction: KeyAction; @Input() defaultKeyAction: KeyAction;
@Input() currentKeymap: Keymap; @Input() currentKeymap: Keymap;
@Input() currentLayer: number; @Input() currentLayer: number;
@Input() keyPosition: ClientRect; @Input() keyPosition: any;
@Input() wrapPosition: ClientRect; @Input() wrapPosition: any;
@Input() visible: boolean; @Input() visible: boolean;
@Output() cancel = new EventEmitter<any>(); @Output() cancel = new EventEmitter<any>();

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. For example, on US keyboards next to Tab there is the Q key, but it's й on Russian keyboards, so in this case choose Q instead of й in Agent."
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];
@@ -115,7 +115,7 @@ export class KeypressTabComponent extends Tab implements OnChanges {
const scTypePair = this.toScancodeTypePair(this.selectedScancodeOption); const scTypePair = this.toScancodeTypePair(this.selectedScancodeOption);
keystrokeAction.scancode = scTypePair[0]; keystrokeAction.scancode = scTypePair[0];
if (scTypePair[1] === 'media') { if (scTypePair[1] === 'media') {
keystrokeAction.type = KeystrokeType.shortMedia; keystrokeAction.type = keystrokeAction.scancode > 255 ? KeystrokeType.longMedia : KeystrokeType.shortMedia;
} else { } else {
keystrokeAction.type = KeystrokeType[scTypePair[1]]; keystrokeAction.type = KeystrokeType[scTypePair[1]];
} }

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