58 Commits

Author SHA1 Message Date
László Monda
5393501f68 Update CHANGELOG.md 2018-01-16 19:43:10 +01:00
László Monda
99e020d66f Bump Agent version to 1.1.0 and update package.json and the changelog accordingly. 2018-01-15 19:02:29 +01:00
László Monda
2c74ce8d3e Reference firmware 8.1.0 and adopt the newly introduced "v" git tag prefix for firmware tags. 2018-01-15 12:04:11 +01:00
László Monda
3cd2d208b9 Make get-i2c-health.js output uptime and baud rate. 2018-01-15 01:37:52 +01:00
László Monda
d0cd30f915 Add get-i2c-health.js 2018-01-15 01:21:56 +01:00
László Monda
010a23aaeb Extract slaveI2cErrorBufferToString() 2018-01-15 01:07:23 +01:00
László Monda
c723fe2651 Simplify get-slave-i2c-errors.js by using padEnd() 2018-01-15 00:55:38 +01:00
László Monda
95caa58624 Utilize uhk.getUint* in get-slave-i2c-errors.js 2018-01-15 00:37:31 +01:00
László Monda
9089f088b6 Clean up get-left-firmware-version.js a bit. 2018-01-15 00:31:24 +01:00
László Monda
1aeb4e8326 Make get-left-firmware-version.js display module protocol version, too. 2018-01-15 00:07:30 +01:00
László Monda
96b9226adb Fix script to display the correct firmware version. 2018-01-15 00:02:57 +01:00
László Monda
7c065f4368 Remove --buspal speed specification because it gets disrespected by the firmware anyways. 2018-01-14 22:13:31 +01:00
Róbert Kiss
a8108b9abf revert: Revert auto write user configuration (#558) 2018-01-14 19:32:41 +01:00
László Monda
c7baa00720 Add get-uptime.js 2018-01-14 18:33:14 +01:00
László Monda
5cdf2282f8 Add {get,set}-i2c-baud-rate.js 2018-01-14 18:15:50 +01:00
László Monda
89221faf60 Add set-i2c-baud-rate.js 2018-01-14 00:45:51 +01:00
László Monda
3b70c84c61 Merge branch 'master' of github.com:UltimateHackingKeyboard/agent 2018-01-13 19:33:28 +01:00
László Monda
5b1f4cb584 On the mouse speed section of the key action popover remove the bottom sentence and slightly rephrase the top sentence. 2018-01-13 19:30:08 +01:00
Mikko Lakomaa
3ee6c680a1 Agent menu (#540)
* Add generate version module script

* Remove Fork me on GitHub banner

* Add app-version.ts

* Revert "Add app-version.ts"

This reverts commit fe1a37e631.

* Add app-version.ts

* Add agent icon class

* Move settings component under agent folder

* Add AboutComponent

* Add agent routes

* Add index.ts for agent folder

* Fix agent folder imports in shared module

* Add agent menu to side menu, with Settings and About pages under it

* Fix agent icon alignment in side menu

* Simplify About page

* Make Agent menu 0 level in side menu

* Remove bottom Settings menu

* Fix Agent menu closing if My UHK is closed in side menu

* Fix version text alignment in auto update settings

* Remove github fork ribbon styles

* use package.json instead of app-version.ts

* fix OpenUrlInNewWindow naming

* fix lint request

* fix: firmware download url calculation
2018-01-13 17:10:21 +01:00
László Monda
fdcf64d5c6 Only display minutes in the I2C error logger script. 2018-01-13 00:06:18 +01:00
László Monda
6c327ee414 Add I2C logger script. 2018-01-11 02:57:49 +01:00
László Monda
b6bdd1486c Make update-module-firmware.js more robust and able to recover bricked modules (including the left half) by utilizing the newly added wait-for-kboot-idle.js 2018-01-10 03:15:19 +01:00
László Monda
bd5be98d99 Restore wdi-simple.exe and 50-uhk60-rules.cmd just in case. 2018-01-08 06:13:37 +01:00
László Monda
802e6a4649 Update README.md 2018-01-08 06:03:58 +01:00
László Monda
ae11c01725 Tidy up else clauses based on our coding standards. 2018-01-08 05:41:36 +01:00
Róbert Kiss
f0139c55ee feat(user-config): Upload user configuration from json/bin file (#545)
* feat(user-config): Upload user configuration from json/bin file

* fix error message

* remove file extension filter

* apply user config after loaded from file

* add file filter

* remove file filter
2018-01-08 05:29:31 +01:00
László Monda
b3f2e3451e Update README.md 2018-01-07 21:30:03 +01:00
Róbert Kiss
906beaac0e Update README.md (#548) 2018-01-07 21:27:07 +01:00
Andrew Kraut
46f855d1db Update readme (#547)
* Fix typos

* Update build instructions
2018-01-07 19:56:24 +01:00
László Monda
5341d953ff Fix statusCodesToStrings map. 2018-01-07 05:21:08 +01:00
László Monda
bd9a2a0eeb Make get-slave-i2c-errors.js display slave names and I2C error names. 2018-01-06 21:29:14 +01:00
László Monda
4c10954721 Add script which reads I2C errors. 2018-01-05 03:26:26 +01:00
Róbert Kiss
bbce1e0e0f fix(user-config): Validate device, keymap, and macro names (#543)
* fix(user-config): Validate device, keymap, and macro names

* fix device name renaming
2018-01-03 21:06:08 +01:00
László Monda
13f064229f Add keyAssigmentOrder array to uhk60-right/device.json 2018-01-02 04:05:26 +01:00
László Monda
d3295c5666 Bump package.json and changelog to version 1.0.4 2017-12-30 15:29:02 +01:00
László Monda
216793bbb8 Add get-{left,right}-firmware-version.js 2017-12-29 13:34:49 +01:00
László Monda
558c8b0dbf Merge branch 'master' of github.com:UltimateHackingKeyboard/agent 2017-12-29 13:24:26 +01:00
László Monda
e3c65f77df Rename getProperty to getDeviceProperty and getModuleProperties to getModuleProperty. 2017-12-29 13:23:58 +01:00
László Monda
227f8f0d2c Update package-lock.json files. 2017-12-29 13:23:32 +01:00
Mikko Lakomaa
7e0bc39de1 Mouse speed UI (#537)
* Add SliderProps interface to SliderWrapperComponent

* Implement mouse speed page

* Remove duplicate DefaultUserConfigurationService from shared module

* Add Reset speeds to default button

* Move reset button to bottom of mouse speed page

* Change mouse move slider step to 25

* Add label and tooltip handling to SliderWrapperComponent

* Use altered SliderWrapperComponent in mouse speed page

* Use altered SliderWrapperComponent in LED brightness page

* Move Reset mouse speeds confirmation popover to above button

* Add reset mouse speed settings action and effect

* Use ResetMouseSpeedSettingsAction in mouse speed page to reset settings

* Remove unused import
2017-12-29 13:07:01 +01:00
Spencer Owen
c4d3648f73 Invert scroll on mac (#536) 2017-12-28 13:50:11 +01:00
László Monda
547ab738c2 Update package.json and changelog to version 1.0.3 2017-12-28 01:31:38 +01:00
László Monda
3de9e9aa84 Downgrade to firmware 8.0.0 because the left I2C watchdog of firmware 8.0.1 is not proven yet and might make things worse. 2017-12-28 01:09:01 +01:00
László Monda
01ac4c1e8b Note that macro playback is not implemented yet. 2017-12-27 23:30:08 +01:00
László Monda
a0c8849f13 Comment out the add keymap button because the add keymap page is practically useless now. It'll be reimplemented. 2017-12-27 23:06:58 +01:00
László Monda
2a3dfcb0d0 Improve firmware update messages. 2017-12-27 22:47:53 +01:00
László Monda
2b3462c33f Display retry firmware update messages with regular color because they're not errors. 2017-12-27 22:38:07 +01:00
László Monda
a0b838b2e9 Remove the secondary scroll bar that appears on the LED brightness page. 2017-12-27 20:43:25 +01:00
Mikko Lakomaa
90f56c350e LED brightness UI (#520)
* Add nouislider

* Add LEDBrightnessComponent

* Move LEDBrightnessComponent to correct folder

* Add LED brightness page to side menu and device routes

* Add LEDBrightnessComponent to device index file

* Add LEDBrightnessComponent and NouisliderModule to shared module

* Remove ngModelChange from LEDBrightnessComponent until onChange is implemented

* Fix stylelint issue in led brightness component

* Add nouislider files to webpack.config.js

* Add adjusting LED brightness sliders with arrow keys

* Various tweaks to LEDBrightnessComponent

* Fix linting issues in LEDBrightnessComponent

* Allow "::ng-deep" pseudo element in stylelint config

* Add reading LED brightness settings from user configuration

* led-brightness save

* Move slider to its own wrapper component, add debounce for slider change events

* Small fixes to imports and exports of SliderWrapperComponent

* Fix slide component making change event when initial value is set

* Export SliderPips interface

* Fix LED Brightness slider pips

* Add support for value unit in SliderWrapperComponent

* Add a bit of space before LED brightness sliders so the slider handle doesn't go beyond the page in the min position

* Implement onDestroy, fix slider pip values and imports in LEDBrightnessComponent

* Fix imports, implement onDestroy in SliderWrapperComponent

* Move fix for slider pip value style to global styles file

* Reorder stylelint rules
2017-12-27 20:10:55 +01:00
József Farkas
5ceca41e0f feat(renderer): show icon when possible even if the keystroke action has modifier (#533)
Closes #513
2017-12-26 23:23:32 +01:00
László Monda
721a4dc6e7 Bump Agent version to 1.0.2 and update changelog. 2017-12-25 23:55:23 +01:00
László Monda
c9f03b4e57 Reference firmware 8.0.1 and don't include the moduleProtocolVersion field because it's firmware-related, not Agent-related. 2017-12-25 23:39:27 +01:00
László Monda
bbb6839f7e Fix Linux blhost path. 2017-12-25 21:48:41 +01:00
László Monda
dd973c80ea Add install-win-driver-*.bat 2017-12-24 03:56:13 +01:00
László Monda
48574a121a Move zadic-*.exe to packages/zadic 2017-12-24 03:35:13 +01:00
László Monda
8bc2462589 Reluctantly, add back zadic as it's needed to install Windows drivers for blhost to update the firmware on Windows. 2017-12-24 03:12:49 +01:00
László Monda
975ab8a5e9 Update README.md 2017-12-23 19:24:13 +01:00
László Monda
f0a54768d4 Clarify the difference between the two builds. 2017-12-23 19:20:26 +01:00
105 changed files with 1374 additions and 268 deletions

View File

@@ -4,14 +4,50 @@ All notable changes to this project will be documented in this file.
The format is loosely based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/).
## [1.1.0] - 2017-01-15
Firmware: 8.**1**.0 [[release](https://github.com/UltimateHackingKeyboard/firmware/releases/tag/v8.1.0)] | Device Protocol: 4.2.0 | User Config: 4.0.0 | Hardware Config: 1.0.0
- Only accept device, keymap, and macro names upon editing if their trimmed length is non-zero.
- Add diagnostics USB scripts, most notably /packages/usb/{get-i2c-health,set-i2c-baud-rate}.js, some utilizing new device protocol commands and properties. `DEVICEPROTOCOL:MINOR`
- Implement the Device -> Upload device configuration feature.
- Make update-module-firmware.js more robust and able to recover bricked modules (including the left half) by utilizing the newly added wait-for-kboot-idle.js. `DEVICEPROTOCOL:MINOR`
- Add the Agent -> About page containing the version number of Agent.
- On the mouse speed section of the key action popover, remove the now incorrect bottom sentence and slightly rephrase the top sentence.
- Remove --buspal speed specification argument because it gets disrespected by the firmware anyways.
- Fix get-left-firmware-version.js to display the correct firmware version.
## [1.0.4] - 2017-12-30
Firmware: 8.0.0 [[release](https://github.com/UltimateHackingKeyboard/firmware/releases/tag/v8.0.0)] | Device Protocol: 4.0.0 | User Config: 4.0.0 | Hardware Config: 1.0.0
- Add mouse speed settings.
## [1.0.3] - 2017-12-28
Firmware: 8.0.**0** [[release](https://github.com/UltimateHackingKeyboard/firmware/releases/tag/v8.0.0)] | Device Protocol: 4.0.0 | User Config: 4.0.0 | Hardware Config: 1.0.0
- Add LED brightness settings.
- Some key actions, for example Left Arrow were displayed as text with modifiers and as icon without modifires. Now, they're always displayed as icons.
- Clean up firmware update console messages a bit.
- Remove the add keymap button because this feature is not only useless but confusing until it gets reimplemented.
- Explicitly mention on the macro tab of the key action popover that macro playback is not implemented yet.
- Downgrade to firmware 8.0.0 because the left I2C watchdog of firmware 8.0.1 is not proven yet.
## [1.0.2] - 2017-12-25
Firmware: **8.0.1**[[release](https://github.com/UltimateHackingKeyboard/firmware/releases/tag/v8.0.1)] | Device Protocol: 4.0.0 | User Config: 4.0.0 | Hardware Config: 1.0.0
- Fix firmware upgrade on Linux.
## [1.0.1] - 2017-12-22
Firmware: [7.0.0](https://github.com/UltimateHackingKeyboard/firmware/releases/tag/7.0.0) | Device Protocol: 4.0.0 | User Config: 4.0.0 | Hardware Config: 1.0.0
Firmware: 7.0.0[[release](https://github.com/UltimateHackingKeyboard/firmware/releases/tag/v7.0.0)] | Device Protocol: 4.0.0 | User Config: 4.0.0 | Hardware Config: 1.0.0
- Fix Linux privilege escalation when udev rules aren't set up.
## [1.0.0] - 2017-12-14
Firmware: [**7**.0.0](https://github.com/UltimateHackingKeyboard/firmware/releases/tag/7.0.0) | Device Protocol: 4.0.0 | User Config: 4.0.0 | Hardware Config: 1.0.0
Firmware: **7**.0.0[[release](https://github.com/UltimateHackingKeyboard/firmware/releases/tag/v7.0.0)] | Device Protocol: 4.0.0 | User Config: 4.0.0 | Hardware Config: 1.0.0
- First release

View File

@@ -7,23 +7,46 @@ Agent is the configuration application of the [Ultimate Hacking Keyboard](https:
[Give it a whirl!](http://ultimatehackingkeyboard.github.io/agent/)
## Set up instructions
## Two builds to rule them all
First up, make sure that node >=8.1.x and npm >=5.1.x are installed on your system. Next up:
It's worth mentioning that Agent has two builds.
The **electron build** is the desktop application which is meant to be used if you have an actual UHK at hand. It starts with an opening screen which detects your UHK. You cannot get past this screen without connecting a UHK via USB.
The **web build** is meant to be used for demonstration purposes, so people who don't yet own a UHK can get a feel of Agent and its capabilities in their browser. Eventually, WebUSB support will be added to the web build, making it able to communicate with the UHK. Given the sandboxed nature of browsers, the web build will always lack features that the electron build offers, so this won't make the electron build obsolete.
The two builds share code as much as possible.
## Building the electron application
### Step 1: Build Dependencies
You'll need Node.js LTS. Use your OS package manager to install it. [Check the NodeJS site for more info.](https://nodejs.org/en/download/package-manager/ "Installing Node.js via package manager") Mac OS users can simply `brew install node` to get both. Should you need multiple Node.js versions on the same computer, use Node Version Manager for [Mac/Linux](https://github.com/creationix/nvm) or for [Windows](https://github.com/coreybutler/nvm-windows)
You'll also need `libusb`.
On debian-based linux distros, `apt-get install libusb-dev libudev-dev g++` is sufficient.
On Mac OS, use `brew install libusb libusb-compat`.
For everyone else, use the appropriate package manager for your OS.
### Step 2: Build Environment
```
# Execute the following line on Linux. Use relevant package manager and package names on non-Debian based distros.
apt-get install libusb-dev libudev-dev g++
git clone git@github.com:UltimateHackingKeyboard/agent.git
cd agent
npm install
npm run build:electron
npm run electron
npm install # to install Node dependencies
npm run build:electron # to build the agent
npm run electron # to run the newly built agent
```
At this point, Agent should be running on your machine.
## Developing the web application
- The frontend code is located in `packages/uhk-web/`
- Run the project locally with `npm run server:web`
- View the app at `http://localhost:8080`
- The app will automatically reload when you make changes
## Contributing
Wanna contribute? Please let us show you [how](CONTRIBUTING.md).

5
package-lock.json generated
View File

@@ -1,6 +1,6 @@
{
"name": "uhk-agent",
"version": "1.0.0",
"version": "1.0.4",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@@ -4085,7 +4085,8 @@
"jsbn": {
"version": "0.1.1",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"json-schema": {
"version": "0.2.3",

View File

@@ -3,7 +3,11 @@
"private": true,
"author": "Ultimate Gadget Laboratories",
"main": "electron/dist/electron-main.js",
"version": "1.0.1",
"version": "1.1.0",
"firmwareVersion": "8.1.0",
"deviceProtocolVersion": "4.2.0",
"userConfigVersion": "4.0.0",
"hardwareConfigVersion": "1.0.0",
"description": "Agent is the configuration application of the Ultimate Hacking Keyboard.",
"repository": {
"type": "git",
@@ -86,7 +90,6 @@
"server:web": "lerna exec --scope uhk-web npm start",
"server:electron": "lerna exec --scope uhk-web npm run server:renderer",
"electron": "lerna exec --scope uhk-agent npm start",
"electron:auto-write-config": "lerna exec --scope uhk-agent npm run auto-write-config",
"standard-version": "standard-version",
"pack": "node ./scripts/release.js",
"sprites": "node ./scripts/generate-svg-sprites",

View File

@@ -36,7 +36,6 @@
},
"scripts": {
"start": "electron ./dist/electron-main.js",
"auto-write-config": "electron ./dist/electron-main.js --auto-write-config",
"build": "webpack && npm run install:build-deps && npm run build:usb && npm run download-firmware && npm run copy-blhost",
"build:usb": "electron-rebuild -w node-hid -p -m ./dist",
"install:build-deps": "cd ./dist && npm i",

View File

@@ -21,8 +21,7 @@ import * as isDev from 'electron-is-dev';
import { CommandLineInputs } from './models/command-line-inputs';
const optionDefinitions = [
{name: 'addons', type: Boolean},
{name: 'auto-write-config', type: Boolean}
{name: 'addons', type: Boolean}
];
const options: CommandLineInputs = commandLineArgs(optionDefinitions);

View File

@@ -1,4 +1,3 @@
export interface CommandLineInputs {
addons?: boolean;
'auto-write-config'?: boolean;
}

View File

@@ -15,10 +15,5 @@
},
"dependencies": {
"node-hid": "0.5.7"
},
"firmwareVersion": "7.0.0",
"deviceProtocolVersion": "4.0.0",
"moduleProtocolVersion": "3.0.0",
"userConfigVersion": "4.0.0",
"hardwareConfigVersion": "1.0.0"
}
}

View File

@@ -1,7 +1,5 @@
import { BrowserWindow, ipcMain } from 'electron';
import { BrowserWindow, ipcMain, shell } from 'electron';
import { UhkHidDevice } from 'uhk-usb';
import { readFile } from 'fs';
import { join } from 'path';
import { AppStartInfo, IpcEvents, LogService } from 'uhk-common';
import { MainServiceBase } from './main-service-base';
@@ -18,53 +16,30 @@ export class AppService extends MainServiceBase {
ipcMain.on(IpcEvents.app.getAppStartInfo, this.handleAppStartInfo.bind(this));
ipcMain.on(IpcEvents.app.exit, this.exit.bind(this));
ipcMain.on(IpcEvents.app.openUrl, this.openUrl.bind(this));
logService.info('[AppService] init success');
}
private async handleAppStartInfo(event: Electron.Event) {
this.logService.info('[AppService] getAppStartInfo');
const packageJson = await this.getPackageJson();
const response: AppStartInfo = {
commandLineArgs: {
addons: this.options.addons || false,
autoWriteConfig: this.options['auto-write-config'] || false
addons: this.options.addons || false
},
deviceConnected: this.uhkHidDeviceService.deviceConnected(),
hasPermission: this.uhkHidDeviceService.hasPermission(),
agentVersionInfo: {
version: packageJson.version,
firmwareVersion: packageJson.firmwareVersion,
deviceProtocolVersion: packageJson.deviceProtocolVersion,
moduleProtocolVersion: packageJson.moduleProtocolVersion,
userConfigVersion: packageJson.userConfigVersion,
hardwareConfigVersion: packageJson.hardwareConfigVersion
}
hasPermission: this.uhkHidDeviceService.hasPermission()
};
this.logService.info('[AppService] getAppStartInfo response:', response);
return event.sender.send(IpcEvents.app.getAppStartInfoReply, response);
}
/**
* Read the package.json that delivered with the bundle. Do not use require('package.json')
* because the deploy process change the package.json after the build
* @returns {Promise<any>}
*/
private async getPackageJson(): Promise<any> {
return new Promise((resolve, reject) => {
readFile(join(__dirname, 'package.json'), {encoding: 'utf-8'}, (err, data) => {
if (err) {
return reject(err);
}
resolve(JSON.parse(data));
});
});
}
private exit() {
this.logService.info('[AppService] exit');
this.win.close();
}
private openUrl(event: Electron.Event, urls: Array<string>) {
shell.openExternal(urls[0]);
}
}

View File

@@ -1,12 +1,7 @@
import { CommandLineArgs } from './command-line-args';
import { VersionInformation } from './version-information';
export interface AppStartInfo {
commandLineArgs: CommandLineArgs;
deviceConnected: boolean;
hasPermission: boolean;
/**
* This property contains the version information of the deployed agent components
*/
agentVersionInfo: VersionInformation;
}

View File

@@ -1,4 +1,3 @@
export interface CommandLineArgs {
addons: boolean;
autoWriteConfig: boolean;
}

View File

@@ -1,5 +1,3 @@
export namespace Constants {
export const VENDOR_ID = 0x1D50;
export const PRODUCT_ID = 0x6122;
export const MAX_PAYLOAD_SIZE = 64;
export const AGENT_GITHUB_URL = 'https://github.com/UltimateHackingKeyboard/agent';
}

View File

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

View File

@@ -3,6 +3,7 @@ class App {
public static readonly getAppStartInfo = 'app-get-start-info';
public static readonly getAppStartInfoReply = 'app-get-start-info-reply';
public static readonly exit = 'app-exit';
public static readonly openUrl = 'open-url';
}
class AutoUpdate {

View File

@@ -70,7 +70,7 @@ export class UhkBlhost {
let blhostPath;
switch (process.platform) {
case 'linux':
blhostPath = 'linux/amd64/blhost';
blhostPath = 'linux/x86_64/blhost';
break;
case 'darwin':
blhostPath = 'mac/blhost';

View File

@@ -26,14 +26,14 @@ export class UhkOperations {
await this.blhost.runBlhostCommand([...prefix, 'flash-erase-region', '0xc000', '475136']);
await this.blhost.runBlhostCommand([...prefix, 'flash-image', `"${firmwarePath}"`]);
await this.blhost.runBlhostCommand([...prefix, 'reset']);
this.logService.debug('[UhkOperations] End flashing right firmware');
this.logService.debug('[UhkOperations] Right firmware successfully flashed');
}
public async updateLeftModule(firmwarePath = this.getLeftModuleFirmwarePath()) {
this.logService.debug('[UhkOperations] Start flashing left module firmware');
const prefix = [`--usb 0x1d50,0x${EnumerationNameToProductId.buspal.toString(16)}`];
const buspalPrefix = [...prefix, `--buspal i2c,${ModuleSlotToI2cAddress.leftHalf},100k`];
const buspalPrefix = [...prefix, `--buspal i2c,${ModuleSlotToI2cAddress.leftHalf}`];
await this.device.reenumerate(EnumerationModes.NormalKeyboard);
this.device.close();
@@ -58,7 +58,8 @@ export class UhkOperations {
await this.device.sendKbootCommandToModule(ModuleSlotToI2cAddress.leftHalf, KbootCommands.idle);
this.device.close();
this.logService.debug('[UhkOperations] End flashing left module firmware');
this.logService.debug('[UhkOperations] Left firmware successfully flashed');
this.logService.debug('[UhkOperations] Both left and right firmwares successfully flashed');
}
/**

View File

@@ -89,7 +89,7 @@ export async function retry(command: Function, maxTry = 3, logService?: LogServi
throw err;
} else {
if (logService) {
logService.error(`[retry] failed, but try run FUNCTION:\n ${command}, \n retry: ${retryCount}`);
logService.info(`[retry] failed, but try run FUNCTION:\n ${command}, \n retry: ${retryCount}`);
}
}
}

View File

@@ -21,11 +21,13 @@
"prefix": "app",
"styles": [
"../node_modules/bootstrap/dist/css/bootstrap.min.css",
"../node_modules/nouislider/distribute/nouislider.min.css",
"styles.scss"
],
"scripts": [
"../node_modules/bootstrap/dist/js/bootstrap.js",
"../node_modules/select2/dist/js/select2.full.js"
"../node_modules/select2/dist/js/select2.full.js",
"../node_modules/nouislider/distribute/nouislider.js"
],
"environmentSource": "environments/environment.ts",
"environments": {

View File

@@ -5828,6 +5828,11 @@
"dragula": "3.7.2"
}
},
"ng2-nouislider": {
"version": "1.7.6",
"resolved": "https://registry.npmjs.org/ng2-nouislider/-/ng2-nouislider-1.7.6.tgz",
"integrity": "sha1-YMq2P7hUbT+HGT4ImcUn0N2A3gs="
},
"ng2-select2": {
"version": "1.0.0-beta.10",
"resolved": "https://registry.npmjs.org/ng2-select2/-/ng2-select2-1.0.0-beta.10.tgz",
@@ -6843,6 +6848,11 @@
"sort-keys": "1.1.2"
}
},
"nouislider": {
"version": "10.1.0",
"resolved": "https://registry.npmjs.org/nouislider/-/nouislider-10.1.0.tgz",
"integrity": "sha512-lENwxlpoYg4/5gjdaY/PMNHeVL+CMJyrO+7RzXi1MqhSSGwuJsQSJteXCQV5bE2UKEdSLARWrqIF8XSWAq7h+A=="
},
"npm-run-path": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz",

View File

@@ -69,9 +69,11 @@
"less-loader": "4.0.5",
"lodash": "4.17.4",
"ng2-dragula": "1.5.0",
"ng2-nouislider": "^1.7.6",
"ng2-select2": "1.0.0-beta.10",
"ngrx-store-freeze": "0.1.9",
"node-hid": "0.5.4",
"nouislider": "^10.1.0",
"postcss-loader": "1.3.3",
"postcss-url": "5.1.2",
"protractor": "5.1.2",

View File

@@ -7,9 +7,6 @@
<div id="main-content" class="main-content">
<router-outlet></router-outlet>
</div>
<div class="github-fork-ribbon" *ngIf="!(runningInElectron$ | async)">
<a class="" href="https://github.com/UltimateHackingKeyboard/agent" title="Fork me on GitHub">Fork me on GitHub</a>
</div>
<notifier-container></notifier-container>
<progress-button class="save-to-keyboard-button"
*ngIf="(saveToKeyboardState$ | async).showButton"

View File

@@ -1,36 +1,3 @@
/* GitHub ribbon */
.github-fork-ribbon {
background-color: #a00;
overflow: hidden;
white-space: nowrap;
position: fixed;
right: -50px;
bottom: 40px;
z-index: 2000;
/* stylelint-disable indentation */
-webkit-transform: rotate(-45deg);
-moz-transform: rotate(-45deg);
-ms-transform: rotate(-45deg);
-o-transform: rotate(-45deg);
transform: rotate(-45deg);
-webkit-box-shadow: 0 0 10px #888;
-moz-box-shadow: 0 0 10px #888;
box-shadow: 0 0 10px #888;
/* stylelint-enable indentation */
a {
border: 1px solid #faa;
color: #fff;
display: block;
font: bold 81.25% 'Helvetica Neue', Helvetica, Arial, sans-serif;
margin: 1px 0;
padding: 10px 50px;
text-align: center;
text-decoration: none;
text-shadow: 0 0 5px #444;
}
}
main-app {
min-height: 100%;
height: 100%;

View File

@@ -12,7 +12,7 @@ import { UhkDeviceConnectedGuard } from './services/uhk-device-connected.guard';
import { UhkDeviceUninitializedGuard } from './services/uhk-device-uninitialized.guard';
import { UhkDeviceInitializedGuard } from './services/uhk-device-initialized.guard';
import { MainPage } from './pages/main-page/main.page';
import { settingsRoutes } from './components/settings/settings.routes';
import { agentRoutes } from './components/agent/agent.routes';
import { LoadingDevicePageComponent } from './pages/loading-page/loading-device.page';
import { UhkDeviceLoadingGuard } from './services/uhk-device-loading.guard';
import { UhkDeviceLoadedGuard } from './services/uhk-device-loaded.guard';
@@ -42,7 +42,7 @@ const appRoutes: Routes = [
...keymapRoutes,
...macroRoutes,
...addOnRoutes,
...settingsRoutes
...agentRoutes
]
}
];

View File

@@ -0,0 +1,10 @@
<div class="row">
<h1 class="col-xs-12 pane-title">
<i class="uhk-icon uhk-icon-agent-icon"></i>
<span>About</span>
</h1>
<div class="col-xs-12">
<div class="agent-version">Agent version: <span class="text-bold">{{version}}</span></div>
<div><a class="link-github" (click)="openAgentGitHubPage($event)">Agent on GitHub</a></div>
</div>
</div>

View File

@@ -0,0 +1,20 @@
:host {
overflow-y: auto;
display: block;
height: 100%;
width: 100%;
}
.agent {
&-version {
margin-bottom: 1rem;
span {
font-weight: bold;
}
}
}
.link-github {
cursor: pointer;
}

View File

@@ -0,0 +1,27 @@
import { Component } from '@angular/core';
import { Store } from '@ngrx/store';
import { Constants } from 'uhk-common';
import { AppState } from '../../../store';
import { getVersions } from '../../../util';
import { OpenUrlInNewWindowAction } from '../../../store/actions/app';
@Component({
selector: 'about-page',
templateUrl: './about.component.html',
styleUrls: ['./about.component.scss'],
host: {
'class': 'container-fluid'
}
})
export class AboutComponent {
version: string = getVersions().version;
constructor(private store: Store<AppState>) {
}
openAgentGitHubPage(event) {
event.preventDefault();
this.store.dispatch(new OpenUrlInNewWindowAction(Constants.AGENT_GITHUB_URL));
}
}

View File

@@ -0,0 +1,15 @@
import { Routes } from '@angular/router';
import { SettingsComponent } from './settings/settings.component';
import { AboutComponent } from './about/about.component';
export const agentRoutes: Routes = [
{
path: 'settings',
component: SettingsComponent
},
{
path: 'about',
component: AboutComponent
}
];

View File

@@ -0,0 +1,3 @@
export * from './agent.routes';
export * from './about/about.component';
export * from './settings/settings.component';

View File

@@ -2,13 +2,14 @@ import { Component } from '@angular/core';
import { Store } from '@ngrx/store';
import { Observable } from 'rxjs/Observable';
import { AppState, getAutoUpdateSettings, getCheckingForUpdate } from '../../store';
import { AppState, getAutoUpdateSettings, getCheckingForUpdate } from '../../../store';
import {
CheckForUpdateNowAction,
ToggleCheckForUpdateOnStartupAction,
TogglePreReleaseFlagAction
} from '../../store/actions/auto-update-settings';
import { AutoUpdateSettings } from '../../models/auto-update-settings';
} from '../../../store/actions/auto-update-settings';
import { AutoUpdateSettings } from '../../../models/auto-update-settings';
import { getVersions } from '../../../util';
@Component({
selector: 'settings',
@@ -19,8 +20,7 @@ import { AutoUpdateSettings } from '../../models/auto-update-settings';
}
})
export class SettingsComponent {
// TODO: From where do we get the version number? The electron gives back in main process, but the web...
version = '1.0.0';
version: string = getVersions().version;
autoUpdateSettings$: Observable<AutoUpdateSettings>;
checkingForUpdate$: Observable<boolean>;

View File

@@ -19,7 +19,7 @@
<div class="form-group">
<label class="col-sm-2 control-label">Version:</label>
<div class="col-sm-10">
<p class="form-control-static">{{version}}</p>
<p>{{version}}</p>
</div>
</div>

View File

@@ -14,9 +14,11 @@
<span role="button" class="btn-link" (click)="saveConfigurationInBINFormat()">binary</span> format.
</li>
<li>
<button class="btn btn-default"
>Upload device configuration
</button>
<label class="btn btn-default btn-file">
Upload device configuration
<input type="file"
(change)="changeFile($event)">
</label>
</li>
<li>
<button class="btn btn-danger"

View File

@@ -3,7 +3,11 @@ import { Store } from '@ngrx/store';
import { AppState } from '../../../store';
import { ResetUserConfigurationAction } from '../../../store/actions/device';
import { SaveUserConfigInBinaryFileAction, SaveUserConfigInJsonFileAction } from '../../../store/actions/user-config';
import {
LoadUserConfigurationFromFileAction,
SaveUserConfigInBinaryFileAction,
SaveUserConfigInJsonFileAction
} from '../../../store/actions/user-config';
@Component({
selector: 'device-settings',
@@ -29,4 +33,17 @@ export class DeviceConfigurationComponent {
saveConfigurationInBINFormat() {
this.store.dispatch(new SaveUserConfigInBinaryFileAction());
}
changeFile(event): void {
const files = event.srcElement.files;
const fileReader = new FileReader();
fileReader.onloadend = function () {
const arrayBuffer = new Uint8Array(fileReader.result);
this.store.dispatch(new LoadUserConfigurationFromFileAction({
filename: event.srcElement.value,
data: Array.from(arrayBuffer)
}));
}.bind(this);
fileReader.readAsArrayBuffer(files[0]);
}
}

View File

@@ -3,6 +3,7 @@ import { Routes } from '@angular/router';
import { DeviceConfigurationComponent } from './configuration/device-configuration.component';
import { DeviceFirmwareComponent } from './firmware/device-firmware.component';
import { MouseSpeedComponent } from './mouse-speed/mouse-speed.component';
import { LEDBrightnessComponent } from './led-brightness/led-brightness.component';
export const deviceRoutes: Routes = [
{
@@ -21,6 +22,10 @@ export const deviceRoutes: Routes = [
path: 'mouse-speed',
component: MouseSpeedComponent
},
{
path: 'led-brightness',
component: LEDBrightnessComponent
},
{
path: 'firmware',
component: DeviceFirmwareComponent

View File

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

View File

@@ -0,0 +1,40 @@
<h1>
<i class="fa fa-sliders"></i>
<span>LED brightness</span>
</h1>
<div class="row led-setting">
<div class="col-xs-12 col-md-6">
<slider-wrapper
label="LED display icon and layer texts brightness"
[min]="0"
[max]="255"
[step]="1"
[pips]="sliderPips"
[(ngModel)]="iconsAndLayerTextsBrightness"
(ngModelChange)="onSetPropertyValue('iconsAndLayerTextsBrightness', $event)"></slider-wrapper>
</div>
</div>
<div class="row led-setting">
<div class="col-xs-12 col-md-6">
<slider-wrapper
label="LED display alphanumeric segments brightness"
[min]="0"
[max]="255"
[step]="1"
[pips]="sliderPips"
[(ngModel)]="alphanumericSegmentsBrightness"
(ngModelChange)="onSetPropertyValue('alphanumericSegmentsBrightness', $event)"></slider-wrapper>
</div>
</div>
<div class="row led-setting">
<div class="col-xs-12 col-md-6">
<slider-wrapper
label="Key backlight brightness"
[min]="0"
[max]="255"
[step]="1"
[pips]="sliderPips"
[(ngModel)]="keyBacklightBrightness"
(ngModelChange)="onSetPropertyValue('keyBacklightBrightness', $event)"></slider-wrapper>
</div>
</div>

View File

@@ -0,0 +1,15 @@
:host {
overflow-y: auto;
display: block;
height: 100%;
width: 100%;
label {
display: block;
font-weight: normal;
}
.led-setting {
margin-bottom: 6rem;
}
}

View File

@@ -0,0 +1,53 @@
import { AfterViewInit, Component, OnInit, OnDestroy, ViewChildren, QueryList } from '@angular/core';
import { Store } from '@ngrx/store';
import { AppState, getUserConfiguration } from '../../../store';
import { SetUserConfigurationValueAction } from '../../../store/actions/user-config';
import { SliderPips } from '../../slider-wrapper/slider-wrapper.component';
import { Observable } from 'rxjs/Observable';
import { Subscription } from 'rxjs/Subscription';
import { UserConfiguration } from 'uhk-common';
@Component({
selector: 'device-led-brightness',
templateUrl: './led-brightness.component.html',
styleUrls: ['./led-brightness.component.scss'],
host: {
'class': 'container-fluid'
}
})
export class LEDBrightnessComponent implements OnInit, OnDestroy {
public iconsAndLayerTextsBrightness: number = 0;
public alphanumericSegmentsBrightness: number = 0;
public keyBacklightBrightness: number = 0;
public sliderPips: SliderPips = {
mode: 'positions',
values: [0, 50, 100],
density: 6,
stepped: true
};
private userConfig$: Store<UserConfiguration>;
private userConfigSubscription: Subscription;
constructor(private store: Store<AppState>) {}
ngOnInit() {
this.userConfig$ = this.store.select(getUserConfiguration);
this.userConfigSubscription = this.userConfig$.subscribe(config => {
this.iconsAndLayerTextsBrightness = config.iconsAndLayerTextsBrightness;
this.alphanumericSegmentsBrightness = config.alphanumericSegmentsBrightness;
this.keyBacklightBrightness = config.keyBacklightBrightness;
});
}
ngOnDestroy() {
this.userConfigSubscription.unsubscribe();
}
onSetPropertyValue(propertyName: string, value: number): void {
this.store.dispatch(new SetUserConfigurationValueAction({
propertyName,
value
}));
}
}

View File

@@ -2,6 +2,41 @@
<i class="fa fa-sliders"></i>
<span>Mouse speed</span>
</h1>
<p>
Coming soon ...
</p>
<h3>Mouse pointer speed</h3>
<div class="row mouse-speed-setting" *ngFor="let prop of moveProps">
<div class="col-xs-12 col-md-6">
<slider-wrapper
[label]="prop.title"
[tooltip]="prop.tooltip"
[min]="moveSettings.min"
[max]="moveSettings.max"
[step]="moveSettings.step"
[pips]="sliderPips"
[valueUnit]="prop.valueUnit"
[(ngModel)]="prop.value"
(ngModelChange)="onSetPropertyValue(prop.prop, $event)"></slider-wrapper>
</div>
</div>
<h3>Mouse scroll speed</h3>
<div class="row mouse-speed-setting" *ngFor="let prop of scrollProps">
<div class="col-xs-12 col-md-6">
<slider-wrapper
[label]="prop.title"
[tooltip]="prop.tooltip"
[min]="scrollSettings.min"
[max]="scrollSettings.max"
[step]="scrollSettings.step"
[pips]="sliderPips"
[valueUnit]="prop.valueUnit"
[(ngModel)]="prop.value"
(ngModelChange)="onSetPropertyValue(prop.prop, $event)"></slider-wrapper>
</div>
</div>
<button class="btn btn-danger mouse-speed-reset-button"
mwlConfirmationPopover
title="Are you sure?"
placement="top"
confirmText="Yes"
cancelText="No"
(confirm)="resetToDefault()">Reset speeds to default
</button>

View File

@@ -4,4 +4,25 @@
height: 100%;
width: 100%;
label {
display: block;
font-weight: normal;
icon {
display: inline-block;
}
}
.mouse-speed-reset-button {
display: block;
margin-bottom: 4rem;
}
.mouse-speed-setting {
margin-bottom: 6rem;
+ h3 {
margin-top: 2rem;
}
}
}

View File

@@ -1,4 +1,14 @@
import { Component } from '@angular/core';
import { Component, OnInit, OnDestroy } from '@angular/core';
import { Store } from '@ngrx/store';
import { AppState, getUserConfiguration } from '../../../store';
import { SetUserConfigurationValueAction } from '../../../store/actions/user-config';
import { DefaultUserConfigurationService } from '../../../services/default-user-configuration.service';
import { SliderPips, SliderProps } from '../../slider-wrapper/slider-wrapper.component';
import { Subscription } from 'rxjs/Subscription';
import { UserConfiguration } from 'uhk-common';
import { ResetMouseSpeedSettingsAction } from '../../../store/actions/device';
const MOUSE_MOVE_VALUE_MULTIPLIER = 25;
@Component({
selector: 'device-mouse-speed',
@@ -8,5 +18,131 @@ import { Component } from '@angular/core';
'class': 'container-fluid'
}
})
export class MouseSpeedComponent {
export class MouseSpeedComponent implements OnInit, OnDestroy {
public moveProps = [
{
prop: 'mouseMoveInitialSpeed',
title: 'Initial speed',
tooltip: 'When mouse movement begins, this is the starting speed.',
valueUnit: 'px/s',
value: 0
},
{
prop: 'mouseMoveBaseSpeed',
title: 'Base speed',
tooltip: 'This speed is reached after the initial moving speed sufficiently ramps up.',
valueUnit: 'px/s',
value: 0
},
{
prop: 'mouseMoveAcceleration',
title: 'Acceleration',
tooltip: 'The rate of acceleration from the initial movement speed to the base speed.',
valueUnit: 'px/s²',
value: 0
},
{
prop: 'mouseMoveDeceleratedSpeed',
title: 'Decelerated speed',
tooltip: 'This speed is used while moving with the <i>decelerate key</i> pressed.',
valueUnit: 'px/s',
value: 0
},
{
prop: 'mouseMoveAcceleratedSpeed',
title: 'Accelerated speed',
tooltip: 'This speed is used while moving with the <i>accelerate key</i> pressed.',
valueUnit: 'px/s',
value: 0
}
];
public scrollProps = [
{
prop: 'mouseScrollInitialSpeed',
title: 'Initial speed',
tooltip: 'When mouse scrolling begins, this is the starting speed.',
valueUnit: 'pulse/s',
value: 0
},
{
prop: 'mouseScrollBaseSpeed',
title: 'Base speed',
tooltip: 'This speed is reached after the initial scrolling speed sufficiently ramps up.',
valueUnit: 'pulse/s',
value: 0
},
{
prop: 'mouseScrollAcceleration',
title: 'Acceleration',
tooltip: 'The rate of acceleration from the initial scrolling speed to the base speed.',
valueUnit: 'pulse/s²',
value: 0
},
{
prop: 'mouseScrollDeceleratedSpeed',
title: 'Decelerated speed',
tooltip: 'This speed is used while scrolling with the <i>decelerate key</i> pressed.',
valueUnit: 'pulse/s',
value: 0
},
{
prop: 'mouseScrollAcceleratedSpeed',
title: 'Accelerated speed',
tooltip: 'This speed is used while scrolling with the <i>accelerate key</i> pressed.',
valueUnit: 'pulse/s',
value: 0
}
];
public sliderPips: SliderPips = {
mode: 'positions',
values: [0, 50, 100],
density: 6,
stepped: true
};
public moveSettings: SliderProps = {
min: MOUSE_MOVE_VALUE_MULTIPLIER,
max: 6375,
step: MOUSE_MOVE_VALUE_MULTIPLIER
};
public scrollSettings: SliderProps = {
min: 1,
max: 255,
step: 1
};
private userConfig$: Store<UserConfiguration>;
private userConfigSubscription: Subscription;
constructor(private store: Store<AppState>, private defaultUserConfigurationService: DefaultUserConfigurationService) {}
ngOnInit(): void {
this.userConfig$ = this.store.select(getUserConfiguration);
this.userConfigSubscription = this.userConfig$.subscribe(config => {
this.moveProps.forEach(moveProp => {
moveProp.value = config[moveProp.prop] * MOUSE_MOVE_VALUE_MULTIPLIER || 0;
});
this.scrollProps.forEach(scrollProp => {
scrollProp.value = config[scrollProp.prop] || 0;
});
});
}
ngOnDestroy(): void {
this.userConfigSubscription.unsubscribe();
}
onSetPropertyValue(propertyName: string, value: number): void {
this.store.dispatch(new SetUserConfigurationValueAction({
propertyName,
value: propertyName.indexOf('mouseMove') !== -1 ? value / MOUSE_MOVE_VALUE_MULTIPLIER : value
}));
}
resetToDefault() {
this.store.dispatch(new ResetMouseSpeedSettingsAction());
}
}

View File

@@ -74,7 +74,7 @@ export class KeymapHeaderComponent implements OnChanges {
}
editKeymapName(name: string) {
if (name.length === 0) {
if (!util.isValidName(name)) {
this.setName();
return;
}

View File

@@ -59,7 +59,7 @@ export class MacroHeaderComponent implements AfterViewInit, OnChanges {
}
editMacroName(name: string) {
if (name.length === 0) {
if (!util.isValidName(name)) {
this.setName();
return;
}

View File

@@ -2,6 +2,7 @@
<span> No macros are available to choose from. Create a macro first! </span>
</ng-template>
<ng-template [ngIf]="macroOptions.length > 0">
<p><i>Please note that macro playback is not implemented yet. You can bind macros, but they don't have any effect.</i></p>
<div class="macro-selector">
<b> Play macro: </b>
<select2 [data]="macroOptions" [value]="macroOptions[selectedMacroIndex].id" (valueChanged)="onChange($event)" [width]="'100%'"></select2>

View File

@@ -83,7 +83,7 @@
</div>
<div *ngSwitchCase="3" class="mouse__config mouse__config--speed text-center">
<div class="help-text--mouse-speed text-left">
<p>Press this key along with mouse movement/scrolling to accelerate/decelerate the speed of the action.</p>
<p>Press this key along with mouse movement/scrolling to accelerate/decelerate its speed.</p>
</div>
<div class="btn-group btn-group-lg" role="group">
<button class="btn btn-default"
@@ -101,9 +101,6 @@
<span>Accelerate</span>
</button>
</div>
<div class="help-text--mouse-speed last-help text-left">
<p>You can set the multiplier in the <a [routerLink]="['/settings']" title="Settings">settings</a>.</p>
</div>
</div>
<div *ngSwitchDefault>
</div>

View File

@@ -47,16 +47,6 @@
}
}
.help-text--mouse-speed {
margin-bottom: 2rem;
font-size: 0.9em;
color: #666;
p {
margin: 0;
}
}
.details {
.btn-placeholder {
visibility: hidden;
@@ -78,8 +68,3 @@
}
}
}
.help-text--mouse-speed.last-help {
margin-bottom: 0;
margin-top: 2rem;
}

View File

@@ -1,2 +0,0 @@
export * from './settings.component';
export * from './settings.routes';

View File

@@ -1,10 +0,0 @@
import { Routes } from '@angular/router';
import { SettingsComponent } from './settings.component';
export const settingsRoutes: Routes = [
{
path: 'settings',
component: SettingsComponent
}
];

View File

@@ -23,6 +23,12 @@
[class.disabled]="updatingFirmware$ | async">Mouse speed</a>
</div>
</li>
<li class="sidebar__level-2--item">
<div class="sidebar__level-2" [routerLinkActive]="['active']">
<a [routerLink]="['/device/led-brightness']"
[class.disabled]="updatingFirmware$ | async">LED brightness</a>
</div>
</li>
<li class="sidebar__level-2--item">
<div class="sidebar__level-2" [routerLinkActive]="['active']">
<a [routerLink]="['/device/configuration']"
@@ -40,11 +46,11 @@
<li class="sidebar__level-1--item">
<div class="sidebar__level-1">
<i class="fa fa-keyboard-o"></i> Keymaps
<a [routerLink]="['/keymap/add']"
<!--a [routerLink]="['/keymap/add']"
class="btn btn-default pull-right btn-sm"
[class.disabled]="updatingFirmware$ | async">
<i class="fa fa-plus"></i>
</a>
</a-->
<i class="fa fa-chevron-up pull-right"
(click)="toggleHide($event, 'keymap')"></i>
</div>
@@ -111,14 +117,25 @@
</li>
</ul>
</li>
</ul>
</li>
<li class="sidebar__level-0--item" [routerLinkActive]="['active']">
<div class="sidebar__level-0">
<i class="uhk-icon uhk-icon-agent-icon"></i> Agent
<i class="fa fa-chevron-up pull-right"
(click)="toggleHide($event, 'agent')"></i>
</div>
<ul [@toggler]="animation['agent']">
<li class="sidebar__level-2--item">
<div class="sidebar__level-2" [routerLinkActive]="['active']">
<a [routerLink]="['/settings']">Settings</a>
</div>
</li>
<li class="sidebar__level-2--item">
<div class="sidebar__level-2" [routerLinkActive]="['active']">
<a [routerLink]="['/about']">About</a>
</div>
</li>
</ul>
</li>
</ul>
<ul class="menu--bottom" *ngIf="runInElectron$ | async">
<li class="sidebar__level-1--item" [routerLinkActive]="['active']">
<a class="sidebar__level-1" [routerLink]="['/settings']">
<i class="fa fa-gear"></i> Settings
</a>
</li>
</ul>

View File

@@ -64,6 +64,10 @@ ul {
display: none;
cursor: pointer;
}
.uhk-icon-agent-icon {
margin-left: -3px;
}
}
&__level-2 {

View File

@@ -118,7 +118,11 @@ export class SideMenuComponent implements AfterContentInit, OnDestroy {
this.store.dispatch(MacroActions.addMacro());
}
editDeviceName(name): void {
editDeviceName(name: string): void {
if (!util.isValidName(name) || name.trim() === this.deviceNameValue) {
this.setDeviceName();
return;
}
this.store.dispatch(new RenameUserConfigurationAction(name));
}

View File

@@ -0,0 +1,24 @@
<label *ngIf="label">
<span>{{label}}</span>
<icon name="question-circle"
data-toggle="tooltip"
[title]="tooltip"
html="true"
data-placement="bottom"
*ngIf="tooltip"></icon>
</label>
<div class="slider-wrapper">
<div class="slider-container">
<nouislider
[min]="min"
[max]="max"
[step]="step"
[keyboard]="true"
[tooltips]="true"
[(ngModel)]="value"
(ngModelChange)="onSliderChange($event)"></nouislider>
</div>
<div class="slider-value">
<div class="value-indicator">{{value}} {{valueUnit}}</div>
</div>
</div>

View File

@@ -0,0 +1,29 @@
:host {
label {
display: block;
font-weight: normal;
icon {
display: inline-block;
}
}
.slider-wrapper {
display: flex;
flex-direction: row;
padding-left: 1.6rem;
}
.slider-container {
width: 80%;
}
.slider-value {
width: 20%;
}
.value-indicator {
margin: 1rem 1rem 1rem 3rem;
vertical-align: middle;
}
}

View File

@@ -0,0 +1,93 @@
import { AfterViewInit, Component, EventEmitter, forwardRef, Input, Output, OnDestroy, ViewChild } from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
import { NouisliderComponent } from 'ng2-nouislider/src/nouislider';
import { Observable } from 'rxjs/Observable';
import { Observer } from 'rxjs/Observer';
import 'rxjs/add/operator/debounceTime';
import 'rxjs/add/operator/distinctUntilChanged';
export interface SliderPips {
mode: string;
values: number[];
density: number;
stepped?: boolean;
}
export interface SliderProps {
min: number;
max: number;
step?: number;
pips?: SliderPips;
valueUnit?: string;
}
@Component({
selector: 'slider-wrapper',
templateUrl: './slider-wrapper.component.html',
styleUrls: ['./slider-wrapper.component.scss'],
providers: [
{ provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => SliderWrapperComponent), multi: true }
]
})
export class SliderWrapperComponent implements AfterViewInit, ControlValueAccessor, OnDestroy {
@ViewChild(NouisliderComponent) slider: NouisliderComponent;
@Input() label: string;
@Input() tooltip: string;
@Input() min: number;
@Input() max: number;
@Input() step: number;
@Input() pips: SliderPips;
@Input() valueUnit: string;
@Output() onChange = new EventEmitter<number>();
public value: number;
private changeObserver$: Observer<number>;
private changeDebounceTime: number = 300;
ngAfterViewInit(): void {
if (this.pips) {
this.slider.slider.pips(this.pips);
}
// Hide tooltips and show them when dragging slider handle
this.slider.slider.target.querySelector('.noUi-tooltip').style.display = 'none';
this.slider.slider.on('start', function() {
this.target.querySelector('.noUi-tooltip').style.display = 'block';
});
this.slider.slider.on('end', function() {
this.target.querySelector('.noUi-tooltip').style.display = 'none';
});
}
ngOnDestroy() {
if (this.changeObserver$) {
this.changeObserver$.complete();
}
}
writeValue(value: number): void {
this.value = value || this.min;
}
registerOnChange(fn: any): void {
this.propagateChange = fn;
}
registerOnTouched() {}
onSliderChange(value: number): void {
if (!this.changeObserver$) {
Observable.create(observer => {
this.changeObserver$ = observer;
}).debounceTime(this.changeDebounceTime)
.distinctUntilChanged()
.subscribe(this.propagateChange);
return; // No change event on first change as the value is just being set
}
this.changeObserver$.next(value);
}
private propagateChange: any = () => {};
}

View File

@@ -1,5 +1,10 @@
<svg [attr.viewBox]="viewBox" [attr.width]="textContainer.width" [attr.height]="textContainer.height"
[attr.x]="textContainer.x" [attr.y]="textContainer.y" [ngSwitch]="labelType">
<svg:g svg-single-icon-key *ngSwitchCase="'icon'"
[height]="height"
[width]="width"
[icon]="labelSource">
</svg:g>
<svg:g svg-one-line-text-key *ngSwitchCase="'one-line'"
[height]="height"
[width]="width"

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@@ -127,21 +127,23 @@ export class SvgKeystrokeKeyComponent implements OnInit, OnChanges {
}
ngOnChanges() {
let newLabelSource: string[];
if (this.keystrokeAction.hasScancode()) {
const scancode: number = this.keystrokeAction.scancode;
newLabelSource = this.mapper.scanCodeToText(scancode, this.keystrokeAction.type);
if (newLabelSource) {
if (newLabelSource.length === 1) {
this.labelSource = newLabelSource[0];
this.labelType = 'one-line';
} else {
this.labelSource = newLabelSource;
this.labelType = 'two-line';
}
} else {
this.labelSource = this.mapper.scanCodeToSvgImagePath(scancode, this.keystrokeAction.type);
this.labelSource = this.mapper.scanCodeToSvgImagePath(scancode, this.keystrokeAction.type);
if (this.labelSource) {
this.labelType = 'icon';
} else {
let newLabelSource: string[];
newLabelSource = this.mapper.scanCodeToText(scancode, this.keystrokeAction.type);
if (newLabelSource) {
if (newLabelSource.length === 1) {
this.labelSource = newLabelSource[0];
this.labelType = 'one-line';
} else {
this.labelSource = newLabelSource;
this.labelType = 'two-line';
}
}
}
} else {
this.labelType = 'empty';

View File

@@ -0,0 +1,4 @@
export interface UploadFileData {
filename: string;
data: Array<number>;
}

View File

@@ -0,0 +1,4 @@
export interface UserConfigurationValue {
propertyName: string;
value: number;
}

View File

@@ -3,7 +3,7 @@ import { Component } from '@angular/core';
@Component({
selector: 'main-page',
templateUrl: './main.page.html',
styles: [':host{height:100%; display: inline-block; width: 100%}']
styles: [':host{height:100%; width:100%}']
})
export class MainPage {

View File

@@ -26,6 +26,11 @@ export class AppRendererService {
this.ipcRenderer.send(IpcEvents.app.exit);
}
openUrl(url: string): void {
this.logService.info(`[AppRendererService] open url: ${url}`);
this.ipcRenderer.send(IpcEvents.app.openUrl, url);
}
private registerEvents() {
this.ipcRenderer.on(IpcEvents.app.getAppStartInfoReply, (event: string, arg: AppStartInfo) => {
this.dispachStoreAction(new ProcessAppStartInfoAction(arg));

View File

@@ -71,7 +71,11 @@ export class MapperService {
default:
return undefined;
}
return 'assets/compiled_sprite.svg#' + map.get(scanCode);
const id = map.get(scanCode);
if (!id) {
return undefined;
}
return `assets/compiled_sprite.svg#${id}`;
}
public getIcon(iconName: string): string {

View File

@@ -4917,7 +4917,7 @@
null,
{
"keyActionType": "mouse",
"mouseAction": "scrollUp"
"mouseAction": "scrollDown"
},
{
"keyActionType": "mouse",
@@ -4936,7 +4936,7 @@
null,
{
"keyActionType": "mouse",
"mouseAction": "scrollDown"
"mouseAction": "scrollUp"
},
null,
null,
@@ -5926,7 +5926,7 @@
null,
{
"keyActionType": "mouse",
"mouseAction": "scrollUp"
"mouseAction": "scrollDown"
},
{
"keyActionType": "mouse",
@@ -5945,7 +5945,7 @@
null,
{
"keyActionType": "mouse",
"mouseAction": "scrollDown"
"mouseAction": "scrollUp"
},
null,
null,
@@ -6927,7 +6927,7 @@
null,
{
"keyActionType": "mouse",
"mouseAction": "scrollUp"
"mouseAction": "scrollDown"
},
{
"keyActionType": "mouse",
@@ -6946,7 +6946,7 @@
null,
{
"keyActionType": "mouse",
"mouseAction": "scrollDown"
"mouseAction": "scrollUp"
},
null,
null,
@@ -7105,4 +7105,4 @@
]
}
]
}
}

View File

@@ -7,13 +7,15 @@ import { ConfirmationPopoverModule } from 'angular-confirmation-popover';
import { DragulaModule } from 'ng2-dragula/ng2-dragula';
import { Select2Module } from 'ng2-select2/ng2-select2';
import { NouisliderModule } from 'ng2-nouislider';
import { AddOnComponent } from './components/add-on';
import { KeyboardSliderComponent } from './components/keyboard/slider';
import {
DeviceConfigurationComponent,
DeviceFirmwareComponent,
MouseSpeedComponent
MouseSpeedComponent,
LEDBrightnessComponent
} from './components/device';
import { KeymapAddComponent, KeymapEditComponent, KeymapHeaderComponent } from './components/keymap';
import { LayersComponent } from './components/layers';
@@ -41,7 +43,7 @@ import {
} from './components/popover/tab';
import { CaptureKeystrokeButtonComponent } from './components/popover/widgets/capture-keystroke';
import { IconComponent } from './components/popover/widgets/icon';
import { SettingsComponent } from './components/settings';
import { AboutComponent, SettingsComponent } from './components/agent';
import { SideMenuComponent } from './components/side-menu';
import { SvgKeyboardComponent } from './components/svg/keyboard';
import {
@@ -98,6 +100,7 @@ import { LoadingDevicePageComponent } from './pages/loading-page/loading-device.
import { UhkDeviceLoadingGuard } from './services/uhk-device-loading.guard';
import { UhkDeviceLoadedGuard } from './services/uhk-device-loaded.guard';
import { XtermComponent } from './components/xterm/xterm.component';
import { SliderWrapperComponent } from './components/slider-wrapper/slider-wrapper.component';
@NgModule({
declarations: [
@@ -105,6 +108,7 @@ import { XtermComponent } from './components/xterm/xterm.component';
DeviceConfigurationComponent,
DeviceFirmwareComponent,
MouseSpeedComponent,
LEDBrightnessComponent,
KeymapEditComponent,
KeymapHeaderComponent,
NotificationComponent,
@@ -148,6 +152,7 @@ import { XtermComponent } from './components/xterm/xterm.component';
MacroTextTabComponent,
MacroNotFoundComponent,
AddOnComponent,
AboutComponent,
SettingsComponent,
KeyboardSliderComponent,
CancelableDirective,
@@ -163,7 +168,8 @@ import { XtermComponent } from './components/xterm/xterm.component';
MainPage,
ProgressButtonComponent,
LoadingDevicePageComponent,
XtermComponent
XtermComponent,
SliderWrapperComponent
],
imports: [
CommonModule,
@@ -172,6 +178,7 @@ import { XtermComponent } from './components/xterm/xterm.component';
DragulaModule,
routing,
Select2Module,
NouisliderModule,
NotifierModule.withConfig(angularNotifierConfig),
ConfirmationPopoverModule.forRoot({
confirmButtonType: 'danger' // set defaults here
@@ -187,7 +194,6 @@ import { XtermComponent } from './components/xterm/xterm.component';
DataStorageRepositoryService,
DefaultUserConfigurationService,
LogService,
DefaultUserConfigurationService,
AppUpdateRendererService,
AppRendererService,
IpcCommonRenderer,

View File

@@ -1,6 +1,6 @@
import { Action } from '@ngrx/store';
import { AppStartInfo, CommandLineArgs, HardwareConfiguration, Notification, type, VersionInformation } from 'uhk-common';
import { AppStartInfo, CommandLineArgs, HardwareConfiguration, Notification, type } from 'uhk-common';
import { ElectronLogEntry } from '../../models/xterm-log';
const PREFIX = '[app] ';
@@ -16,8 +16,8 @@ export const ActionTypes = {
UNDO_LAST_SUCCESS: type(PREFIX + 'undo last action success'),
DISMISS_UNDO_NOTIFICATION: type(PREFIX + 'dismiss notification action'),
LOAD_HARDWARE_CONFIGURATION_SUCCESS: type(PREFIX + 'load hardware configuration success'),
UPDATE_AGENT_VERSION_INFORMATION: type(PREFIX + 'update agent version information'),
ELECTRON_MAIN_LOG_RECEIVED: type(PREFIX + 'Electron main log received')
ELECTRON_MAIN_LOG_RECEIVED: type(PREFIX + 'Electron main log received'),
OPEN_URL_IN_NEW_WINDOW: type(PREFIX + 'Open URL in new Window')
};
export class AppBootsrappedAction implements Action {
@@ -66,18 +66,18 @@ export class LoadHardwareConfigurationSuccessAction implements Action {
constructor(public payload: HardwareConfiguration) {}
}
export class UpdateAgentVersionInformationAction implements Action {
type = ActionTypes.UPDATE_AGENT_VERSION_INFORMATION;
constructor(public payload: VersionInformation) {}
}
export class ElectronMainLogReceivedAction implements Action {
type = ActionTypes.ELECTRON_MAIN_LOG_RECEIVED;
constructor(public payload: ElectronLogEntry) {}
}
export class OpenUrlInNewWindowAction implements Action {
type = ActionTypes.OPEN_URL_IN_NEW_WINDOW;
constructor(public payload: string) {}
}
export type Actions
= AppStartedAction
| AppBootsrappedAction
@@ -88,6 +88,6 @@ export type Actions
| UndoLastSuccessAction
| DismissUndoNotificationAction
| LoadHardwareConfigurationSuccessAction
| UpdateAgentVersionInformationAction
| ElectronMainLogReceivedAction
| OpenUrlInNewWindowAction
;

View File

@@ -16,6 +16,7 @@ export const ActionTypes = {
SAVE_TO_KEYBOARD_FAILED: type(PREFIX + 'save to keyboard failed'),
HIDE_SAVE_TO_KEYBOARD_BUTTON: type(PREFIX + 'hide save to keyboard button'),
RESET_USER_CONFIGURATION: type(PREFIX + 'reset user configuration'),
RESET_MOUSE_SPEED_SETTINGS: type(PREFIX + 'reset mouse speed settings'),
UPDATE_FIRMWARE: type(PREFIX + 'update firmware'),
UPDATE_FIRMWARE_WITH: type(PREFIX + 'update firmware with'),
UPDATE_FIRMWARE_REPLY: type(PREFIX + 'update firmware reply'),
@@ -109,6 +110,10 @@ export class UpdateFirmwareOkButtonAction implements Action {
type = ActionTypes.UPDATE_FIRMWARE_OK_BUTTON;
}
export class ResetMouseSpeedSettingsAction implements Action {
type = ActionTypes.RESET_MOUSE_SPEED_SETTINGS;
}
export type Actions
= SetPrivilegeOnLinuxAction
| SetPrivilegeOnLinuxReplyAction
@@ -119,6 +124,7 @@ export type Actions
| SaveToKeyboardSuccessAction
| SaveToKeyboardSuccessFailed
| HideSaveToKeyboardButton
| ResetMouseSpeedSettingsAction
| ResetUserConfigurationAction
| UpdateFirmwareAction
| UpdateFirmwareWithAction

View File

@@ -1,5 +1,7 @@
import { Action } from '@ngrx/store';
import { type, UserConfiguration, ConfigurationReply } from 'uhk-common';
import { UserConfigurationValue } from '../../models/user-configuration-value';
import { UploadFileData } from '../../models/upload-file-data';
const PREFIX = '[user-config] ';
@@ -13,7 +15,10 @@ export const ActionTypes = {
SAVE_USER_CONFIG_IN_JSON_FILE: type(PREFIX + 'Save User Config in JSON file'),
SAVE_USER_CONFIG_IN_BIN_FILE: type(PREFIX + 'Save User Config in binary file'),
LOAD_RESET_USER_CONFIGURATION: type(PREFIX + 'Load reset user configuration'),
RENAME_USER_CONFIGURATION: type(PREFIX + 'Rename user configuration')
RENAME_USER_CONFIGURATION: type(PREFIX + 'Rename user configuration'),
SET_USER_CONFIGURATION_VALUE: type(PREFIX + 'Set user configuration value'),
LOAD_USER_CONFIGURATION_FROM_FILE: type(PREFIX + 'Load user configuration from file'),
APPLY_USER_CONFIGURATION_FROM_FILE: type(PREFIX + 'Apply user configuration from file')
};
export class LoadUserConfigAction implements Action {
@@ -67,6 +72,27 @@ export class RenameUserConfigurationAction implements Action {
}
}
export class SetUserConfigurationValueAction implements Action {
type = ActionTypes.SET_USER_CONFIGURATION_VALUE;
constructor(public payload: UserConfigurationValue) {
}
}
export class LoadUserConfigurationFromFileAction implements Action {
type = ActionTypes.LOAD_USER_CONFIGURATION_FROM_FILE;
constructor(public payload: UploadFileData) {
}
}
export class ApplyUserConfigurationFromFileAction implements Action {
type = ActionTypes.APPLY_USER_CONFIGURATION_FROM_FILE;
constructor(public payload: UserConfiguration) {
}
}
export type Actions
= LoadUserConfigAction
| LoadUserConfigSuccessAction
@@ -77,4 +103,7 @@ export type Actions
| SaveUserConfigInBinaryFileAction
| LoadResetUserConfigurationAction
| RenameUserConfigurationAction
| SetUserConfigurationValueAction
| LoadUserConfigurationFromFileAction
| ApplyUserConfigurationFromFileAction
;

View File

@@ -16,19 +16,15 @@ import {
ApplyCommandLineArgsAction,
AppStartedAction,
DismissUndoNotificationAction,
OpenUrlInNewWindowAction,
ProcessAppStartInfoAction,
ShowNotificationAction,
UndoLastAction,
UpdateAgentVersionInformationAction
UndoLastAction
} from '../actions/app';
import { AppRendererService } from '../../services/app-renderer.service';
import { AppUpdateRendererService } from '../../services/app-update-renderer.service';
import {
ActionTypes as DeviceActions,
ConnectionStateChangedAction,
SaveToKeyboardSuccessAction
} from '../actions/device';
import { AppState, autoWriteUserConfiguration } from '../index';
import { ConnectionStateChangedAction } from '../actions/device';
import { AppState, runningInElectron } from '../index';
@Injectable()
export class ApplicationEffects {
@@ -66,8 +62,7 @@ export class ApplicationEffects {
new ConnectionStateChangedAction({
connected: appInfo.deviceConnected,
hasPermission: appInfo.hasPermission
}),
new UpdateAgentVersionInformationAction(appInfo.agentVersionInfo)
})
];
});
@@ -76,12 +71,16 @@ export class ApplicationEffects {
.map(action => action.payload)
.mergeMap((action: Action) => [action, new DismissUndoNotificationAction()]);
@Effect({dispatch: false}) saveToKeyboardSuccess$ = this.actions$
.ofType<SaveToKeyboardSuccessAction>(DeviceActions.SAVE_TO_KEYBOARD_SUCCESS)
.withLatestFrom(this.store.select(autoWriteUserConfiguration))
.do(([action, autoWriteUserConfig]) => {
if (autoWriteUserConfig) {
this.appRendererService.exit();
@Effect({dispatch: false}) openUrlInNewWindow$ = this.actions$
.ofType<OpenUrlInNewWindowAction>(ActionTypes.OPEN_URL_IN_NEW_WINDOW)
.withLatestFrom(this.store.select(runningInElectron))
.do(([action, inElectron]) => {
const url = action.payload;
if (inElectron) {
this.appRendererService.openUrl(url);
} else {
window.open(url, '_blank');
}
});

View File

@@ -130,6 +130,30 @@ export class DeviceEffects {
.switchMap(() => Observable.of(new HideSaveToKeyboardButton()))
);
@Effect()
resetMouseSpeedSettings$: Observable<Action> = this.actions$
.ofType(ActionTypes.RESET_MOUSE_SPEED_SETTINGS)
.switchMap(() => {
const config = this.defaultUserConfigurationService.getDefault();
const mouseSpeedDefaultSettings = {};
const mouseSpeedProps = [
'mouseMoveInitialSpeed',
'mouseMoveAcceleration',
'mouseMoveDeceleratedSpeed',
'mouseMoveBaseSpeed',
'mouseMoveAcceleratedSpeed',
'mouseScrollInitialSpeed',
'mouseScrollAcceleration',
'mouseScrollDeceleratedSpeed',
'mouseScrollBaseSpeed',
'mouseScrollAcceleratedSpeed'
];
mouseSpeedProps.forEach(prop => {
mouseSpeedDefaultSettings[prop] = config[prop];
});
return Observable.of(new LoadResetUserConfigurationAction(<UserConfiguration>mouseSpeedDefaultSettings));
});
@Effect() resetUserConfiguration$: Observable<Action> = this.actions$
.ofType(ActionTypes.RESET_USER_CONFIGURATION)
.switchMap(() => {
@@ -138,7 +162,7 @@ export class DeviceEffects {
});
@Effect() saveResetUserConfigurationToDevice$ = this.actions$
.ofType(UserConfigActions.LOAD_RESET_USER_CONFIGURATION)
.ofType(UserConfigActions.LOAD_RESET_USER_CONFIGURATION, UserConfigActions.APPLY_USER_CONFIGURATION_FROM_FILE)
.switchMap(() => Observable.of(new SaveConfigurationAction()));
@Effect({dispatch: false}) updateFirmware$ = this.actions$

View File

@@ -15,26 +15,38 @@ import 'rxjs/add/observable/of';
import 'rxjs/add/observable/empty';
import {
ConfigurationReply, HardwareConfiguration, LogService, NotificationType, UhkBuffer,
ConfigurationReply,
HardwareConfiguration,
LogService,
NotificationType,
UhkBuffer,
UserConfiguration
} from 'uhk-common';
import {
ActionTypes, LoadConfigFromDeviceReplyAction, LoadUserConfigSuccessAction, RenameUserConfigurationAction,
ActionTypes,
ApplyUserConfigurationFromFileAction,
LoadConfigFromDeviceReplyAction,
LoadUserConfigSuccessAction,
LoadUserConfigurationFromFileAction,
RenameUserConfigurationAction,
SaveUserConfigSuccessAction
} from '../actions/user-config';
import { DataStorageRepositoryService } from '../../services/datastorage-repository.service';
import { DefaultUserConfigurationService } from '../../services/default-user-configuration.service';
import { AppState, autoWriteUserConfiguration, getPrevUserConfiguration, getUserConfiguration } from '../index';
import { AppState, getPrevUserConfiguration, getUserConfiguration } from '../index';
import { KeymapAction, KeymapActions, MacroAction, MacroActions } from '../actions';
import {
DismissUndoNotificationAction, LoadHardwareConfigurationSuccessAction, ShowNotificationAction,
DismissUndoNotificationAction,
LoadHardwareConfigurationSuccessAction,
ShowNotificationAction,
UndoLastAction
} from '../actions/app';
import { SaveConfigurationAction, ShowSaveToKeyboardButtonAction } from '../actions/device';
import { ShowSaveToKeyboardButtonAction } from '../actions/device';
import { DeviceRendererService } from '../../services/device-renderer.service';
import { UndoUserConfigData } from '../../models/undo-user-config-data';
import { UploadFileData } from '../../models/upload-file-data';
@Injectable()
export class UserConfigEffects {
@@ -72,7 +84,7 @@ export class UserConfigEffects {
KeymapActions.SET_DEFAULT, KeymapActions.REMOVE, KeymapActions.SAVE_KEY,
MacroActions.ADD, MacroActions.DUPLICATE, MacroActions.EDIT_NAME, MacroActions.REMOVE, MacroActions.ADD_ACTION,
MacroActions.SAVE_ACTION, MacroActions.DELETE_ACTION, MacroActions.REORDER_ACTION,
ActionTypes.RENAME_USER_CONFIGURATION) as
ActionTypes.RENAME_USER_CONFIGURATION, ActionTypes.SET_USER_CONFIGURATION_VALUE) as
Observable<KeymapAction | MacroAction | RenameUserConfigurationAction>)
.withLatestFrom(this.store.select(getUserConfiguration), this.store.select(getPrevUserConfiguration))
.mergeMap(([action, config, prevUserConfiguration]) => {
@@ -185,16 +197,34 @@ export class UserConfigEffects {
saveAs(blob, 'UserConfiguration.bin');
});
@Effect() loadUserConfigurationSuccess$ = this.actions$
.ofType(ActionTypes.LOAD_USER_CONFIG_SUCCESS)
.withLatestFrom(this.store.select(autoWriteUserConfiguration))
.switchMap(([action, autoWriteUserConfig]) => {
this.logService.debug('[UserConfigEffect] LOAD_USER_CONFIG_SUCCESS', {autoWriteUserConfig});
if (autoWriteUserConfig) {
return Observable.of(new SaveConfigurationAction());
}
else {
return Observable.empty();
@Effect() loadUserConfigurationFromFile$ = this.actions$
.ofType<LoadUserConfigurationFromFileAction>(ActionTypes.LOAD_USER_CONFIGURATION_FROM_FILE)
.map(action => action.payload)
.map((info: UploadFileData) => {
try {
const userConfig = new UserConfiguration();
if (info.filename.endsWith('.bin')) {
userConfig.fromBinary(UhkBuffer.fromArray(info.data));
} else {
const buffer = new Buffer(info.data);
const json = buffer.toString();
userConfig.fromJsonObject(JSON.parse(json));
}
if (userConfig.userConfigMajorVersion) {
return new ApplyUserConfigurationFromFileAction(userConfig);
}
return new ShowNotificationAction({
type: NotificationType.Error,
message: 'Invalid configuration specified.'
});
} catch (err) {
return new ShowNotificationAction({
type: NotificationType.Error,
message: 'Invalid configuration specified.'
});
}
});

View File

@@ -39,11 +39,11 @@ export const metaReducers: MetaReducer<AppState>[] = environment.production
: [storeFreeze];
export const getUserConfiguration = (state: AppState) => state.userConfiguration;
export const getDeviceName = (state: AppState) => state.userConfiguration.deviceName;
export const getDeviceName = createSelector(getUserConfiguration, fromUserConfig.getDeviceName);
export const appState = (state: AppState) => state.app;
export const showAddonMenu = createSelector(appState, fromApp.showAddonMenu);
export const autoWriteUserConfiguration = createSelector(appState, fromApp.autoWriteUserConfiguration);
export const getUndoableNotification = createSelector(appState, fromApp.getUndoableNotification);
export const getPrevUserConfiguration = createSelector(appState, fromApp.getPrevUserConfiguration);
export const runningInElectron = createSelector(appState, fromApp.runningInElectron);

View File

@@ -7,11 +7,11 @@ import { ActionTypes, ShowNotificationAction } from '../actions/app';
import { ActionTypes as UserConfigActionTypes } from '../actions/user-config';
import { ActionTypes as DeviceActionTypes } from '../actions/device';
import { KeyboardLayout } from '../../keyboard/keyboard-layout.enum';
import { getVersions } from '../../util';
export interface State {
started: boolean;
showAddonMenu: boolean;
autoWriteUserConfiguration: boolean;
undoableNotification?: Notification;
navigationCountAfterNotification: number;
prevUserConfig?: UserConfiguration;
@@ -24,10 +24,10 @@ export interface State {
export const initialState: State = {
started: false,
showAddonMenu: false,
autoWriteUserConfiguration: false,
navigationCountAfterNotification: 0,
runningInElectron: runInElectron(),
configLoading: true
configLoading: true,
agentVersionInfo: getVersions()
};
export function reducer(state = initialState, action: Action & { payload: any }) {
@@ -42,8 +42,7 @@ export function reducer(state = initialState, action: Action & { payload: any })
case ActionTypes.APPLY_COMMAND_LINE_ARGS: {
return {
...state,
showAddonMenu: action.payload.addons,
autoWriteUserConfiguration: action.payload.autoWriteConfig
showAddonMenu: action.payload.addons
};
}
@@ -116,18 +115,12 @@ export function reducer(state = initialState, action: Action & { payload: any })
};
}
case ActionTypes.UPDATE_AGENT_VERSION_INFORMATION:
return {
...state,
agentVersionInfo: action.payload
};
default:
return state;
}
}
export const showAddonMenu = (state: State) => state.showAddonMenu;
export const autoWriteUserConfiguration = (state: State) => state.autoWriteUserConfiguration;
export const getUndoableNotification = (state: State) => state.undoableNotification;
export const getPrevUserConfiguration = (state: State) => state.prevUserConfig;
export const runningInElectron = (state: State) => state.runningInElectron;

View File

@@ -4,10 +4,11 @@ import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/of';
import 'rxjs/add/operator/map';
import { KeyAction, Keymap, KeyActionHelper, Layer, Macro, Module, SwitchLayerAction, UserConfiguration } from 'uhk-common';
import { KeyAction, KeyActionHelper, Keymap, Layer, Macro, Module, SwitchLayerAction, UserConfiguration } from 'uhk-common';
import { KeymapActions, MacroActions } from '../actions';
import { AppState } from '../index';
import { ActionTypes } from '../actions/user-config';
import { isValidName } from '../../util';
export const initialState: UserConfiguration = new UserConfiguration();
@@ -15,6 +16,7 @@ export function reducer(state = initialState, action: Action & { payload?: any }
const changedUserConfiguration: UserConfiguration = Object.assign(new UserConfiguration(), state);
switch (action.type) {
case ActionTypes.APPLY_USER_CONFIGURATION_FROM_FILE:
case ActionTypes.LOAD_RESET_USER_CONFIGURATION:
case ActionTypes.LOAD_USER_CONFIG_SUCCESS: {
return Object.assign(changedUserConfiguration, action.payload);
@@ -31,7 +33,11 @@ export function reducer(state = initialState, action: Action & { payload?: any }
break;
}
case KeymapActions.EDIT_NAME: {
const name: string = action.payload.name;
if (!isValidName(action.payload.name)) {
break;
}
const name: string = action.payload.name.trim();
const duplicate = state.keymaps.some((keymap: Keymap) => {
return keymap.name === name && keymap.abbreviation !== action.payload.abbr;
@@ -169,7 +175,11 @@ export function reducer(state = initialState, action: Action & { payload?: any }
break;
}
case MacroActions.EDIT_NAME: {
const name: string = action.payload.name;
if (!isValidName(action.payload.name)) {
break;
}
const name: string = action.payload.name.trim();
const duplicate = state.macros.some((macro: Macro) => {
return macro.id !== action.payload.id && macro.name === name;
@@ -242,7 +252,14 @@ export function reducer(state = initialState, action: Action & { payload?: any }
break;
case ActionTypes.RENAME_USER_CONFIGURATION: {
changedUserConfiguration.deviceName = action.payload;
if (isValidName(action.payload)) {
changedUserConfiguration.deviceName = action.payload.trim();
}
break;
}
case ActionTypes.SET_USER_CONFIGURATION_VALUE: {
changedUserConfiguration[action.payload.propertyName] = action.payload.value;
break;
}
@@ -394,3 +411,5 @@ function setKeyActionToLayer(newLayer: Layer, moduleIndex: number, keyIndex: num
newModule.keyActions = newModule.keyActions.slice();
newModule.keyActions[keyIndex] = newKeyAction;
}
export const getDeviceName = (state: UserConfiguration) => state.deviceName;

View File

@@ -1 +1,3 @@
export * from './html-helper';
export * from './validators';
export * from './version-helper';

View File

@@ -0,0 +1,3 @@
export function isValidName(name: string): boolean {
return name && name.trim().length > 0;
}

View File

@@ -0,0 +1,22 @@
import { VersionInformation } from 'uhk-common';
const collectVersions = (): VersionInformation => {
const pkgJson = require('../../../../../package.json');
return {
version: pkgJson['version'],
firmwareVersion: pkgJson['firmwareVersion'],
deviceProtocolVersion: pkgJson['deviceProtocolVersion'],
moduleProtocolVersion: pkgJson['moduleProtocolVersion'],
userConfigVersion: pkgJson['userConfigVersion'],
hardwareConfigVersion: pkgJson['hardwareConfigVersion']
};
};
let versions: VersionInformation;
export const getVersions = (): VersionInformation => {
if (!versions) {
versions = collectVersions();
}
return versions;
};

View File

@@ -4,5 +4,72 @@
"description": "Right UHK60 keyboard half",
"slot": "right-half",
"keyCount": 35,
"pointerCount": 0
"pointerCount": 0,
"keyAssignmentOrder": [
[1, 2],
[1, 3],
[1, 4],
[1, 5],
[1, 6],
[1, 7],
[0, 1],
[0, 2],
[0, 3],
[0, 4],
[0, 5],
[0, 6],
[0, 7],
[1, 1],
[1, 8],
[1, 9],
[1, 10],
[1, 11],
[1, 12],
[1, 14],
[0, 15],
[0, 8],
[0, 9],
[0, 10],
[0, 11],
[0, 12],
[0, 13],
[0, 14],
[1, 15],
[1, 16],
[1, 17],
[1, 18],
[1, 19],
[1, 21],
[0, 22],
[0, 16],
[0, 17],
[0, 18],
[0, 19],
[0, 20],
[0, 21],
[1, 22],
[1, 24],
[1, 25],
[1, 26],
[1, 27],
[1, 28],
[0, 23],
[0, 24],
[0, 25],
[0, 26],
[0, 27],
[0, 28],
[1, 29],
[1, 30],
[1, 31],
[1, 32],
[1, 34],
[0, 30],
[0, 32],
[0, 33],
[0, 34],
[0, 35],
[1, 33],
[0, 31]
]
}

View File

@@ -90,3 +90,28 @@ a.disabled {
width: 100%;
display: inline-block;
}
.noUi-value {
top: 2rem;
}
.btn-file {
position: relative;
overflow: hidden;
input[type=file] {
position: absolute;
top: 0;
right: 0;
min-width: 100%;
min-height: 100%;
font-size: 100px;
text-align: right;
filter: alpha(opacity=0);
opacity: 0;
outline: none;
background: white;
cursor: inherit;
display: block;
}
}

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="16" height="16" viewBox="0 0 16 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>
<?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"><path fill="#333" d="M3 0C1.338 0 0 1.338 0 3v9c0 1.662 1.338 3 3 3h9c1.662 0 3-1.338 3-3V3c0-1.662-1.338-3-3-3H3zM1.375 3.75a.36.36 0 0 1 .125 0H6c.375 0 .75.375.75.375s.375.375.75.375.75-.375.75-.375.375-.375.75-.375h4.5c.75 0 .75.75.75.75V6c0 2.25-1.5 2.25-1.5 2.25H9c-.375 0-.75-1.125-.75-1.125S7.875 6 7.5 6s-.75 1.125-.75 1.125S6.375 8.25 6 8.25H2.25S.75 8.25.75 6V4.5c0-.562.414-.715.625-.75z"/></svg></svg>

Before

Width:  |  Height:  |  Size: 698 B

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -6,3 +6,8 @@
@extend %svg-common;
background-position: 0 0;
}
.uhk-icon-agent-icon {
@extend %svg-common;
background-position: 100% 0;
}

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="15px" height="15px" viewBox="0 0 15 15" enable-background="new 0 0 15 15" xml:space="preserve">
<path fill="#333333" d="M3,0C1.338,0,0,1.338,0,3v9c0,1.662,1.338,3,3,3h9c1.662,0,3-1.338,3-3V3c0-1.662-1.338-3-3-3H3z
M1.375,3.75c0.07-0.012,0.125,0,0.125,0H6c0.375,0,0.75,0.375,0.75,0.375S7.125,4.5,7.5,4.5s0.75-0.375,0.75-0.375
S8.625,3.75,9,3.75h4.5c0.75,0,0.75,0.75,0.75,0.75V6c0,2.25-1.5,2.25-1.5,2.25H9c-0.375,0-0.75-1.125-0.75-1.125S7.875,6,7.5,6
S6.75,7.125,6.75,7.125S6.375,8.25,6,8.25H2.25c0,0-1.5,0-1.5-2.25V4.5C0.75,3.938,1.164,3.785,1.375,3.75z"/>
</svg>

After

Width:  |  Height:  |  Size: 935 B

View File

@@ -85,10 +85,12 @@ module.exports = {
],
"scripts": [
"script-loader!./node_modules/bootstrap/dist/js/bootstrap.js",
"script-loader!./node_modules/select2/dist/js/select2.full.js"
"script-loader!./node_modules/select2/dist/js/select2.full.js",
"script-loader!./node_modules/nouislider/distribute/nouislider.js"
],
"styles": [
"./node_modules/bootstrap/dist/css/bootstrap.min.css",
"./node_modules/nouislider/distribute/nouislider.min.css",
"./src/styles.scss"
]
},
@@ -126,6 +128,7 @@ module.exports = {
{
"exclude": [
path.join(process.cwd(), "node_modules/bootstrap/dist/css/bootstrap.min.css"),
path.join(process.cwd(), "node_modules/nouislider/distribute/nouislider.min.css"),
path.join(process.cwd(), "src/styles.scss")
],
"test": /\.css$/,
@@ -150,6 +153,7 @@ module.exports = {
{
"exclude": [
path.join(process.cwd(), "node_modules/bootstrap/dist/css/bootstrap.min.css"),
path.join(process.cwd(), "node_modules/nouislider/distribute/nouislider.min.css"),
path.join(process.cwd(), "src/styles.scss")
],
"test": /\.scss$|\.sass$/,
@@ -182,6 +186,7 @@ module.exports = {
{
"exclude": [
path.join(process.cwd(), "node_modules/bootstrap/dist/css/bootstrap.min.css"),
path.join(process.cwd(), "node_modules/nouislider/distribute/nouislider.min.css"),
path.join(process.cwd(), "src/styles.scss")
],
"test": /\.less$/,
@@ -212,6 +217,7 @@ module.exports = {
{
"exclude": [
path.join(process.cwd(), "node_modules/bootstrap/dist/css/bootstrap.min.css"),
path.join(process.cwd(), "node_modules/nouislider/distribute/nouislider.min.css"),
path.join(process.cwd(), "src/styles.scss")
],
"test": /\.styl$/,
@@ -243,6 +249,7 @@ module.exports = {
{
"include": [
path.join(process.cwd(), "node_modules/bootstrap/dist/css/bootstrap.min.css"),
path.join(process.cwd(), "node_modules/nouislider/distribute/nouislider.min.css"),
path.join(process.cwd(), "src/styles.scss")
],
"test": /\.css$/,
@@ -267,6 +274,7 @@ module.exports = {
{
"include": [
path.join(process.cwd(), "node_modules/bootstrap/dist/css/bootstrap.min.css"),
path.join(process.cwd(), "node_modules/nouislider/distribute/nouislider.min.css"),
path.join(process.cwd(), "src/styles.scss")
],
"test": /\.scss$|\.sass$/,
@@ -299,6 +307,7 @@ module.exports = {
{
"include": [
path.join(process.cwd(), "node_modules/bootstrap/dist/css/bootstrap.min.css"),
path.join(process.cwd(), "node_modules/nouislider/distribute/nouislider.min.css"),
path.join(process.cwd(), "src/styles.scss")
],
"test": /\.less$/,
@@ -329,6 +338,7 @@ module.exports = {
{
"include": [
path.join(process.cwd(), "node_modules/bootstrap/dist/css/bootstrap.min.css"),
path.join(process.cwd(), "node_modules/nouislider/distribute/nouislider.min.css"),
path.join(process.cwd(), "src/styles.scss")
],
"test": /\.styl$/,

View File

@@ -2,7 +2,7 @@
const uhk = require('./uhk');
const device = uhk.getUhkDevice();
const sendData = new Buffer([uhk.usbCommands.getProperty, uhk.devicePropertyIds.configSizes]);
const sendData = new Buffer([uhk.usbCommands.getDeviceProperty, uhk.devicePropertyIds.configSizes]);
device.write(uhk.getTransferData(sendData));
const response = Buffer.from(device.readSync());

View File

@@ -0,0 +1,13 @@
#!/usr/bin/env node
const path = require('path');
const uhk = require('./uhk');
const device = uhk.getUhkDevice();
let buffer = new Buffer(uhk.pushUint32([uhk.usbCommands.getDeviceProperty, uhk.devicePropertyIds.i2cBaudRate]));
//console.log(buffer);
device.write(uhk.getTransferData(buffer));
let response = device.readSync();
//console.log(Buffer.from(response));
let requestedBaudRate = uhk.getUint32(response, 2);
let actualBaudRate = uhk.getUint32(response, 6);
console.log(`requestedBaudRate:${requestedBaudRate} | actualBaudRate:${actualBaudRate} | I2C0_F:0b${response[1].toString(2).padStart(8, '0')}`)

71
packages/usb/get-i2c-health.js Executable file
View File

@@ -0,0 +1,71 @@
#!/usr/bin/env node
const process = require('process');
const uhk = require('./uhk');
function slaveI2cErrorBufferToString(buffer, slaveId) {
let statusCount = buffer[1];
const slaveIdToName = [
'leftHalf',
'leftAddon',
'rightAddon',
'rightLedDriver',
'leftLedDriver',
'kboot',
];
let str = `${slaveIdToName[slaveId].padEnd(14)}: `;
const statusCodesToStrings = {
0: 'nak',
1: 'failure',
1100: 'busy',
1101: 'idle',
1102: 'nak',
1103: 'arbitrationLost',
1104: 'timeout',
20000: 'idleSlave',
20001: 'idleCycle',
};
for (let i=0; i<statusCount; i++) {
let status = uhk.getUint32(buffer, i*8+2);
let count = uhk.getUint32(buffer, i*8+4+2);
str += `${statusCodesToStrings[status]}:${count} `;
}
return str;
}
function convertMs(milliseconds) {
let days, hours, minutes, seconds;
seconds = Math.floor(milliseconds / 1000);
minutes = Math.floor(seconds / 60);
seconds = seconds % 60;
hours = Math.floor(minutes / 60);
minutes = minutes % 60;
days = Math.floor(hours / 24);
hours = hours % 24;
return {days, hours, minutes, seconds};
}
const device = uhk.getUhkDevice();
device.write(uhk.getTransferData(new Buffer(uhk.pushUint32([uhk.usbCommands.getDeviceProperty, uhk.devicePropertyIds.uptime]))));
let response = device.readSync();
let uptimeMs = uhk.getUint32(response, 1);
let uptime = convertMs(uptimeMs);
console.log(`uptime: ${uptime.days}d ${String(uptime.hours).padStart(2, '0')}:${String(uptime.minutes).padStart(2, '0')}:${String(uptime.seconds).padStart(2, '0')}`)
device.write(uhk.getTransferData(new Buffer(uhk.pushUint32([uhk.usbCommands.getDeviceProperty, uhk.devicePropertyIds.i2cBaudRate]))));
response = device.readSync();
let requestedBaudRate = uhk.getUint32(response, 2);
let actualBaudRate = uhk.getUint32(response, 6);
console.log(`requestedBaudRate:${requestedBaudRate} | actualBaudRate:${actualBaudRate} | I2C0_F:0b${response[1].toString(2).padStart(8, '0')}`)
for (let slaveId=0; slaveId<6; slaveId++) {
device.write(uhk.getTransferData(new Buffer([uhk.usbCommands.getSlaveI2cErrors, slaveId])));
let response = Buffer.from(device.readSync());
let str = slaveI2cErrorBufferToString(response, slaveId);
console.log(str);
}

View File

@@ -0,0 +1,18 @@
#!/usr/bin/env node
const uhk = require('./uhk');
const device = uhk.getUhkDevice();
const sendData = new Buffer([uhk.usbCommands.getModuleProperty, uhk.moduleSlotToId.leftHalf, uhk.modulePropertyIds.protocolVersions]);
//console.log(sendData)
device.write(uhk.getTransferData(sendData));
const response = Buffer.from(device.readSync());
//console.log(response)
const moduleProtocolMajorVersion = uhk.getUint16(response, 2);
const moduleProtocolMinorVersion = uhk.getUint16(response, 4);
const moduleProtocolPatchVersion = uhk.getUint16(response, 6);
const firmwareMajorVersion = uhk.getUint16(response, 8);
const firmwareMinorVersion = uhk.getUint16(response, 10);
const firmwarePatchVersion = uhk.getUint16(response, 12);
console.log(`moduleProtocolVersion: ${moduleProtocolMajorVersion}.${moduleProtocolMinorVersion}.${moduleProtocolPatchVersion}`);
console.log(`firmwareVersion: ${firmwareMajorVersion}.${firmwareMinorVersion}.${firmwarePatchVersion}`);

View File

@@ -3,7 +3,7 @@ const uhk = require('./uhk');
const device = uhk.getUhkDevice();
function getModuleState() {
const payload = new Buffer([uhk.usbCommands.getModuleProperties, 1]);
const payload = new Buffer([uhk.usbCommands.getModuleProperty, 1]);
console.log('Sending ', uhk.bufferToString(payload));
device.write(uhk.getTransferData(payload));
const receivedBuffer = device.readSync();

View File

@@ -0,0 +1,14 @@
#!/usr/bin/env node
const uhk = require('./uhk');
const device = uhk.getUhkDevice();
const sendData = new Buffer([uhk.usbCommands.getDeviceProperty, uhk.devicePropertyIds.protocolVersions]);
//console.log(sendData)
device.write(uhk.getTransferData(sendData));
const response = Buffer.from(device.readSync());
//console.log(response)
const firmwareMajorVersion = uhk.getUint16(response, 1);
const firmwareMinorVersion = uhk.getUint16(response, 3);
const firmwarePatchVersion = uhk.getUint16(response, 5);
console.log(`firmwareVersion: ${firmwareMajorVersion}.${firmwareMinorVersion}.${firmwarePatchVersion}`);

View File

@@ -0,0 +1,56 @@
#!/usr/bin/env node
const process = require('process');
const uhk = require('./uhk');
const slaveId = parseInt(process.argv[2]);
const device = uhk.getUhkDevice();
const sendData = new Buffer([uhk.usbCommands.getSlaveI2cErrors, slaveId]);
device.write(uhk.getTransferData(sendData));
const response = Buffer.from(device.readSync());
//console.log(response);
let status = response[0];
if (status != 0) {
console.log('Invalid slave id');
process.exit(1);
}
function slaveI2cErrorBufferToString(buffer) {
let statusCount = buffer[1];
const slaveIdToName = [
'leftHalf',
'leftAddon',
'rightAddon',
'rightLedDriver',
'leftLedDriver',
'kboot',
];
let str = `${slaveIdToName[slaveId].padEnd(14)}: `;
const statusCodesToStrings = {
0: 'nak',
1: 'failure',
1100: 'busy',
1101: 'idle',
1102: 'nak',
1103: 'arbitrationLost',
1104: 'timeout',
20000: 'idleSlave',
20001: 'idleCycle',
};
for (let i=0; i<statusCount; i++) {
let status = uhk.getUint32(buffer, i*8+2);
let count = uhk.getUint32(buffer, i*8+4+2);
str += `${statusCodesToStrings[status]}:${count} `;
}
return str;
}
let str = slaveI2cErrorBufferToString(response);
console.log(str);

25
packages/usb/get-uptime.js Executable file
View File

@@ -0,0 +1,25 @@
#!/usr/bin/env node
const path = require('path');
const uhk = require('./uhk');
const device = uhk.getUhkDevice();
function convertMs(milliseconds) {
let days, hours, minutes, seconds;
seconds = Math.floor(milliseconds / 1000);
minutes = Math.floor(seconds / 60);
seconds = seconds % 60;
hours = Math.floor(minutes / 60);
minutes = minutes % 60;
days = Math.floor(hours / 24);
hours = hours % 24;
return {days, hours, minutes, seconds};
}
let buffer = new Buffer(uhk.pushUint32([uhk.usbCommands.getDeviceProperty, uhk.devicePropertyIds.uptime]));
//console.log(buffer);
device.write(uhk.getTransferData(buffer));
let response = device.readSync();
//console.log(Buffer.from(response));
let uptimeMs = uhk.getUint32(response, 1);
let uptime = convertMs(uptimeMs);
console.log(`Uptime: ${uptime.days}d ${String(uptime.hours).padStart(2, '0')}:${String(uptime.minutes).padStart(2, '0')}:${String(uptime.seconds).padStart(2, '0')}`)

View File

@@ -6,7 +6,7 @@ let counter = 1;
while (true) {
console.log(`hidapi sync test ${counter++}`);
const sendData = new Buffer([uhk.usbCommands.getProperty, uhk.devicePropertyIds.configSizes]);
const sendData = new Buffer([uhk.usbCommands.getDeviceProperty, uhk.devicePropertyIds.configSizes]);
device.write(uhk.getTransferData(sendData));
device.readSync()
}

10
packages/usb/log-i2c-errors.sh Executable file
View File

@@ -0,0 +1,10 @@
#!/usr/bin/env bash
(
startTime=`date +%s`
while true; do
s="$((`date +%s`-$startTime))"
echo "$((s/60))m"
for i in `seq 0 5`; do ./get-slave-i2c-errors.js $i; done
done
) 2>&1 | tee $1

View File

@@ -10,7 +10,7 @@ let offset = 0;
let configBuffer = new Buffer(0);
let chunkSizeToRead;
const payload = new Buffer([uhk.usbCommands.getProperty, uhk.devicePropertyIds.configSizes]);
const payload = new Buffer([uhk.usbCommands.getDeviceProperty, uhk.devicePropertyIds.configSizes]);
device.write(uhk.getTransferData(payload));
let buffer = Buffer.from(device.readSync());
const hardwareConfigMaxSize = buffer[1] + (buffer[2]<<8);

View File

@@ -0,0 +1,24 @@
#!/usr/bin/env node
const path = require('path');
const uhk = require('./uhk');
const device = uhk.getUhkDevice();
let programName = path.basename(process.argv[1]);
if (process.argv.length !== 3) {
console.log(`Usage: ${programName} <baud rate in Hz>
Adjust the frequency of the main I2C bus via which the right keyboard half communicates with the left half and the add-on modules.
Example values:
* 400000 Hz - Highest speed. This will probably make communication unreliable. Not recommended.
* 100000 Hz - Default speed. Should work in most cases.
* 10000 Hz - Low speed. Should help when connection is spotty. It'll make initial LED display updates visibly slower.
You're free to use any value in between and test the results.`);
process.exit(1);
}
let bps = process.argv[2];
let buffer = new Buffer(uhk.pushUint32([uhk.usbCommands.setI2cBaudRate], +bps));
//console.log(buffer);
device.write(uhk.getTransferData(buffer));
let response = device.readSync();
//console.log(Buffer.from(response));

View File

@@ -14,6 +14,22 @@ function bufferToString(buffer) {
return str;
}
function getUint16(buffer, offset) {
return (buffer[offset]) + (buffer[offset+1] * 2**8);
}
function getUint32(buffer, offset) {
return (buffer[offset]) + (buffer[offset+1] * 2**8) + (buffer[offset+2] * 2**16) + (buffer[offset+3] * 2**24);
}
function pushUint32(array, value) {
array.push((value >> 0) & 0xff);
array.push((value >> 8) & 0xff);
array.push((value >> 16) & 0xff);
array.push((value >> 24) & 0xff);
return array;
}
function getUhkDevice() {
const foundDevice = HID.devices().find(device =>
device.vendorId === 0x1d50 && device.productId === 0x6122 &&
@@ -57,12 +73,15 @@ let eepromOperations = {
exports = module.exports = moduleExports = {
bufferToString,
getUint16,
getUint32,
pushUint32,
getUhkDevice,
getBootloaderDevice,
getTransferData,
checkModuleSlot,
usbCommands: {
getProperty : 0x00,
getDeviceProperty : 0x00,
reenumerate : 0x01,
jumpToModuleBootloader : 0x02,
sendKbootCommandToModule: 0x03,
@@ -76,7 +95,9 @@ exports = module.exports = moduleExports = {
getDebugBuffer : 0x0b,
getAdcValue : 0x0c,
setLedPwmBrightness : 0x0d,
getModuleProperties : 0x0e,
getModuleProperty : 0x0e,
getSlaveI2cErrors : 0x0f,
setI2cBaudRate : 0x10,
},
enumerationModes: {
bootloader: 0,
@@ -101,6 +122,12 @@ exports = module.exports = moduleExports = {
deviceProtocolVersion: 0,
protocolVersions: 1,
configSizes: 2,
currentKbootCommand: 3,
i2cBaudRate: 4,
uptime: 5,
},
modulePropertyIds: {
protocolVersions: 0,
},
configBufferIds,
eepromOperations,

View File

@@ -19,11 +19,12 @@ checkFirmwareImage(firmwareImage, extension);
const usbDir = `${__dirname}`;
const blhostUsb = getBlhostCmd(uhk.enumerationNameToProductId.buspal);
const blhostBuspal = `${blhostUsb} --buspal i2c,${i2cAddress},100k`;
const blhostBuspal = `${blhostUsb} --buspal i2c,${i2cAddress}`;
config.verbose = true;
exec(`${usbDir}/send-kboot-command-to-module.js ping ${moduleSlot}`);
exec(`${usbDir}/jump-to-module-bootloader.js ${moduleSlot}`);
exec(`${usbDir}/wait-for-kboot-idle.js`);
exec(`${usbDir}/reenumerate.js buspal`);
execRetry(`${blhostBuspal} get-property 1`);
exec(`${blhostBuspal} flash-erase-all-unsecure`);

View File

@@ -0,0 +1,22 @@
#!/usr/bin/env node
const uhk = require('./uhk');
function getCurrentKbootCommand() {
device.write(uhk.getTransferData(new Buffer([uhk.usbCommands.getDeviceProperty, uhk.devicePropertyIds.currentKbootCommand])));
const response = Buffer.from(device.readSync());
const currentKbootCommand = response[1];
if (currentKbootCommand == 0) {
console.log('Bootloader pinged.');
process.exit(0);
} else {
console.log("Cannot ping the bootloader. Please reconnect the left keyboard half. It probably needs several tries, so keep reconnecting until you see this message.");
}
}
const device = uhk.getUhkDevice();
getCurrentKbootCommand();
setInterval(() => {
getCurrentKbootCommand();
}, 500);

View File

@@ -23,7 +23,7 @@ let offset = 0;
let configBuffer = fs.readFileSync(configBin);
let chunkSizeToRead;
const payload = new Buffer([uhk.usbCommands.getProperty, uhk.devicePropertyIds.configSizes]);
const payload = new Buffer([uhk.usbCommands.getDeviceProperty, uhk.devicePropertyIds.configSizes]);
device.write(uhk.getTransferData(payload));
let buffer = Buffer.from(device.readSync());

View File

@@ -0,0 +1,4 @@
wdi-simple.exe --vid 0x1d50 --pid 0x6120 --iid 0 --type 1 --silent
wdi-simple.exe --vid 0x1d50 --pid 0x6121 --iid 0 --type 1 --silent
wdi-simple.exe --vid 0x1d50 --pid 0x6122 --iid 0 --type 1 --silent
wdi-simple.exe --vid 0x1d50 --pid 0x6123 --iid 0 --type 1 --silent

View File

@@ -0,0 +1 @@
zadic-ia32.exe --vid 0x1d50 --pid 0x6120 --iface 0 --usealldevices --noprompt

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