feat(config): Read / write hardware configuration area (#423)

* add write-hca.js

* refactor: Move config serializer into the uhk-common package

* refactor: Move getTransferBuffers into the uhk-usb package

* refactor: delete obsoleted classes

* build: add uhk-usb build command

* refactor: move eeprom transfer to uhk-usb package

* fix: Fix write-hca.js

* feat: load hardware config from the device and

* style: fix ts lint errors

* build: fix rxjs dependency resolve

* test: Add jasmine unit test framework to the tet serializer

* fix(user-config): A "type": "basic", properties to the "keystroke" action types

* feat(usb): set chmod+x on write-hca.js

* feat(usb): Create USB logger

* style: Fix type

* build: Add chalk to dependencies.

Chalk will colorize the output
This commit is contained in:
Róbert Kiss
2017-09-26 18:57:27 +02:00
committed by László Monda
parent 1122784bdb
commit 9294bede50
130 changed files with 9108 additions and 1991 deletions

View File

@@ -32,7 +32,6 @@ install:
before_script: before_script:
- npm run build - npm run build
- npm run build:test
- npm run lint - npm run lint
script: script:

22
package-lock.json generated
View File

@@ -16,13 +16,19 @@
"integrity": "sha1-ixuKArfpLRDOibzRbnroza5xKZ8=", "integrity": "sha1-ixuKArfpLRDOibzRbnroza5xKZ8=",
"dev": true, "dev": true,
"requires": { "requires": {
"@types/node": "8.0.25" "@types/node": "8.0.30"
} }
}, },
"@types/jasmine": {
"version": "2.6.0",
"resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-2.6.0.tgz",
"integrity": "sha512-1ZZdFvYA5zARDXPj5+VF0bwDZWH/o0QQWJVDc5srdC/DngcCZXskR33eR/4PielGvBjLQpQOd6KiQbmtqVkeZA==",
"dev": true
},
"@types/node": { "@types/node": {
"version": "8.0.25", "version": "8.0.30",
"resolved": "https://registry.npmjs.org/@types/node/-/node-8.0.25.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-8.0.30.tgz",
"integrity": "sha512-zT+t9841g1HsjLtPMCYxmb1U4pcZ2TOegAKiomlmj6bIziuaEYHUavxLE9NRwdntY0vOCrgHho6OXjDX7fm/Kw==", "integrity": "sha512-IaQtG3DWe9gRsmk1DqNnYyRVjGDVcBdZywkRVF2f62Boe8XKmlR7lNcwC6pk4V4W8nk+Zu+vdGMsOdRTDj1JPA==",
"dev": true "dev": true
}, },
"@types/node-hid": { "@types/node-hid": {
@@ -37,7 +43,7 @@
"integrity": "sha1-+9YF06q7WccUFqj1ku7krinppuE=", "integrity": "sha1-+9YF06q7WccUFqj1ku7krinppuE=",
"dev": true, "dev": true,
"requires": { "requires": {
"@types/node": "8.0.25" "@types/node": "8.0.30"
} }
}, },
"7zip": { "7zip": {
@@ -8816,9 +8822,9 @@
"dev": true "dev": true
}, },
"typescript": { "typescript": {
"version": "2.4.2", "version": "2.5.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-2.4.2.tgz", "resolved": "https://registry.npmjs.org/typescript/-/typescript-2.5.2.tgz",
"integrity": "sha1-+DlfhdRZJ2BnyYiqQYN6j4KHCEQ=", "integrity": "sha1-A4qV99m7tCCxvzW6MdTFwd0//jQ=",
"dev": true "dev": true
}, },
"uglify-js": { "uglify-js": {

View File

@@ -16,7 +16,8 @@
"devDependencies": { "devDependencies": {
"@types/electron-devtools-installer": "^2.0.2", "@types/electron-devtools-installer": "^2.0.2",
"@types/electron-settings": "^3.0.0", "@types/electron-settings": "^3.0.0",
"@types/node": "~8.0.25", "@types/jasmine": "2.6.0",
"@types/node": "8.0.30",
"@types/node-hid": "^0.5.2", "@types/node-hid": "^0.5.2",
"@types/usb": "^1.1.3", "@types/usb": "^1.1.3",
"autoprefixer": "^6.5.3", "autoprefixer": "^6.5.3",
@@ -44,25 +45,27 @@
"ts-loader": "^2.3.1", "ts-loader": "^2.3.1",
"ts-node": "~3.0.4", "ts-node": "~3.0.4",
"tslint": "~5.5.0", "tslint": "~5.5.0",
"typescript": "^2.4.2", "typescript": "2.5.2",
"webpack": "^2.4.1" "webpack": "^2.4.1"
}, },
"scripts": { "scripts": {
"postinstall": "lerna bootstrap", "postinstall": "lerna bootstrap",
"test": "lerna exec --scope test-serializer npm test", "test": "lerna exec --scope test-serializer npm test",
"lint": "run-s -scn lint:ts lint:style", "lint": "run-s -scn lint:ts lint:style",
"lint:ts": "run-p -sn lint:ts:electron-main lint:ts:electron-renderer lint:ts:web lint:ts:test-serializer", "lint:ts": "run-p -sn lint:ts:electron-main lint:ts:electron-renderer lint:ts:web lint:ts:test-serializer lint:ts:uhk-usb",
"lint:ts:electron-main": "tslint --type-check --project ./packages/uhk-agent/tsconfig.json", "lint:ts:electron-main": "tslint --type-check --project ./packages/uhk-agent/tsconfig.json",
"lint:ts:electron-renderer": "tslint --type-check --project ./packages/uhk-web/src/tsconfig.renderer.json", "lint:ts:electron-renderer": "tslint --type-check --project ./packages/uhk-web/src/tsconfig.renderer.json",
"lint:ts:web": "tslint --type-check --project ./packages/uhk-web/src/tsconfig.app.json", "lint:ts:web": "tslint --type-check --project ./packages/uhk-web/src/tsconfig.app.json",
"lint:ts:test-serializer": "tslint --type-check --project ./packages/test-serializer/tsconfig.json", "lint:ts:test-serializer": "tslint --type-check --project ./packages/test-serializer/tsconfig.json",
"lint:ts:uhk-usb": "tslint --type-check --project ./packages/uhk-usb/tsconfig.json",
"lint:style": "stylelint \"packages/uhk-agent/src/**/*.scss\" \"packages/uhk-web/src/**/*.scss\" --syntax scss", "lint:style": "stylelint \"packages/uhk-agent/src/**/*.scss\" \"packages/uhk-web/src/**/*.scss\" --syntax scss",
"build": "run-s build:web build:electron", "build": "run-s build:web build:electron build:usb build:common",
"build:web": "lerna exec --scope uhk-web npm run build", "build:web": "lerna exec --scope uhk-web npm run build",
"build:electron": "cross-env AOT_BUILD=true run-s -sn build:electron:renderer build:electron:main", "build:electron": "cross-env AOT_BUILD=true run-s -sn build:electron:renderer build:electron:main",
"build:electron:main": "lerna exec --scope uhk-agent npm run build", "build:electron:main": "lerna exec --scope uhk-agent npm run build",
"build:electron:renderer": "lerna exec --scope uhk-web npm run build:renderer", "build:electron:renderer": "lerna exec --scope uhk-web npm run build:renderer",
"build:test": "lerna exec --scope test-serializer npm run build", "build:common": "lerna exec --scope uhk-common npm run build",
"build:usb": "lerna exec --scope uhk-usb npm run build",
"server:web": "lerna exec --scope uhk-web npm start", "server:web": "lerna exec --scope uhk-web npm start",
"server:electron": "lerna exec --scope uhk-web npm run server:renderer", "server:electron": "lerna exec --scope uhk-web npm run server:renderer",
"electron": "lerna exec --scope uhk-agent npm start", "electron": "lerna exec --scope uhk-agent npm start",
@@ -70,6 +73,6 @@
"pack": "node ./scripts/release.js", "pack": "node ./scripts/release.js",
"sprites": "node ./scripts/generate-svg-sprites", "sprites": "node ./scripts/generate-svg-sprites",
"release": "node ./scripts/release.js", "release": "node ./scripts/release.js",
"clean": "lerna exec rimraf ./node_modules ./dist && rimraf ./node_modules ./dist package-lock.json" "clean": "lerna exec rimraf ./node_modules ./dist && rimraf ./node_modules ./dist"
} }
} }

View File

@@ -1,4 +1,3 @@
uhk-config.bin user-config.bin
uhk-config-serialized.json user-config-serialized.json
uhk-config-serialized.bin user-config-serialized.bin
test-serializer.js

View File

@@ -0,0 +1,8 @@
{
"spec_dir": "spec",
"spec_files": [
"**/*[sS]pec.ts"
],
"stopSpecOnExpectationFailure": true,
"random": false
}

1029
packages/test-serializer/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -16,10 +16,16 @@
"dependencies": { "dependencies": {
}, },
"devDependencies": { "devDependencies": {
"uhk-web": "^1.0.0" "@types/jasmine": "^2.6.0",
"@types/node": "8.0.30",
"jasmine": "^2.8.0",
"jasmine-core": "^2.8.0",
"jasmine-node": "2.0.0",
"jasmine-ts": "^0.2.1",
"ts-node": "3.3.0",
"uhk-common": "1.0.0"
}, },
"scripts": { "scripts": {
"build": "webpack", "test": "jasmine-ts --config=jasmine.json"
"test": "node ./test-serializer.js"
} }
} }

