34 Commits

Author SHA1 Message Date
Róbert Kiss
8c7d625573 build: set publisherName for win release 2018-11-15 22:33:22 +01:00
Róbert Kiss
52a57c0e87 build: upgrade electron-builder and remove unsupported appMetadata (#860) 2018-11-14 23:40:28 +01:00
Róbert Kiss
a52b34fc3f Revert "chore: rebuild node-hid to hide USB usage data (#853)" (#861)
This reverts commit 1a14ac020e.
2018-11-14 23:14:05 +01:00
Róbert Kiss
1a14ac020e chore: rebuild node-hid to hide USB usage data (#853) 2018-11-14 22:22:24 +01:00
László Monda
330f7e72be Bump Agent version to 1.2.12 and update changelog. 2018-11-14 19:57:35 +01:00
Róbert Kiss
1ed6669ced fix: compare available devices by vid, pid, interface on linux (#854) 2018-11-13 21:44:22 +01:00
Róbert Kiss
108d60a497 feat: show os specific modifiers for macro actions (#855) 2018-11-12 23:22:26 +01:00
Róbert Kiss
1a9bd7de83 feat: show udev rules on missing device screen (#842)
* feat: show udev rules on missing device screen

* delete MissingDeviceComponent

* feat: change privilege and message device screen texts

* feat: change message device screen texts

* fix: load config from device

* fix: load config when hasPermission = true

* fix: load app start info after set permission rules

* fix: action dispatch

* fix: load config from device when UdevRulesInfo.Ok

* fix: load config from device when 0 device available

* feat: add extra space between the "old udev rule" and permission button
2018-11-09 01:30:40 +01:00
Róbert Kiss
10cd06c70b feat: allow 8.4.3 firmware features (#845)
* feat: allow 8.4.3 firmware features

* feat: remove "layer-double-tap" command line argument
2018-11-05 19:51:39 +01:00
László Monda
84b6c33c54 Upgrade to firmware 8.5.3 2018-10-30 04:02:24 +01:00
Róbert Kiss
18808eae9c fix: user Renderer2 instead of deprecated Renderer (#844) 2018-10-30 03:06:44 +01:00
Róbert Kiss
ca74b0a76b feat: log USB device list before check permission (#837) 2018-10-22 21:32:16 +02:00
Róbert Kiss
b45d60efb2 feat: log USB device list before check permission (#837) 2018-10-22 07:34:14 +02:00
Richard Prillwitz
a4e3696078 chore: upgrade dependencies (#828)
* fixed vulnerabilities;

* update lerna from 3.1.4 to 3.2.0;

* fixed linter complaining about two empty lines in scss file;

* use fixed versions,
2018-10-16 19:17:12 +02:00
Róbert Kiss
2b963993d2 fix: stop event propagation when capturing keypress action (#817)
* fix: stop event propagation when capturing keypress action

* fix: save to keyboard shortcut allowed when keypress not capturing
2018-10-14 20:43:35 +02:00
László Monda
e333022043 Add international scancodes. Fixes #807. 2018-10-09 23:02:07 +02:00
Róbert Kiss
2e2a59ccb8 feat: remove Stop/Eject keypress action (#819)
* feat: remove Stop/Eject keypress action

* feat: remove WWW and Launch Web Browser keypress actions
2018-10-08 02:35:48 +02:00
Róbert Kiss
6f073ad718 feat: use icon for play/pause keypress action (#818)
* feat: use icon for play/pause keypress action

* fix: use rectangle layout in svg-single-icon-key
2018-10-08 02:32:02 +02:00
László Monda
166834e46c Update udev rules based on the instructions of https://github.com/node-hid/node-hid/releases/tag/v0.7.2 2018-10-08 00:22:14 +02:00
László Monda
0ff2364b9e Update from firmware 8.2.5 to 8.5.2 2018-10-08 00:00:08 +02:00
Róbert Kiss
2cbfc6a11e fix: ui break when press tab key in keymap short name control (#816) 2018-10-07 23:21:25 +02:00
Róbert Kiss
404ccc7b2b chore: use default electron menu on mac in development mode (#815) 2018-10-07 22:42:39 +02:00
Róbert Kiss
42e413ab65 chore: upgrade node-hid => 0.7.3 (#814) 2018-10-07 22:29:10 +02:00
Róbert Kiss
d57ef66038 feat: disable other popover tabs if the current key is layer switcher action (#813) 2018-10-07 22:14:36 +02:00
Róbert Kiss
843b4cbf68 feat: add "Edit" menu to MacOs build (#812) 2018-10-07 21:02:28 +02:00
Róbert Kiss
7e4b7c5c8b fix: macro action text overlap (#811) 2018-10-07 18:31:15 +02:00
László Monda
2ff65537a0 Update ISSUE_TEMPLATE 2018-10-06 20:28:13 +02:00
László Monda
6e2b1fb18d Add usbReportSemaphore to uhk.variableNametoId 2018-10-04 19:27:19 +02:00
László Monda
3e621a2818 Bump version to 1.2.11 and update package.json 2018-10-03 05:43:41 +02:00
Róbert Kiss
247ec4c1b2 feat: add backspace and caps lock icons (#803)
Summary:
- svg sprite generate with xmlns:xlink="http://www.w3.org/1999/xlink" namespace
- uhk-icon-agent-icon css class renamed to uhk-icon-pure-agent-icon because
  it collided with agent icon sprites
- added backspace and caps lock icons
2018-10-01 02:06:23 +02:00
Róbert Kiss
edcff069fd fix: write agent version into the log when upgrade firmware (#802) 2018-09-30 22:48:42 +02:00
László Monda
8afdeac306 Fix right and middle mouse click macro actions which were exchanged. Fixes #794. 2018-09-27 00:35:13 +02:00
László Monda
425f861451 Add issue template regarding Karabiner Elements. 2018-09-26 00:05:14 +02:00
László Monda
5a843ed02c Include Agent version to the firmware update log. 2018-09-25 15:48:27 +02:00
88 changed files with 4283 additions and 3367 deletions

View File

@@ -6,6 +6,34 @@ The format is loosely based on [Keep a Changelog](http://keepachangelog.com/en/1
Every Agent version includes the most recent firmware version. See the [firmware changelog](https://github.com/UltimateHackingKeyboard/firmware/blob/master/CHANGELOG.md). Every Agent version includes the most recent firmware version. See the [firmware changelog](https://github.com/UltimateHackingKeyboard/firmware/blob/master/CHANGELOG.md).
## [1.2.12] - 2018-11-14
Firmware: 8.**5.3** [[release](https://github.com/UltimateHackingKeyboard/firmware/releases/tag/v8.5.3)] | Device Protocol: 4.4.0 | User Config: 4.0.1 | Hardware Config: 1.0.0
- When the firmware of the right keyboard half is larger or equal than 8.4.3 then display the "Lock layer when double tapping this key" checkbox and remove "... macro playback is not implemented yet..." notices.
- Upgrade to node-hid 0.7.3 which utilizes the hidraw USB driver on Linux instead of libusb.
- Update udev rules for the new hidraw based node-hid.
- Improve the "Cannot find your UHK" and the privilege escalation screens to show more relevant messages when transitioning from the libusb based node-hid to the hidraw based node-hid.
- Fix the rendering of macro actions, so that their text doesn't overlap.
- Add "International {1,2,3}" and "Language {1,2}" keypress actions.
- Add icon for the Play/Pause keypress action.
- Remove the Stop/Eject keypress action.
- Make the "Type text" macro action accept clipboard data on Mac.
- Display "You can't change this mapping because on the base layer a layer switcher key targets this key." in the key action popover whenever it applies.
- Fix UI bug which could be triggered by tapping Tab in the keymap abbreviation input.
- Don't trigger Agent shortcuts when capturing keypresses.
- Log USB device list before checking permissions.
- Show OS-specific modifiers in the title bar of macro actions.
- Only show the device list on Linux when the list actually changes.
## [1.2.11] - 2018-10-03
Firmware: 8.2.5 [[release](https://github.com/UltimateHackingKeyboard/firmware/releases/tag/v8.2.5)] | Device Protocol: 4.4.0 | User Config: 4.0.1 | Hardware Config: 1.0.0
- Add backspace and caps lock icons which avoids the overlap of their old texts.
- Fix right and middle mouse click macro actions which were exchanged.
- Include Agent version to the firmware update log.
## [1.2.10] - 2018-09-24 ## [1.2.10] - 2018-09-24
Firmware: 8.2.5 [[release](https://github.com/UltimateHackingKeyboard/firmware/releases/tag/v8.2.5)] | Device Protocol: 4.4.0 | User Config: 4.0.1 | Hardware Config: 1.0.0 Firmware: 8.2.5 [[release](https://github.com/UltimateHackingKeyboard/firmware/releases/tag/v8.2.5)] | Device Protocol: 4.4.0 | User Config: 4.0.1 | Hardware Config: 1.0.0

8
ISSUE_TEMPLATE Normal file
View File

@@ -0,0 +1,8 @@
Before submitting a new issue, make sure to do the following:
1. If you're using Karabiner Elements on your Mac, close it!
2. Install the latest Agent:
https://github.com/UltimateHackingKeyboard/agent/releases/latest
3. Use Agent to update to the latest firmware:
https://github.com/UltimateHackingKeyboard/firmware/releases/latest
4. Try to reproduce the issue, and only report it if it still persists.

5109
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -3,8 +3,8 @@
"private": true, "private": true,
"author": "Ultimate Gadget Laboratories", "author": "Ultimate Gadget Laboratories",
"main": "electron/dist/electron-main.js", "main": "electron/dist/electron-main.js",
"version": "1.2.10", "version": "1.2.12",
"firmwareVersion": "8.2.5", "firmwareVersion": "8.5.3",
"deviceProtocolVersion": "4.4.0", "deviceProtocolVersion": "4.4.0",
"userConfigVersion": "4.0.1", "userConfigVersion": "4.0.1",
"hardwareConfigVersion": "1.0.0", "hardwareConfigVersion": "1.0.0",
@@ -27,8 +27,9 @@
"@types/jsonfile": "4.0.1", "@types/jsonfile": "4.0.1",
"@types/lodash-es": "4.17.0", "@types/lodash-es": "4.17.0",
"@types/node": "8.0.53", "@types/node": "8.0.53",
"@types/node-hid": "0.5.2", "@types/node-hid": "0.7.0",
"@types/request": "2.0.8", "@types/request": "2.0.8",
"@types/semver": "5.5.0",
"@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",
@@ -41,7 +42,7 @@
"decompress-tarbz2": "4.1.1", "decompress-tarbz2": "4.1.1",
"devtron": "1.4.0", "devtron": "1.4.0",
"electron": "1.8.7", "electron": "1.8.7",
"electron-builder": "20.8.1", "electron-builder": "20.34.0",
"electron-debug": "1.5.0", "electron-debug": "1.5.0",
"electron-devtools-installer": "2.2.3", "electron-devtools-installer": "2.2.3",
"electron-log": "2.2.16", "electron-log": "2.2.16",
@@ -57,17 +58,17 @@
"jasmine-node": "2.0.1", "jasmine-node": "2.0.1",
"jasmine-ts": "0.2.1", "jasmine-ts": "0.2.1",
"jsonfile": "4.0.0", "jsonfile": "4.0.0",
"lerna": "3.1.4", "lerna": "3.2.0",
"lodash-es": "4.17.4", "lodash-es": "4.17.4",
"mkdirp": "0.5.1", "mkdirp": "0.5.1",
"node-hid": "0.5.7", "node-hid": "0.7.3",
"npm-run-all": "4.0.2", "npm-run-all": "4.0.2",
"pre-commit": "1.2.2", "pre-commit": "1.2.2",
"request": "2.88.0", "request": "2.88.0",
"rimraf": "2.6.1", "rimraf": "2.6.1",
"standard-version": "4.2.0", "standard-version": "4.4.0",
"stylelint": "9.5.0", "stylelint": "9.6.0",
"svg-sprite": "1.4.0", "svg-sprite": "1.5.0",
"ts-loader": "2.3.1", "ts-loader": "2.3.1",
"ts-node": "7.0.1", "ts-node": "7.0.1",
"tslint": "5.9.1", "tslint": "5.9.1",

View File

@@ -36,9 +36,9 @@
"integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==" "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw=="
}, },
"are-we-there-yet": { "are-we-there-yet": {
"version": "1.1.4", "version": "1.1.5",
"resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.4.tgz", "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz",
"integrity": "sha1-u13KOCu5TwXhUZQ3PRb9O6HKEQ0=", "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==",
"requires": { "requires": {
"delegates": "^1.0.0", "delegates": "^1.0.0",
"readable-stream": "^2.0.6" "readable-stream": "^2.0.6"
@@ -87,9 +87,9 @@
"integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=" "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI="
}, },
"chownr": { "chownr": {
"version": "1.0.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/chownr/-/chownr-1.0.1.tgz", "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.1.tgz",
"integrity": "sha1-4qdQQqlVGQi+vSW4Uj1fl2nXkYE=" "integrity": "sha512-j38EvO5+LHX84jlo6h4UzmOwi0UgW61WRyPtJz4qaadK5eY3BTS5TY/S1Stc3Uk2lIM6TPevAlULiEJwie860g=="
}, },
"code-point-at": { "code-point-at": {
"version": "1.1.0", "version": "1.1.0",
@@ -221,9 +221,9 @@
} }
}, },
"deep-extend": { "deep-extend": {
"version": "0.4.2", "version": "0.6.0",
"resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.4.2.tgz", "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz",
"integrity": "sha1-SLaZwn4zS/ifEIkr5DL25MfTSn8=" "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA=="
}, },
"delegates": { "delegates": {
"version": "1.0.0", "version": "1.0.0",
@@ -244,9 +244,9 @@
} }
}, },
"expand-template": { "expand-template": {
"version": "1.1.0", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/expand-template/-/expand-template-1.1.0.tgz", "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-1.1.1.tgz",
"integrity": "sha512-kkjwkMqj0h4w/sb32ERCDxCQkREMCAgS39DscDnSwDsbxnwwM1BTZySdC3Bn1lhY7vL08n9GoO/fVTynjDgRyQ==" "integrity": "sha512-cebqLtV8KOZfw0UI8TEFWxtczxxC1jvyUvx6H4fyp1K1FN7A4Q+uggVUlOsI1K8AGU0rwOGqP8nCapdrw8CYQg=="
}, },
"fd-slicer": { "fd-slicer": {
"version": "1.0.1", "version": "1.0.1",
@@ -378,18 +378,18 @@
} }
}, },
"mimic-response": { "mimic-response": {
"version": "1.0.0", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.0.tgz", "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz",
"integrity": "sha1-3z02Uqc/3ta5sLJBRub9BSNTRY4=" "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ=="
}, },
"minimist": { "minimist": {
"version": "1.2.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
"integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ="
}, },
"mkdirp": { "mkdirp": {
"version": "0.5.1", "version": "0.5.1",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", "resolved": "http://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
"integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
"requires": { "requires": {
"minimist": "0.0.8" "minimist": "0.0.8"
@@ -397,32 +397,32 @@
"dependencies": { "dependencies": {
"minimist": { "minimist": {
"version": "0.0.8", "version": "0.0.8",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", "resolved": "http://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
"integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0="
} }
} }
}, },
"nan": { "nan": {
"version": "2.10.0", "version": "2.11.1",
"resolved": "https://registry.npmjs.org/nan/-/nan-2.10.0.tgz", "resolved": "https://registry.npmjs.org/nan/-/nan-2.11.1.tgz",
"integrity": "sha512-bAdJv7fBLhWC+/Bls0Oza+mvTaNQtP+1RyhhhvD95pgUJz6XM5IzgmxOkItJ9tkoCiplvAnXI1tNmmUD/eScyA==" "integrity": "sha512-iji6k87OSXa0CcrLl9z+ZiYSuR2o+c0bGuNmXdrhTQTakxytAFsC56SArGYoiHlJlFoHSnvmhpceZJaXkVuOtA=="
}, },
"node-abi": { "node-abi": {
"version": "2.3.0", "version": "2.4.5",
"resolved": "https://registry.npmjs.org/node-abi/-/node-abi-2.3.0.tgz", "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-2.4.5.tgz",
"integrity": "sha512-zwm6vU3SsVgw3e9fu48JBaRBCJGIvAgysDsqtf5+vEexFE71bEOtaMWb5zr/zODZNzTPtQlqUUpC79k68Hspow==", "integrity": "sha512-aa/UC6Nr3+tqhHGRsAuw/edz7/q9nnetBrKWxj6rpTtm+0X9T1qU7lIEHMS3yN9JwAbRiKUbRRFy1PLz/y3aaA==",
"requires": { "requires": {
"semver": "^5.4.1" "semver": "^5.4.1"
} }
}, },
"node-hid": { "node-hid": {
"version": "0.5.7", "version": "0.7.3",
"resolved": "https://registry.npmjs.org/node-hid/-/node-hid-0.5.7.tgz", "resolved": "https://registry.npmjs.org/node-hid/-/node-hid-0.7.3.tgz",
"integrity": "sha512-dwwpOetL2+MGYgivbO22ML+45ieCGbueWv1rYxRgBoEc2QMp6UF6ZucEkYts1IA3YPWJNkmpGh6dqQ85n19szw==", "integrity": "sha512-LOCqWqcOlng+Kn1Qj/54zrPVfCagg1O7RlSgMmugykBcoYvUud6BswTrJM2aXuBac+bCCm3lA2srRG8YfmyXZQ==",
"requires": { "requires": {
"bindings": "^1.3.0", "bindings": "^1.3.0",
"nan": "^2.6.2", "nan": "^2.10.0",
"prebuild-install": "^2.2.2" "prebuild-install": "^4.0.0"
} }
}, },
"noop-logger": { "noop-logger": {
@@ -493,9 +493,9 @@
} }
}, },
"prebuild-install": { "prebuild-install": {
"version": "2.5.1", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-2.5.1.tgz", "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-4.0.0.tgz",
"integrity": "sha512-3DX9L6pzwc1m1ksMkW3Ky2WLgPQUBiySOfXVl3WZyAeJSyJb4wtoH9OmeRGcubAWsMlLiL8BTHbwfm/jPQE9Ag==", "integrity": "sha512-7tayxeYboJX0RbVzdnKyGl2vhQRWr6qfClEXDhOkXjuaOKCw2q8aiuFhONRYVsG/czia7KhpykIlI2S2VaPunA==",
"requires": { "requires": {
"detect-libc": "^1.0.3", "detect-libc": "^1.0.3",
"expand-template": "^1.0.2", "expand-template": "^1.0.2",
@@ -529,11 +529,11 @@
} }
}, },
"rc": { "rc": {
"version": "1.2.6", "version": "1.2.8",
"resolved": "https://registry.npmjs.org/rc/-/rc-1.2.6.tgz", "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz",
"integrity": "sha1-6xiYnG1PTxYsOZ953dKfODVWgJI=", "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==",
"requires": { "requires": {
"deep-extend": "~0.4.0", "deep-extend": "^0.6.0",
"ini": "~1.3.0", "ini": "~1.3.0",
"minimist": "^1.2.0", "minimist": "^1.2.0",
"strip-json-comments": "~2.0.1" "strip-json-comments": "~2.0.1"
@@ -567,9 +567,9 @@
} }
}, },
"semver": { "semver": {
"version": "5.5.0", "version": "5.5.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.1.tgz",
"integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==" "integrity": "sha512-PqpAxfrEhlSUWge8dwIp4tZnQ25DIOthpiaHNIthsjEFQD6EvqUKUDM7L8O2rShkFccYo1VjJR0coWfNkCubRw=="
}, },
"set-blocking": { "set-blocking": {
"version": "2.0.0", "version": "2.0.0",
@@ -587,9 +587,9 @@
"integrity": "sha1-c0TLuLbib7J9ZrL8hvn21Zl1IcY=" "integrity": "sha1-c0TLuLbib7J9ZrL8hvn21Zl1IcY="
}, },
"simple-get": { "simple-get": {
"version": "2.7.0", "version": "2.8.1",
"resolved": "https://registry.npmjs.org/simple-get/-/simple-get-2.7.0.tgz", "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-2.8.1.tgz",
"integrity": "sha512-RkE9rGPHcxYZ/baYmgJtOSM63vH0Vyq+ma5TijBcLla41SWlh8t6XYIGMR/oeZcmr+/G8k+zrClkkVrtnQ0esg==", "integrity": "sha512-lSSHRSw3mQNUGPAYRqo7xy9dhKmxFXIjLjp4KHpf99GEH2VH7C3AM+Qfx6du6jhfUi6Vm7XnbEVEf7Wb6N8jRw==",
"requires": { "requires": {
"decompress-response": "^3.3.0", "decompress-response": "^3.3.0",
"once": "^1.3.1", "once": "^1.3.1",
@@ -616,7 +616,7 @@
}, },
"strip-ansi": { "strip-ansi": {
"version": "3.0.1", "version": "3.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", "resolved": "http://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
"integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
"requires": { "requires": {
"ansi-regex": "^2.0.0" "ansi-regex": "^2.0.0"
@@ -641,9 +641,9 @@
"integrity": "sha1-2kFbc+BeKgf7/lQEAb/8eQxPfdk=" "integrity": "sha1-2kFbc+BeKgf7/lQEAb/8eQxPfdk="
}, },
"tar-fs": { "tar-fs": {
"version": "1.16.0", "version": "1.16.3",
"resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-1.16.0.tgz", "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-1.16.3.tgz",
"integrity": "sha512-I9rb6v7mjWLtOfCau9eH5L7sLJyU2BnxtEZRQ5Mt+eRKmf1F0ohXmT/Jc3fr52kDvjJ/HV5MH3soQfPL5bQ0Yg==", "integrity": "sha512-NvCeXpYx7OsmOh8zIOP/ebG55zZmxLE0etfWRbWok+q2Qo8x/vOR/IJT1taADXPe+jsiu9axDb3X4B+iIgNlKw==",
"requires": { "requires": {
"chownr": "^1.0.1", "chownr": "^1.0.1",
"mkdirp": "^0.5.1", "mkdirp": "^0.5.1",
@@ -738,11 +738,11 @@
"integrity": "sha1-Zws6+8VS4LVd9rd4DKdGFfI60cs=" "integrity": "sha1-Zws6+8VS4LVd9rd4DKdGFfI60cs="
}, },
"wide-align": { "wide-align": {
"version": "1.1.2", "version": "1.1.3",
"resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.2.tgz", "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz",
"integrity": "sha512-ijDLlyQ7s6x1JgCLur53osjm/UXUYD9+0PbYKrBsYisYXzCxN+HC3mYDNy/dWdmf3AwqwU3CXwDCvsNgGK1S0w==", "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==",
"requires": { "requires": {
"string-width": "^1.0.2" "string-width": "^1.0.2 || 2"
} }
}, },
"wrappy": { "wrappy": {

View File

@@ -17,7 +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",
"node-hid": "0.5.7", "node-hid": "0.7.3",
"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",
@@ -31,10 +31,10 @@
"scripts": { "scripts": {
"start": "electron ./dist/electron-main.js", "start": "electron ./dist/electron-main.js",
"electron:spe": "electron ./dist/electron-main.js --spe", "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-to-tmp-folder",
"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",
"download-firmware": "node ../../scripts/download-firmware.js", "download-firmware": "node ../../scripts/download-firmware.js",
"copy-blhost": "node ../../scripts/copy-blhost.js" "copy-to-tmp-folder": "node ../../scripts/copy-to-tmp-folder.js"
} }
} }

View File

@@ -18,12 +18,11 @@ 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 { setMenu } from './electron-menu';
const optionDefinitions = [ const optionDefinitions = [
{name: 'addons', type: Boolean}, {name: 'addons', type: Boolean},
{name: 'spe', type: Boolean}, // simulate privilege escalation error {name: 'spe', type: Boolean} // simulate privilege escalation error
// show 'Lock layer when double tapping this key' checkbox on 'Layer' tab of the config popover
{name: 'layer-double-tap', type: Boolean}
]; ];
const options: CommandLineArgs = commandLineArgs(optionDefinitions); const options: CommandLineArgs = commandLineArgs(optionDefinitions);
@@ -101,9 +100,9 @@ function createWindow() {
}, },
icon: path.join(__dirname, 'renderer/assets/images/agent-app-icon.png') icon: path.join(__dirname, 'renderer/assets/images/agent-app-icon.png')
}); });
win.setMenuBarVisibility(false); setMenu(win);
win.maximize(); win.maximize();
uhkHidDeviceService = new UhkHidDevice(logger, options); uhkHidDeviceService = new UhkHidDevice(logger, options, packagesDir);
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, packagesDir); deviceService = new DeviceService(logger, win, uhkHidDeviceService, uhkOperations, packagesDir);

View File

@@ -0,0 +1,37 @@
import { app, BrowserWindow, Menu, systemPreferences } from 'electron';
import * as isDev from 'electron-is-dev';
export const setMenu = (win: BrowserWindow): void => {
if (process.platform !== 'darwin' || isDev) {
win.setMenuBarVisibility(false);
return;
}
const template = [
{
label: app.getName(),
submenu: [
{role: 'quit'}
]
},
{
label: 'Edit',
submenu: [
{role: 'cut'},
{role: 'copy'},
{role: 'paste'},
{role: 'delete'},
{role: 'selectall'}
]
}
];
// hide "Start Dictation" submenu item in Edit menu
systemPreferences.setUserDefault('NSDisabledDictationMenuItem', 'boolean', true as any);
// hide "Emoji & Symbols" submenu item in Edit menu
systemPreferences.setUserDefault('NSDisabledCharacterPaletteMenuItem', 'boolean', false as any);
const menu = Menu.buildFromTemplate(template);
Menu.setApplicationMenu(menu);
};

View File

@@ -14,6 +14,6 @@
"npm": ">=5.1.0 <6.0.0" "npm": ">=5.1.0 <6.0.0"
}, },
"dependencies": { "dependencies": {
"node-hid": "0.5.7" "node-hid": "0.7.3"
} }
} }

View File

@@ -23,15 +23,12 @@ 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 deviceConnectionState = await this.uhkHidDeviceService.getDeviceConnectionStateAsync();
const response: AppStartInfo = { const response: AppStartInfo = {
deviceConnectionState,
commandLineArgs: { commandLineArgs: {
addons: this.options.addons || false, addons: this.options.addons || false
layerDoubleTap: this.options['layer-double-tap'] || false
}, },
deviceConnected: deviceConnectionState.connected,
hasPermission: deviceConnectionState.hasPermission,
bootloaderActive: deviceConnectionState.bootloaderActive,
platform: process.platform as string, platform: process.platform as string,
osVersion: os.release() osVersion: os.release()
}; };

View File

@@ -1,4 +1,5 @@
import { ipcMain } from 'electron'; import { ipcMain } from 'electron';
import { isEqual } from 'lodash';
import { import {
ConfigurationReply, ConfigurationReply,
DeviceConnectionState, DeviceConnectionState,
@@ -9,17 +10,19 @@ import {
IpcResponse, IpcResponse,
LogService, LogService,
mapObjectToUserConfigBinaryBuffer, mapObjectToUserConfigBinaryBuffer,
SaveUserConfigurationData SaveUserConfigurationData,
UpdateFirmwareData
} from 'uhk-common'; } from 'uhk-common';
import { deviceConnectionStateComparer, snooze, UhkHidDevice, UhkOperations } from 'uhk-usb'; import { snooze, UhkHidDevice, UhkOperations } from 'uhk-usb';
import { Observable } from 'rxjs/Observable'; import { Observable } from 'rxjs/Observable';
import { Subscription } from 'rxjs/Subscription'; import { Subscription } from 'rxjs/Subscription';
import { emptyDir } from 'fs-extra'; import { emptyDir } from 'fs-extra';
import * as path from 'path'; import * as path from 'path';
import 'rxjs/add/observable/interval'; import 'rxjs/add/observable/interval';
import 'rxjs/add/observable/fromPromise';
import 'rxjs/add/operator/switchMap';
import 'rxjs/add/operator/startWith'; import 'rxjs/add/operator/startWith';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/do'; import 'rxjs/add/operator/do';
import 'rxjs/add/operator/distinctUntilChanged'; import 'rxjs/add/operator/distinctUntilChanged';
@@ -157,10 +160,12 @@ export class DeviceService {
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 FirmwareUpgradeIpcResponse(); const response = new FirmwareUpgradeIpcResponse();
const data: UpdateFirmwareData = JSON.parse(args[0]);
let firmwarePathData: TmpFirmware; let firmwarePathData: TmpFirmware;
try { try {
this.logService.debug('Agent version:', data.versionInformation.version);
const hardwareModules = await this.getHardwareModules(false); const hardwareModules = await this.getHardwareModules(false);
this.logService.debug('Device right firmware version:', hardwareModules.rightModuleInfo.firmwareVersion); this.logService.debug('Device right firmware version:', hardwareModules.rightModuleInfo.firmwareVersion);
this.logService.debug('Device left firmware version:', hardwareModules.leftModuleInfo.firmwareVersion); this.logService.debug('Device left firmware version:', hardwareModules.leftModuleInfo.firmwareVersion);
@@ -168,8 +173,8 @@ export class DeviceService {
this.device.resetDeviceCache(); this.device.resetDeviceCache();
this.stopPollTimer(); this.stopPollTimer();
if (args && args.length > 0) { if (data.firmware) {
firmwarePathData = await saveTmpFirmware(args[0]); firmwarePathData = await saveTmpFirmware(data.firmware);
const packageJson = await getPackageJsonFromPathAsync(firmwarePathData.packageJsonPath); const packageJson = await getPackageJsonFromPathAsync(firmwarePathData.packageJsonPath);
this.logService.debug('New firmware version:', packageJson.firmwareVersion); this.logService.debug('New firmware version:', packageJson.firmwareVersion);
@@ -250,8 +255,8 @@ export class DeviceService {
this.pollTimer$ = Observable.interval(1000) this.pollTimer$ = Observable.interval(1000)
.startWith(0) .startWith(0)
.map(() => this.device.getDeviceConnectionState()) .switchMap(() => Observable.fromPromise(this.device.getDeviceConnectionStateAsync()))
.distinctUntilChanged<DeviceConnectionState>(deviceConnectionStateComparer) .distinctUntilChanged<DeviceConnectionState>(isEqual)
.do((state: DeviceConnectionState) => { .do((state: DeviceConnectionState) => {
this.win.webContents.send(IpcEvents.device.deviceConnectionStateChanged, state); this.win.webContents.send(IpcEvents.device.deviceConnectionStateChanged, state);
this.logService.info('[DeviceService] Device connection state changed to:', state); this.logService.info('[DeviceService] Device connection state changed to:', state);

View File

@@ -6,7 +6,7 @@ import * as decompressTarbz from 'decompress-tarbz2';
import { TmpFirmware } from '../models/tmp-firmware'; import { TmpFirmware } from '../models/tmp-firmware';
export async function saveTmpFirmware(data: string): Promise<TmpFirmware> { export async function saveTmpFirmware(data: Array<number>): Promise<TmpFirmware> {
const tmpDirectory = dirSync(); const tmpDirectory = dirSync();
const zipFilePath = path.join(tmpDirectory.name, 'firmware.bz2'); const zipFilePath = path.join(tmpDirectory.name, 'firmware.bz2');
@@ -21,10 +21,9 @@ export async function saveTmpFirmware(data: string): Promise<TmpFirmware> {
}; };
} }
function writeDataToFile(data: string, filePath: string): Promise<void> { function writeDataToFile(data: Array<number>, filePath: string): Promise<void> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const array: Array<number> = JSON.parse(data); const buffer = new Buffer(data);
const buffer = new Buffer(array);
fs.writeFile(filePath, buffer, err => { fs.writeFile(filePath, buffer, err => {
if (err) { if (err) {

View File

@@ -3,9 +3,9 @@ import { UhkBuffer } from '../../uhk-buffer';
import { MacroAction, MacroActionId, MacroMouseSubAction, macroActionType } from './macro-action'; import { MacroAction, MacroActionId, MacroMouseSubAction, macroActionType } from './macro-action';
export enum MouseButtons { export enum MouseButtons {
Left = 1 << 0, Left = 0,
Middle = 1 << 1, Right = 1,
Right = 1 << 2 Middle = 2
} }
export interface JsObjectMouseButtonMacroAction { export interface JsObjectMouseButtonMacroAction {

View File

@@ -454,14 +454,6 @@
"scancode": 182 "scancode": 182
} }
}, },
{
"id": "132",
"text": "Stop/Eject",
"additional": {
"type": "media",
"scancode": 204
}
},
{ {
"id": "133", "id": "133",
"text": "Play/Pause", "text": "Play/Pause",
@@ -502,14 +494,6 @@
"scancode": 184 "scancode": 184
} }
}, },
{
"id": "138",
"text": "WWW",
"additional": {
"type": "media",
"scancode": 138
}
},
{ {
"id": "145", "id": "145",
"text": "History Back", "text": "History Back",
@@ -531,14 +515,6 @@
{ {
"text": "Launch application", "text": "Launch application",
"children": [ "children": [
{
"id": "142",
"text": "Launch Web Browser",
"additional": {
"type": "media",
"scancode": 406
}
},
{ {
"id": "143", "id": "143",
"text": "Launch Email Client", "text": "Launch Email Client",
@@ -703,5 +679,50 @@
"text": "." "text": "."
} }
] ]
},
{
"text": "International",
"children": [
{
"id": "235",
"text": "International 1",
"additional": {
"type": "basic",
"scancode": 135
}
},
{
"id": "236",
"text": "International 2",
"additional": {
"type": "basic",
"scancode": 136
}
},
{
"id": "237",
"text": "International 3",
"additional": {
"type": "basic",
"scancode": 137
}
},
{
"id": "244",
"text": "Language 1",
"additional": {
"type": "basic",
"scancode": 144
}
},
{
"id": "245",
"text": "Language 2",
"additional": {
"type": "basic",
"scancode": 145
}
}
]
} }
] ]

View File

@@ -4,8 +4,9 @@ import { Keymap } from './keymap';
import { Macro } from './macro'; import { Macro } from './macro';
import { ModuleConfiguration } from './module-configuration'; import { ModuleConfiguration } from './module-configuration';
import { ConfigSerializer } from '../config-serializer'; import { ConfigSerializer } from '../config-serializer';
import { KeystrokeAction } from './key-action'; import { KeystrokeAction, NoneAction } from './key-action';
import { SecondaryRoleAction } from './secondary-role-action'; import { SecondaryRoleAction } from './secondary-role-action';
import { isScancodeExists } from './scancode-checker';
export class UserConfiguration { export class UserConfiguration {
@@ -249,6 +250,10 @@ export class UserConfiguration {
keyAction.secondaryRoleAction === SecondaryRoleAction.mouse) { keyAction.secondaryRoleAction === SecondaryRoleAction.mouse) {
(keyAction as any)._secondaryRoleAction = undefined; (keyAction as any)._secondaryRoleAction = undefined;
} }
if (keyAction.hasScancode() && !isScancodeExists(keyAction.scancode)) {
module.keyActions[keyActionId] = new NoneAction();
}
} }
} }
} }

View File

@@ -1,10 +1,9 @@
import { CommandLineArgs } from './command-line-args'; import { CommandLineArgs } from './command-line-args';
import { DeviceConnectionState } from './device-connection-state';
export interface AppStartInfo { export interface AppStartInfo {
commandLineArgs: CommandLineArgs; commandLineArgs: CommandLineArgs;
deviceConnected: boolean; deviceConnectionState: DeviceConnectionState;
hasPermission: boolean;
bootloaderActive: boolean;
platform: string; platform: string;
osVersion: string; osVersion: string;
} }

View File

@@ -7,9 +7,4 @@ export interface CommandLineArgs {
* simulate privilege escalation error * simulate privilege escalation error
*/ */
spe?: boolean; spe?: boolean;
/**
* show 'Lock layer when double tapping this key' checkbox on 'Layer' tab of the config popover
* if it false the checkbox invisible and the value of the checkbox = true
*/
layerDoubleTap?: boolean;
} }

View File

@@ -1,5 +1,9 @@
import { UdevRulesInfo } from './udev-rules-info';
export interface DeviceConnectionState { export interface DeviceConnectionState {
connected: boolean; connected: boolean;
hasPermission: boolean; hasPermission: boolean;
bootloaderActive: boolean; bootloaderActive: boolean;
zeroInterfaceAvailable: boolean;
udevRulesInfo: UdevRulesInfo;
} }

View File

@@ -8,3 +8,5 @@ export * from './device-connection-state';
export * from './hardware-modules'; export * from './hardware-modules';
export * from './hardware-module-info'; export * from './hardware-module-info';
export * from './save-user-configuration-data'; export * from './save-user-configuration-data';
export * from './udev-rules-info';
export * from './update-firmware-data';

View File

@@ -0,0 +1,16 @@
/**
* What is the state of the udev rules.
* Only on Linux need extra udev rules.
*/
export enum UdevRulesInfo {
Unkonwn,
Ok,
/**
* Udev rules not exists need to setup on Linux
*/
NeedToSetup,
/**
* Udev rules exist but different than expected on Linux
*/
Different
}

View File

@@ -0,0 +1,6 @@
import { VersionInformation } from './version-information';
export interface UpdateFirmwareData {
versionInformation: VersionInformation;
firmware?: Array<number>;
}

View File

@@ -21,9 +21,9 @@
"integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==" "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw=="
}, },
"are-we-there-yet": { "are-we-there-yet": {
"version": "1.1.4", "version": "1.1.5",
"resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.4.tgz", "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz",
"integrity": "sha1-u13KOCu5TwXhUZQ3PRb9O6HKEQ0=", "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==",
"requires": { "requires": {
"delegates": "^1.0.0", "delegates": "^1.0.0",
"readable-stream": "^2.0.6" "readable-stream": "^2.0.6"
@@ -36,17 +36,36 @@
}, },
"bl": { "bl": {
"version": "1.2.2", "version": "1.2.2",
"resolved": "https://registry.npmjs.org/bl/-/bl-1.2.2.tgz", "resolved": "http://registry.npmjs.org/bl/-/bl-1.2.2.tgz",
"integrity": "sha512-e8tQYnZodmebYDWGH7KMRvtzKXaJHx3BbilrgZCfvyLUYdKpK1t5PSPmpkny/SgiTSCnjfLW7v5rlONXVFkQEA==", "integrity": "sha512-e8tQYnZodmebYDWGH7KMRvtzKXaJHx3BbilrgZCfvyLUYdKpK1t5PSPmpkny/SgiTSCnjfLW7v5rlONXVFkQEA==",
"requires": { "requires": {
"readable-stream": "^2.3.5", "readable-stream": "^2.3.5",
"safe-buffer": "^5.1.1" "safe-buffer": "^5.1.1"
} }
}, },
"buffer-alloc": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz",
"integrity": "sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==",
"requires": {
"buffer-alloc-unsafe": "^1.1.0",
"buffer-fill": "^1.0.0"
}
},
"buffer-alloc-unsafe": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz",
"integrity": "sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg=="
},
"buffer-fill": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz",
"integrity": "sha1-+PeLdniYiO858gXNY39o5wISKyw="
},
"chownr": { "chownr": {
"version": "1.0.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/chownr/-/chownr-1.0.1.tgz", "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.1.tgz",
"integrity": "sha1-4qdQQqlVGQi+vSW4Uj1fl2nXkYE=" "integrity": "sha512-j38EvO5+LHX84jlo6h4UzmOwi0UgW61WRyPtJz4qaadK5eY3BTS5TY/S1Stc3Uk2lIM6TPevAlULiEJwie860g=="
}, },
"code-point-at": { "code-point-at": {
"version": "1.1.0", "version": "1.1.0",
@@ -72,9 +91,9 @@
} }
}, },
"deep-extend": { "deep-extend": {
"version": "0.4.2", "version": "0.6.0",
"resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.4.2.tgz", "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz",
"integrity": "sha1-SLaZwn4zS/ifEIkr5DL25MfTSn8=" "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA=="
}, },
"delegates": { "delegates": {
"version": "1.0.0", "version": "1.0.0",
@@ -95,9 +114,14 @@
} }
}, },
"expand-template": { "expand-template": {
"version": "1.1.0", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/expand-template/-/expand-template-1.1.0.tgz", "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-1.1.1.tgz",
"integrity": "sha512-kkjwkMqj0h4w/sb32ERCDxCQkREMCAgS39DscDnSwDsbxnwwM1BTZySdC3Bn1lhY7vL08n9GoO/fVTynjDgRyQ==" "integrity": "sha512-cebqLtV8KOZfw0UI8TEFWxtczxxC1jvyUvx6H4fyp1K1FN7A4Q+uggVUlOsI1K8AGU0rwOGqP8nCapdrw8CYQg=="
},
"fs-constants": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
"integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow=="
}, },
"gauge": { "gauge": {
"version": "2.7.4", "version": "2.7.4",
@@ -148,18 +172,18 @@
"integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
}, },
"mimic-response": { "mimic-response": {
"version": "1.0.0", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.0.tgz", "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz",
"integrity": "sha1-3z02Uqc/3ta5sLJBRub9BSNTRY4=" "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ=="
}, },
"minimist": { "minimist": {
"version": "1.2.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
"integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ="
}, },
"mkdirp": { "mkdirp": {
"version": "0.5.1", "version": "0.5.1",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", "resolved": "http://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
"integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
"requires": { "requires": {
"minimist": "0.0.8" "minimist": "0.0.8"
@@ -167,32 +191,32 @@
"dependencies": { "dependencies": {
"minimist": { "minimist": {
"version": "0.0.8", "version": "0.0.8",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", "resolved": "http://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
"integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0="
} }
} }
}, },
"nan": { "nan": {
"version": "2.10.0", "version": "2.11.1",
"resolved": "https://registry.npmjs.org/nan/-/nan-2.10.0.tgz", "resolved": "https://registry.npmjs.org/nan/-/nan-2.11.1.tgz",
"integrity": "sha512-bAdJv7fBLhWC+/Bls0Oza+mvTaNQtP+1RyhhhvD95pgUJz6XM5IzgmxOkItJ9tkoCiplvAnXI1tNmmUD/eScyA==" "integrity": "sha512-iji6k87OSXa0CcrLl9z+ZiYSuR2o+c0bGuNmXdrhTQTakxytAFsC56SArGYoiHlJlFoHSnvmhpceZJaXkVuOtA=="
}, },
"node-abi": { "node-abi": {
"version": "2.3.0", "version": "2.4.5",
"resolved": "https://registry.npmjs.org/node-abi/-/node-abi-2.3.0.tgz", "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-2.4.5.tgz",
"integrity": "sha512-zwm6vU3SsVgw3e9fu48JBaRBCJGIvAgysDsqtf5+vEexFE71bEOtaMWb5zr/zODZNzTPtQlqUUpC79k68Hspow==", "integrity": "sha512-aa/UC6Nr3+tqhHGRsAuw/edz7/q9nnetBrKWxj6rpTtm+0X9T1qU7lIEHMS3yN9JwAbRiKUbRRFy1PLz/y3aaA==",
"requires": { "requires": {
"semver": "^5.4.1" "semver": "^5.4.1"
} }
}, },
"node-hid": { "node-hid": {
"version": "0.5.7", "version": "0.7.3",
"resolved": "https://registry.npmjs.org/node-hid/-/node-hid-0.5.7.tgz", "resolved": "https://registry.npmjs.org/node-hid/-/node-hid-0.7.3.tgz",
"integrity": "sha512-dwwpOetL2+MGYgivbO22ML+45ieCGbueWv1rYxRgBoEc2QMp6UF6ZucEkYts1IA3YPWJNkmpGh6dqQ85n19szw==", "integrity": "sha512-LOCqWqcOlng+Kn1Qj/54zrPVfCagg1O7RlSgMmugykBcoYvUud6BswTrJM2aXuBac+bCCm3lA2srRG8YfmyXZQ==",
"requires": { "requires": {
"bindings": "^1.3.0", "bindings": "^1.3.0",
"nan": "^2.6.2", "nan": "^2.10.0",
"prebuild-install": "^2.2.2" "prebuild-install": "^4.0.0"
} }
}, },
"noop-logger": { "noop-logger": {
@@ -235,9 +259,9 @@
"integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=" "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M="
}, },
"prebuild-install": { "prebuild-install": {
"version": "2.5.1", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-2.5.1.tgz", "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-4.0.0.tgz",
"integrity": "sha512-3DX9L6pzwc1m1ksMkW3Ky2WLgPQUBiySOfXVl3WZyAeJSyJb4wtoH9OmeRGcubAWsMlLiL8BTHbwfm/jPQE9Ag==", "integrity": "sha512-7tayxeYboJX0RbVzdnKyGl2vhQRWr6qfClEXDhOkXjuaOKCw2q8aiuFhONRYVsG/czia7KhpykIlI2S2VaPunA==",
"requires": { "requires": {
"detect-libc": "^1.0.3", "detect-libc": "^1.0.3",
"expand-template": "^1.0.2", "expand-template": "^1.0.2",
@@ -271,11 +295,11 @@
} }
}, },
"rc": { "rc": {
"version": "1.2.6", "version": "1.2.8",
"resolved": "https://registry.npmjs.org/rc/-/rc-1.2.6.tgz", "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz",
"integrity": "sha1-6xiYnG1PTxYsOZ953dKfODVWgJI=", "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==",
"requires": { "requires": {
"deep-extend": "~0.4.0", "deep-extend": "^0.6.0",
"ini": "~1.3.0", "ini": "~1.3.0",
"minimist": "^1.2.0", "minimist": "^1.2.0",
"strip-json-comments": "~2.0.1" "strip-json-comments": "~2.0.1"
@@ -283,7 +307,7 @@
}, },
"readable-stream": { "readable-stream": {
"version": "2.3.6", "version": "2.3.6",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
"integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
"requires": { "requires": {
"core-util-is": "~1.0.0", "core-util-is": "~1.0.0",
@@ -296,14 +320,14 @@
} }
}, },
"safe-buffer": { "safe-buffer": {
"version": "5.1.1", "version": "5.1.2",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
"integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==" "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
}, },
"semver": { "semver": {
"version": "5.5.0", "version": "5.5.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.1.tgz",
"integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==" "integrity": "sha512-PqpAxfrEhlSUWge8dwIp4tZnQ25DIOthpiaHNIthsjEFQD6EvqUKUDM7L8O2rShkFccYo1VjJR0coWfNkCubRw=="
}, },
"set-blocking": { "set-blocking": {
"version": "2.0.0", "version": "2.0.0",
@@ -321,9 +345,9 @@
"integrity": "sha1-c0TLuLbib7J9ZrL8hvn21Zl1IcY=" "integrity": "sha1-c0TLuLbib7J9ZrL8hvn21Zl1IcY="
}, },
"simple-get": { "simple-get": {
"version": "2.7.0", "version": "2.8.1",
"resolved": "https://registry.npmjs.org/simple-get/-/simple-get-2.7.0.tgz", "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-2.8.1.tgz",
"integrity": "sha512-RkE9rGPHcxYZ/baYmgJtOSM63vH0Vyq+ma5TijBcLla41SWlh8t6XYIGMR/oeZcmr+/G8k+zrClkkVrtnQ0esg==", "integrity": "sha512-lSSHRSw3mQNUGPAYRqo7xy9dhKmxFXIjLjp4KHpf99GEH2VH7C3AM+Qfx6du6jhfUi6Vm7XnbEVEf7Wb6N8jRw==",
"requires": { "requires": {
"decompress-response": "^3.3.0", "decompress-response": "^3.3.0",
"once": "^1.3.1", "once": "^1.3.1",
@@ -350,7 +374,7 @@
}, },
"strip-ansi": { "strip-ansi": {
"version": "3.0.1", "version": "3.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", "resolved": "http://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
"integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
"requires": { "requires": {
"ansi-regex": "^2.0.0" "ansi-regex": "^2.0.0"
@@ -362,9 +386,9 @@
"integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=" "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo="
}, },
"tar-fs": { "tar-fs": {
"version": "1.16.0", "version": "1.16.3",
"resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-1.16.0.tgz", "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-1.16.3.tgz",
"integrity": "sha512-I9rb6v7mjWLtOfCau9eH5L7sLJyU2BnxtEZRQ5Mt+eRKmf1F0ohXmT/Jc3fr52kDvjJ/HV5MH3soQfPL5bQ0Yg==", "integrity": "sha512-NvCeXpYx7OsmOh8zIOP/ebG55zZmxLE0etfWRbWok+q2Qo8x/vOR/IJT1taADXPe+jsiu9axDb3X4B+iIgNlKw==",
"requires": { "requires": {
"chownr": "^1.0.1", "chownr": "^1.0.1",
"mkdirp": "^0.5.1", "mkdirp": "^0.5.1",
@@ -384,16 +408,24 @@
} }
}, },
"tar-stream": { "tar-stream": {
"version": "1.5.5", "version": "1.6.2",
"resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-1.5.5.tgz", "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-1.6.2.tgz",
"integrity": "sha512-mQdgLPc/Vjfr3VWqWbfxW8yQNiJCbAZ+Gf6GDu1Cy0bdb33ofyiNGBtAY96jHFhDuivCwgW1H9DgTON+INiXgg==", "integrity": "sha512-rzS0heiNf8Xn7/mpdSVVSMAWAoy9bfb1WOTYC78Z0UQKeKa/CWS8FOq0lKGNa8DWKAn9gxjCvMLYc5PGXYlK2A==",
"requires": { "requires": {
"bl": "^1.0.0", "bl": "^1.0.0",
"buffer-alloc": "^1.2.0",
"end-of-stream": "^1.0.0", "end-of-stream": "^1.0.0",
"readable-stream": "^2.0.0", "fs-constants": "^1.0.0",
"readable-stream": "^2.3.0",
"to-buffer": "^1.1.1",
"xtend": "^4.0.0" "xtend": "^4.0.0"
} }
}, },
"to-buffer": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.1.1.tgz",
"integrity": "sha512-lx9B5iv7msuFYE3dytT+KE5tap+rNYw+K4jVkb9R/asAb+pbBSM17jtunHplhBe6RRJdZx3Pn2Jph24O32mOVg=="
},
"tunnel-agent": { "tunnel-agent": {
"version": "0.6.0", "version": "0.6.0",
"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
@@ -413,11 +445,11 @@
"integrity": "sha1-Zws6+8VS4LVd9rd4DKdGFfI60cs=" "integrity": "sha1-Zws6+8VS4LVd9rd4DKdGFfI60cs="
}, },
"wide-align": { "wide-align": {
"version": "1.1.2", "version": "1.1.3",
"resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.2.tgz", "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz",
"integrity": "sha512-ijDLlyQ7s6x1JgCLur53osjm/UXUYD9+0PbYKrBsYisYXzCxN+HC3mYDNy/dWdmf3AwqwU3CXwDCvsNgGK1S0w==", "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==",
"requires": { "requires": {
"string-width": "^1.0.2" "string-width": "^1.0.2 || 2"
} }
}, },
"wrappy": { "wrappy": {

View File

@@ -11,7 +11,7 @@
"@types/node": "8.0.28" "@types/node": "8.0.28"
}, },
"dependencies": { "dependencies": {
"node-hid": "0.5.7", "node-hid": "0.7.3",
"uhk-common": "1.0.0" "uhk-common": "1.0.0"
} }
} }

View File

@@ -1,5 +1,8 @@
import { Device, devices, HID } from 'node-hid'; import { Device, devices, HID } from 'node-hid';
import { CommandLineArgs, DeviceConnectionState, isEqualArray, LogService } from 'uhk-common'; import { pathExists } from 'fs-extra';
import * as path from 'path';
import { platform } from 'os';
import { CommandLineArgs, DeviceConnectionState, isEqualArray, LogService, UdevRulesInfo } from 'uhk-common';
import { import {
ConfigBufferId, ConfigBufferId,
@@ -13,7 +16,7 @@ import {
UsbCommand, UsbCommand,
UsbVariables UsbVariables
} from './constants'; } from './constants';
import { bufferToString, getTransferData, isUhkDevice, retry, snooze } from './util'; import { bufferToString, getFileContentAsync, getTransferData, isUhkDevice, isUhkZeroInterface, retry, snooze } from './util';
export const BOOTLOADER_TIMEOUT_MS = 5000; export const BOOTLOADER_TIMEOUT_MS = 5000;
@@ -28,9 +31,11 @@ export class UhkHidDevice {
private _prevDevices = []; private _prevDevices = [];
private _device: HID; private _device: HID;
private _hasPermission = false; private _hasPermission = false;
private _udevRulesInfo = UdevRulesInfo.Unkonwn;
constructor(private logService: LogService, constructor(private logService: LogService,
private options: CommandLineArgs) { private options: CommandLineArgs,
private rootDir: string) {
} }
/** /**
@@ -50,7 +55,11 @@ export class UhkHidDevice {
return true; return true;
} }
const dev = devices().find((x: Device) => isUhkDevice(x) || x.productId === Constants.BOOTLOADER_ID); this.logService.debug('[UhkHidDevice] Devices before check permission:');
const devs = devices();
this.logDevices(devs);
const dev = devs.find((x: Device) => isUhkZeroInterface(x) || x.productId === Constants.BOOTLOADER_ID);
if (!dev) { if (!dev) {
return true; return true;
@@ -70,20 +79,26 @@ export class UhkHidDevice {
} }
/** /**
* Return with true is an UHK Device is connected to the computer. * Return with the USB device communication sate.
* @returns {boolean} * @returns {DeviceConnectionState}
*/ */
public getDeviceConnectionState(): DeviceConnectionState { public async getDeviceConnectionStateAsync(): Promise<DeviceConnectionState> {
const devs = devices(); const devs = devices();
const result: DeviceConnectionState = { const result: DeviceConnectionState = {
bootloaderActive: false, bootloaderActive: false,
connected: false, connected: false,
hasPermission: this.hasPermission() zeroInterfaceAvailable: false,
hasPermission: this.hasPermission(),
udevRulesInfo: await this.getUdevInfoAsync()
}; };
for (const dev of devs) { for (const dev of devs) {
if (isUhkDevice(dev)) { if (isUhkDevice(dev)) {
result.connected = true; result.connected = true;
}
if (isUhkZeroInterface(dev)) {
result.zeroInterfaceAvailable = true;
} else if (dev.vendorId === Constants.VENDOR_ID && } else if (dev.vendorId === Constants.VENDOR_ID &&
dev.productId === Constants.BOOTLOADER_ID) { dev.productId === Constants.BOOTLOADER_ID) {
result.bootloaderActive = true; result.bootloaderActive = true;
@@ -258,17 +273,25 @@ export class UhkHidDevice {
private connectToDevice(): HID { private connectToDevice(): HID {
try { try {
const devs = devices(); const devs = devices();
if (!isEqualArray(this._prevDevices, devs)) { let compareDevices = devs as any;
if (platform() === 'linux') {
compareDevices = devs.map(x => ({
productId: x.productId,
vendorId: x.vendorId,
interface: x.interface
}));
}
if (!isEqualArray(this._prevDevices, compareDevices)) {
this.logService.debug('[UhkHidDevice] Available devices:'); this.logService.debug('[UhkHidDevice] Available devices:');
for (const logDevice of devs) { this.logDevices(devs);
this.logService.debug(JSON.stringify(logDevice)); this._prevDevices = compareDevices;
}
this._prevDevices = devs;
} else { } else {
this.logService.debug('[UhkHidDevice] Available devices unchanged'); this.logService.debug('[UhkHidDevice] Available devices unchanged');
} }
const dev = devs.find(isUhkDevice); const dev = devs.find(isUhkZeroInterface);
if (!dev) { if (!dev) {
this.logService.debug('[UhkHidDevice] UHK Device not found:'); this.logService.debug('[UhkHidDevice] UHK Device not found:');
@@ -277,13 +300,43 @@ export class UhkHidDevice {
const device = new HID(dev.path); const device = new HID(dev.path);
this.logService.debug('[UhkHidDevice] Used device:', JSON.stringify(dev)); this.logService.debug('[UhkHidDevice] Used device:', JSON.stringify(dev));
return device; return device;
} } catch (err) {
catch (err) {
this.logService.error('[UhkHidDevice] Can not create device:', err); this.logService.error('[UhkHidDevice] Can not create device:', err);
} }
return null; return null;
} }
private logDevices(devs: Array<Device>): void {
for (const logDevice of devs) {
this.logService.debug(JSON.stringify(logDevice));
}
}
private async getUdevInfoAsync(): Promise<UdevRulesInfo> {
if (this._udevRulesInfo === UdevRulesInfo.Ok) {
return UdevRulesInfo.Ok;
}
if (process.platform === 'win32' || process.platform === 'darwin') {
this._udevRulesInfo = UdevRulesInfo.Ok;
return UdevRulesInfo.Ok;
}
if (!(await pathExists('/etc/udev/rules.d/50-uhk60.rules'))) {
return UdevRulesInfo.NeedToSetup;
}
const expectedUdevSettings = await getFileContentAsync(path.join(this.rootDir, 'rules/50-uhk60.rules'));
const currentUdevSettings = await getFileContentAsync('/etc/udev/rules.d/50-uhk60.rules');
if (isEqualArray(expectedUdevSettings, currentUdevSettings)) {
this._udevRulesInfo = UdevRulesInfo.Ok;
return UdevRulesInfo.Ok;
}
return UdevRulesInfo.Different;
}
} }
function kbootCommandName(module: ModuleSlotToI2cAddress): string { function kbootCommandName(module: ModuleSlotToI2cAddress): string {

View File

@@ -1,5 +1,7 @@
import { Device } from 'node-hid'; import { Device } from 'node-hid';
import { DeviceConnectionState, LogService } from 'uhk-common'; import { readFile } from 'fs-extra';
import { EOL } from 'os';
import { LogService } from 'uhk-common';
import { Constants, UsbCommand } from './constants'; import { Constants, UsbCommand } from './constants';
@@ -98,13 +100,7 @@ export async function retry(command: Function, maxTry = 3, logService?: LogServi
} }
} }
export const deviceConnectionStateComparer = (a: DeviceConnectionState, b: DeviceConnectionState): boolean => { export const isUhkZeroInterface = (dev: Device): 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 && return dev.vendorId === Constants.VENDOR_ID &&
dev.productId === Constants.PRODUCT_ID && dev.productId === Constants.PRODUCT_ID &&
// hidapi can not read the interface number on Mac, so check the usage page and usage // hidapi can not read the interface number on Mac, so check the usage page and usage
@@ -112,3 +108,17 @@ export const isUhkDevice = (dev: Device): boolean => {
(dev.usagePage === (0xFF00 | 0x00) && dev.usage === 0x01) || // New firmware (dev.usagePage === (0xFF00 | 0x00) && dev.usage === 0x01) || // New firmware
dev.interface === 0); dev.interface === 0);
}; };
export const isUhkDevice = (dev: Device): boolean => {
return dev.vendorId === Constants.VENDOR_ID &&
(dev.productId === Constants.PRODUCT_ID || dev.productId === Constants.BOOTLOADER_ID);
};
export const getFileContentAsync = async (filePath: string): Promise<Array<string>> => {
const fileContent = await readFile(filePath, {encoding: 'utf-8'});
return fileContent
.split(EOL)
.map(x => x.trim())
.filter(x => !x.startsWith('#') && x.length > 0);
};

View File

@@ -9540,9 +9540,9 @@
} }
}, },
"semver": { "semver": {
"version": "5.5.0", "version": "5.6.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", "resolved": "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz",
"integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==", "integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==",
"dev": true "dev": true
}, },
"semver-dsl": { "semver-dsl": {

View File

@@ -70,6 +70,7 @@
"reselect": "3.0.1", "reselect": "3.0.1",
"rxjs": "5.5.8", "rxjs": "5.5.8",
"typescript": "2.6.2", "typescript": "2.6.2",
"semver": "5.6.0",
"uhk-common": "1.0.0", "uhk-common": "1.0.0",
"xml-loader": "1.2.1", "xml-loader": "1.2.1",
"zone.js": "0.8.26", "zone.js": "0.8.26",

View File

@@ -13,7 +13,8 @@ import {
getShowAppUpdateAvailable, getShowAppUpdateAvailable,
deviceConfigurationLoaded, deviceConfigurationLoaded,
runningInElectron, runningInElectron,
saveToKeyboardState saveToKeyboardState,
keypressCapturing
} from './store'; } from './store';
import { ProgressButtonState } from './store/reducers/progress-button-state'; import { ProgressButtonState } from './store/reducers/progress-button-state';
@@ -42,7 +43,9 @@ export class MainAppComponent implements OnDestroy {
runningInElectron$: Observable<boolean>; runningInElectron$: Observable<boolean>;
saveToKeyboardState: ProgressButtonState; saveToKeyboardState: ProgressButtonState;
private keypressCapturing: boolean;
private saveToKeyboardStateSubscription: Subscription; private saveToKeyboardStateSubscription: Subscription;
private keypressCapturingSubscription: Subscription;
constructor(private store: Store<AppState>) { constructor(private store: Store<AppState>) {
this.showUpdateAvailable$ = store.select(getShowAppUpdateAvailable); this.showUpdateAvailable$ = store.select(getShowAppUpdateAvailable);
@@ -50,10 +53,13 @@ export class MainAppComponent implements OnDestroy {
this.runningInElectron$ = store.select(runningInElectron); this.runningInElectron$ = store.select(runningInElectron);
this.saveToKeyboardStateSubscription = store.select(saveToKeyboardState) this.saveToKeyboardStateSubscription = store.select(saveToKeyboardState)
.subscribe(data => this.saveToKeyboardState = data); .subscribe(data => this.saveToKeyboardState = data);
this.keypressCapturingSubscription = store.select(keypressCapturing)
.subscribe(data => this.keypressCapturing = data);
} }
ngOnDestroy(): void { ngOnDestroy(): void {
this.saveToKeyboardStateSubscription.unsubscribe(); this.saveToKeyboardStateSubscription.unsubscribe();
this.keypressCapturingSubscription.unsubscribe();
} }
@HostListener('document:keydown', ['$event']) @HostListener('document:keydown', ['$event'])
@@ -61,7 +67,8 @@ export class MainAppComponent implements OnDestroy {
if (this.saveToKeyboardState.showButton && if (this.saveToKeyboardState.showButton &&
event.ctrlKey && event.ctrlKey &&
event.key === 's' && event.key === 's' &&
!event.defaultPrevented) { !event.defaultPrevented &&
!this.keypressCapturing) {
this.clickedOnProgressButton(this.saveToKeyboardState.action); this.clickedOnProgressButton(this.saveToKeyboardState.action);
event.preventDefault(); event.preventDefault();
} }

View File

@@ -1,6 +1,6 @@
<div class="row"> <div class="row">
<h1 class="col-xs-12 pane-title"> <h1 class="col-xs-12 pane-title">
<i class="uhk-icon uhk-icon-agent-icon"></i> <i class="uhk-icon uhk-icon-pure-agent-icon"></i>
<span>About</span> <span>About</span>
</h1> </h1>
<div class="col-xs-12"> <div class="col-xs-12">

View File

@@ -8,4 +8,3 @@
font-size: 16px; font-size: 16px;
text-align: center; text-align: center;
} }

View File

@@ -14,7 +14,7 @@ import 'rxjs/add/operator/combineLatest';
import { saveAs } from 'file-saver'; import { saveAs } from 'file-saver';
import { allowLayerDoubleTap, AppState, getKeyboardLayout } from '../../../store'; import { layerDoubleTapSupported, AppState, getKeyboardLayout } from '../../../store';
import { getKeymap, getKeymaps, getUserConfiguration } from '../../../store/reducers/user-configuration'; import { getKeymap, getKeymaps, getUserConfiguration } from '../../../store/reducers/user-configuration';
import { KeyboardLayout } from '../../../keyboard/keyboard-layout.enum'; import { KeyboardLayout } from '../../../keyboard/keyboard-layout.enum';
import { KeymapActions } from '../../../store/actions'; import { KeymapActions } from '../../../store/actions';
@@ -51,7 +51,7 @@ export class KeymapEditComponent {
.map((keymaps: Keymap[]) => keymaps.length > 1); .map((keymaps: Keymap[]) => keymaps.length > 1);
this.keyboardLayout$ = store.select(getKeyboardLayout); this.keyboardLayout$ = store.select(getKeyboardLayout);
this.allowLayerDoubleTap$ = store.select(allowLayerDoubleTap); this.allowLayerDoubleTap$ = store.select(layerDoubleTapSupported);
} }
downloadKeymap() { downloadKeymap() {

View File

@@ -93,10 +93,17 @@
<h4 *ngIf="activeTab === TabName.Hold">Hold mouse button</h4> <h4 *ngIf="activeTab === TabName.Hold">Hold mouse button</h4>
<h4 *ngIf="activeTab === TabName.Release">Release mouse button</h4> <h4 *ngIf="activeTab === TabName.Release">Release mouse button</h4>
<div class="btn-group"> <div class="btn-group">
<button *ngFor="let buttonLabel of buttonLabels; let buttonIndex = index" <button class="btn btn-default"
class="btn btn-default" [class.btn-primary]="hasButton(MouseButtons.Left)"
[class.btn-primary]="hasButton(buttonIndex)" (click)="setMouseClick(MouseButtons.Left)">Left
(click)="setMouseClick(buttonIndex)">{{buttonLabel}} </button>
<button class="btn btn-default"
[class.btn-primary]="hasButton(MouseButtons.Middle)"
(click)="setMouseClick(MouseButtons.Middle)">Middle
</button>
<button class="btn btn-default"
[class.btn-primary]="hasButton(MouseButtons.Right)"
(click)="setMouseClick(MouseButtons.Right)">Right
</button> </button>
</div> </div>
</div> </div>

View File

@@ -1,10 +1,11 @@
import { Component, Input, OnInit, ViewChild } from '@angular/core'; import { Component, Input, OnInit, ViewChild } from '@angular/core';
import { import {
MacroMouseSubAction,
MouseButtons,
MouseButtonMacroAction, MouseButtonMacroAction,
MoveMouseMacroAction, MoveMouseMacroAction,
ScrollMouseMacroAction, ScrollMouseMacroAction
MacroMouseSubAction
} from 'uhk-common'; } from 'uhk-common';
import { Tab } from '../../../../popover/tab'; import { Tab } from '../../../../popover/tab';
import { MacroBaseComponent } from '../macro-base.component'; import { MacroBaseComponent } from '../macro-base.component';
@@ -33,6 +34,7 @@ export class MacroMouseTabComponent extends MacroBaseComponent implements OnInit
@ViewChild('tab') selectedTab: Tab; @ViewChild('tab') selectedTab: Tab;
/* tslint:disable:variable-name: It is an enum type. So it can start with uppercase. */ /* tslint:disable:variable-name: It is an enum type. So it can start with uppercase. */
MouseButtons = MouseButtons;
TabName = TabName; TabName = TabName;
/* tslint:enable:variable-name */ /* tslint:enable:variable-name */
activeTab: TabName; activeTab: TabName;

View File

@@ -4,7 +4,6 @@ import {
Component, Component,
ElementRef, ElementRef,
Input, Input,
Renderer,
ViewChild ViewChild
} from '@angular/core'; } from '@angular/core';
import { TextMacroAction } from 'uhk-common'; import { TextMacroAction } from 'uhk-common';
@@ -23,14 +22,14 @@ export class MacroTextTabComponent extends MacroBaseComponent implements OnInit,
@Input() macroAction: TextMacroAction; @Input() macroAction: TextMacroAction;
@ViewChild('macroTextInput') input: ElementRef; @ViewChild('macroTextInput') input: ElementRef;
constructor(private renderer: Renderer) { super(); } constructor() { super(); }
ngOnInit() { ngOnInit() {
this.init(); this.init();
} }
ngAfterViewInit() { ngAfterViewInit() {
this.renderer.invokeElementMethod(this.input.nativeElement, 'focus'); this.input.nativeElement.focus();
} }
onTextChange() { onTextChange() {

View File

@@ -5,6 +5,7 @@
></macro-header> ></macro-header>
<macro-list <macro-list
[macro]="macro" [macro]="macro"
[macroPlaybackSupported]="macroPlaybackSupported$ | async"
(add)="addAction($event.macroId, $event.action)" (add)="addAction($event.macroId, $event.action)"
(edit)="editAction($event.macroId, $event.index, $event.action)" (edit)="editAction($event.macroId, $event.index, $event.action)"
(delete)="deleteAction($event.macroId, $event.index, $event.action)" (delete)="deleteAction($event.macroId, $event.index, $event.action)"

View File

@@ -3,11 +3,12 @@ import { ActivatedRoute } from '@angular/router';
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
import { Macro, MacroAction } from 'uhk-common'; import { Macro, MacroAction } from 'uhk-common';
import { Observable } from 'rxjs/Observable';
import { Subscription } from 'rxjs/Subscription'; 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'; import { AppState, macroPlaybackSupported } from '../../../store';
import { getMacro } from '../../../store/reducers/user-configuration'; import { getMacro } from '../../../store/reducers/user-configuration';
@Component({ @Component({
@@ -22,6 +23,7 @@ export class MacroEditComponent implements OnDestroy {
macro: Macro; macro: Macro;
isNew: boolean; isNew: boolean;
macroId: number; macroId: number;
macroPlaybackSupported$: Observable<boolean>;
private subscription: Subscription; private subscription: Subscription;
constructor(private store: Store<AppState>, public route: ActivatedRoute) { constructor(private store: Store<AppState>, public route: ActivatedRoute) {
@@ -37,6 +39,7 @@ export class MacroEditComponent implements OnDestroy {
}); });
this.isNew = this.route.snapshot.params['empty'] === 'new'; this.isNew = this.route.snapshot.params['empty'] === 'new';
this.macroPlaybackSupported$ = this.store.select(macroPlaybackSupported);
} }
ngOnDestroy() { ngOnDestroy() {

View File

@@ -5,6 +5,7 @@ import {
KeyMacroAction, KeyMacroAction,
KeyModifiers, KeyModifiers,
MacroAction, MacroAction,
MouseButtons,
MouseButtonMacroAction, MouseButtonMacroAction,
MoveMouseMacroAction, MoveMouseMacroAction,
ScrollMouseMacroAction, ScrollMouseMacroAction,
@@ -154,7 +155,7 @@ export class MacroItemComponent implements OnInit, OnChanges {
// Tap/press/release modifiers // Tap/press/release modifiers
for (let i = KeyModifiers.leftCtrl; i <= KeyModifiers.rightGui; i <<= 1) { for (let i = KeyModifiers.leftCtrl; i <= KeyModifiers.rightGui; i <<= 1) {
if (action.isModifierActive(i)) { if (action.isModifierActive(i)) {
this.title += ' ' + KeyModifiers[i]; this.title += ' ' + this.mapper.getOsSpecificModifierTextByValue(i);
} }
} }
} }
@@ -197,12 +198,11 @@ export class MacroItemComponent implements OnInit, OnChanges {
this.title = 'Release mouse button: '; this.title = 'Release mouse button: ';
} }
const buttonLabels: string[] = ['Left', 'Middle', 'Right'];
const selectedButtons: boolean[] = action.getMouseButtons(); const selectedButtons: boolean[] = action.getMouseButtons();
const selectedButtonLabels: string[] = []; const selectedButtonLabels: string[] = [];
selectedButtons.forEach((isSelected, idx) => { selectedButtons.forEach((isSelected, idx) => {
if (isSelected && buttonLabels[idx]) { if (isSelected && MouseButtons[idx]) {
selectedButtonLabels.push(buttonLabels[idx]); selectedButtonLabels.push(MouseButtons[idx]);
} }
}); });
this.title += selectedButtonLabels.join(', '); this.title += selectedButtonLabels.join(', ');

View File

@@ -1,6 +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> <p *ngIf="!macroPlaybackSupported"><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

@@ -35,6 +35,7 @@ import { MacroItemComponent } from '../item';
}) })
export class MacroListComponent { export class MacroListComponent {
@Input() macro: Macro; @Input() macro: Macro;
@Input() macroPlaybackSupported: boolean;
@ViewChildren(forwardRef(() => MacroItemComponent)) macroItems: QueryList<MacroItemComponent>; @ViewChildren(forwardRef(() => MacroItemComponent)) macroItems: QueryList<MacroItemComponent>;
@Output() add = new EventEmitter(); @Output() add = new EventEmitter();

View File

@@ -1 +1 @@
<uhk-message header="Cannot find your UHK" subtitle="Please plug it in!"></uhk-message> <uhk-message [header]="state.header" [subtitle]="state.subtitle"></uhk-message>

View File

@@ -1,14 +1,27 @@
import { Component } from '@angular/core'; import { Component, OnDestroy } from '@angular/core';
import { Store } from '@ngrx/store';
import { Subscription } from 'rxjs/Subscription';
import 'rxjs/add/operator/distinctUntilChanged'; import { AppState, getMissingDeviceState } from '../../store';
import 'rxjs/add/operator/ignoreElements'; import { MissingDeviceState } from '../../models/missing-device-state';
import 'rxjs/add/operator/takeWhile';
@Component({ @Component({
selector: 'missing-device', selector: 'missing-device',
templateUrl: './missing-device.component.html' templateUrl: './missing-device.component.html'
}) })
export class MissingDeviceComponent { export class MissingDeviceComponent implements OnDestroy {
constructor() {} state: MissingDeviceState;
private stateSubscription: Subscription;
constructor(private store: Store<AppState>) {
this.stateSubscription = this.store
.select(getMissingDeviceState)
.subscribe(state => this.state = state);
}
ngOnDestroy(): void {
this.stateSubscription.unsubscribe();
}
} }

View File

@@ -8,40 +8,14 @@
<div class="arrowCustom"></div> <div class="arrowCustom"></div>
<div class="popover-title menu-tabs"> <div class="popover-title menu-tabs">
<ul class="nav nav-tabs popover-menu"> <ul class="nav nav-tabs popover-menu">
<li #keypress [class.active]="activeTab === tabName.Keypress" (click)="selectTab(tabName.Keypress)"> <li *ngFor="let tab of tabHeaders; trackBy:trackTabHeader"
[class.active]="activeTab === tab.tabName"
[class.disabled]="tab.disabled"
(click)="selectTab(tab)">
<a class="menu-tabs--item"> <a class="menu-tabs--item">
<i class="fa fa-keyboard-o"></i> <i class="fa"
<span>Keypress</span> [ngClass]="tab.icon"></i>
</a> <span>{{ tab.text }}</span>
</li>
<li #layer [class.active]="activeTab === tabName.Layer" (click)="selectTab(tabName.Layer)">
<a class="menu-tabs--item">
<i class="fa fa-clone"></i>
<span>Layer</span>
</a>
</li>
<li #mouse [class.active]="activeTab === tabName.Mouse" (click)="selectTab(tabName.Mouse)">
<a class="menu-tabs--item">
<i class="fa fa-mouse-pointer"></i>
<span>Mouse</span>
</a>
</li>
<li #macro [class.active]="activeTab === tabName.Macro" (click)="selectTab(tabName.Macro)">
<a class="menu-tabs--item">
<i class="fa fa-play"></i>
<span>Macro</span>
</a>
</li>
<li #keymap [class.active]="activeTab === tabName.Keymap" (click)="selectTab(tabName.Keymap)">
<a class="menu-tabs--item">
<i class="fa fa-keyboard-o"></i>
<span>Keymap</span>
</a>
</li>
<li #none [class.active]="activeTab === tabName.None" (click)="selectTab(tabName.None)">
<a class="menu-tabs--item">
<i class="fa fa-ban"></i>
<span>None</span>
</a> </a>
</li> </li>
</ul> </ul>
@@ -53,30 +27,31 @@
[allowRemapOnAllKeymapWarning]="true" [allowRemapOnAllKeymapWarning]="true"
[remapInfo]="remapInfo" [remapInfo]="remapInfo"
[showLayerSwitcherInSecondaryRoles]="currentLayer === 0" [showLayerSwitcherInSecondaryRoles]="currentLayer === 0"
(validAction)="keyActionValid=$event" (validAction)="setKeyActionValidState($event)"
(keyActionChange)="keystrokeActionChange($event)" (keyActionChange)="keystrokeActionChange($event)"
></keypress-tab> ></keypress-tab>
<layer-tab #tab *ngSwitchCase="tabName.Layer" class="popover-content" <layer-tab #tab *ngSwitchCase="tabName.Layer" class="popover-content"
[defaultKeyAction]="defaultKeyAction" [defaultKeyAction]="defaultKeyAction"
[currentLayer]="currentLayer" [currentLayer]="currentLayer"
[allowLayerDoubleTap]="allowLayerDoubleTap" [allowLayerDoubleTap]="allowLayerDoubleTap"
(validAction)="keyActionValid=$event" (validAction)="setKeyActionValidState($event)"
></layer-tab> ></layer-tab>
<mouse-tab #tab *ngSwitchCase="tabName.Mouse" class="popover-content" <mouse-tab #tab *ngSwitchCase="tabName.Mouse" class="popover-content"
[defaultKeyAction]="defaultKeyAction" [defaultKeyAction]="defaultKeyAction"
(validAction)="keyActionValid=$event" (validAction)="setKeyActionValidState($event)"
></mouse-tab> ></mouse-tab>
<macro-tab #tab *ngSwitchCase="tabName.Macro" class="popover-content" <macro-tab #tab *ngSwitchCase="tabName.Macro" class="popover-content"
[defaultKeyAction]="defaultKeyAction" [defaultKeyAction]="defaultKeyAction"
(validAction)="keyActionValid=$event" [macroPlaybackSupported]="macroPlaybackSupported$ | async"
(validAction)="setKeyActionValidState($event)"
></macro-tab> ></macro-tab>
<keymap-tab #tab *ngSwitchCase="tabName.Keymap" class="popover-content" <keymap-tab #tab *ngSwitchCase="tabName.Keymap" class="popover-content"
[defaultKeyAction]="defaultKeyAction" [defaultKeyAction]="defaultKeyAction"
[keymaps]="keymaps$ | async" [keymaps]="keymaps$ | async"
(validAction)="keyActionValid=$event" (validAction)="setKeyActionValidState($event)"
></keymap-tab> ></keymap-tab>
<none-tab #tab *ngSwitchCase="tabName.None" class="popover-content" <none-tab #tab *ngSwitchCase="tabName.None" class="popover-content"
(validAction)="keyActionValid=$event" (validAction)="setKeyActionValidState($event)"
></none-tab> ></none-tab>
</div> </div>
<div class="popover-action"> <div class="popover-action">

View File

@@ -28,6 +28,11 @@
.nav-tabs > li { .nav-tabs > li {
overflow: hidden; overflow: hidden;
cursor: pointer;
&.disabled {
cursor: not-allowed;
}
} }
.arrowCustom { .arrowCustom {
@@ -85,7 +90,6 @@
.menu-tabs--item { .menu-tabs--item {
display: flex; display: flex;
align-items: center; align-items: center;
cursor: pointer;
i { i {
margin-right: 0.25em; margin-right: 0.25em;

View File

@@ -32,7 +32,7 @@ import {
import { Tab } from './tab'; import { Tab } from './tab';
import { AppState } from '../../store'; import { AppState, macroPlaybackSupported } from '../../store';
import { getKeymaps } from '../../store/reducers/user-configuration'; import { getKeymaps } from '../../store/reducers/user-configuration';
import { KeyActionRemap } from '../../models/key-action-remap'; import { KeyActionRemap } from '../../models/key-action-remap';
import { RemapInfo } from '../../models/remap-info'; import { RemapInfo } from '../../models/remap-info';
@@ -46,6 +46,13 @@ enum TabName {
None None
} }
export interface TabHeader {
text: string;
icon: string;
tabName: TabName;
disabled?: boolean;
}
@Component({ @Component({
selector: 'popover', selector: 'popover',
templateUrl: './popover.component.html', templateUrl: './popover.component.html',
@@ -108,6 +115,39 @@ export class PopoverComponent implements OnChanges {
animationState: string; animationState: string;
shadowKeyAction: KeyAction; shadowKeyAction: KeyAction;
disableRemapOnAllLayer = false; disableRemapOnAllLayer = false;
tabHeaders: TabHeader[] = [
{
tabName: TabName.Keypress,
icon: 'fa-keyboard-o',
text: 'Keypress'
},
{
tabName: TabName.Layer,
icon: 'fa-clone',
text: 'Layer'
},
{
tabName: TabName.Mouse,
icon: 'fa-mouse-pointer',
text: 'Mouse'
},
{
tabName: TabName.Macro,
icon: 'fa-play',
text: 'Macro'
},
{
tabName: TabName.Keymap,
icon: 'fa-keyboard-o',
text: 'Keymap'
},
{
tabName: TabName.None,
icon: 'fa-ban',
text: 'None'
}
];
macroPlaybackSupported$: Observable<boolean>;
private readonly currentKeymap$ = new BehaviorSubject<Keymap>(undefined); private readonly currentKeymap$ = new BehaviorSubject<Keymap>(undefined);
@@ -119,6 +159,7 @@ export class PopoverComponent implements OnChanges {
.map(([keymaps, currentKeymap]: [Keymap[], Keymap]) => .map(([keymaps, currentKeymap]: [Keymap[], Keymap]) =>
keymaps.filter((keymap: Keymap) => currentKeymap.abbreviation !== keymap.abbreviation) keymaps.filter((keymap: Keymap) => currentKeymap.abbreviation !== keymap.abbreviation)
); );
this.macroPlaybackSupported$ = store.select(macroPlaybackSupported);
} }
ngOnChanges(change: SimpleChanges) { ngOnChanges(change: SimpleChanges) {
@@ -127,24 +168,30 @@ export class PopoverComponent implements OnChanges {
} }
if (change['defaultKeyAction']) { if (change['defaultKeyAction']) {
let tab: TabName; let tab: TabHeader;
this.disableRemapOnAllLayer = false; this.disableRemapOnAllLayer = false;
if (this.defaultKeyAction instanceof KeystrokeAction) { if (this.defaultKeyAction instanceof KeystrokeAction) {
this.keystrokeActionChange(this.defaultKeyAction); this.keystrokeActionChange(this.defaultKeyAction);
tab = TabName.Keypress; tab = this.tabHeaders[0];
} else if (this.defaultKeyAction instanceof SwitchLayerAction) { } else if (this.defaultKeyAction instanceof SwitchLayerAction) {
tab = TabName.Layer; tab = this.tabHeaders[1];
} else if (this.defaultKeyAction instanceof MouseAction) { } else if (this.defaultKeyAction instanceof MouseAction) {
tab = TabName.Mouse; tab = this.tabHeaders[2];
} else if (this.defaultKeyAction instanceof PlayMacroAction) { } else if (this.defaultKeyAction instanceof PlayMacroAction) {
tab = TabName.Macro; tab = this.tabHeaders[3];
} else if (this.defaultKeyAction instanceof SwitchKeymapAction) { } else if (this.defaultKeyAction instanceof SwitchKeymapAction) {
tab = TabName.Keymap; tab = this.tabHeaders[4];
} else { } else {
tab = TabName.None; tab = this.tabHeaders[5];
} }
for (const tabHeader of this.tabHeaders) {
const allowOnlyLayerTab = tab.tabName === TabName.Layer && this.currentLayer !== 0;
tabHeader.disabled = allowOnlyLayerTab && tabHeader.tabName !== TabName.Layer;
console.log(tabHeader);
}
this.selectTab(tab); this.selectTab(tab);
} }
@@ -193,9 +240,13 @@ export class PopoverComponent implements OnChanges {
} }
} }
selectTab(tab: TabName): void { selectTab(tab: TabHeader): void {
this.activeTab = tab; if (tab.disabled) {
if (tab === TabName.Keypress) { return;
}
this.activeTab = tab.tabName;
if (tab.tabName === TabName.Keypress) {
this.keystrokeActionChange(this.defaultKeyAction as KeystrokeAction); this.keystrokeActionChange(this.defaultKeyAction as KeystrokeAction);
} }
} }
@@ -228,6 +279,15 @@ export class PopoverComponent implements OnChanges {
} }
} }
setKeyActionValidState($event: boolean): void {
this.keyActionValid = $event;
this.cdRef.markForCheck();
}
trackTabHeader(index: number, tabItem: TabHeader): string {
return tabItem.tabName.toString();
}
private calculatePosition() { private calculatePosition() {
const offsetLeft: number = this.wrapPosition.left + 265; // 265 is a width of the side menu with a margin const offsetLeft: number = this.wrapPosition.left + 265; // 265 is a width of the side menu with a margin
const popover: HTMLElement = this.popoverHost.nativeElement; const popover: HTMLElement = this.popoverHost.nativeElement;

View File

@@ -63,7 +63,7 @@ export class LayerTabComponent extends Tab implements OnChanges {
this.isNotBase = this.currentLayer > 0; this.isNotBase = this.currentLayer > 0;
} }
this.validAction.emit(true); this.validAction.emit(!this.isNotBase);
} }
keyActionValid(): boolean { keyActionValid(): boolean {

View File

@@ -2,7 +2,7 @@
<span> No macros are available to choose from. Create a macro first! </span> <span> No macros are available to choose from. Create a macro first! </span>
</ng-template> </ng-template>
<ng-template [ngIf]="macroOptions.length > 0"> <ng-template [ngIf]="macroOptions.length > 0">
<p><i>Please note that macro playback is not implemented yet. You can bind macros, but they won't have any effect until firmware support is implemented. We're working on this.</i></p> <p *ngIf="!macroPlaybackSupported"><i>Please note that macro playback is not implemented yet. You can bind macros, but they won't have any effect until firmware support is implemented. We're working on this.</i></p>
<div class="macro-selector"> <div class="macro-selector">
<b> Play macro: </b> <b> Play macro: </b>
<ngx-select [items]="macroOptions" <ngx-select [items]="macroOptions"

View File

@@ -17,6 +17,7 @@ import { SelectOptionData } from '../../../../models/select-option-data';
}) })
export class MacroTabComponent extends Tab implements OnInit, OnChanges, OnDestroy { export class MacroTabComponent extends Tab implements OnInit, OnChanges, OnDestroy {
@Input() defaultKeyAction: KeyAction; @Input() defaultKeyAction: KeyAction;
@Input() macroPlaybackSupported: boolean;
macros: Macro[]; macros: Macro[];
macroOptions: Array<SelectOptionData>; macroOptions: Array<SelectOptionData>;

View File

@@ -1,6 +1,10 @@
import { ChangeDetectionStrategy, Component, EventEmitter, HostListener, Input, Output } from '@angular/core'; import { ChangeDetectionStrategy, Component, EventEmitter, HostListener, Input, Output } from '@angular/core';
import { Store } from '@ngrx/store';
import { CaptureService } from '../../../../services/capture.service'; import { CaptureService } from '../../../../services/capture.service';
import { KeyModifierModel } from '../../../../models/key-modifier-model'; import { KeyModifierModel } from '../../../../models/key-modifier-model';
import { AppState } from '../../../../store';
import { StartKeypressCapturingAction, StopKeypressCapturingAction } from '../../../../store/actions/app';
@Component({ @Component({
selector: 'capture-keystroke-button', selector: 'capture-keystroke-button',
@@ -17,7 +21,8 @@ export class CaptureKeystrokeButtonComponent {
private first: boolean; // enable usage of Enter to start capturing private first: boolean; // enable usage of Enter to start capturing
private scanCodePressed: boolean; private scanCodePressed: boolean;
constructor(private captureService: CaptureService) { constructor(private captureService: CaptureService,
private store: Store<AppState>) {
this.record = false; this.record = false;
this.captureService.initModifiers(); this.captureService.initModifiers();
this.captureService.populateMapping(); this.captureService.populateMapping();
@@ -28,9 +33,11 @@ export class CaptureKeystrokeButtonComponent {
onKeyUp(e: KeyboardEvent) { onKeyUp(e: KeyboardEvent) {
if (this.scanCodePressed) { if (this.scanCodePressed) {
e.preventDefault(); e.preventDefault();
e.stopPropagation();
this.scanCodePressed = false; this.scanCodePressed = false;
} else if (this.record && !this.first) { } else if (this.record && !this.first) {
e.preventDefault(); e.preventDefault();
e.stopPropagation();
this.saveScanCode(); this.saveScanCode();
} }
} }
@@ -54,6 +61,7 @@ export class CaptureKeystrokeButtonComponent {
} else if (code === enter) { } else if (code === enter) {
this.record = true; this.record = true;
this.first = true; this.first = true;
this.store.dispatch(new StartKeypressCapturingAction());
} }
} }
@@ -65,6 +73,7 @@ export class CaptureKeystrokeButtonComponent {
start(): void { start(): void {
this.record = true; this.record = true;
this.store.dispatch(new StartKeypressCapturingAction());
} }
private saveScanCode(code?: number) { private saveScanCode(code?: number) {
@@ -84,5 +93,6 @@ export class CaptureKeystrokeButtonComponent {
private reset() { private reset() {
this.first = false; this.first = false;
this.captureService.initModifiers(); this.captureService.initModifiers();
this.store.dispatch(new StopKeypressCapturingAction());
} }
} }

View File

@@ -2,8 +2,14 @@
<uhk-message header="Cannot talk to your UHK" <uhk-message header="Cannot talk to your UHK"
subtitle="Your UHK has been detected, but its permissions are not set up yet, so Agent can't talk to it."></uhk-message> subtitle="Your UHK has been detected, but its permissions are not set up yet, so Agent can't talk to it."></uhk-message>
<button class="btn btn-default btn-lg btn-primary" <div *ngIf="state.updateUdevRules">
(click)="setUpPermissions()"> Set up permissions You seem to have an old udev rule file installed. New Agent versions require and updated udev rule file to find your UHK.
</div>
<button class="btn btn-default btn-lg btn-primary mt-10"
(click)="setUpPermissions()">
<span *ngIf="!state.updateUdevRules">Set up permissions</span>
<span *ngIf="state.updateUdevRules">Update udev rule file</span>
</button> </button>
<div class="mt-10"> <div class="mt-10">
@@ -22,21 +28,7 @@
<div *ngIf="state.showWhatWillThisDoContent"> <div *ngIf="state.showWhatWillThisDoContent">
If you want to set up permissions manually: If you want to set up permissions manually:
<ol> <udev-rules></udev-rules>
<li>Open a terminal.</li>
<li>Run <code>su</code> to become root.</li>
<li>Paste the following script, and <a class="link-inline" (click)="retry()">retry</a>.</li>
</ol>
<div class="copy-container">
<span class="fa fa-2x fa-copy"
ngxClipboard
[cbContent]="command"
title="Copy to clipboard"
data-toggle="tooltip"
data-placement="top"></span>
<pre><code>{{ command }}</code></pre>
</div>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -18,15 +18,6 @@ export class PrivilegeCheckerComponent implements OnInit, OnDestroy {
state: PrivilagePageSate; state: PrivilagePageSate;
command = `cat <<EOF >/etc/udev/rules.d/50-uhk60.rules
# Ultimate Hacking Keyboard rules
# These are the udev rules for accessing the USB interfaces of the UHK as non-root users.
# Copy this file to /etc/udev/rules.d and physically reconnect the UHK afterwards.
SUBSYSTEMS=="usb", ATTRS{idVendor}=="1d50", ATTRS{idProduct}=="612[0-7]", MODE:="0666"
EOF
udevadm trigger
udevadm settle`;
private stateSubscription: Subscription; private stateSubscription: Subscription;
constructor(private store: Store<AppState>, constructor(private store: Store<AppState>,

View File

@@ -131,7 +131,7 @@
</li> </li>
<li class="sidebar__level-0--item" [routerLinkActive]="['active']"> <li class="sidebar__level-0--item" [routerLinkActive]="['active']">
<div class="sidebar__level-0"> <div class="sidebar__level-0">
<i class="uhk-icon uhk-icon-agent-icon"></i> Agent <i class="uhk-icon uhk-icon-pure-agent-icon"></i> Agent
<i class="fa fa-chevron-up pull-right" <i class="fa fa-chevron-up pull-right"
(click)="toggleHide($event, 'agent')"></i> (click)="toggleHide($event, 'agent')"></i>
</div> </div>

View File

@@ -8,8 +8,9 @@
[attr.x]="0" [attr.x]="0"
[attr.y]="textY" [attr.y]="textY"
[attr.text-anchor]="'middle'" [attr.text-anchor]="'middle'"
[attr.font-size]="11"> [attr.font-size]="fontSize">
<tspan [attr.x]="spanX">{{ text }}</tspan> <tspan [attr.x]="spanX" [attr.dy]="text1Y">{{ text1 }}</tspan>
<tspan [attr.x]="spanX" [attr.dy]="text2Y">{{ text2 }}</tspan>
</svg:text> </svg:text>
<svg:g svg-secondary-role <svg:g svg-secondary-role
*ngIf="secondaryText" *ngIf="secondaryText"

Before

Width:  |  Height:  |  Size: 499 B

After

Width:  |  Height:  |  Size: 596 B

View File

@@ -2,6 +2,7 @@ import { Component, Input, ChangeDetectionStrategy, OnChanges, SimpleChanges } f
import { isRectangleAsSecondaryRoleKey } from '../util'; import { isRectangleAsSecondaryRoleKey } from '../util';
import { SECONDARY_ROLE_BOTTOM_MARGIN } from '../../constants'; import { SECONDARY_ROLE_BOTTOM_MARGIN } from '../../constants';
import { getContentWidth } from '../../../../util';
@Component({ @Component({
selector: 'g[svg-icon-text-key]', selector: 'g[svg-icon-text-key]',
@@ -21,8 +22,14 @@ export class SvgIconTextKeyComponent implements OnChanges {
useY: number; useY: number;
textY: number; textY: number;
spanX: number; spanX: number;
textWidth: number;
secondaryTextY: number; secondaryTextY: number;
secondaryHeight: number; secondaryHeight: number;
fontSize: number;
text1: string;
text1Y: number;
text2: string;
text2Y: number;
constructor() { constructor() {
} }
@@ -32,22 +39,120 @@ export class SvgIconTextKeyComponent implements OnChanges {
} }
private calculatePositions(): void { private calculatePositions(): void {
let textYModifier = 0;
let secondaryYModifier = 0; let secondaryYModifier = 0;
if (this.secondaryText && isRectangleAsSecondaryRoleKey(this.width, this.height)) { if (this.secondaryText && isRectangleAsSecondaryRoleKey(this.width, this.height)) {
textYModifier = this.height / 5;
secondaryYModifier = 5; secondaryYModifier = 5;
} }
const isRectangle = this.width > this.height * 1.8;
this.useWidth = this.width / 3; this.useWidth = this.width / 3;
this.useHeight = this.height / 3; this.useHeight = this.height / 3;
this.useX = (this.width > 2 * this.height) ? 0 : this.width / 3;
this.useY = (this.width > 2 * this.height) ? this.height / 3 : this.height / 10;
this.textY = (this.width > 2 * this.height) ? this.height / 2 : this.height * 0.6;
this.spanX = (this.width > 2 * this.height) ? this.width * 0.6 : this.width / 2;
this.secondaryHeight = this.height / 4; if (isRectangle) {
this.secondaryTextY = this.height - this.secondaryHeight - SECONDARY_ROLE_BOTTOM_MARGIN - secondaryYModifier; this.textWidth = this.width * 0.65;
this.useX = 0;
this.useY = this.height / 3;
this.spanX = this.width * 0.6;
} else {
this.textWidth = this.width * 0.95;
this.useX = this.width / 3;
this.useY = this.height / 10;
this.spanX = this.width / 2;
}
if (this.secondaryText) {
this.secondaryHeight = this.height / 4;
this.secondaryTextY = this.height - this.secondaryHeight - SECONDARY_ROLE_BOTTOM_MARGIN - secondaryYModifier;
} else {
this.secondaryHeight = 0;
this.secondaryTextY = 0;
}
this.fontSize = 19;
this.text1 = '';
this.text2 = '';
while (this.fontSize > 10 && !this.isFullTextVisible()) {
this.calculateTexts(isRectangle);
this.fontSize--;
}
}
private calculateTexts(isRectangle: boolean): void {
if (!this.text) {
return;
}
this.text1 = this.getText(0);
this.text2 = this.getText(this.text1.length);
const lineHeight = this.fontSize;
const lines = this.text2 ? 1 : 0;
if (isRectangle) {
const textboxHeight = this.height - this.secondaryHeight;
this.textY = textboxHeight / 2 - 0.5 * lines * lineHeight;
} else {
const textboxHeight = this.height - this.secondaryHeight + this.useHeight;
this.textY = textboxHeight / 2 - 0.5 * lines * lineHeight;
}
this.text1Y = 0;
this.text2Y = this.text1Y + 1.2 * lines * lineHeight;
}
private getText(startPosition: number): string {
const style: CSSStyleDeclaration = {
font: `${this.fontSize}px Helvetica`
} as any;
let result = '';
let lastSpacePosition = 0;
for (let i = startPosition; i < this.text.length; i++) {
const char = this.text[i];
// skip space if result start with space
if (char === ' ' && result === '') {
continue;
}
const newText = result += char;
const textWidth = getContentWidth(style, newText);
if (char === ' ') {
lastSpacePosition = i;
}
if (textWidth > this.textWidth) {
break;
}
result = newText;
}
if (lastSpacePosition > 0 && lastSpacePosition < result.length) {
result = result.substr(0, lastSpacePosition);
} else if (this.fontSize === 11) {
const cleanResult = result.replace(/ /g, '');
const cleanText = this.text.substr(startPosition).replace(/ /g, '');
if (cleanResult.length < cleanText.length) {
result = result.substring(0, result.length - 3) + '...';
}
}
return result;
}
private isFullTextVisible(): boolean {
const visibleText = (this.text1 + this.text2).replace(/ /g, '');
if (this.text2.endsWith('...')) {
return true;
}
const textLength = this.text.replace(/ /g, '').length;
return visibleText.length === textLength;
} }
} }

View File

@@ -1,5 +1,5 @@
import { import {
Component, ElementRef, EventEmitter, HostListener, Input, OnChanges, OnDestroy, OnInit, Output, Renderer, Component, ElementRef, EventEmitter, HostListener, Input, OnChanges, OnDestroy, OnInit, Output,
SimpleChange, ChangeDetectionStrategy SimpleChange, ChangeDetectionStrategy
} from '@angular/core'; } from '@angular/core';
import { animate, group, state, style, transition, trigger } from '@angular/animations'; import { animate, group, state, style, transition, trigger } from '@angular/animations';
@@ -29,6 +29,7 @@ import { getMacros } from '../../../../store/reducers/user-configuration';
import { SvgKeyCaptureEvent, SvgKeyClickEvent } from '../../../../models/svg-key-events'; import { SvgKeyCaptureEvent, SvgKeyClickEvent } from '../../../../models/svg-key-events';
import { OperatingSystem } from '../../../../models/operating-system'; import { OperatingSystem } from '../../../../models/operating-system';
import { KeyModifierModel } from '../../../../models/key-modifier-model'; import { KeyModifierModel } from '../../../../models/key-modifier-model';
import { StartKeypressCapturingAction, StopKeypressCapturingAction } from '../../../../store/actions/app';
enum LabelTypes { enum LabelTypes {
KeystrokeKey, KeystrokeKey,
@@ -107,10 +108,9 @@ export class SvgKeyboardKeyComponent implements OnInit, OnChanges, OnDestroy {
constructor( constructor(
private mapper: MapperService, private mapper: MapperService,
store: Store<AppState>, private store: Store<AppState>,
private element: ElementRef, private element: ElementRef,
private captureService: CaptureService, private captureService: CaptureService
private renderer: Renderer
) { ) {
this.subscription = store.let(getMacros()) this.subscription = store.let(getMacros())
.subscribe((macros: Macro[]) => this.macros = macros); .subscribe((macros: Macro[]) => this.macros = macros);
@@ -136,7 +136,7 @@ export class SvgKeyboardKeyComponent implements OnInit, OnChanges, OnDestroy {
onMouseDown(e: MouseEvent) { onMouseDown(e: MouseEvent) {
if ((e.which === 2 || e.button === 2) && this.capturingEnabled) { if ((e.which === 2 || e.button === 2) && this.capturingEnabled) {
e.preventDefault(); e.preventDefault();
this.renderer.invokeElementMethod(this.element.nativeElement, 'focus'); this.element.nativeElement.focus();
if (this.recording) { if (this.recording) {
this.reset(); this.reset();
@@ -145,6 +145,7 @@ export class SvgKeyboardKeyComponent implements OnInit, OnChanges, OnDestroy {
this.recordAnimation = 'active'; this.recordAnimation = 'active';
this.shiftPressed = e.shiftKey; this.shiftPressed = e.shiftKey;
this.altPressed = e.altKey; this.altPressed = e.altKey;
this.store.dispatch(new StartKeypressCapturingAction());
} }
} }
} }
@@ -242,6 +243,7 @@ export class SvgKeyboardKeyComponent implements OnInit, OnChanges, OnDestroy {
this.captureService.initModifiers(); this.captureService.initModifiers();
this.shiftPressed = false; this.shiftPressed = false;
this.altPressed = false; this.altPressed = false;
this.store.dispatch(new StopKeypressCapturingAction());
} }
private saveScanCode(code = 0) { private saveScanCode(code = 0) {

View File

@@ -1,7 +1,7 @@
<svg:use [attr.xlink:href]="icon" <svg:use [attr.xlink:href]="icon"
[attr.width]="svgWidth" [attr.width]="svgWidth"
[attr.height]="svgHeight" [attr.height]="svgHeight"
[attr.x]="svgWidth" [attr.x]="svgX"
[attr.y]="svgHeight"> [attr.y]="svgHeight">
</svg:use> </svg:use>
<svg:g svg-secondary-role <svg:g svg-secondary-role

Before

Width:  |  Height:  |  Size: 340 B

After

Width:  |  Height:  |  Size: 336 B

View File

@@ -16,6 +16,7 @@ export class SvgSingleIconKeyComponent implements OnChanges {
svgHeight: number; svgHeight: number;
svgWidth: number; svgWidth: number;
svgX: number;
secondaryTextY: number; secondaryTextY: number;
secondaryHeight: number; secondaryHeight: number;
@@ -34,8 +35,9 @@ export class SvgSingleIconKeyComponent implements OnChanges {
secondaryYModifier = 5; secondaryYModifier = 5;
} }
this.svgWidth = this.width / 3; this.svgWidth = this.width / 2.075;
this.svgHeight = this.height / 3; this.svgHeight = this.height / 3;
this.svgX = (this.width - this.svgWidth) / 2;
this.secondaryHeight = this.height / 4; this.secondaryHeight = this.height / 4;
this.secondaryTextY = this.height - this.secondaryHeight - SECONDARY_ROLE_BOTTOM_MARGIN - secondaryYModifier; this.secondaryTextY = this.height - this.secondaryHeight - SECONDARY_ROLE_BOTTOM_MARGIN - secondaryYModifier;
} }

View File

@@ -9,7 +9,6 @@ import {
OnChanges, OnChanges,
OnInit, OnInit,
Output, Output,
Renderer,
SimpleChanges, SimpleChanges,
ViewChild ViewChild
} from '@angular/core'; } from '@angular/core';
@@ -100,8 +99,7 @@ export class SvgKeyboardWrapComponent implements OnInit, OnChanges {
constructor( constructor(
private store: Store<AppState>, private store: Store<AppState>,
private mapper: MapperService, private mapper: MapperService,
private element: ElementRef, private element: ElementRef
private renderer: Renderer
) { ) {
this.keyEditConfig = { this.keyEditConfig = {
moduleId: undefined, moduleId: undefined,
@@ -216,7 +214,7 @@ export class SvgKeyboardWrapComponent implements OnInit, OnChanges {
this.keyPosition = this.keyElement.getBoundingClientRect(); this.keyPosition = this.keyElement.getBoundingClientRect();
this.popoverInitKeyAction = keyAction; this.popoverInitKeyAction = keyAction;
this.popoverShown = true; this.popoverShown = true;
this.renderer.invokeElementMethod(this.popover.nativeElement, 'focus'); this.popover.nativeElement.focus();
} }
showTooltip(keyAction: KeyAction, event: MouseEvent): void { showTooltip(keyAction: KeyAction, event: MouseEvent): void {

View File

@@ -0,0 +1,15 @@
<ol>
<li>Open a terminal.</li>
<li>Run <code>su</code> to become root.</li>
<li>Paste the following script:</li>
</ol>
<div class="copy-container">
<span class="fa fa-2x fa-copy"
ngxClipboard
[cbContent]="command"
title="Copy to clipboard"
data-toggle="tooltip"
data-placement="top"></span>
<pre><code>{{ command }}</code></pre>
</div>

View File

@@ -0,0 +1,19 @@
import { ChangeDetectionStrategy, Component } from '@angular/core';
@Component({
selector: 'udev-rules',
changeDetection: ChangeDetectionStrategy.OnPush,
templateUrl: './udev-rules.component.html'
})
export class UdevRulesComponent {
command = `cat <<EOF >/etc/udev/rules.d/50-uhk60.rules
# Ultimate Hacking Keyboard rules
# These are the udev rules for accessing the USB interfaces of the UHK as non-root users.
# Copy this file to /etc/udev/rules.d and physically reconnect the UHK afterwards.
SUBSYSTEM=="input", GROUP="input", MODE="0666"
SUBSYSTEMS=="usb", ATTRS{idVendor}=="1d50", ATTRS{idProduct}=="612[0-7]", MODE:="0666", GROUP="plugdev"
KERNEL=="hidraw*", ATTRS{idVendor}=="1d50", ATTRS{idProduct}=="612[0-7]", MODE="0666", GROUP="plugdev"
EOF
udevadm trigger
udevadm settle`;
}

View File

@@ -1,5 +1,8 @@
<div class="pull-right"> <div class="pull-right">
<div class="alert alert-warning alert-dismissible" role="alert" [@slideInOut]="slideInOut"> <div *ngIf="text"
[@slideInOut]
class="alert alert-warning alert-dismissible"
role="alert">
<button type="button" <button type="button"
class="close" class="close"
aria-label="Close" aria-label="Close"

View File

@@ -1,5 +1,5 @@
import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnChanges, Output, SimpleChanges } from '@angular/core'; import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnChanges, Output, SimpleChanges } from '@angular/core';
import { animate, state, style, transition, trigger } from '@angular/animations'; import { animate, style, transition, trigger } from '@angular/animations';
import { Notification } from 'uhk-common'; import { Notification } from 'uhk-common';
@@ -9,16 +9,17 @@ import { Notification } from 'uhk-common';
styleUrls: ['./undoable-notifier.component.scss'], styleUrls: ['./undoable-notifier.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
animations: [ animations: [
trigger('slideInOut', [ trigger(
state('in', style({ 'slideInOut', [
transform: 'translate3d(0, 0, 0)' transition(':enter', [
})), style({transform: 'translateX(100%)'}),
state('out', style({ animate('400ms ease-in-out', style({transform: 'translateX(0)'}))
transform: 'translate3d(200%, 0, 0)' ]),
})), transition(':leave', [
transition('in => out', animate('400ms ease-in-out')), style({transform: 'translateX(0)'}),
transition('out => in', animate('400ms ease-in-out')) animate('400ms ease-in-out', style({transform: 'translateX(100%)'}))
]) ])
])
] ]
}) })
export class UndoableNotifierComponent implements OnChanges { export class UndoableNotifierComponent implements OnChanges {
@@ -28,16 +29,14 @@ export class UndoableNotifierComponent implements OnChanges {
@Output() close = new EventEmitter(); @Output() close = new EventEmitter();
@Output() undo = new EventEmitter(); @Output() undo = new EventEmitter();
get slideInOut() {
return this.notification ? 'in' : 'out';
}
ngOnChanges(changes: SimpleChanges): void { ngOnChanges(changes: SimpleChanges): void {
if (changes['notification']) { if (changes['notification']) {
const not: Notification = changes['notification'].currentValue; const not: Notification = changes['notification'].currentValue;
if (not) { if (not) {
this.text = not.message; this.text = not.message;
this.undoable = !!not.extra; this.undoable = !!not.extra;
} else {
this.text = null;
} }
} }
} }

View File

@@ -1,4 +1,4 @@
import { Directive, ElementRef, HostListener, Renderer } from '@angular/core'; import { Directive, ElementRef, HostListener, Renderer2 } from '@angular/core';
@Directive({ @Directive({
selector: '[cancelable]' selector: '[cancelable]'
@@ -7,15 +7,15 @@ export class CancelableDirective {
private originalValue: string; private originalValue: string;
constructor(private elementRef: ElementRef, private renderer: Renderer) { } constructor(private elementRef: ElementRef, private renderer: Renderer2) { }
@HostListener('focus') onFocus(): void { @HostListener('focus') onFocus(): void {
this.originalValue = this.elementRef.nativeElement.value; this.originalValue = this.elementRef.nativeElement.value;
} }
@HostListener('keyup.escape') onEscape(): void { @HostListener('keyup.escape') onEscape(): void {
this.renderer.setElementProperty(this.elementRef.nativeElement, 'value', this.originalValue); this.renderer.setProperty(this.elementRef.nativeElement, 'value', this.originalValue);
this.renderer.invokeElementMethod(this.elementRef.nativeElement, 'blur'); this.elementRef.nativeElement.blur();
} }
} }

View File

@@ -0,0 +1,4 @@
export interface MissingDeviceState {
header: string;
subtitle: string;
}

View File

@@ -2,4 +2,5 @@ export interface PrivilagePageSate {
showWhatWillThisDo: boolean; showWhatWillThisDo: boolean;
showWhatWillThisDoContent: boolean; showWhatWillThisDoContent: boolean;
permissionSetupFailed: boolean; permissionSetupFailed: boolean;
updateUdevRules: boolean;
} }

View File

@@ -1,7 +1,14 @@
import { Injectable, NgZone } from '@angular/core'; import { Injectable, NgZone } from '@angular/core';
import { Action, Store } from '@ngrx/store'; import { Action, Store } from '@ngrx/store';
import { DeviceConnectionState, IpcEvents, IpcResponse, LogService, SaveUserConfigurationData } from 'uhk-common'; import {
DeviceConnectionState,
IpcEvents,
IpcResponse,
LogService,
SaveUserConfigurationData,
UpdateFirmwareData
} from 'uhk-common';
import { AppState } from '../store'; import { AppState } from '../store';
import { IpcCommonRenderer } from './ipc-common-renderer'; import { IpcCommonRenderer } from './ipc-common-renderer';
import { import {
@@ -34,12 +41,8 @@ export class DeviceRendererService {
this.ipcRenderer.send(IpcEvents.device.loadConfigurations); this.ipcRenderer.send(IpcEvents.device.loadConfigurations);
} }
updateFirmware(data?: Array<number>): void { updateFirmware(data: UpdateFirmwareData): void {
if (data) { this.ipcRenderer.send(IpcEvents.device.updateFirmware, JSON.stringify(data));
this.ipcRenderer.send(IpcEvents.device.updateFirmware, JSON.stringify(data));
} else {
this.ipcRenderer.send(IpcEvents.device.updateFirmware);
}
} }
startConnectionPoller(): void { startConnectionPoller(): void {

View File

@@ -175,6 +175,13 @@ export class MapperService {
]; ];
} }
public getOsSpecificModifierTextByValue(value: KeyModifiers): string {
const keyModifier = [...this.getLeftKeyModifiers(), ...this.getRightKeyModifiers()]
.find(modifier => modifier.value === value);
return (keyModifier || {text: ''}).text;
}
private initOsSpecificText(): void { private initOsSpecificText(): void {
this.osSpecificTexts = new Map<string, string>(); this.osSpecificTexts = new Map<string, string>();
@@ -305,24 +312,26 @@ export class MapperService {
this.basicScanCodeTextMap.set(113, ['F22']); this.basicScanCodeTextMap.set(113, ['F22']);
this.basicScanCodeTextMap.set(114, ['F23']); this.basicScanCodeTextMap.set(114, ['F23']);
this.basicScanCodeTextMap.set(115, ['F24']); this.basicScanCodeTextMap.set(115, ['F24']);
this.basicScanCodeTextMap.set(135, ['Int1']);
this.basicScanCodeTextMap.set(136, ['Int2']);
this.basicScanCodeTextMap.set(137, ['Int3']);
this.basicScanCodeTextMap.set(144, ['Lang1']);
this.basicScanCodeTextMap.set(145, ['Lang2']);
this.basicScanCodeTextMap.set(176, ['00']); this.basicScanCodeTextMap.set(176, ['00']);
this.basicScanCodeTextMap.set(177, ['000']); this.basicScanCodeTextMap.set(177, ['000']);
this.mediaScanCodeTextMap = new Map<number, string[]>(); this.mediaScanCodeTextMap = new Map<number, string[]>();
this.mediaScanCodeTextMap.set(138, ['WWW']);
this.mediaScanCodeTextMap.set(176, ['Play']); this.mediaScanCodeTextMap.set(176, ['Play']);
this.mediaScanCodeTextMap.set(177, ['Pause']); this.mediaScanCodeTextMap.set(177, ['Pause']);
this.mediaScanCodeTextMap.set(181, ['Next']); this.mediaScanCodeTextMap.set(181, ['Next']);
this.mediaScanCodeTextMap.set(182, ['Prev']); this.mediaScanCodeTextMap.set(182, ['Prev']);
this.mediaScanCodeTextMap.set(183, ['Stop']); this.mediaScanCodeTextMap.set(183, ['Stop']);
this.mediaScanCodeTextMap.set(184, ['Eject']); this.mediaScanCodeTextMap.set(184, ['Eject']);
this.mediaScanCodeTextMap.set(204, ['Eject', 'Stop']);
this.mediaScanCodeTextMap.set(205, ['Pause', 'Play']); this.mediaScanCodeTextMap.set(205, ['Pause', 'Play']);
this.mediaScanCodeTextMap.set(226, ['Mute']); this.mediaScanCodeTextMap.set(226, ['Mute']);
this.mediaScanCodeTextMap.set(233, ['Vol +']); this.mediaScanCodeTextMap.set(233, ['Vol +']);
this.mediaScanCodeTextMap.set(234, ['Vol -']); this.mediaScanCodeTextMap.set(234, ['Vol -']);
this.mediaScanCodeTextMap.set(406, ['Launch Web Browser']);
this.mediaScanCodeTextMap.set(394, ['Launch Email Client']); this.mediaScanCodeTextMap.set(394, ['Launch Email Client']);
this.mediaScanCodeTextMap.set(402, ['Launch Calculator']); this.mediaScanCodeTextMap.set(402, ['Launch Calculator']);
@@ -337,6 +346,8 @@ export class MapperService {
private initScancodeIcons(): void { private initScancodeIcons(): void {
this.basicScancodeIcons = new Map<number, string>(); this.basicScancodeIcons = new Map<number, string>();
this.basicScancodeIcons.set(42, 'icon-kbd__backspace');
this.basicScancodeIcons.set(57, 'icon-kbd__caps-lock');
this.basicScancodeIcons.set(79, 'icon-kbd__mod--arrow-right'); this.basicScancodeIcons.set(79, 'icon-kbd__mod--arrow-right');
this.basicScancodeIcons.set(80, 'icon-kbd__mod--arrow-left'); this.basicScancodeIcons.set(80, 'icon-kbd__mod--arrow-left');
this.basicScancodeIcons.set(81, 'icon-kbd__mod--arrow-down'); this.basicScancodeIcons.set(81, 'icon-kbd__mod--arrow-down');
@@ -344,17 +355,16 @@ export class MapperService {
this.basicScancodeIcons.set(101, 'icon-kbd__mod--menu'); this.basicScancodeIcons.set(101, 'icon-kbd__mod--menu');
this.mediaScancodeIcons = new Map<number, string>(); this.mediaScancodeIcons = new Map<number, string>();
this.mediaScancodeIcons.set(138, 'icon-kbd__fn--browser');
this.mediaScancodeIcons.set(176, 'icon-kbd__media--play'); this.mediaScancodeIcons.set(176, 'icon-kbd__media--play');
this.mediaScancodeIcons.set(177, 'icon-kbd__media--pause'); this.mediaScancodeIcons.set(177, 'icon-kbd__media--pause');
this.mediaScancodeIcons.set(181, 'icon-kbd__media--next'); this.mediaScancodeIcons.set(181, 'icon-kbd__media--next');
this.mediaScancodeIcons.set(182, 'icon-kbd__media--prev'); this.mediaScancodeIcons.set(182, 'icon-kbd__media--prev');
this.mediaScancodeIcons.set(184, 'icon-kbd__fn--eject'); this.mediaScancodeIcons.set(184, 'icon-kbd__fn--eject');
this.mediaScancodeIcons.set(205, 'icon-kbd__play-pause');
this.mediaScancodeIcons.set(226, 'icon-kbd__media--mute'); this.mediaScancodeIcons.set(226, 'icon-kbd__media--mute');
this.mediaScancodeIcons.set(233, 'icon-kbd__media--vol-up'); this.mediaScancodeIcons.set(233, 'icon-kbd__media--vol-up');
this.mediaScancodeIcons.set(234, 'icon-kbd__media--vol-down'); this.mediaScancodeIcons.set(234, 'icon-kbd__media--vol-down');
this.mediaScancodeIcons.set(406, 'icon-kbd__media--web-browser');
this.mediaScancodeIcons.set(394, 'icon-kbd__media--email-client'); this.mediaScancodeIcons.set(394, 'icon-kbd__media--email-client');
this.mediaScancodeIcons.set(402, 'icon-kbd__media--calculator'); this.mediaScancodeIcons.set(402, 'icon-kbd__media--calculator');

View File

@@ -111,6 +111,7 @@ import { UhkDeviceBootloaderNotActiveGuard } from './services/uhk-device-bootloa
import { FileUploadComponent } from './components/file-upload'; import { FileUploadComponent } from './components/file-upload';
import { AutoGrowInputComponent } from './components/auto-grow-input'; import { AutoGrowInputComponent } from './components/auto-grow-input';
import { HelpPageComponent } from './components/agent/help-page/help-page.component'; import { HelpPageComponent } from './components/agent/help-page/help-page.component';
import { UdevRulesComponent } from './components/udev-rules/udev-rules.component';
@NgModule({ @NgModule({
declarations: [ declarations: [
@@ -188,7 +189,8 @@ import { HelpPageComponent } from './components/agent/help-page/help-page.compon
AutoGrowInputComponent, AutoGrowInputComponent,
HelpPageComponent, HelpPageComponent,
ExternalUrlDirective, ExternalUrlDirective,
SvgSecondaryRoleComponent SvgSecondaryRoleComponent,
UdevRulesComponent
], ],
imports: [ imports: [
CommonModule, CommonModule,

View File

@@ -20,7 +20,9 @@ export const ActionTypes = {
OPEN_URL_IN_NEW_WINDOW: type(PREFIX + 'Open URL in new Window'), OPEN_URL_IN_NEW_WINDOW: type(PREFIX + 'Open URL in new Window'),
PRIVILEGE_WHAT_WILL_THIS_DO: type(PREFIX + 'What will this do clicked'), PRIVILEGE_WHAT_WILL_THIS_DO: type(PREFIX + 'What will this do clicked'),
SETUP_PERMISSION_ERROR: type(PREFIX + 'Setup permission error'), SETUP_PERMISSION_ERROR: type(PREFIX + 'Setup permission error'),
LOAD_APP_START_INFO: type(PREFIX + 'Load app start info') LOAD_APP_START_INFO: type(PREFIX + 'Load app start info'),
START_KEYPRESS_CAPTURING: type(PREFIX + 'Start keypress capturing'),
STOP_KEYPRESS_CAPTURING: type(PREFIX + 'Stop keypress capturing')
}; };
export class AppBootsrappedAction implements Action { export class AppBootsrappedAction implements Action {
@@ -103,6 +105,14 @@ export class LoadAppStartInfoAction implements Action {
type = ActionTypes.LOAD_APP_START_INFO; type = ActionTypes.LOAD_APP_START_INFO;
} }
export class StartKeypressCapturingAction implements Action {
type = ActionTypes.START_KEYPRESS_CAPTURING;
}
export class StopKeypressCapturingAction implements Action {
type = ActionTypes.STOP_KEYPRESS_CAPTURING;
}
export type Actions export type Actions
= AppStartedAction = AppStartedAction
| AppBootsrappedAction | AppBootsrappedAction
@@ -118,4 +128,6 @@ export type Actions
| PrivilegeWhatWillThisDoAction | PrivilegeWhatWillThisDoAction
| SetupPermissionErrorAction | SetupPermissionErrorAction
| LoadAppStartInfoAction | LoadAppStartInfoAction
| StartKeypressCapturingAction
| StopKeypressCapturingAction
; ;

View File

@@ -66,11 +66,7 @@ export class ApplicationEffects {
this.logService.debug('[AppEffect][processStartInfo] payload:', appInfo); this.logService.debug('[AppEffect][processStartInfo] payload:', appInfo);
return [ return [
new ApplyAppStartInfoAction(appInfo), new ApplyAppStartInfoAction(appInfo),
new ConnectionStateChangedAction({ new ConnectionStateChangedAction(appInfo.deviceConnectionState)
connected: appInfo.deviceConnected,
hasPermission: appInfo.hasPermission,
bootloaderActive: appInfo.bootloaderActive
})
]; ];
}); });

View File

@@ -18,6 +18,7 @@ import {
HardwareConfiguration, HardwareConfiguration,
IpcResponse, IpcResponse,
NotificationType, NotificationType,
UdevRulesInfo,
UserConfiguration UserConfiguration
} from 'uhk-common'; } from 'uhk-common';
import { import {
@@ -39,9 +40,10 @@ import {
UpdateFirmwareSuccessAction, UpdateFirmwareSuccessAction,
UpdateFirmwareWithAction UpdateFirmwareWithAction
} from '../actions/device'; } from '../actions/device';
import { AppRendererService } from '../../services/app-renderer.service';
import { DeviceRendererService } from '../../services/device-renderer.service'; import { DeviceRendererService } from '../../services/device-renderer.service';
import { SetupPermissionErrorAction, ShowNotificationAction } from '../actions/app'; import { SetupPermissionErrorAction, ShowNotificationAction } from '../actions/app';
import { AppState, getRouterState } from '../index'; import { AppState, deviceConnected, getRouterState } from '../index';
import { import {
ActionTypes as UserConfigActions, ActionTypes as UserConfigActions,
ApplyUserConfigurationFromFileAction, ApplyUserConfigurationFromFileAction,
@@ -50,13 +52,14 @@ import {
} from '../actions/user-config'; } from '../actions/user-config';
import { DefaultUserConfigurationService } from '../../services/default-user-configuration.service'; import { DefaultUserConfigurationService } from '../../services/default-user-configuration.service';
import { DataStorageRepositoryService } from '../../services/datastorage-repository.service'; import { DataStorageRepositoryService } from '../../services/datastorage-repository.service';
import { getVersions } from '../../util';
@Injectable() @Injectable()
export class DeviceEffects { export class DeviceEffects {
@Effect() @Effect()
deviceConnectionStateChange$: Observable<Action> = this.actions$ deviceConnectionStateChange$: Observable<Action> = this.actions$
.ofType<ConnectionStateChangedAction>(ActionTypes.CONNECTION_STATE_CHANGED) .ofType<ConnectionStateChangedAction>(ActionTypes.CONNECTION_STATE_CHANGED)
.withLatestFrom(this.store.select(getRouterState)) .withLatestFrom(this.store.select(getRouterState), this.store.select(deviceConnected))
.do(([action, route]) => { .do(([action, route]) => {
const state = action.payload; const state = action.payload;
@@ -64,23 +67,24 @@ export class DeviceEffects {
return; return;
} }
if (!state.hasPermission) { if (!state.hasPermission || !state.zeroInterfaceAvailable) {
this.router.navigate(['/privilege']); return this.router.navigate(['/privilege']);
} }
else if (state.bootloaderActive) {
this.router.navigate(['/recovery-device']);
}
else if (state.connected) {
this.router.navigate(['/']);
}
else {
this.router.navigate(['/detection']);
}
})
.switchMap(([action, route]) => {
const state = action.payload;
if (state.connected && state.hasPermission) { if (state.bootloaderActive) {
return this.router.navigate(['/recovery-device']);
}
if (state.connected && state.zeroInterfaceAvailable) {
return this.router.navigate(['/']);
}
return this.router.navigate(['/detection']);
})
.switchMap(([action, route, connected]) => {
const payload = action.payload;
if (connected && payload.hasPermission && payload.zeroInterfaceAvailable) {
return Observable.of(new LoadConfigFromDeviceAction()); return Observable.of(new LoadConfigFromDeviceAction());
} }
@@ -98,16 +102,13 @@ export class DeviceEffects {
setPrivilegeOnLinuxReply$: Observable<Action> = this.actions$ setPrivilegeOnLinuxReply$: Observable<Action> = this.actions$
.ofType<SetPrivilegeOnLinuxReplyAction>(ActionTypes.SET_PRIVILEGE_ON_LINUX_REPLY) .ofType<SetPrivilegeOnLinuxReplyAction>(ActionTypes.SET_PRIVILEGE_ON_LINUX_REPLY)
.map(action => action.payload) .map(action => action.payload)
.map((response: any): any => { .switchMap((response: any): any => {
if (response.success) { if (response.success) {
return new ConnectionStateChangedAction({ this.appRendererService.getAppStartInfo();
connected: true, return Observable.empty();
hasPermission: true,
bootloaderActive: false
});
} }
return new SetupPermissionErrorAction(response.error); return Observable.of(new SetupPermissionErrorAction(response.error));
}); });
@Effect({dispatch: false}) @Effect({dispatch: false})
@@ -201,12 +202,17 @@ export class DeviceEffects {
@Effect({dispatch: false}) updateFirmware$ = this.actions$ @Effect({dispatch: false}) updateFirmware$ = this.actions$
.ofType<UpdateFirmwareAction>(ActionTypes.UPDATE_FIRMWARE) .ofType<UpdateFirmwareAction>(ActionTypes.UPDATE_FIRMWARE)
.do(() => this.deviceRendererService.updateFirmware()); .do(() => this.deviceRendererService.updateFirmware({
versionInformation: getVersions()
}));
@Effect({dispatch: false}) updateFirmwareWith$ = this.actions$ @Effect({dispatch: false}) updateFirmwareWith$ = this.actions$
.ofType<UpdateFirmwareWithAction>(ActionTypes.UPDATE_FIRMWARE_WITH) .ofType<UpdateFirmwareWithAction>(ActionTypes.UPDATE_FIRMWARE_WITH)
.map(action => action.payload) .map(action => action.payload)
.do(data => this.deviceRendererService.updateFirmware(data)); .do(data => this.deviceRendererService.updateFirmware({
versionInformation: getVersions(),
firmware: data
}));
@Effect() updateFirmwareReply$ = this.actions$ @Effect() updateFirmwareReply$ = this.actions$
.ofType<UpdateFirmwareReplyAction>(ActionTypes.UPDATE_FIRMWARE_REPLY) .ofType<UpdateFirmwareReplyAction>(ActionTypes.UPDATE_FIRMWARE_REPLY)
@@ -237,6 +243,7 @@ export class DeviceEffects {
constructor(private actions$: Actions, constructor(private actions$: Actions,
private router: Router, private router: Router,
private appRendererService: AppRendererService,
private deviceRendererService: DeviceRendererService, private deviceRendererService: DeviceRendererService,
private store: Store<AppState>, private store: Store<AppState>,
private dataStorageRepository: DataStorageRepositoryService, private dataStorageRepository: DataStorageRepositoryService,

View File

@@ -1,8 +1,8 @@
import { createSelector } from 'reselect'; import { createSelector } from 'reselect';
import { ActionReducerMap, MetaReducer } from '@ngrx/store'; import { ActionReducerMap, MetaReducer } from '@ngrx/store';
import { RouterReducerState, routerReducer } from '@ngrx/router-store'; import { routerReducer, RouterReducerState } from '@ngrx/router-store';
import { storeFreeze } from 'ngrx-store-freeze'; import { storeFreeze } from 'ngrx-store-freeze';
import { Keymap, UserConfiguration } from 'uhk-common'; import { HardwareModules, Keymap, UserConfiguration } from 'uhk-common';
import * as fromUserConfig from './reducers/user-configuration'; import * as fromUserConfig from './reducers/user-configuration';
import * as fromPreset from './reducers/preset'; import * as fromPreset from './reducers/preset';
@@ -14,6 +14,8 @@ import * as fromSelectors from './reducers/selectors';
import { initProgressButtonState } from './reducers/progress-button-state'; import { initProgressButtonState } from './reducers/progress-button-state';
import { environment } from '../../environments/environment'; import { environment } from '../../environments/environment';
import { RouterStateUrl } from './router-util'; import { RouterStateUrl } from './router-util';
import { PrivilagePageSate } from '../models/privilage-page-sate';
import { isVersionGte } from '../util';
// State interface for the application // State interface for the application
export interface AppState { export interface AppState {
@@ -45,15 +47,14 @@ export const getUserConfiguration = (state: AppState) => state.userConfiguration
export const appState = (state: AppState) => state.app; export const appState = (state: AppState) => state.app;
export const showAddonMenu = createSelector(appState, fromApp.showAddonMenu); export const showAddonMenu = createSelector(appState, fromApp.showAddonMenu);
export const allowLayerDoubleTap = createSelector(appState, fromApp.allowLayerDoubleTap);
export const getUndoableNotification = createSelector(appState, fromApp.getUndoableNotification); export const getUndoableNotification = createSelector(appState, fromApp.getUndoableNotification);
export const getPrevUserConfiguration = createSelector(appState, fromApp.getPrevUserConfiguration); export const getPrevUserConfiguration = createSelector(appState, fromApp.getPrevUserConfiguration);
export const runningInElectron = createSelector(appState, fromApp.runningInElectron); export const runningInElectron = createSelector(appState, fromApp.runningInElectron);
export const getKeyboardLayout = createSelector(appState, fromApp.getKeyboardLayout); export const getKeyboardLayout = createSelector(appState, fromApp.getKeyboardLayout);
export const deviceConfigurationLoaded = createSelector(appState, fromApp.deviceConfigurationLoaded); export const deviceConfigurationLoaded = createSelector(appState, fromApp.deviceConfigurationLoaded);
export const getAgentVersionInfo = createSelector(appState, fromApp.getAgentVersionInfo); export const getAgentVersionInfo = createSelector(appState, fromApp.getAgentVersionInfo);
export const getPrivilegePageState = createSelector(appState, fromApp.getPrivilagePageState);
export const getOperatingSystem = createSelector(appState, fromSelectors.getOperatingSystem); export const getOperatingSystem = createSelector(appState, fromSelectors.getOperatingSystem);
export const keypressCapturing = createSelector(appState, fromApp.keypressCapturing);
export const runningOnNotSupportedWindows = createSelector(appState, fromApp.runningOnNotSupportedWindows); export const runningOnNotSupportedWindows = createSelector(appState, fromApp.runningOnNotSupportedWindows);
export const firmwareUpgradeAllowed = createSelector(runningOnNotSupportedWindows, notSupportedOs => !notSupportedOs); export const firmwareUpgradeAllowed = createSelector(runningOnNotSupportedWindows, notSupportedOs => !notSupportedOs);
@@ -65,14 +66,21 @@ export const getAutoUpdateSettings = createSelector(appUpdateSettingsState, auto
export const getCheckingForUpdate = createSelector(appUpdateSettingsState, autoUpdateSettings.checkingForUpdate); export const getCheckingForUpdate = createSelector(appUpdateSettingsState, autoUpdateSettings.checkingForUpdate);
export const deviceState = (state: AppState) => state.device; export const deviceState = (state: AppState) => state.device;
export const isDeviceConnected = createSelector(deviceState, fromDevice.isDeviceConnected); export const deviceConnected = createSelector(
export const deviceConnected = createSelector(runningInElectron, isDeviceConnected, (electron, connected) => { runningInElectron, deviceState, appState,
return !electron ? true : connected; (electron, device, app) => {
}); if (!electron) {
export const devicePermission = createSelector(deviceState, fromDevice.hasDevicePermission); return true;
export const hasDevicePermission = createSelector(runningInElectron, devicePermission, (electron, permission) => { }
return !electron ? true : permission;
}); if (app.platform === 'linux') {
return device.connected && (device.zeroInterfaceAvailable || device.updatingFirmware);
}
return device.connected;
});
export const hasDevicePermission = createSelector(deviceState, fromDevice.hasDevicePermission);
export const getMissingDeviceState = createSelector(deviceState, fromDevice.getMissingDeviceState);
export const saveToKeyboardStateSelector = createSelector(deviceState, fromDevice.getSaveToKeyboardState); export const saveToKeyboardStateSelector = createSelector(deviceState, fromDevice.getSaveToKeyboardState);
export const saveToKeyboardState = createSelector(runningInElectron, saveToKeyboardStateSelector, (electron, state) => { export const saveToKeyboardState = createSelector(runningInElectron, saveToKeyboardStateSelector, (electron, state) => {
return electron ? state : initProgressButtonState; return electron ? state : initProgressButtonState;
@@ -87,6 +95,18 @@ export const getRestoreUserConfiguration = createSelector(deviceState, fromDevic
export const bootloaderActive = createSelector(deviceState, fromDevice.bootloaderActive); export const bootloaderActive = createSelector(deviceState, fromDevice.bootloaderActive);
export const firmwareUpgradeFailed = createSelector(deviceState, fromDevice.firmwareUpgradeFailed); export const firmwareUpgradeFailed = createSelector(deviceState, fromDevice.firmwareUpgradeFailed);
export const firmwareUpgradeSuccess = createSelector(deviceState, fromDevice.firmwareUpgradeSuccess); export const firmwareUpgradeSuccess = createSelector(deviceState, fromDevice.firmwareUpgradeSuccess);
export const getUpdateUdevRules = createSelector(deviceState, fromDevice.updateUdevRules);
export const getPrivilegePageState = createSelector(appState, getUpdateUdevRules, (app, updateUdevRules): PrivilagePageSate => {
const permissionSetupFailed = !!app.permissionError;
return {
permissionSetupFailed,
updateUdevRules,
showWhatWillThisDo: !app.privilegeWhatWillThisDoClicked && !permissionSetupFailed,
showWhatWillThisDoContent: app.privilegeWhatWillThisDoClicked || permissionSetupFailed
};
});
export const getSideMenuPageState = createSelector( export const getSideMenuPageState = createSelector(
showAddonMenu, showAddonMenu,
@@ -112,3 +132,13 @@ export const getSideMenuPageState = createSelector(
); );
export const getRouterState = (state: AppState) => state.router; export const getRouterState = (state: AppState) => state.router;
export const macroPlaybackSupported = createSelector(getHardwareModules, (hardwareModules: HardwareModules): boolean => {
return isVersionGte(hardwareModules.rightModuleInfo.firmwareVersion, '8.4.3');
});
export const layerDoubleTapSupported = createSelector(
getHardwareModules,
(hardwareModules: HardwareModules): boolean => {
return isVersionGte(hardwareModules.rightModuleInfo.firmwareVersion, '8.4.3');
}
);

View File

@@ -32,6 +32,7 @@ export interface State {
permissionError?: any; permissionError?: any;
platform?: string; platform?: string;
osVersion?: string; osVersion?: string;
keypressCapturing: boolean;
} }
export const initialState: State = { export const initialState: State = {
@@ -41,7 +42,8 @@ export const initialState: State = {
runningInElectron: runInElectron(), runningInElectron: runInElectron(),
configLoading: true, configLoading: true,
agentVersionInfo: getVersions(), agentVersionInfo: getVersions(),
privilegeWhatWillThisDoClicked: false privilegeWhatWillThisDoClicked: false,
keypressCapturing: false
}; };
export function reducer(state = initialState, action: Action & { payload: any }) { export function reducer(state = initialState, action: Action & { payload: any }) {
@@ -151,13 +153,24 @@ export function reducer(state = initialState, action: Action & { payload: any })
permissionError: null permissionError: null
}; };
case ActionTypes.START_KEYPRESS_CAPTURING:
return {
...state,
keypressCapturing: true
};
case ActionTypes.STOP_KEYPRESS_CAPTURING:
return {
...state,
keypressCapturing: false
};
default: default:
return state; return state;
} }
} }
export const showAddonMenu = (state: State) => state.commandLineArgs.addons; export const showAddonMenu = (state: State) => state.commandLineArgs.addons;
export const allowLayerDoubleTap = (state: State) => state.commandLineArgs.layerDoubleTap;
export const getUndoableNotification = (state: State) => state.undoableNotification; export const getUndoableNotification = (state: State) => state.undoableNotification;
export const getPrevUserConfiguration = (state: State) => state.prevUserConfig; export const getPrevUserConfiguration = (state: State) => state.prevUserConfig;
export const runningInElectron = (state: State) => state.runningInElectron; export const runningInElectron = (state: State) => state.runningInElectron;
@@ -170,15 +183,6 @@ export const getKeyboardLayout = (state: State): KeyboardLayout => {
}; };
export const deviceConfigurationLoaded = (state: State) => !state.runningInElectron ? true : !!state.hardwareConfig; export const deviceConfigurationLoaded = (state: State) => !state.runningInElectron ? true : !!state.hardwareConfig;
export const getAgentVersionInfo = (state: State) => state.agentVersionInfo || {} as VersionInformation; export const getAgentVersionInfo = (state: State) => state.agentVersionInfo || {} as VersionInformation;
export const getPrivilagePageState = (state: State): PrivilagePageSate => {
const permissionSetupFailed = !!state.permissionError;
return {
permissionSetupFailed,
showWhatWillThisDo: !state.privilegeWhatWillThisDoClicked && !permissionSetupFailed,
showWhatWillThisDoContent: state.privilegeWhatWillThisDoClicked || permissionSetupFailed
};
};
export const runningOnNotSupportedWindows = (state: State): boolean => { export const runningOnNotSupportedWindows = (state: State): boolean => {
if (!state.osVersion || state.platform !== 'win32') { if (!state.osVersion || state.platform !== 'win32') {
@@ -191,3 +195,5 @@ export const runningOnNotSupportedWindows = (state: State): boolean => {
return osMajor < 6 || (osMajor === 6 && osMinor < 2); return osMajor < 6 || (osMajor === 6 && osMinor < 2);
}; };
export const keypressCapturing = (state: State): boolean => state.keypressCapturing;

View File

@@ -1,12 +1,12 @@
import { Action } from '@ngrx/store'; import { Action } from '@ngrx/store';
import { HardwareModules } from 'uhk-common'; import { HardwareModules, UdevRulesInfo } from 'uhk-common';
import { import {
ActionTypes, ActionTypes,
ConnectionStateChangedAction, ConnectionStateChangedAction,
HardwareModulesLoadedAction, HardwareModulesLoadedAction,
SaveConfigurationAction,
HasBackupUserConfigurationAction, HasBackupUserConfigurationAction,
SaveConfigurationAction,
UpdateFirmwareFailedAction, UpdateFirmwareFailedAction,
UpdateFirmwareSuccessAction UpdateFirmwareSuccessAction
} from '../actions/device'; } from '../actions/device';
@@ -14,11 +14,14 @@ import { ActionTypes as AppActions, ElectronMainLogReceivedAction } from '../act
import { initProgressButtonState, ProgressButtonState } from './progress-button-state'; import { initProgressButtonState, ProgressButtonState } from './progress-button-state';
import { XtermCssClass, XtermLog } from '../../models/xterm-log'; import { XtermCssClass, XtermLog } from '../../models/xterm-log';
import { RestoreConfigurationState } from '../../models/restore-configuration-state'; import { RestoreConfigurationState } from '../../models/restore-configuration-state';
import { MissingDeviceState } from '../../models/missing-device-state';
export interface State { export interface State {
connected: boolean; connected: boolean;
hasPermission: boolean; hasPermission: boolean;
bootloaderActive: boolean; bootloaderActive: boolean;
zeroInterfaceAvailable: boolean;
udevRuleInfo: UdevRulesInfo;
saveToKeyboard: ProgressButtonState; saveToKeyboard: ProgressButtonState;
savingToKeyboard: boolean; savingToKeyboard: boolean;
updatingFirmware: boolean; updatingFirmware: boolean;
@@ -35,6 +38,8 @@ export const initialState: State = {
connected: true, connected: true,
hasPermission: true, hasPermission: true,
bootloaderActive: false, bootloaderActive: false,
zeroInterfaceAvailable: true,
udevRuleInfo: UdevRulesInfo.Unkonwn,
saveToKeyboard: initProgressButtonState, saveToKeyboard: initProgressButtonState,
savingToKeyboard: false, savingToKeyboard: false,
updatingFirmware: false, updatingFirmware: false,
@@ -61,7 +66,9 @@ export function reducer(state = initialState, action: Action): State {
...state, ...state,
connected: data.connected, connected: data.connected,
hasPermission: data.hasPermission, hasPermission: data.hasPermission,
bootloaderActive: data.bootloaderActive zeroInterfaceAvailable: data.zeroInterfaceAvailable,
bootloaderActive: data.bootloaderActive,
udevRuleInfo: data.udevRulesInfo
}; };
} }
@@ -222,7 +229,20 @@ export function reducer(state = initialState, action: Action): State {
export const updatingFirmware = (state: State) => state.updatingFirmware; export const updatingFirmware = (state: State) => state.updatingFirmware;
export const isDeviceConnected = (state: State) => state.connected || state.updatingFirmware; export const isDeviceConnected = (state: State) => state.connected || state.updatingFirmware;
export const hasDevicePermission = (state: State) => state.hasPermission; export const hasDevicePermission = (state: State) => state.udevRuleInfo === UdevRulesInfo.Ok;
export const getMissingDeviceState = (state: State): MissingDeviceState => {
if (state.connected && !state.zeroInterfaceAvailable) {
return {
header: 'Cannot find your UHK',
subtitle: 'Please reconnect it!'
};
}
return {
header: 'Cannot find your UHK',
subtitle: 'Please plug it in!'
};
};
export const getSaveToKeyboardState = (state: State) => state.saveToKeyboard; export const getSaveToKeyboardState = (state: State) => state.saveToKeyboard;
export const xtermLog = (state: State) => state.log; export const xtermLog = (state: State) => state.log;
export const getHardwareModules = (state: State) => state.modules; export const getHardwareModules = (state: State) => state.modules;
@@ -236,3 +256,4 @@ export const getBackupUserConfigurationState = (state: State): RestoreConfigurat
export const bootloaderActive = (state: State) => state.bootloaderActive; export const bootloaderActive = (state: State) => state.bootloaderActive;
export const firmwareUpgradeFailed = (state: State) => state.firmwareUpdateFailed; export const firmwareUpgradeFailed = (state: State) => state.firmwareUpdateFailed;
export const firmwareUpgradeSuccess = (state: State) => state.firmwareUpdateSuccess; export const firmwareUpgradeSuccess = (state: State) => state.firmwareUpdateSuccess;
export const updateUdevRules = (state: State) => state.udevRuleInfo === UdevRulesInfo.Different;

View File

@@ -1,3 +1,4 @@
import * as semver from 'semver';
import { VersionInformation } from 'uhk-common'; import { VersionInformation } from 'uhk-common';
const collectVersions = (): VersionInformation => { const collectVersions = (): VersionInformation => {
@@ -20,3 +21,11 @@ export const getVersions = (): VersionInformation => {
} }
return versions; return versions;
}; };
export const isVersionGte = (v1: string, v2: string): boolean => {
if (!v1) {
return false;
}
return semver.gte(v1, v2);
};

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 88 KiB

After

Width:  |  Height:  |  Size: 18 KiB

View File

@@ -16,6 +16,10 @@ html, body {
height: 100%; height: 100%;
} }
.uhk-icon-pure-agent-icon {
background: url('assets/images/agent-icon.png') no-repeat;
}
.uhk-icon { .uhk-icon {
display: inline-block; display: inline-block;
width: 1em; width: 1em;

View File

@@ -1 +1 @@
<?xml version="1.0" encoding="utf-8"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg width="31" height="16" viewBox="0 0 31 16" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><svg width="16" height="16" viewBox="0 0 16 16" id="icon-0401-usb-stick" xmlns="http://www.w3.org/2000/svg"><path d="M6.5 2a.5.5 0 0 0-.5.5v1a.5.5 0 0 0 1 0v-1a.5.5 0 0 0-.5-.5zM8.5 2a.5.5 0 0 0-.5.5v1a.5.5 0 0 0 1 0v-1a.5.5 0 0 0-.5-.5z"/><path d="M11.5 5H11V.5a.5.5 0 0 0-.5-.5h-6a.5.5 0 0 0-.5.5V5h-.5a.5.5 0 0 0-.5.5v9.375c1 1.5 8 1.5 9 0V5.5a.5.5 0 0 0-.5-.5zM5 13.5a.5.5 0 0 1-1 0v-6a.5.5 0 0 1 1 0v6zM10 5H5V1h5v4z"/></svg><svg width="15" height="15" viewBox="0 0 15 15" id="icon-agent-icon" x="16" xmlns="http://www.w3.org/2000/svg"><defs><linearGradient x2="1" gradientUnits="userSpaceOnUse" gradientTransform="matrix(12 0 0 -12 0 6)" id="abb"><stop offset="0" stop-color="#85878d"/><stop offset=".001" stop-color="#85878d"/><stop offset=".001" stop-color="#5d5f63"/><stop offset=".526" stop-color="#ccc"/><stop offset="1" stop-color="#5d5c62"/></linearGradient><linearGradient id="aba" gradientTransform="matrix(12 0 0 -12 0 6)" gradientUnits="userSpaceOnUse" x2="1"><stop offset="0" stop-color="#85878d"/><stop offset=".001" stop-color="#85878d"/><stop offset=".001" stop-color="#85878d"/><stop offset=".495" stop-color="#ccc"/><stop offset="1" stop-color="#5d5c62"/></linearGradient><clipPath id="abc"><path d="M2.398 12A2.393 2.393 0 0 1 0 9.602V2.398A2.393 2.393 0 0 1 2.398 0h7.203A2.394 2.394 0 0 1 12 2.398v7.204A2.394 2.394 0 0 1 9.601 12H2.398zM.602 7.199v1.199c0 .454.329.575.5.602.054.008.097 0 .097 0h3.602c.301 0 .597-.301.597-.301s.301-.301.602-.301.602.301.602.301.296.301.597.301h3.602c.597 0 .597-.602.597-.602V7.199c0-1.801-1.199-1.801-1.199-1.801h-3c-.301 0-.597.903-.597.903s-.301.898-.602.898-.602-.898-.602-.898-.296-.903-.597-.903h-3s-1.199 0-1.199 1.801"/></clipPath><linearGradient gradientUnits="userSpaceOnUse" x2="11.746" y1="11.542" x1=".915" id="abd" xlink:href="#aba"/><linearGradient y2="-.051" x2="12" y1="11.898" x1=".102" gradientUnits="userSpaceOnUse" id="abe" xlink:href="#abb"/></defs><g transform="matrix(1.25 0 0 -1.25 0 15)"><path d="M11.473 5.117H.416v4.266h11.057V5.117z" fill="#343434"/><g clip-path="url(#abc)" fill="url(#abd)"><path d="M2.398 12A2.393 2.393 0 0 1 0 9.602V2.398A2.393 2.393 0 0 1 2.398 0h7.203A2.394 2.394 0 0 1 12 2.398v7.204A2.394 2.394 0 0 1 9.601 12H2.398zM.602 7.199v1.199c0 .454.329.575.5.602.054.008.097 0 .097 0h3.602c.301 0 .597-.301.597-.301s.301-.301.602-.301.602.301.602.301.296.301.597.301h3.602c.597 0 .597-.602.597-.602V7.199c0-1.801-1.199-1.801-1.199-1.801h-3c-.301 0-.597.903-.597.903s-.301.898-.602.898-.602-.898-.602-.898-.296-.903-.597-.903h-3s-1.199 0-1.199 1.801" fill="url(#abe)"/></g></g></svg></svg> <?xml version="1.0" encoding="utf-8"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg width="31" height="16" viewBox="0 0 31 16" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><svg width="16" height="16" viewBox="0 0 16 16" id="icon-0401-usb-stick" xmlns="http://www.w3.org/2000/svg"><path d="M6.5 2a.5.5 0 0 0-.5.5v1a.5.5 0 0 0 1 0v-1a.5.5 0 0 0-.5-.5zM8.5 2a.5.5 0 0 0-.5.5v1a.5.5 0 0 0 1 0v-1a.5.5 0 0 0-.5-.5z"/><path d="M11.5 5H11V.5a.5.5 0 0 0-.5-.5h-6a.5.5 0 0 0-.5.5V5h-.5a.5.5 0 0 0-.5.5v9.375c1 1.5 8 1.5 9 0V5.5a.5.5 0 0 0-.5-.5zM5 13.5a.5.5 0 0 1-1 0v-6a.5.5 0 0 1 1 0v6zM10 5H5V1h5v4z"/></svg><svg width="15" height="15" viewBox="0 0 15 15" id="icon-agent-icon" x="16" xmlns="http://www.w3.org/2000/svg"><defs><linearGradient x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse" gradientTransform="matrix(12 0 0 -12 0 6)" spreadMethod="pad" id="bb"><stop offset="0" stop-color="#85878d"/><stop offset=".001" stop-color="#85878d"/><stop offset=".001" stop-color="#5d5f63"/><stop offset=".526" stop-color="#ccc"/><stop offset="1" stop-color="#5d5c62"/></linearGradient><linearGradient id="ba" spreadMethod="pad" gradientTransform="matrix(12 0 0 -12 0 6)" gradientUnits="userSpaceOnUse" y2="0" x2="1" y1="0" x1="0"><stop offset="0" stop-color="#85878d"/><stop offset=".001" stop-color="#85878d"/><stop offset=".001" stop-color="#85878d"/><stop offset=".495" stop-color="#ccc"/><stop offset="1" stop-color="#5d5c62"/></linearGradient><clipPath clipPathUnits="userSpaceOnUse" id="bc"><path d="M2.398 12A2.393 2.393 0 0 1 0 9.602V2.398A2.393 2.393 0 0 1 2.398 0h7.203A2.394 2.394 0 0 1 12 2.398v7.204A2.394 2.394 0 0 1 9.601 12H2.398zM.602 7.199v1.199c0 .454.329.575.5.602.054.008.097 0 .097 0h3.602c.301 0 .597-.301.597-.301s.301-.301.602-.301.602.301.602.301.296.301.597.301h3.602c.597 0 .597-.602.597-.602V7.199c0-1.801-1.199-1.801-1.199-1.801h-3c-.301 0-.597.903-.597.903s-.301.898-.602.898-.602-.898-.602-.898-.296-.903-.597-.903h-3s-1.199 0-1.199 1.801"/></clipPath><linearGradient gradientUnits="userSpaceOnUse" y2="0" x2="11.746" y1="11.542" x1=".915" id="bd" xlink:href="#ba"/><linearGradient y2="-.051" x2="12" y1="11.898" x1=".102" gradientUnits="userSpaceOnUse" id="be" xlink:href="#bb"/></defs><path d="M14.341 8.604H.52V3.27h13.821v5.333z" fill="#343434"/><g clip-path="url(#bc)" fill="url(#bd)" transform="matrix(1.25 0 0 -1.25 0 15)"><path d="M2.398 12A2.393 2.393 0 0 1 0 9.602V2.398A2.393 2.393 0 0 1 2.398 0h7.203A2.394 2.394 0 0 1 12 2.398v7.204A2.394 2.394 0 0 1 9.601 12H2.398zM.602 7.199v1.199c0 .454.329.575.5.602.054.008.097 0 .097 0h3.602c.301 0 .597-.301.597-.301s.301-.301.602-.301.602.301.602.301.296.301.597.301h3.602c.597 0 .597-.602.597-.602V7.199c0-1.801-1.199-1.801-1.199-1.801h-3c-.301 0-.597.903-.597.903s-.301.898-.602.898-.602-.898-.602-.898-.296-.903-.597-.903h-3s-1.199 0-1.199 1.801" fill="url(#be)"/></g></svg></svg>

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

View File

@@ -8,5 +8,6 @@
} }
.uhk-icon-agent-icon { .uhk-icon-agent-icon {
background: url('assets/images/agent-icon.png') no-repeat; @extend %svg-common;
background-position: 100% 0;
} }

View File

@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generated by IcoMoon.io -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16" viewBox="0 0 16 16">
<path fill="#000000" d="M8 0c-4.418 0-8 3.582-8 8s3.582 8 8 8 8-3.582 8-8-3.582-8-8-8zM8 15c-0.984 0-1.92-0.203-2.769-0.57l3.643-4.098c0.081-0.092 0.126-0.21 0.126-0.332v-1.5c0-0.276-0.224-0.5-0.5-0.5-1.765 0-3.628-1.835-3.646-1.854-0.094-0.094-0.221-0.146-0.354-0.146h-2c-0.276 0-0.5 0.224-0.5 0.5v3c0 0.189 0.107 0.363 0.276 0.447l1.724 0.862v2.936c-1.813-1.265-3-3.366-3-5.745 0-1.074 0.242-2.091 0.674-3h1.826c0.133 0 0.26-0.053 0.354-0.146l2-2c0.094-0.094 0.146-0.221 0.146-0.354v-1.21c0.634-0.189 1.305-0.29 2-0.29 1.1 0 2.141 0.254 3.067 0.706-0.065 0.055-0.128 0.112-0.188 0.172-0.567 0.567-0.879 1.32-0.879 2.121s0.312 1.555 0.879 2.121c0.569 0.569 1.332 0.879 2.119 0.879 0.049 0 0.099-0.001 0.149-0.004 0.216 0.809 0.605 2.917-0.131 5.818-0.007 0.027-0.011 0.055-0.013 0.082-1.271 1.298-3.042 2.104-5.002 2.104z"></path>
</svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generated by IcoMoon.io -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16" viewBox="0 0 16 16">
<path fill="#000000" d="M8 0c-4.418 0-8 3.582-8 8s3.582 8 8 8 8-3.582 8-8-3.582-8-8-8zM8 15c-0.984 0-1.92-0.203-2.769-0.57l3.643-4.098c0.081-0.092 0.126-0.21 0.126-0.332v-1.5c0-0.276-0.224-0.5-0.5-0.5-1.765 0-3.628-1.835-3.646-1.854-0.094-0.094-0.221-0.146-0.354-0.146h-2c-0.276 0-0.5 0.224-0.5 0.5v3c0 0.189 0.107 0.363 0.276 0.447l1.724 0.862v2.936c-1.813-1.265-3-3.366-3-5.745 0-1.074 0.242-2.091 0.674-3h1.826c0.133 0 0.26-0.053 0.354-0.146l2-2c0.094-0.094 0.146-0.221 0.146-0.354v-1.21c0.634-0.189 1.305-0.29 2-0.29 1.1 0 2.141 0.254 3.067 0.706-0.065 0.055-0.128 0.112-0.188 0.172-0.567 0.567-0.879 1.32-0.879 2.121s0.312 1.555 0.879 2.121c0.569 0.569 1.332 0.879 2.119 0.879 0.049 0 0.099-0.001 0.149-0.004 0.216 0.809 0.605 2.917-0.131 5.818-0.007 0.027-0.011 0.055-0.013 0.082-1.271 1.298-3.042 2.104-5.002 2.104z"></path>
</svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -29,9 +29,9 @@
"integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==" "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw=="
}, },
"are-we-there-yet": { "are-we-there-yet": {
"version": "1.1.4", "version": "1.1.5",
"resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.4.tgz", "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz",
"integrity": "sha1-u13KOCu5TwXhUZQ3PRb9O6HKEQ0=", "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==",
"requires": { "requires": {
"delegates": "^1.0.0", "delegates": "^1.0.0",
"readable-stream": "^2.0.6" "readable-stream": "^2.0.6"
@@ -96,9 +96,9 @@
} }
}, },
"chownr": { "chownr": {
"version": "1.0.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/chownr/-/chownr-1.0.1.tgz", "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.1.tgz",
"integrity": "sha1-4qdQQqlVGQi+vSW4Uj1fl2nXkYE=" "integrity": "sha512-j38EvO5+LHX84jlo6h4UzmOwi0UgW61WRyPtJz4qaadK5eY3BTS5TY/S1Stc3Uk2lIM6TPevAlULiEJwie860g=="
}, },
"code-point-at": { "code-point-at": {
"version": "1.1.0", "version": "1.1.0",
@@ -219,9 +219,9 @@
} }
}, },
"deep-extend": { "deep-extend": {
"version": "0.4.2", "version": "0.6.0",
"resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.4.2.tgz", "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz",
"integrity": "sha1-SLaZwn4zS/ifEIkr5DL25MfTSn8=" "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA=="
}, },
"delegates": { "delegates": {
"version": "1.0.0", "version": "1.0.0",
@@ -253,9 +253,9 @@
"integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ="
}, },
"expand-template": { "expand-template": {
"version": "1.1.0", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/expand-template/-/expand-template-1.1.0.tgz", "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-1.1.1.tgz",
"integrity": "sha512-kkjwkMqj0h4w/sb32ERCDxCQkREMCAgS39DscDnSwDsbxnwwM1BTZySdC3Bn1lhY7vL08n9GoO/fVTynjDgRyQ==" "integrity": "sha512-cebqLtV8KOZfw0UI8TEFWxtczxxC1jvyUvx6H4fyp1K1FN7A4Q+uggVUlOsI1K8AGU0rwOGqP8nCapdrw8CYQg=="
}, },
"fd-slicer": { "fd-slicer": {
"version": "1.0.1", "version": "1.0.1",
@@ -405,9 +405,9 @@
} }
}, },
"mimic-response": { "mimic-response": {
"version": "1.0.0", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.0.tgz", "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz",
"integrity": "sha1-3z02Uqc/3ta5sLJBRub9BSNTRY4=" "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ=="
}, },
"minimatch": { "minimatch": {
"version": "3.0.4", "version": "3.0.4",
@@ -424,7 +424,7 @@
}, },
"mkdirp": { "mkdirp": {
"version": "0.5.1", "version": "0.5.1",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", "resolved": "http://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
"integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
"requires": { "requires": {
"minimist": "0.0.8" "minimist": "0.0.8"
@@ -432,32 +432,32 @@
"dependencies": { "dependencies": {
"minimist": { "minimist": {
"version": "0.0.8", "version": "0.0.8",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", "resolved": "http://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
"integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0="
} }
} }
}, },
"nan": { "nan": {
"version": "2.10.0", "version": "2.11.1",
"resolved": "https://registry.npmjs.org/nan/-/nan-2.10.0.tgz", "resolved": "https://registry.npmjs.org/nan/-/nan-2.11.1.tgz",
"integrity": "sha512-bAdJv7fBLhWC+/Bls0Oza+mvTaNQtP+1RyhhhvD95pgUJz6XM5IzgmxOkItJ9tkoCiplvAnXI1tNmmUD/eScyA==" "integrity": "sha512-iji6k87OSXa0CcrLl9z+ZiYSuR2o+c0bGuNmXdrhTQTakxytAFsC56SArGYoiHlJlFoHSnvmhpceZJaXkVuOtA=="
}, },
"node-abi": { "node-abi": {
"version": "2.3.0", "version": "2.4.5",
"resolved": "https://registry.npmjs.org/node-abi/-/node-abi-2.3.0.tgz", "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-2.4.5.tgz",
"integrity": "sha512-zwm6vU3SsVgw3e9fu48JBaRBCJGIvAgysDsqtf5+vEexFE71bEOtaMWb5zr/zODZNzTPtQlqUUpC79k68Hspow==", "integrity": "sha512-aa/UC6Nr3+tqhHGRsAuw/edz7/q9nnetBrKWxj6rpTtm+0X9T1qU7lIEHMS3yN9JwAbRiKUbRRFy1PLz/y3aaA==",
"requires": { "requires": {
"semver": "^5.4.1" "semver": "^5.4.1"
} }
}, },
"node-hid": { "node-hid": {
"version": "0.5.7", "version": "0.7.3",
"resolved": "https://registry.npmjs.org/node-hid/-/node-hid-0.5.7.tgz", "resolved": "https://registry.npmjs.org/node-hid/-/node-hid-0.7.3.tgz",
"integrity": "sha512-dwwpOetL2+MGYgivbO22ML+45ieCGbueWv1rYxRgBoEc2QMp6UF6ZucEkYts1IA3YPWJNkmpGh6dqQ85n19szw==", "integrity": "sha512-LOCqWqcOlng+Kn1Qj/54zrPVfCagg1O7RlSgMmugykBcoYvUud6BswTrJM2aXuBac+bCCm3lA2srRG8YfmyXZQ==",
"requires": { "requires": {
"bindings": "^1.3.0", "bindings": "^1.3.0",
"nan": "^2.6.2", "nan": "^2.10.0",
"prebuild-install": "^2.2.2" "prebuild-install": "^4.0.0"
} }
}, },
"noop-logger": { "noop-logger": {
@@ -538,9 +538,9 @@
} }
}, },
"prebuild-install": { "prebuild-install": {
"version": "2.5.1", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-2.5.1.tgz", "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-4.0.0.tgz",
"integrity": "sha512-3DX9L6pzwc1m1ksMkW3Ky2WLgPQUBiySOfXVl3WZyAeJSyJb4wtoH9OmeRGcubAWsMlLiL8BTHbwfm/jPQE9Ag==", "integrity": "sha512-7tayxeYboJX0RbVzdnKyGl2vhQRWr6qfClEXDhOkXjuaOKCw2q8aiuFhONRYVsG/czia7KhpykIlI2S2VaPunA==",
"requires": { "requires": {
"detect-libc": "^1.0.3", "detect-libc": "^1.0.3",
"expand-template": "^1.0.2", "expand-template": "^1.0.2",
@@ -574,11 +574,11 @@
} }
}, },
"rc": { "rc": {
"version": "1.2.6", "version": "1.2.8",
"resolved": "https://registry.npmjs.org/rc/-/rc-1.2.6.tgz", "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz",
"integrity": "sha1-6xiYnG1PTxYsOZ953dKfODVWgJI=", "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==",
"requires": { "requires": {
"deep-extend": "~0.4.0", "deep-extend": "^0.6.0",
"ini": "~1.3.0", "ini": "~1.3.0",
"minimist": "^1.2.0", "minimist": "^1.2.0",
"strip-json-comments": "~2.0.1" "strip-json-comments": "~2.0.1"
@@ -638,9 +638,9 @@
} }
}, },
"semver": { "semver": {
"version": "5.5.0", "version": "5.5.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.1.tgz",
"integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==" "integrity": "sha512-PqpAxfrEhlSUWge8dwIp4tZnQ25DIOthpiaHNIthsjEFQD6EvqUKUDM7L8O2rShkFccYo1VjJR0coWfNkCubRw=="
}, },
"set-blocking": { "set-blocking": {
"version": "2.0.0", "version": "2.0.0",
@@ -679,9 +679,9 @@
"integrity": "sha1-c0TLuLbib7J9ZrL8hvn21Zl1IcY=" "integrity": "sha1-c0TLuLbib7J9ZrL8hvn21Zl1IcY="
}, },
"simple-get": { "simple-get": {
"version": "2.7.0", "version": "2.8.1",
"resolved": "https://registry.npmjs.org/simple-get/-/simple-get-2.7.0.tgz", "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-2.8.1.tgz",
"integrity": "sha512-RkE9rGPHcxYZ/baYmgJtOSM63vH0Vyq+ma5TijBcLla41SWlh8t6XYIGMR/oeZcmr+/G8k+zrClkkVrtnQ0esg==", "integrity": "sha512-lSSHRSw3mQNUGPAYRqo7xy9dhKmxFXIjLjp4KHpf99GEH2VH7C3AM+Qfx6du6jhfUi6Vm7XnbEVEf7Wb6N8jRw==",
"requires": { "requires": {
"decompress-response": "^3.3.0", "decompress-response": "^3.3.0",
"once": "^1.3.1", "once": "^1.3.1",
@@ -708,7 +708,7 @@
}, },
"strip-ansi": { "strip-ansi": {
"version": "3.0.1", "version": "3.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", "resolved": "http://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
"integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
"requires": { "requires": {
"ansi-regex": "^2.0.0" "ansi-regex": "^2.0.0"
@@ -736,9 +736,9 @@
} }
}, },
"tar-fs": { "tar-fs": {
"version": "1.16.0", "version": "1.16.3",
"resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-1.16.0.tgz", "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-1.16.3.tgz",
"integrity": "sha512-I9rb6v7mjWLtOfCau9eH5L7sLJyU2BnxtEZRQ5Mt+eRKmf1F0ohXmT/Jc3fr52kDvjJ/HV5MH3soQfPL5bQ0Yg==", "integrity": "sha512-NvCeXpYx7OsmOh8zIOP/ebG55zZmxLE0etfWRbWok+q2Qo8x/vOR/IJT1taADXPe+jsiu9axDb3X4B+iIgNlKw==",
"requires": { "requires": {
"chownr": "^1.0.1", "chownr": "^1.0.1",
"mkdirp": "^0.5.1", "mkdirp": "^0.5.1",
@@ -815,11 +815,11 @@
"integrity": "sha1-Zws6+8VS4LVd9rd4DKdGFfI60cs=" "integrity": "sha1-Zws6+8VS4LVd9rd4DKdGFfI60cs="
}, },
"wide-align": { "wide-align": {
"version": "1.1.2", "version": "1.1.3",
"resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.2.tgz", "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz",
"integrity": "sha512-ijDLlyQ7s6x1JgCLur53osjm/UXUYD9+0PbYKrBsYisYXzCxN+HC3mYDNy/dWdmf3AwqwU3CXwDCvsNgGK1S0w==", "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==",
"requires": { "requires": {
"string-width": "^1.0.2" "string-width": "^1.0.2 || 2"
} }
}, },
"wrappy": { "wrappy": {

View File

@@ -14,7 +14,7 @@
"commander": "^2.11.0", "commander": "^2.11.0",
"decompress": "^4.2.0", "decompress": "^4.2.0",
"decompress-tarbz2": "^4.1.1", "decompress-tarbz2": "^4.1.1",
"node-hid": "0.5.7", "node-hid": "0.7.3",
"shelljs": "^0.7.8", "shelljs": "^0.7.8",
"tmp": "0.0.33", "tmp": "0.0.33",
"uhk-common": "1.0.0", "uhk-common": "1.0.0",

View File

@@ -476,6 +476,7 @@ uhk = exports = module.exports = moduleExports = {
testUsbStack: 1, testUsbStack: 1,
debounceTimePress: 2, debounceTimePress: 2,
debounceTimeRelease: 3, debounceTimeRelease: 3,
usbReportSemaphore: 4,
}, },
vendorId: 0x1D50, vendorId: 0x1D50,
devicePropertyIds: { devicePropertyIds: {

View File

@@ -1,4 +1,6 @@
# Ultimate Hacking Keyboard rules # Ultimate Hacking Keyboard rules
# These are the udev rules for accessing the USB interfaces of the UHK as non-root users. # These are the udev rules for accessing the USB interfaces of the UHK as non-root users.
# Copy this file to /etc/udev/rules.d and physically reconnect the UHK afterwards. # Copy this file to /etc/udev/rules.d and physically reconnect the UHK afterwards.
SUBSYSTEMS=="usb", ATTRS{idVendor}=="1d50", ATTRS{idProduct}=="612[0-7]", MODE:="0666" SUBSYSTEM=="input", GROUP="input", MODE="0666"
SUBSYSTEMS=="usb", ATTRS{idVendor}=="1d50", ATTRS{idProduct}=="612[0-7]", MODE:="0666", GROUP="plugdev"
KERNEL=="hidraw*", ATTRS{idVendor}=="1d50", ATTRS{idProduct}=="612[0-7]", MODE="0666", GROUP="plugdev"

View File

@@ -1,10 +0,0 @@
const fse = require('fs-extra');
const path = require('path');
fse.copy(
path.join(__dirname, '../packages/usb/blhost'),
path.join(__dirname, '../tmp/packages/blhost'),
{
overwrite:true,
recursive:true
});

View File

@@ -0,0 +1,27 @@
const fse = require('fs-extra');
const path = require('path');
const copyOptions = {
overwrite: true,
recursive: true
};
const promises = [];
promises.push(
fse.copy(
path.join(__dirname, '../packages/usb/blhost'),
path.join(__dirname, '../tmp/packages/blhost'),
copyOptions)
);
promises.push(
fse.copy(
path.join(__dirname, '../rules'),
path.join(__dirname, '../tmp/rules'),
copyOptions)
);
Promise
.all(promises)
.catch(console.error);

View File

@@ -19,6 +19,12 @@ let config = {
'sprite': 'compiled_sprite.svg', 'sprite': 'compiled_sprite.svg',
bust: false bust: false
} }
},
svg: { // General options for created SVG files
namespaceIDs: true,
rootAttributes: {
"xmlns:xlink":"http://www.w3.org/1999/xlink"
}
} }
}; };

View File

@@ -102,14 +102,6 @@ if (TEST_BUILD || gitTag) {
builder.build({ builder.build({
dir: DIR, dir: DIR,
targets: target, targets: target,
appMetadata: {
main: 'electron-main.js',
name: 'UHK Agent',
author: {
name: 'Ultimate Gadget Laboratories'
},
version: rootJson.version
},
config: { config: {
directories: { directories: {
app: electron_build_folder app: electron_build_folder
@@ -124,6 +116,7 @@ if (TEST_BUILD || gitTag) {
}, },
win: { win: {
extraResources, extraResources,
publisherName: 'Ultimate Gadget Laboratories Kft.',
certificateFile: path.join(__dirname, 'certs/windows-cert.p12') certificateFile: path.join(__dirname, 'certs/windows-cert.p12')
}, },
linux: { linux: {