View File

@@ -0,0 +1,33 @@
import { UhkBuffer, UserConfiguration } from '../../uhk-common/index';
const fs = require('fs');
const userConfig = JSON.parse(fs.readFileSync('../uhk-web/src/app/services/user-config.json'));
describe('Test Serializer', () => {
it('full config match', () => {
const config1Js = userConfig;
const config1Ts: UserConfiguration = new UserConfiguration().fromJsonObject(config1Js);
const config1Buffer = new UhkBuffer();
config1Ts.toBinary(config1Buffer);
const config1BufferContent = config1Buffer.getBufferContent();
fs.writeFileSync('user-config.bin', config1BufferContent);
config1Buffer.offset = 0;
console.log();
const config2Ts = new UserConfiguration().fromBinary(config1Buffer);
console.log('\n');
const config2Js = config2Ts.toJsonObject();
const config2Buffer = new UhkBuffer();
config2Ts.toBinary(config2Buffer);
fs.writeFileSync('user-config-serialized.json', JSON.stringify(config2Js, undefined, 4));
const config2BufferContent = config1Buffer.getBufferContent();
fs.writeFileSync('user-config-serialized.bin', config2BufferContent);
expect(config1Js).toEqual(config2Js);
const buffersContentsAreEqual: boolean = Buffer.compare(config1BufferContent, config2BufferContent) === 0;
expect(buffersContentsAreEqual).toBe(true);
});
});

View File

@@ -1,45 +0,0 @@
import { UserConfiguration } from '../uhk-web/src/app/config-serializer/config-items/user-configuration';
import { UhkBuffer } from '../uhk-web/src/app/config-serializer/uhk-buffer';
const assert = require('assert');
const fs = require('fs');
const userConfig = JSON.parse(fs.readFileSync('../uhk-web/src/app/config-serializer/user-config.json'));
const config1Js = userConfig;
const config1Ts: UserConfiguration = new UserConfiguration().fromJsonObject(config1Js);
const config1Buffer = new UhkBuffer();
config1Ts.toBinary(config1Buffer);
const config1BufferContent = config1Buffer.getBufferContent();
fs.writeFileSync('user-config.bin', config1BufferContent);
config1Buffer.offset = 0;
console.log();
const config2Ts = new UserConfiguration().fromBinary(config1Buffer);
console.log('\n');
const config2Js = config2Ts.toJsonObject();
const config2Buffer = new UhkBuffer();
config2Ts.toBinary(config2Buffer);
fs.writeFileSync('user-config-serialized.json', JSON.stringify(config2Js, undefined, 4));
const config2BufferContent = config1Buffer.getBufferContent();
fs.writeFileSync('user-config-serialized.bin', config2BufferContent);
console.log('\n');
let returnValue = 0;
try {
assert.deepEqual(config1Js, config2Js);
console.log('JSON configurations are identical.');
} catch (error) {
console.log('JSON configurations differ.');
returnValue = 1;
}
const buffersContentsAreEqual: boolean = Buffer.compare(config1BufferContent, config2BufferContent) === 0;
if (buffersContentsAreEqual) {
console.log('Binary configurations are identical.');
} else {
console.log('Binary configurations differ.');
returnValue += 2;
}
process.exit(returnValue);

View File

@@ -5,10 +5,7 @@
"moduleResolution": "node", "moduleResolution": "node",
"experimentalDecorators": true, "experimentalDecorators": true,
"typeRoots": [ "typeRoots": [
"../node_modules/@types" "./node_modules/@types"
],
"types": [
"node"
] ]
} }
} }

File diff suppressed because it is too large Load Diff

View File

@@ -392,9 +392,9 @@
} }
}, },
"electron": { "electron": {
"version": "1.7.6", "version": "1.7.5",
"resolved": "https://registry.npmjs.org/electron/-/electron-1.7.6.tgz", "resolved": "https://registry.npmjs.org/electron/-/electron-1.7.5.tgz",
"integrity": "sha1-+2nqMb0D3w7/JH8m8LU4vSm27nI=", "integrity": "sha1-BloxAr+LhxAt9QxQmF/v5sVpBFs=",
"requires": { "requires": {
"@types/node": "7.0.43", "@types/node": "7.0.43",
"electron-download": "3.3.0", "electron-download": "3.3.0",
@@ -432,9 +432,9 @@
"integrity": "sha1-ihBD4ys6HaHD9VPc4oznZCRhZ+M=" "integrity": "sha1-ihBD4ys6HaHD9VPc4oznZCRhZ+M="
}, },
"electron-log": { "electron-log": {
"version": "2.2.9", "version": "2.2.6",
"resolved": "https://registry.npmjs.org/electron-log/-/electron-log-2.2.9.tgz", "resolved": "https://registry.npmjs.org/electron-log/-/electron-log-2.2.6.tgz",
"integrity": "sha512-WNMSipQYurNxY14RO6IKgcxcZg1e4aNVpUUJK9q7Bqe0TZEKn1e5h4HiQKhTgVLqKrUn++ugOZrty450P9vpjA==" "integrity": "sha1-zPo+CbOfMhRoyQpkJwE4CjQHnxo="
}, },
"electron-rebuild": { "electron-rebuild": {
"version": "1.6.0", "version": "1.6.0",

View File

@@ -22,7 +22,8 @@
"electron-settings": "3.0.14", "electron-settings": "3.0.14",
"electron-updater": "2.2.0", "electron-updater": "2.2.0",
"node-hid": "0.5.4", "node-hid": "0.5.4",
"sudo-prompt": "^7.0.0" "sudo-prompt": "^7.0.0",
"uhk-usb": "1.0.0"
}, },
"devDependencies": { "devDependencies": {
"uhk-common": "^1.0.0" "uhk-common": "^1.0.0"

View File

@@ -8,6 +8,7 @@ import { autoUpdater } from 'electron-updater';
import * as path from 'path'; import * as path from 'path';
import * as url from 'url'; import * as url from 'url';
import * as commandLineArgs from 'command-line-args'; import * as commandLineArgs from 'command-line-args';
import { UhkHidDevice } from 'uhk-usb';
// import { ElectronDataStorageRepositoryService } from './services/electron-datastorage-repository.service'; // import { ElectronDataStorageRepositoryService } from './services/electron-datastorage-repository.service';
import { CommandLineArgs } from 'uhk-common'; import { CommandLineArgs } from 'uhk-common';
@@ -16,7 +17,6 @@ import { logger } from './services/logger.service';
import { AppUpdateService } from './services/app-update.service'; import { AppUpdateService } from './services/app-update.service';
import { AppService } from './services/app.service'; import { AppService } from './services/app.service';
import { SudoService } from './services/sudo.service'; import { SudoService } from './services/sudo.service';
import { UhkHidDeviceService } from './services/uhk-hid-device.service';
const optionDefinitions = [ const optionDefinitions = [
{ name: 'addons', type: Boolean, defaultOption: false } { name: 'addons', type: Boolean, defaultOption: false }
@@ -33,7 +33,7 @@ let win: Electron.BrowserWindow;
autoUpdater.logger = logger; autoUpdater.logger = logger;
let deviceService: DeviceService; let deviceService: DeviceService;
let uhkHidDeviceService: UhkHidDeviceService; let uhkHidDeviceService: UhkHidDevice;
let appUpdateService: AppUpdateService; let appUpdateService: AppUpdateService;
let appService: AppService; let appService: AppService;
let sudoService: SudoService; let sudoService: SudoService;
@@ -51,7 +51,7 @@ function createWindow() {
}); });
win.setMenuBarVisibility(false); win.setMenuBarVisibility(false);
win.maximize(); win.maximize();
uhkHidDeviceService = new UhkHidDeviceService(logger); uhkHidDeviceService = new UhkHidDevice(logger);
deviceService = new DeviceService(logger, win, uhkHidDeviceService); deviceService = new DeviceService(logger, win, uhkHidDeviceService);
appUpdateService = new AppUpdateService(logger, win, app); appUpdateService = new AppUpdateService(logger, win, app);
appService = new AppService(logger, win, deviceService, options, uhkHidDeviceService); appService = new AppService(logger, win, deviceService, options, uhkHidDeviceService);

View File

@@ -1,16 +1,16 @@
import { ipcMain, BrowserWindow } from 'electron'; import { ipcMain, BrowserWindow } from 'electron';
import { UhkHidDevice } from 'uhk-usb';
import { CommandLineArgs, IpcEvents, AppStartInfo, LogService } from 'uhk-common'; import { CommandLineArgs, IpcEvents, AppStartInfo, LogService } from 'uhk-common';
import { MainServiceBase } from './main-service-base'; import { MainServiceBase } from './main-service-base';
import { DeviceService } from './device.service'; import { DeviceService } from './device.service';
import { UhkHidDeviceService } from './uhk-hid-device.service';
export class AppService extends MainServiceBase { export class AppService extends MainServiceBase {
constructor(protected logService: LogService, constructor(protected logService: LogService,
protected win: Electron.BrowserWindow, protected win: Electron.BrowserWindow,
private deviceService: DeviceService, private deviceService: DeviceService,
private options: CommandLineArgs, private options: CommandLineArgs,
private uhkHidDeviceService: UhkHidDeviceService) { private uhkHidDeviceService: UhkHidDevice) {
super(logService, win); super(logService, win);
ipcMain.on(IpcEvents.app.getAppStartInfo, this.handleAppStartInfo.bind(this)); ipcMain.on(IpcEvents.app.getAppStartInfo, this.handleAppStartInfo.bind(this));

View File

@@ -1,10 +1,10 @@
import { ipcMain } from 'electron'; import { ipcMain } from 'electron';
import { IpcEvents, LogService, IpcResponse, ConfigurationReply } from 'uhk-common';
import { Constants, IpcEvents, LogService, IpcResponse } from 'uhk-common'; import { Constants, EepromTransfer, SystemPropertyIds, UsbCommand } from 'uhk-usb';
import { Observable } from 'rxjs/Observable'; import { Observable } from 'rxjs/Observable';
import { Subscription } from 'rxjs/Subscription'; import { Subscription } from 'rxjs/Subscription';
import { Device, devices } from 'node-hid'; import { Device, devices } from 'node-hid';
import { UhkHidDevice } from 'uhk-usb';
import 'rxjs/add/observable/interval'; import 'rxjs/add/observable/interval';
import 'rxjs/add/operator/startWith'; import 'rxjs/add/operator/startWith';
@@ -12,38 +12,6 @@ import 'rxjs/add/operator/map';
import 'rxjs/add/operator/do'; import 'rxjs/add/operator/do';
import 'rxjs/add/operator/distinctUntilChanged'; import 'rxjs/add/operator/distinctUntilChanged';
import { UhkHidDeviceService } from './uhk-hid-device.service';
/**
* UHK USB Communications command. All communication package should have start with a command code.
*/
enum Command {
GetProperty = 0,
UploadConfig = 8,
ApplyConfig = 9,
LaunchEepromTransfer = 12,
ReadUserConfig = 15,
GetKeyboardState = 16
}
enum EepromTransfer {
ReadHardwareConfig = 0,
WriteHardwareConfig = 1,
ReadUserConfig = 2,
WriteUserConfig = 3
}
enum SystemPropertyIds {
UsbProtocolVersion = 0,
BridgeProtocolVersion = 1,
DataModelVersion = 2,
FirmwareVersion = 3,
HardwareConfigSize = 4,
UserConfigSize = 5
}
const snooze = ms => new Promise(resolve => setTimeout(resolve, ms));
/** /**
* IpcMain pair of the UHK Communication * IpcMain pair of the UHK Communication
* Functionality: * Functionality:
@@ -57,10 +25,10 @@ export class DeviceService {
constructor(private logService: LogService, constructor(private logService: LogService,
private win: Electron.BrowserWindow, private win: Electron.BrowserWindow,
private device: UhkHidDeviceService) { private device: UhkHidDevice) {
this.pollUhkDevice(); this.pollUhkDevice();
ipcMain.on(IpcEvents.device.saveUserConfiguration, this.saveUserConfiguration.bind(this)); ipcMain.on(IpcEvents.device.saveUserConfiguration, this.saveUserConfiguration.bind(this));
ipcMain.on(IpcEvents.device.loadUserConfiguration, this.loadUserConfiguration.bind(this)); ipcMain.on(IpcEvents.device.loadConfigurations, this.loadConfigurations.bind(this));
logService.debug('[DeviceService] init success'); logService.debug('[DeviceService] init success');
} }
@@ -76,32 +44,64 @@ export class DeviceService {
* Return with the actual UserConfiguration from UHK Device * Return with the actual UserConfiguration from UHK Device
* @returns {Promise<Buffer>} * @returns {Promise<Buffer>}
*/ */
public async loadUserConfiguration(event: Electron.Event): Promise<void> { public async loadConfigurations(event: Electron.Event): Promise<void> {
try {
const userConfiguration = await this.loadConfiguration(
SystemPropertyIds.UserConfigSize,
UsbCommand.ReadUserConfig,
'user configuration');
const hardwareConfiguration = await this.loadConfiguration(
SystemPropertyIds.HardwareConfigSize,
UsbCommand.ReadHardwareConfig,
'hardware configuration');
const response: ConfigurationReply = {
success: true,
userConfiguration,
hardwareConfiguration
};
event.sender.send(IpcEvents.device.loadConfigurationReply, JSON.stringify(response));
} catch (error) {
const response: ConfigurationReply = {
success: false,
error: error.message
};
event.sender.send(IpcEvents.device.loadConfigurationReply, JSON.stringify(response));
} finally {
this.device.close();
}
}
/**
* Return with the actual user / hardware fonfiguration from UHK Device
* @returns {Promise<Buffer>}
*/
public async loadConfiguration(property: SystemPropertyIds, config: UsbCommand, configName: string): Promise<string> {
let response = []; let response = [];
try { try {
this.logService.debug('[DeviceService] USB[T]: Read user configuration size from keyboard'); this.logService.debug(`[DeviceService] USB[T]: Read ${configName} size from keyboard`);
const configSize = await this.getUserConfigSizeFromKeyboard(); const configSize = await this.getConfigSizeFromKeyboard(property);
const chunkSize = 63; const chunkSize = 63;
let offset = 0; let offset = 0;
let configBuffer = new Buffer(0); let configBuffer = new Buffer(0);
this.logService.debug('[DeviceService] USB[T]: Read user configuration from keyboard'); this.logService.debug(`[DeviceService] USB[T]: Read ${configName} from keyboard`);
while (offset < configSize) { while (offset < configSize) {
const chunkSizeToRead = Math.min(chunkSize, configSize - offset); const chunkSizeToRead = Math.min(chunkSize, configSize - offset);
const writeBuffer = Buffer.from([Command.ReadUserConfig, chunkSizeToRead, offset & 0xff, offset >> 8]); const writeBuffer = Buffer.from([config, chunkSizeToRead, offset & 0xff, offset >> 8]);
const readBuffer = await this.device.write(writeBuffer); const readBuffer = await this.device.write(writeBuffer);
configBuffer = Buffer.concat([configBuffer, new Buffer(readBuffer.slice(1, chunkSizeToRead + 1))]); configBuffer = Buffer.concat([configBuffer, new Buffer(readBuffer.slice(1, chunkSizeToRead + 1))]);
offset += chunkSizeToRead; offset += chunkSizeToRead;
} }
response = UhkHidDeviceService.convertBufferToIntArray(configBuffer); response = UhkHidDevice.convertBufferToIntArray(configBuffer);
return Promise.resolve(JSON.stringify(response));
} catch (error) { } catch (error) {
this.logService.error('[DeviceService] getUserConfigFromEeprom error', error); const errMsg = `[DeviceService] ${configName} from eeprom error`;
} finally { this.logService.error(errMsg, error);
this.device.close(); throw new Error(errMsg);
} }
event.sender.send(IpcEvents.device.loadUserConfigurationReply, JSON.stringify(response));
} }
/** /**
@@ -127,11 +127,11 @@ export class DeviceService {
} }
/** /**
* Return the UserConfiguration size from the UHK Device * Return the user / hardware configuration size from the UHK Device
* @returns {Promise<number>} * @returns {Promise<number>}
*/ */
private async getUserConfigSizeFromKeyboard(): Promise<number> { private async getConfigSizeFromKeyboard(property: SystemPropertyIds): Promise<number> {
const buffer = await this.device.write(new Buffer([Command.GetProperty, SystemPropertyIds.UserConfigSize])); const buffer = await this.device.write(new Buffer([UsbCommand.GetProperty, property]));
const configSize = buffer[1] + (buffer[2] << 8); const configSize = buffer[1] + (buffer[2] << 8);
this.logService.debug('[DeviceService] User config size:', configSize); this.logService.debug('[DeviceService] User config size:', configSize);
return configSize; return configSize;
@@ -144,7 +144,7 @@ export class DeviceService {
this.logService.debug('[DeviceService] USB[T]: Write user configuration to keyboard'); this.logService.debug('[DeviceService] USB[T]: Write user configuration to keyboard');
await this.sendUserConfigToKeyboard(json); await this.sendUserConfigToKeyboard(json);
this.logService.debug('[DeviceService] USB[T]: Write user configuration to EEPROM'); this.logService.debug('[DeviceService] USB[T]: Write user configuration to EEPROM');
await this.writeUserConfigToEeprom(); await this.device.writeConfigToEeprom(EepromTransfer.WriteUserConfig);
response.success = true; response.success = true;
} }
@@ -168,48 +168,12 @@ export class DeviceService {
*/ */
private async sendUserConfigToKeyboard(json: string): Promise<void> { private async sendUserConfigToKeyboard(json: string): Promise<void> {
const buffer: Buffer = new Buffer(JSON.parse(json).data); const buffer: Buffer = new Buffer(JSON.parse(json).data);
const fragments = this.getTransferBuffers(buffer); const fragments = UhkHidDevice.getTransferBuffers(UsbCommand.UploadUserConfig, buffer);
for (const fragment of fragments) { for (const fragment of fragments) {
await this.device.write(fragment); await this.device.write(fragment);
} }
this.logService.debug('[DeviceService] USB[T]: Apply user configuration to keyboard'); this.logService.debug('[DeviceService] USB[T]: Apply user configuration to keyboard');
const applyBuffer = new Buffer([Command.ApplyConfig]); const applyBuffer = new Buffer([UsbCommand.ApplyConfig]);
await this.device.write(applyBuffer); await this.device.write(applyBuffer);
} }
private async writeUserConfigToEeprom(): Promise<void> {
await this.device.write(new Buffer([Command.LaunchEepromTransfer, EepromTransfer.WriteUserConfig]));
await this.waitUntilKeyboardBusy();
}
private async waitUntilKeyboardBusy(): Promise<void> {
while (true) {
const buffer = await this.device.write(new Buffer([Command.GetKeyboardState]));
if (buffer[1] === 0) {
break;
}
this.logService.debug('Keyboard is busy, wait...');
await snooze(200);
}
}
/**
* Split the whole UserConfiguration package into 64 byte fragments
* @param {Buffer} configBuffer
* @returns {Buffer[]}
* @private
*/
private getTransferBuffers(configBuffer: Buffer): Buffer[] {
const fragments: Buffer[] = [];
const MAX_SENDING_PAYLOAD_SIZE = Constants.MAX_PAYLOAD_SIZE - 4;
for (let offset = 0; offset < configBuffer.length; offset += MAX_SENDING_PAYLOAD_SIZE) {
const length = offset + MAX_SENDING_PAYLOAD_SIZE < configBuffer.length
? MAX_SENDING_PAYLOAD_SIZE
: configBuffer.length - offset;
const header = new Buffer([Command.UploadConfig, length, offset & 0xFF, offset >> 8]);
fragments.push(Buffer.concat([header, configBuffer.slice(offset, offset + length)]));
}
return fragments;
}
} }

View File

@@ -1,3 +1,4 @@
export * from './src/util'; export * from './src/util';
export * from './src/models'; export * from './src/models';
export * from './src/services'; export * from './src/services';
export * from './src/config-serializer';

View File

@@ -20,6 +20,11 @@
"resolved": "https://registry.npmjs.org/@ngrx/store/-/store-2.2.3.tgz", "resolved": "https://registry.npmjs.org/@ngrx/store/-/store-2.2.3.tgz",
"integrity": "sha1-570RSfHEQgjxzEdENT8PmKDx9Xs=" "integrity": "sha1-570RSfHEQgjxzEdENT8PmKDx9Xs="
}, },
"@types/node": {
"version": "8.0.28",
"resolved": "https://registry.npmjs.org/@types/node/-/node-8.0.28.tgz",
"integrity": "sha512-HupkFXEv3O3KSzcr3Ylfajg0kaerBg1DyaZzRBBQfrU3NN1mTBRE7sCveqHwXLS5Yrjvww8qFzkzYQQakG9FuQ=="
},
"tslib": { "tslib": {
"version": "1.7.1", "version": "1.7.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.7.1.tgz", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.7.1.tgz",

View File

@@ -3,15 +3,17 @@
"private": true, "private": true,
"version": "1.0.0", "version": "1.0.0",
"description": "Common Library contains the common code for uhk-agent (electron-main) and web (electron-renderer) modules", "description": "Common Library contains the common code for uhk-agent (electron-main) and web (electron-renderer) modules",
"main": "src/index.js", "main": "dist/index.js",
"scripts": { "scripts": {
"build": "tsc",
"test": "echo \"Error: no test specified\" && exit 1" "test": "echo \"Error: no test specified\" && exit 1"
}, },
"author": "", "author": "",
"license": "ISC", "license": "ISC",
"devDependencies": { "devDependencies": {
"@angular/core": "^4.3.3", "@angular/core": "4.3.3",
"@ngrx/core": "1.2.0", "@ngrx/core": "1.2.0",
"@ngrx/store": "^2.2.3" "@ngrx/store": "2.2.3",
"@types/node": "8.0.28"
} }
} }

View File

@@ -6,13 +6,13 @@ The configuration of the UHK is unusually complex for a keyboard, and is compose
## Setup ## Setup
Given that the development dependencies are installed on your system you should be able to build the configuration serializer tester by executing `npm run build:test` in this directory, then start the test by running `node test-serializer.js`. Given that the development dependencies are installed on your system you should be able to run the configuration serializer tester test by running `npm run test` in this directorx.
## Configuration representations ## Configuration representations
There are 3 different representations of the configuration, each filling a specific purpose. There are 3 different representations of the configuration, each filling a specific purpose.
The **JavaScript representation** is optimally suited to be serialized as JSON, and saved to the file system, or transmitted over the network. As a plaintext format, it's human-readable and easily editable. See [user-config.json](user-config.json) for an example configuration. The **JavaScript representation** is optimally suited to be serialized as JSON, and saved to the file system, or transmitted over the network. As a plaintext format, it's human-readable and easily editable. See [user-config.json](../../../uhk-web/src/app/services/user-config.json) for an example configuration.
The **TypeScript representation** is structurally similar to the JavaScript representation, but it features strongly typed TypeScript objects instead of typeless JavaScript objects. This representation is meant to be used by Agent. Extensive, per-property [assertion](assert.ts) takes place upon initializing the TypeScript objects to ensure the integrity of the configuration. The **TypeScript representation** is structurally similar to the JavaScript representation, but it features strongly typed TypeScript objects instead of typeless JavaScript objects. This representation is meant to be used by Agent. Extensive, per-property [assertion](assert.ts) takes place upon initializing the TypeScript objects to ensure the integrity of the configuration.
@@ -75,7 +75,7 @@ KeyActions.toJsObject: <KeyActions length="9">
## Testing the serializer ## Testing the serializer
[test-serializer.ts](test-serializer.ts) is designed to test the serializer by taking [user-config.json](user-config.json), and transforming it to TypeScript representation, then to binary representation, then finally back to JavaScript representation. This should exercise every major code path. [test-serializer.ts](test-serializer.ts) is designed to test the serializer by taking [user-config.json](../../../uhk-web/src/app/services/user-config.json), and transforming it to TypeScript representation, then to binary representation, then finally back to JavaScript representation. This should exercise every major code path.
If the testing is successful the following should be displayed: If the testing is successful the following should be displayed:

View File

@@ -0,0 +1,11 @@
export * from './key-action';
export * from './macro-action';
export * from './hardware-configuration';
export * from './key-modifiers';
export * from './keymap';
export * from './layer';
export * from './long-press-action';
export * from './macro';
export * from './module';
export * from './module-configuration';
export * from './user-configuration';

View File

@@ -1,8 +1,9 @@
export * from './key-action'; export * from './key-action';
export * from './keystroke-action'; export * from './keystroke-action';
export * from './keystroke-type';
export * from './mouse-action'; export * from './mouse-action';
export * from './none-action'; export * from './none-action';
export * from './play-macro-action'; export * from './play-macro-action';
export * from './switch-keymap-action'; export * from './switch-keymap-action';
export * from './switch-layer-action'; export * from './switch-layer-action';
export * from './helper'; export { Helper as KeyActionHelper } from './helper';

View File

@@ -3,7 +3,6 @@ import { Layer } from './layer';
import { Macro } from './macro'; import { Macro } from './macro';
import { SwitchLayerAction } from './key-action/switch-layer-action'; import { SwitchLayerAction } from './key-action/switch-layer-action';
import { KeyAction } from './key-action/key-action'; import { KeyAction } from './key-action/key-action';
import { ConfigSerializer } from '../config-serializer';
import { UserConfiguration } from './user-configuration'; import { UserConfiguration } from './user-configuration';
export class Keymap { export class Keymap {

View File

@@ -2,7 +2,6 @@ import { UhkBuffer } from '../uhk-buffer';
import { Macro } from './macro'; import { Macro } from './macro';
import { Module } from './module'; import { Module } from './module';
import { UserConfiguration } from './user-configuration'; import { UserConfiguration } from './user-configuration';
import { ConfigSerializer } from '../config-serializer';
export class Layer { export class Layer {

View File

@@ -5,4 +5,4 @@ export * from './move-mouse-macro-action';
export * from './mouse-button-macro-action'; export * from './mouse-button-macro-action';
export * from './scroll-mouse-macro-action'; export * from './scroll-mouse-macro-action';
export * from './text-macro-action'; export * from './text-macro-action';
export * from './helper'; export { Helper as MacroActionHelper } from './helper';

View File

@@ -1,6 +1,7 @@
import { assertUInt8 } from '../assert'; import { assertUInt8 } from '../assert';
import { UhkBuffer } from '../uhk-buffer'; import { UhkBuffer } from '../uhk-buffer';
import { Helper as MacroActionHelper, MacroAction } from './macro-action'; import { MacroAction } from './macro-action';
import { Helper as MacroActionHelper } from './macro-action/helper';
export class Macro { export class Macro {

View File

@@ -1,6 +1,6 @@
import { assertEnum, assertUInt8 } from '../assert'; import { assertEnum, assertUInt8 } from '../assert';
import { UhkBuffer } from '../uhk-buffer'; import { UhkBuffer } from '../uhk-buffer';
import { Helper as KeyActionHelper, KeyAction, NoneAction, PlayMacroAction, SwitchKeymapAction } from './key-action'; import { KeyActionHelper, KeyAction, NoneAction, PlayMacroAction, SwitchKeymapAction } from './key-action';
import { Macro } from './macro'; import { Macro } from './macro';
import { UserConfiguration } from './user-configuration'; import { UserConfiguration } from './user-configuration';

View File

@@ -0,0 +1,4 @@
export * from './config-items';
export * from './assert';
export * from './config-serializer';
export * from './uhk-buffer';

View File

@@ -4,6 +4,24 @@ export class UhkBuffer {
(<any>element).toBinary(buffer); // TODO: Remove any (<any>element).toBinary(buffer); // TODO: Remove any
} }
static fromArray(data: Array<number>): UhkBuffer {
if (data.length < 1) {
return null;
}
const uhkBuffer = new UhkBuffer();
let hasNonZeroValue = false;
for (const num of data) {
if (num > 0) {
hasNonZeroValue = true;
}
uhkBuffer.writeUInt8(num);
}
uhkBuffer.offset = 0;
return uhkBuffer;
}
private static eepromSize = 32 * 1024; private static eepromSize = 32 * 1024;
private static maxCompactLength = 0xFFFF; private static maxCompactLength = 0xFFFF;
private static longCompactLengthPrefix = 0xFF; private static longCompactLengthPrefix = 0xFF;

View File

@@ -0,0 +1,6 @@
export interface ConfigurationReply {
success: boolean;
userConfiguration?: string;
hardwareConfiguration?: string;
error?: string;
}

View File

@@ -2,3 +2,4 @@ export * from './command-line-args';
export * from './notification'; export * from './notification';
export * from './ipc-response'; export * from './ipc-response';
export * from './app-start-info'; export * from './app-start-info';
export * from './configuration-reply';

View File

@@ -1,17 +1,14 @@
import { Injectable } from '@angular/core';
@Injectable()
export class LogService { export class LogService {
error(...args: any[]): void { error(...args: any[]): void {
console.error(args); console.error(args);
} }
debug(...args: any[]): void { debug(...args: any[]): void {
console.debug(args); console.log(args);
} }
silly(...args: any[]): void { silly(...args: any[]): void {
console.debug(args); console.log(args);
} }
info(...args: any[]): void { info(...args: any[]): void {

View File

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

View File

@@ -22,8 +22,8 @@ class Device {
public static readonly deviceConnectionStateChanged = 'device-connection-state-changed'; public static readonly deviceConnectionStateChanged = 'device-connection-state-changed';
public static readonly saveUserConfiguration = 'device-save-user-configuration'; public static readonly saveUserConfiguration = 'device-save-user-configuration';
public static readonly saveUserConfigurationReply = 'device-save-user-configuration-reply'; public static readonly saveUserConfigurationReply = 'device-save-user-configuration-reply';
public static readonly loadUserConfiguration = 'device-load-user-configuration'; public static readonly loadConfigurations = 'device-load-configuration';
public static readonly loadUserConfigurationReply = 'device-load-user-configuration-reply'; public static readonly loadConfigurationReply = 'device-load-configuration-reply';
} }
export class IpcEvents { export class IpcEvents {

View File

@@ -0,0 +1,6 @@
export namespace LogRegExps {
export const transferRegExp = /USB\[T]:/;
export const writeRegExp = /USB\[W]:/;
export const readRegExp = /USB\[R]: 00/;
export const errorRegExp = /(?:(USB\[R]: ([^0]|0[^0])))/;
}

View File

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

View File

@@ -0,0 +1 @@
export * from './src';

781
packages/uhk-usb/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,17 @@
{
"name": "uhk-usb",
"version": "1.0.0",
"description": "Agent preliminary USB code",
"main": "dist/index.js",
"license": "GPL-3.0",
"scripts": {
"build": "tsc"
},
"devDependencies": {
"@types/node": "8.0.28"
},
"dependencies": {
"node-hid": "0.5.4",
"uhk-common": "1.0.0"
}
}

View File

@@ -0,0 +1,35 @@
export namespace Constants {
export const VENDOR_ID = 0x1D50;
export const PRODUCT_ID = 0x6122;
export const MAX_PAYLOAD_SIZE = 64;
}
/**
* UHK USB Communications command. All communication package should have start with a command code.
*/
export enum UsbCommand {
GetProperty = 0,
UploadUserConfig = 8,
ApplyConfig = 9,
LaunchEepromTransfer = 12,
ReadHardwareConfig = 13,
WriteHardwareConfig = 14,
ReadUserConfig = 15,
GetKeyboardState = 16
}
export enum EepromTransfer {
ReadHardwareConfig = 0,
WriteHardwareConfig = 1,
ReadUserConfig = 2,
WriteUserConfig = 3
}
export enum SystemPropertyIds {
UsbProtocolVersion = 0,
BridgeProtocolVersion = 1,
DataModelVersion = 2,
FirmwareVersion = 3,
HardwareConfigSize = 4,
UserConfigSize = 5
}

View File

@@ -0,0 +1,2 @@
export * from './constants';
export * from './uhk-hid-device';

View File

@@ -1,11 +1,14 @@
import { Device, devices, HID } from 'node-hid'; import { Device, devices, HID } from 'node-hid';
import { LogService } from 'uhk-common';
import { Constants, LogService } from 'uhk-common'; import { Constants, EepromTransfer, UsbCommand } from './constants';
const snooze = ms => new Promise(resolve => setTimeout(resolve, ms));
/** /**
* HID API wrapper to support unified logging and async write * HID API wrapper to support unified logging and async write
*/ */
export class UhkHidDeviceService { export class UhkHidDevice {
/** /**
* Convert the Buffer to number[] * Convert the Buffer to number[]
* @param {Buffer} buffer * @param {Buffer} buffer
@@ -17,6 +20,27 @@ export class UhkHidDeviceService {
return Array.prototype.slice.call(buffer, 0); return Array.prototype.slice.call(buffer, 0);
} }
/**
* Split the communication package into 64 byte fragments
* @param {UsbCommand} usbCommand
* @param {Buffer} configBuffer
* @returns {Buffer[]}
* @private
*/
public static getTransferBuffers(usbCommand: UsbCommand, configBuffer: Buffer): Buffer[] {
const fragments: Buffer[] = [];
const MAX_SENDING_PAYLOAD_SIZE = Constants.MAX_PAYLOAD_SIZE - 4;
for (let offset = 0; offset < configBuffer.length; offset += MAX_SENDING_PAYLOAD_SIZE) {
const length = offset + MAX_SENDING_PAYLOAD_SIZE < configBuffer.length
? MAX_SENDING_PAYLOAD_SIZE
: configBuffer.length - offset;
const header = new Buffer([usbCommand, length, offset & 0xFF, offset >> 8]);
fragments.push(Buffer.concat([header, configBuffer.slice(offset, offset + length)]));
}
return fragments;
}
/** /**
* Create the communication package that will send over USB and * Create the communication package that will send over USB and
* - add usb report code as 1st byte * - add usb report code as 1st byte
@@ -27,7 +51,7 @@ export class UhkHidDeviceService {
* @static * @static
*/ */
private static getTransferData(buffer: Buffer): number[] { private static getTransferData(buffer: Buffer): number[] {
const data = UhkHidDeviceService.convertBufferToIntArray(buffer); const data = UhkHidDevice.convertBufferToIntArray(buffer);
// if data start with 0 need to add additional leading zero because HID API remove it. // if data start with 0 need to add additional leading zero because HID API remove it.
// https://github.com/node-hid/node-hid/issues/187 // https://github.com/node-hid/node-hid/issues/187
if (data.length > 0 && data[0] === 0 && process.platform === 'win32') { if (data.length > 0 && data[0] === 0 && process.platform === 'win32') {
@@ -108,7 +132,7 @@ export class UhkHidDeviceService {
this.logService.error('[UhkHidDevice] Transfer error: ', err); this.logService.error('[UhkHidDevice] Transfer error: ', err);
return reject(err); return reject(err);
} }
const logString = UhkHidDeviceService.bufferToString(receivedData); const logString = UhkHidDevice.bufferToString(receivedData);
this.logService.debug('[UhkHidDevice] USB[R]:', logString); this.logService.debug('[UhkHidDevice] USB[R]:', logString);
if (receivedData[0] !== 0) { if (receivedData[0] !== 0) {
@@ -118,12 +142,17 @@ export class UhkHidDeviceService {
return resolve(Buffer.from(receivedData)); return resolve(Buffer.from(receivedData));
}); });
const sendData = UhkHidDeviceService.getTransferData(buffer); const sendData = UhkHidDevice.getTransferData(buffer);
this.logService.debug('[UhkHidDevice] USB[W]:', UhkHidDeviceService.bufferToString(sendData)); this.logService.debug('[UhkHidDevice] USB[W]:', UhkHidDevice.bufferToString(sendData));
device.write(sendData); device.write(sendData);
}); });
} }
public async writeConfigToEeprom(transferType: EepromTransfer): Promise<void> {
await this.write(new Buffer([UsbCommand.LaunchEepromTransfer, transferType]));
await this.waitUntilKeyboardBusy();
}
/** /**
* Close the communication chanel with UHK Device * Close the communication chanel with UHK Device
*/ */
@@ -136,6 +165,17 @@ export class UhkHidDeviceService {
this._device = null; this._device = null;
} }
private async waitUntilKeyboardBusy(): Promise<void> {
while (true) {
const buffer = await this.write(new Buffer([UsbCommand.GetKeyboardState]));
if (buffer[1] === 0) {
break;
}
this.logService.debug('Keyboard is busy, wait...');
await snooze(200);
}
}
/** /**
* Return the stored version of HID device. If not exist try to initialize. * Return the stored version of HID device. If not exist try to initialize.
* @returns {HID} * @returns {HID}

View File

@@ -0,0 +1,21 @@
{
"compileOnSave": false,
"compilerOptions": {
"sourceMap": true,
"outDir": "./dist",
"declaration": false,
"module": "commonjs",
"moduleResolution": "node",
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"target": "es2016",
"typeRoots": [
"node_modules/@types"
],
"lib": [
"es2015.iterable",
"dom",
"es2016"
]
}
}

View File

@@ -2,6 +2,7 @@ import { Component, HostListener, ViewEncapsulation } from '@angular/core';
import { animate, style, transition, trigger } from '@angular/animations'; import { animate, style, transition, trigger } from '@angular/animations';
import { Observable } from 'rxjs/Observable'; import { Observable } from 'rxjs/Observable';
import { Action, Store } from '@ngrx/store'; import { Action, Store } from '@ngrx/store';
import { UhkBuffer } from 'uhk-common';
import 'rxjs/add/operator/last'; import 'rxjs/add/operator/last';
@@ -14,7 +15,6 @@ import {
saveToKeyboardState saveToKeyboardState
} from './store'; } from './store';
import { getUserConfiguration } from './store/reducers/user-configuration'; import { getUserConfiguration } from './store/reducers/user-configuration';
import { UhkBuffer } from './config-serializer/uhk-buffer';
import { ProgressButtonState } from './store/reducers/progress-button-state'; import { ProgressButtonState } from './store/reducers/progress-button-state';
@Component({ @Component({

View File

@@ -1,13 +1,14 @@
<svg-keyboard *ngFor="let layer of layers; let index = index; trackBy: trackKeyboard" <svg-keyboard *ngFor="let layer of layers; let index = index; trackBy: trackKeyboard"
[@layerState]="layerAnimationState[index]" [@layerState]="layerAnimationState[index]"
[moduleConfig]="layer.modules" [moduleConfig]="layer.modules"
[keybindAnimationEnabled]="keybindAnimationEnabled" [keybindAnimationEnabled]="keybindAnimationEnabled"
[halvesSplit]="halvesSplit" [halvesSplit]="halvesSplit"
[capturingEnabled]="capturingEnabled" [capturingEnabled]="capturingEnabled"
[selectedKey]="selectedKey" [selectedKey]="selectedKey"
[selected]="selectedKey?.layerId === index" [selected]="selectedKey?.layerId === index"
(keyClick)="keyClick.emit($event)" [keyboardLayout]="keyboardLayout"
(keyHover)="keyHover.emit($event)" (keyClick)="keyClick.emit($event)"
(capture)="capture.emit($event)" (keyHover)="keyHover.emit($event)"
(capture)="capture.emit($event)"
> >
</svg-keyboard> </svg-keyboard>

Before

Width:  |  Height:  |  Size: 511 B

After

Width:  |  Height:  |  Size: 659 B

View File

@@ -1,7 +1,8 @@
import { Component, ChangeDetectionStrategy, Input, Output, EventEmitter, OnChanges, SimpleChanges } from '@angular/core'; import { Component, ChangeDetectionStrategy, Input, Output, EventEmitter, OnChanges, SimpleChanges } from '@angular/core';
import { animate, keyframes, state, style, transition, trigger } from '@angular/animations'; import { animate, keyframes, state, style, transition, trigger } from '@angular/animations';
import { Layer } from 'uhk-common';
import { Layer } from '../../../config-serializer/config-items/layer'; import { KeyboardLayout } from '../../../keyboard/keyboard-layout.enum';
type AnimationKeyboard = type AnimationKeyboard =
'leftIn' | 'leftIn' |
@@ -69,6 +70,7 @@ export class KeyboardSliderComponent implements OnChanges {
@Input() capturingEnabled: boolean; @Input() capturingEnabled: boolean;
@Input() halvesSplit: boolean; @Input() halvesSplit: boolean;
@Input() selectedKey: { layerId: number, moduleId: number, keyId: number }; @Input() selectedKey: { layerId: number, moduleId: number, keyId: number };
@Input() keyboardLayout = KeyboardLayout.ANSI;
@Output() keyClick = new EventEmitter(); @Output() keyClick = new EventEmitter();
@Output() keyHover = new EventEmitter(); @Output() keyHover = new EventEmitter();
@Output() capture = new EventEmitter(); @Output() capture = new EventEmitter();

View File

@@ -1,12 +1,12 @@
import { Component } from '@angular/core'; import { Component } from '@angular/core';
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
import { Keymap } from 'uhk-common';
import { BehaviorSubject } from 'rxjs/BehaviorSubject'; import { BehaviorSubject } from 'rxjs/BehaviorSubject';
import { Observable } from 'rxjs/Observable'; import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/combineLatest'; import 'rxjs/add/operator/combineLatest';
import 'rxjs/add/operator/publishReplay'; import 'rxjs/add/operator/publishReplay';
import { Keymap } from '../../../config-serializer/config-items/keymap';
import { AppState } from '../../../store'; import { AppState } from '../../../store';
import { KeymapActions } from '../../../store/actions'; import { KeymapActions } from '../../../store/actions';

View File

@@ -1,5 +1,6 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { CanActivate, Router } from '@angular/router'; import { CanActivate, Router } from '@angular/router';
import { Keymap } from 'uhk-common';
import { Observable } from 'rxjs/Observable'; import { Observable } from 'rxjs/Observable';
@@ -10,7 +11,6 @@ import 'rxjs/add/operator/switchMap';
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
import { Keymap } from '../../../config-serializer/config-items/keymap';
import { AppState } from '../../../store/index'; import { AppState } from '../../../store/index';
import { getKeymaps } from '../../../store/reducers/user-configuration'; import { getKeymaps } from '../../../store/reducers/user-configuration';

View File

@@ -1,6 +1,10 @@
<ng-template [ngIf]="keymap$ | async"> <ng-template [ngIf]="keymap$ | async">
<keymap-header [keymap]="keymap$ | async" [deletable]="deletable$ | async" (downloadClick)="downloadKeymap()"></keymap-header> <keymap-header [keymap]="keymap$ | async"
<svg-keyboard-wrap [keymap]="keymap$ | async" [halvesSplit]="keyboardSplit"></svg-keyboard-wrap> [deletable]="deletable$ | async"
(downloadClick)="downloadKeymap()"></keymap-header>
<svg-keyboard-wrap [keymap]="keymap$ | async"
[halvesSplit]="keyboardSplit"
[keyboardLayout]="keyboardLayout$ | async"></svg-keyboard-wrap>
</ng-template> </ng-template>
<div *ngIf="!(keymap$ | async)" class="not-found"> <div *ngIf="!(keymap$ | async)" class="not-found">

View File

@@ -1,8 +1,9 @@
import { Component, HostListener, ViewChild } from '@angular/core'; import { Component, HostListener, ViewChild } from '@angular/core';
import { ActivatedRoute } from '@angular/router'; import { ActivatedRoute } from '@angular/router';
import { Store } from '@ngrx/store';
import { Keymap } from 'uhk-common';
import '@ngrx/core/add/operator/select'; import '@ngrx/core/add/operator/select';
import { Store } from '@ngrx/store';
import { Observable } from 'rxjs/Observable'; import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/first'; import 'rxjs/add/operator/first';
@@ -15,11 +16,11 @@ import 'rxjs/add/operator/combineLatest';
import { saveAs } from 'file-saver'; import { saveAs } from 'file-saver';
import { Keymap } from '../../../config-serializer/config-items/keymap'; import { AppState, getKeyboardLayout } from '../../../store';
import { AppState } from '../../../store';
import { getKeymap, getKeymaps, getUserConfiguration } from '../../../store/reducers/user-configuration'; import { getKeymap, getKeymaps, getUserConfiguration } from '../../../store/reducers/user-configuration';
import 'rxjs/add/operator/pluck'; import 'rxjs/add/operator/pluck';
import { SvgKeyboardWrapComponent } from '../../svg/wrap/svg-keyboard-wrap.component'; import { SvgKeyboardWrapComponent } from '../../svg/wrap/svg-keyboard-wrap.component';
import { KeyboardLayout } from '../../../keyboard/keyboard-layout.enum';
@Component({ @Component({
selector: 'keymap-edit', selector: 'keymap-edit',
@@ -37,6 +38,7 @@ export class KeymapEditComponent {
deletable$: Observable<boolean>; deletable$: Observable<boolean>;
keymap$: Observable<Keymap>; keymap$: Observable<Keymap>;
keyboardLayout$: Observable<KeyboardLayout>;
constructor(protected store: Store<AppState>, constructor(protected store: Store<AppState>,
route: ActivatedRoute) { route: ActivatedRoute) {
@@ -49,6 +51,8 @@ export class KeymapEditComponent {
this.deletable$ = store.let(getKeymaps()) this.deletable$ = store.let(getKeymaps())
.map((keymaps: Keymap[]) => keymaps.length > 1); .map((keymaps: Keymap[]) => keymaps.length > 1);
this.keyboardLayout$ = store.select(getKeyboardLayout);
} }
downloadKeymap() { downloadKeymap() {

View File

@@ -10,11 +10,10 @@ import {
SimpleChanges, SimpleChanges,
ViewChild ViewChild
} from '@angular/core'; } from '@angular/core';
import { Keymap } from 'uhk-common';
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
import { Keymap } from '../../../config-serializer/config-items/keymap';
import { AppState } from '../../../store'; import { AppState } from '../../../store';
import { KeymapActions } from '../../../store/actions'; import { KeymapActions } from '../../../store/actions';

View File

@@ -8,8 +8,8 @@ import {
MoveMouseMacroAction, MoveMouseMacroAction,
MouseButtonMacroAction, MouseButtonMacroAction,
TextMacroAction, TextMacroAction,
Helper as MacroActionHelper MacroActionHelper
} from '../../../config-serializer/config-items/macro-action'; } from 'uhk-common';
import { MacroDelayTabComponent, MacroMouseTabComponent, MacroKeyTabComponent, MacroTextTabComponent } from './tab'; import { MacroDelayTabComponent, MacroMouseTabComponent, MacroKeyTabComponent, MacroTextTabComponent } from './tab';
enum TabName { enum TabName {

View File

@@ -6,8 +6,8 @@ import {
OnInit, OnInit,
ViewChild ViewChild
} from '@angular/core'; } from '@angular/core';
import { DelayMacroAction } from 'uhk-common';
import { DelayMacroAction } from '../../../../../config-serializer/config-items/macro-action';
import { MacroBaseComponent } from '../macro-base.component'; import { MacroBaseComponent } from '../macro-base.component';
const INITIAL_DELAY = 0.5; // In seconds const INITIAL_DELAY = 0.5; // In seconds

View File

@@ -1,7 +1,6 @@
import { Component, Input, OnInit, ViewChild } from '@angular/core'; import { Component, Input, OnInit, ViewChild } from '@angular/core';
import { KeystrokeAction } from '../../../../../config-serializer/config-items/key-action'; import { KeyMacroAction, KeystrokeAction, MacroSubAction } from 'uhk-common';
import { KeyMacroAction, MacroSubAction } from '../../../../../config-serializer/config-items/macro-action';
import { KeypressTabComponent, Tab } from '../../../../popover/tab'; import { KeypressTabComponent, Tab } from '../../../../popover/tab';
import { MacroBaseComponent } from '../macro-base.component'; import { MacroBaseComponent } from '../macro-base.component';

View File

@@ -5,7 +5,7 @@ import {
MoveMouseMacroAction, MoveMouseMacroAction,
ScrollMouseMacroAction, ScrollMouseMacroAction,
MacroSubAction MacroSubAction
} from '../../../../../config-serializer/config-items/macro-action'; } from 'uhk-common';
import { Tab } from '../../../../popover/tab'; import { Tab } from '../../../../popover/tab';
import { MacroBaseComponent } from '../macro-base.component'; import { MacroBaseComponent } from '../macro-base.component';

View File

@@ -7,8 +7,8 @@ import {
Renderer, Renderer,
ViewChild ViewChild
} from '@angular/core'; } from '@angular/core';
import { TextMacroAction } from 'uhk-common';
import { TextMacroAction } from '../../../../../config-serializer/config-items/macro-action';
import { MacroBaseComponent } from '../macro-base.component'; import { MacroBaseComponent } from '../macro-base.component';
@Component({ @Component({

View File

@@ -1,14 +1,11 @@
import { Component, OnDestroy } from '@angular/core'; import { Component, OnDestroy } from '@angular/core';
import { ActivatedRoute } from '@angular/router'; import { ActivatedRoute } from '@angular/router';
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
import { Macro, MacroAction } from 'uhk-common';
import { Subscription } from 'rxjs/Subscription'; import { Subscription } from 'rxjs/Subscription';
import 'rxjs/add/operator/pluck'; import 'rxjs/add/operator/pluck';
import { Macro } from '../../../config-serializer/config-items/macro';
import { MacroAction } from '../../../config-serializer/config-items/macro-action/macro-action';
import { MacroActions } from '../../../store/actions'; import { MacroActions } from '../../../store/actions';
import { AppState } from '../../../store/index'; import { AppState } from '../../../store/index';
import { getMacro } from '../../../store/reducers/user-configuration'; import { getMacro } from '../../../store/reducers/user-configuration';

View File

@@ -9,10 +9,8 @@ import {
SimpleChanges, SimpleChanges,
ViewChild ViewChild
} from '@angular/core'; } from '@angular/core';
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
import { Macro } from 'uhk-common';
import { Macro } from '../../../config-serializer/config-items/macro';
import { MacroActions } from '../../../store/actions'; import { MacroActions } from '../../../store/actions';
import { AppState } from '../../../store/index'; import { AppState } from '../../../store/index';

View File

@@ -1,16 +1,15 @@
import { Component, Input, Output, EventEmitter, OnChanges, OnInit, SimpleChanges } from '@angular/core'; import { Component, Input, Output, EventEmitter, OnChanges, OnInit, SimpleChanges } from '@angular/core';
import { animate, state, style, transition, trigger } from '@angular/animations'; import { animate, state, style, transition, trigger } from '@angular/animations';
import { KeyModifiers } from '../../../config-serializer/config-items/key-modifiers';
import { import {
DelayMacroAction, DelayMacroAction,
KeyMacroAction, KeyMacroAction,
KeyModifiers,
MacroAction, MacroAction,
MouseButtonMacroAction, MouseButtonMacroAction,
MoveMouseMacroAction, MoveMouseMacroAction,
ScrollMouseMacroAction, ScrollMouseMacroAction,
TextMacroAction TextMacroAction
} from '../../../config-serializer/config-items/macro-action'; } from 'uhk-common';
import { MapperService } from '../../../services/mapper.service'; import { MapperService } from '../../../services/mapper.service';

View File

@@ -1,10 +1,8 @@
import { Component, EventEmitter, Input, Output, QueryList, ViewChildren, forwardRef } from '@angular/core'; import { Component, EventEmitter, Input, Output, QueryList, ViewChildren, forwardRef } from '@angular/core';
import { animate, state, style, transition, trigger } from '@angular/animations'; import { animate, state, style, transition, trigger } from '@angular/animations';
import { DragulaService } from 'ng2-dragula/ng2-dragula'; import { DragulaService } from 'ng2-dragula/ng2-dragula';
import { Macro, MacroAction } from 'uhk-common';
import { Macro } from '../../../config-serializer/config-items/macro';
import { MacroAction } from '../../../config-serializer/config-items/macro-action';
import { MacroItemComponent } from '../item'; import { MacroItemComponent } from '../item';
@Component({ @Component({

View File

@@ -1,5 +1,6 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { CanActivate, Router } from '@angular/router'; import { CanActivate, Router } from '@angular/router';
import { Macro } from 'uhk-common';
import { Observable } from 'rxjs/Observable'; import { Observable } from 'rxjs/Observable';
@@ -10,7 +11,6 @@ import { Store } from '@ngrx/store';
import { AppState } from '../../../store/index'; import { AppState } from '../../../store/index';
import { getMacros } from '../../../store/reducers/user-configuration'; import { getMacros } from '../../../store/reducers/user-configuration';
import { Macro } from '../../../config-serializer/config-items/macro';
@Injectable() @Injectable()
export class MacroNotFoundGuard implements CanActivate { export class MacroNotFoundGuard implements CanActivate {

View File

@@ -19,13 +19,13 @@ import 'rxjs/add/operator/map';
import { import {
KeyAction, KeyAction,
Keymap,
KeystrokeAction, KeystrokeAction,
MouseAction, MouseAction,
PlayMacroAction, PlayMacroAction,
SwitchKeymapAction, SwitchKeymapAction,
SwitchLayerAction SwitchLayerAction
} from '../../config-serializer/config-items/key-action'; } from 'uhk-common';
import { Keymap } from '../../config-serializer/config-items/keymap';
import { Tab } from './tab/tab'; import { Tab } from './tab/tab';

View File

@@ -1,9 +1,7 @@
import { ChangeDetectionStrategy, Component, Input, OnChanges, SimpleChanges } from '@angular/core'; import { ChangeDetectionStrategy, Component, Input, OnChanges, SimpleChanges } from '@angular/core';
import { Select2OptionData } from 'ng2-select2/ng2-select2'; import { Select2OptionData } from 'ng2-select2/ng2-select2';
import { Keymap, KeyAction, SwitchKeymapAction } from 'uhk-common';
import { KeyAction, SwitchKeymapAction } from '../../../../config-serializer/config-items/key-action';
import { Keymap } from '../../../../config-serializer/config-items/keymap';
import { Tab } from '../tab'; import { Tab } from '../tab';
@Component({ @Component({

View File

@@ -1,12 +1,9 @@
import { Component, Input, OnChanges } from '@angular/core'; import { Component, Input, OnChanges } from '@angular/core';
import { Select2OptionData, Select2TemplateFunction } from 'ng2-select2'; import { Select2OptionData, Select2TemplateFunction } from 'ng2-select2';
import { KeyAction, KeystrokeAction, KeystrokeType } from 'uhk-common';
import { KeyAction, KeystrokeAction } from '../../../../config-serializer/config-items/key-action';
import { Tab } from '../tab'; import { Tab } from '../tab';
import { MapperService } from '../../../../services/mapper.service'; import { MapperService } from '../../../../services/mapper.service';
import { KeystrokeType } from '../../../../config-serializer/config-items/key-action/keystroke-type';
@Component({ @Component({
selector: 'keypress-tab', selector: 'keypress-tab',

View File

@@ -1,6 +1,5 @@
import { Component, HostBinding, Input, OnChanges, SimpleChanges } from '@angular/core'; import { Component, HostBinding, Input, OnChanges, SimpleChanges } from '@angular/core';
import { KeyAction, LayerName, SwitchLayerAction } from 'uhk-common';
import { KeyAction, LayerName, SwitchLayerAction } from '../../../../config-serializer/config-items/key-action';
import { Tab } from '../tab'; import { Tab } from '../tab';

View File

@@ -1,13 +1,8 @@
import { Component, Input, OnChanges, OnDestroy, OnInit } from '@angular/core'; import { Component, Input, OnChanges, OnDestroy, OnInit } from '@angular/core';
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
import { Subscription } from 'rxjs/Subscription'; import { Subscription } from 'rxjs/Subscription';
import { Select2OptionData } from 'ng2-select2/ng2-select2'; import { Select2OptionData } from 'ng2-select2/ng2-select2';
import { KeyAction, Macro, PlayMacroAction } from 'uhk-common';
import { KeyAction, PlayMacroAction } from '../../../../config-serializer/config-items/key-action';
import { Macro } from '../../../../config-serializer/config-items/macro';
import { Tab } from '../tab'; import { Tab } from '../tab';

View File

@@ -1,6 +1,6 @@
import { Component, Input, OnChanges } from '@angular/core'; import { Component, Input, OnChanges } from '@angular/core';
import { KeyAction, MouseAction, MouseActionParam } from 'uhk-common';
import { KeyAction, MouseAction, MouseActionParam } from '../../../../config-serializer/config-items/key-action';
import { Tab } from '../tab'; import { Tab } from '../tab';
@Component({ @Component({

View File

@@ -1,6 +1,5 @@
import { EventEmitter, Output } from '@angular/core'; import { EventEmitter, Output } from '@angular/core';
import { KeyAction } from 'uhk-common';
import { KeyAction } from '../../../config-serializer/config-items/key-action';
export abstract class Tab { export abstract class Tab {
@Output() validAction = new EventEmitter<boolean>(); @Output() validAction = new EventEmitter<boolean>();

View File

@@ -1,5 +1,6 @@
import { Component, Renderer } from '@angular/core'; import { Component, Renderer } from '@angular/core';
import { animate, state, style, transition, trigger } from '@angular/animations'; import { animate, state, style, transition, trigger } from '@angular/animations';
import { Keymap, Macro } from 'uhk-common';
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
@@ -8,9 +9,6 @@ import 'rxjs/add/operator/do';
import 'rxjs/add/operator/map'; import 'rxjs/add/operator/map';
import 'rxjs/add/operator/let'; import 'rxjs/add/operator/let';
import { Keymap } from '../../config-serializer/config-items/keymap';
import { Macro } from '../../config-serializer/config-items/macro';
import { AppState, showAddonMenu, runningInElectron } from '../../store'; import { AppState, showAddonMenu, runningInElectron } from '../../store';
import { MacroActions } from '../../store/actions'; import { MacroActions } from '../../store/actions';
import { getKeymaps, getMacros } from '../../store/reducers/user-configuration'; import { getKeymaps, getMacros } from '../../store/reducers/user-configuration';

View File

@@ -1,9 +1,10 @@
import { Component, EventEmitter, Input, OnInit, Output, SimpleChanges, ChangeDetectionStrategy } from '@angular/core'; import { Component, EventEmitter, Input, OnInit, Output, SimpleChanges, ChangeDetectionStrategy } from '@angular/core';
import { animate, state, trigger, style, transition } from '@angular/animations'; import { animate, state, trigger, style, transition } from '@angular/animations';
import { Module } from 'uhk-common';
import { Module } from '../../../config-serializer/config-items/module';
import { SvgModule } from '../module'; import { SvgModule } from '../module';
import { SvgModuleProviderService } from '../../../services/svg-module-provider.service'; import { SvgModuleProviderService } from '../../../services/svg-module-provider.service';
import { KeyboardLayout } from '../../../keyboard/keyboard-layout.enum';
@Component({ @Component({
selector: 'svg-keyboard', selector: 'svg-keyboard',
@@ -29,6 +30,7 @@ export class SvgKeyboardComponent implements OnInit {
@Input() selectedKey: { layerId: number, moduleId: number, keyId: number }; @Input() selectedKey: { layerId: number, moduleId: number, keyId: number };
@Input() selected: boolean; @Input() selected: boolean;
@Input() halvesSplit: boolean; @Input() halvesSplit: boolean;
@Input() keyboardLayout = KeyboardLayout.ANSI;
@Output() keyClick = new EventEmitter(); @Output() keyClick = new EventEmitter();
@Output() keyHover = new EventEmitter(); @Output() keyHover = new EventEmitter();
@Output() capture = new EventEmitter(); @Output() capture = new EventEmitter();
@@ -45,13 +47,17 @@ export class SvgKeyboardComponent implements OnInit {
} }
ngOnInit() { ngOnInit() {
this.modules = this.svgModuleProvider.getSvgModules(); this.setModules();
} }
ngOnChanges(changes: SimpleChanges) { ngOnChanges(changes: SimpleChanges) {
if (changes.halvesSplit) { if (changes.halvesSplit) {
this.updateModuleAnimationStates(); this.updateModuleAnimationStates();
} }
if (changes['keyboardLayout']) {
this.setModules();
}
} }
onKeyClick(moduleId: number, keyId: number, keyTarget: HTMLElement): void { onKeyClick(moduleId: number, keyId: number, keyTarget: HTMLElement): void {
@@ -87,4 +93,7 @@ export class SvgKeyboardComponent implements OnInit {
} }
} }
private setModules() {
this.modules = this.svgModuleProvider.getSvgModules(this.keyboardLayout);
}
} }

View File

@@ -10,15 +10,15 @@ import { Subscription } from 'rxjs/Subscription';
import { import {
KeyAction, KeyAction,
KeyModifiers,
KeystrokeAction, KeystrokeAction,
LayerName, LayerName,
Macro,
MouseAction, MouseAction,
PlayMacroAction, PlayMacroAction,
SwitchKeymapAction, SwitchKeymapAction,
SwitchLayerAction SwitchLayerAction
} from '../../../../config-serializer/config-items/key-action'; } from 'uhk-common';
import { KeyModifiers } from '../../../../config-serializer/config-items/key-modifiers';
import { Macro } from '../../../../config-serializer/config-items/macro';
import { CaptureService } from '../../../../services/capture.service'; import { CaptureService } from '../../../../services/capture.service';
import { MapperService } from '../../../../services/mapper.service'; import { MapperService } from '../../../../services/mapper.service';

View File

@@ -1,7 +1,6 @@
import { Component, Input, OnChanges, OnInit, ChangeDetectionStrategy } from '@angular/core'; import { Component, Input, OnChanges, OnInit, ChangeDetectionStrategy } from '@angular/core';
import { KeyModifiers, KeystrokeAction } from 'uhk-common';
import { KeystrokeAction } from '../../../../config-serializer/config-items/key-action';
import { KeyModifiers } from '../../../../config-serializer/config-items/key-modifiers';
import { MapperService } from '../../../../services/mapper.service'; import { MapperService } from '../../../../services/mapper.service';
class SvgAttributes { class SvgAttributes {

View File

@@ -1,6 +1,6 @@
import { Component, Input, OnChanges, ChangeDetectionStrategy } from '@angular/core'; import { Component, Input, OnChanges, ChangeDetectionStrategy } from '@angular/core';
import { MouseAction, MouseActionParam } from '../../../../config-serializer/config-items/key-action'; import { MouseAction, MouseActionParam } from 'uhk-common';
@Component({ @Component({
selector: 'g[svg-mouse-key]', selector: 'g[svg-mouse-key]',

View File

@@ -1,6 +1,5 @@
import { Component, EventEmitter, Input, Output, ChangeDetectionStrategy } from '@angular/core'; import { Component, EventEmitter, Input, Output, ChangeDetectionStrategy } from '@angular/core';
import { KeyAction } from 'uhk-common';
import { KeyAction } from '../../../config-serializer/config-items/key-action';
import { SvgKeyboardKey } from '../keys'; import { SvgKeyboardKey } from '../keys';

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