Compare commits
99 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6e1f0ded9e | ||
|
|
d58386ef4b | ||
|
|
179c982bfb | ||
|
|
a7d07dbf4c | ||
|
|
0ca922d24a | ||
|
|
a6f1aa15a5 | ||
|
|
fc2d025cc4 | ||
|
|
8b5ae106bd | ||
|
|
44639bbf53 | ||
|
|
9fcce9234a | ||
|
|
b26fecfc7a | ||
|
|
1b15911783 | ||
|
|
148dd8d361 | ||
|
|
0d9ac50999 | ||
|
|
e19e4bc5a4 | ||
|
|
bdd79a5a9a | ||
|
|
fb4e05fdc4 | ||
|
|
01fcf9053a | ||
|
|
533c2f13d2 | ||
|
|
b9c32b46a9 | ||
|
|
f9b7260be6 | ||
|
|
847694d590 | ||
|
|
58178a5c7b | ||
|
|
9b93b4dac5 | ||
|
|
478dac0621 | ||
|
|
f196fcdaa2 | ||
|
|
0b420ff516 | ||
|
|
7656af76e4 | ||
|
|
beed546ae4 | ||
|
|
bf94370f2f | ||
|
|
2476049681 | ||
|
|
f8d8b6d213 | ||
|
|
05bbce1d50 | ||
|
|
32494fa228 | ||
|
|
e0ce38988e | ||
|
|
5c660c549d | ||
|
|
510b914e26 | ||
|
|
cf64fc0c08 | ||
|
|
b25bc9d81d | ||
|
|
2f00a5eaf4 | ||
|
|
e8fe0f8d3e | ||
|
|
e84dbf2c15 | ||
|
|
990ff8e980 | ||
|
|
1ca8e67e52 | ||
|
|
23cb583bf7 | ||
|
|
d5cc735b85 | ||
|
|
1981311136 | ||
|
|
bbb5d4a35b | ||
|
|
58ef40fb02 | ||
|
|
b8f35df155 | ||
|
|
c3e712851c | ||
|
|
2eaa1e0634 | ||
|
|
6ee21bcd7a | ||
|
|
10ae68ad4b | ||
|
|
02044ae1d0 | ||
|
|
3f99d47bba | ||
|
|
9beadb4aac | ||
|
|
d9fb7a4b42 | ||
|
|
83912ec21f | ||
|
|
6c7232a5ba | ||
|
|
65fc8b5efb | ||
|
|
7a64191955 | ||
|
|
1a413c824e | ||
|
|
e545c9d67b | ||
|
|
8650fef7ae | ||
|
|
5c618869a2 | ||
|
|
1b8d6949e0 | ||
|
|
aabc0a8746 | ||
|
|
9589398834 | ||
|
|
933a715ea5 | ||
|
|
df14e2d569 | ||
|
|
4f8a0247d3 | ||
|
|
85ec5f6b6a | ||
|
|
739b830f47 | ||
|
|
482cff3d3b | ||
|
|
9284ae5032 | ||
|
|
cac6fdc190 | ||
|
|
ca9bf60a1b | ||
|
|
bb7edb8e4d | ||
|
|
0d9c976eb8 | ||
|
|
288d4f75b6 | ||
|
|
73e07eae2d | ||
|
|
8e620caac5 | ||
|
|
0d4e1acf76 | ||
|
|
88c42d58b1 | ||
|
|
5099e904fc | ||
|
|
5476f7c3a5 | ||
|
|
e0bb0bcca3 | ||
|
|
124c3ec29b | ||
|
|
2310320b8a | ||
|
|
67346b4cda | ||
|
|
662ca0152f | ||
|
|
6358528438 | ||
|
|
02f1053d46 | ||
|
|
a44a7dc5f8 | ||
|
|
02d57fdabf | ||
|
|
38f6688930 | ||
|
|
6ca12d0ccd | ||
|
|
acd17ac657 |
@@ -11,7 +11,7 @@ matrix:
|
||||
fast_finish: true
|
||||
include:
|
||||
- os: osx
|
||||
osx_image: xcode8.3
|
||||
osx_image: xcode9.3beta
|
||||
- os: linux
|
||||
env: CC=clang CXX=clang++ npm_config_clang=1
|
||||
compiler: clang
|
||||
@@ -45,7 +45,6 @@ addons:
|
||||
|
||||
install:
|
||||
- nvm install
|
||||
- npm i -g npm@5.6.0
|
||||
- npm install
|
||||
|
||||
before_script:
|
||||
|
||||
36
CHANGELOG.md
36
CHANGELOG.md
@@ -4,7 +4,41 @@ 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
|
||||
Every Agent version includes the most recent firmware version. See the [firmware changelog](https://github.com/UltimateHackingKeyboard/firmware/blob/master/CHANGELOG.md).
|
||||
|
||||
## [1.1.3] - 2018-04-06
|
||||
|
||||
Firmware: 8.1.**5** [[release](https://github.com/UltimateHackingKeyboard/firmware/releases/tag/v8.1.5)] | Device Protocol: 4.2.0 | User Config: 4.0.0 | Hardware Config: 1.0.0
|
||||
|
||||
- Show the firmware versions of the left and right keyboard halves on the firmware page.
|
||||
- Fix menu scancode.
|
||||
- Make the tooltip text regarding non-US characters easier to understand.
|
||||
- On the Device Configuration page change terminology from download/upload to export/import for greater clarity.
|
||||
|
||||
## [1.1.2] - 2018-03-09
|
||||
|
||||
Firmware: 8.1.**4** [[release](https://github.com/UltimateHackingKeyboard/firmware/releases/tag/v8.1.4)] | Device Protocol: 4.2.0 | User Config: 4.0.0 | Hardware Config: 1.0.0
|
||||
|
||||
- Fix the configuration serializer so that the correct key actions get serialized, and the save button always appears when needed.
|
||||
- Add instructions to the firmware page to aid users.
|
||||
- Fix code signing on OSX.
|
||||
- Sign Agent on Windows.
|
||||
|
||||
## [1.1.1] - 2018-02-13
|
||||
|
||||
Firmware: 8.1.**2** [[release](https://github.com/UltimateHackingKeyboard/firmware/releases/tag/v8.1.2)] | Device Protocol: 4.2.0 | User Config: 4.0.0 | Hardware Config: 1.0.0
|
||||
|
||||
- Sign Agent on OSX resulting in easier installation.
|
||||
- Add per-keymap description field.
|
||||
- Sort keymaps and macros alphabetically within the key action popover.
|
||||
- Add tooltip regarding non-US scancodes.
|
||||
- When deleting a macro, also delete the relevant play macro actions.
|
||||
- Make the reset configuration button persist the reset configuration in Agent-web.
|
||||
- Make Agent able to unbrick bricked modules.
|
||||
- Assign "switch to test keymap" action on all keymaps in the default configuration.
|
||||
- Add keymap descriptions in the default configuration.
|
||||
|
||||
## [1.1.0] - 2018-01-15
|
||||
|
||||
Firmware: 8.**1**.0 [[release](https://github.com/UltimateHackingKeyboard/firmware/releases/tag/v8.1.0)] | Device Protocol: 4.2.0 | User Config: 4.0.0 | Hardware Config: 1.0.0
|
||||
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
os: unstable
|
||||
|
||||
clone_folder: c:\projects\uhk-agent
|
||||
|
||||
environment:
|
||||
GH_TOKEN:
|
||||
secure: 3IebpEKmC39codi1wT6dXx8mql4/mCL1JzZ7lir7GQ5MWRnCxlED2OXbiKHHigDV
|
||||
CSC_LINK: c:\projects\uhk-agent\scripts\certs\windows-cert.p12
|
||||
matrix:
|
||||
- nodejs_version: "8"
|
||||
|
||||
@@ -18,7 +21,6 @@ shallow_clone: true
|
||||
|
||||
install:
|
||||
- ps: Install-Product node $env:nodejs_version
|
||||
- npm i -g npm@5.6.0
|
||||
- choco install chromium
|
||||
- set CI=true
|
||||
- set PATH=%APPDATA%\npm;%PATH%
|
||||
|
||||
2825
package-lock.json
generated
2825
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
22
package.json
22
package.json
@@ -3,8 +3,8 @@
|
||||
"private": true,
|
||||
"author": "Ultimate Gadget Laboratories",
|
||||
"main": "electron/dist/electron-main.js",
|
||||
"version": "1.1.0",
|
||||
"firmwareVersion": "8.1.0",
|
||||
"version": "1.1.3",
|
||||
"firmwareVersion": "8.1.5",
|
||||
"deviceProtocolVersion": "4.2.0",
|
||||
"userConfigVersion": "4.0.0",
|
||||
"hardwareConfigVersion": "1.0.0",
|
||||
@@ -30,24 +30,26 @@
|
||||
"@types/usb": "1.1.3",
|
||||
"autoprefixer": "6.5.3",
|
||||
"buffer": "5.0.6",
|
||||
"copyfiles": "^2.0.0",
|
||||
"copy-webpack-plugin": "4.0.1",
|
||||
"core-js": "2.4.1",
|
||||
"cross-env": "5.0.5",
|
||||
"decompress": "4.2.0",
|
||||
"decompress-tarbz2": "^4.1.1",
|
||||
"devtron": "1.4.0",
|
||||
"electron": "1.7.5",
|
||||
"electron-builder": "19.45.5",
|
||||
"electron-debug": "1.4.0",
|
||||
"electron-devtools-installer": "2.2.0",
|
||||
"electron-log": "2.2.9",
|
||||
"electron-rebuild": "1.6.0",
|
||||
"electron-settings": "3.1.2",
|
||||
"electron": "1.8.4",
|
||||
"electron-builder": "20.8.1",
|
||||
"electron-debug": "1.5.0",
|
||||
"electron-devtools-installer": "2.2.3",
|
||||
"electron-log": "2.2.14",
|
||||
"electron-rebuild": "1.7.3",
|
||||
"electron-settings": "3.1.4",
|
||||
"electron-updater": "2.21.4",
|
||||
"exports-loader": "0.6.3",
|
||||
"file-loader": "0.10.0",
|
||||
"fs-extra": "4.0.2",
|
||||
"jsonfile": "4.0.0",
|
||||
"lerna": "2.0.0",
|
||||
"lerna": "2.9.0",
|
||||
"mkdirp": "0.5.1",
|
||||
"npm-run-all": "4.0.2",
|
||||
"pre-commit": "1.2.2",
|
||||
|
||||
2950
packages/uhk-agent/package-lock.json
generated
2950
packages/uhk-agent/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -17,12 +17,6 @@
|
||||
"command-line-args": "4.0.7",
|
||||
"decompress": "4.2.0",
|
||||
"decompress-bzip2": "4.0.0",
|
||||
"electron": "1.7.9",
|
||||
"electron-is-dev": "0.1.2",
|
||||
"electron-log": "2.2.9",
|
||||
"electron-rebuild": "1.6.0",
|
||||
"electron-settings": "3.1.2",
|
||||
"electron-updater": "2.15.0",
|
||||
"node-hid": "0.5.4",
|
||||
"sudo-prompt": "7.0.0",
|
||||
"tmp": "0.0.33",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { ipcMain } from 'electron';
|
||||
import { ConfigurationReply, DeviceConnectionState, IpcEvents, IpcResponse, LogService } from 'uhk-common';
|
||||
import { ConfigurationReply, DeviceConnectionState, HardwareModules, IpcEvents, IpcResponse, LogService } from 'uhk-common';
|
||||
import { snooze, UhkHidDevice, UhkOperations } from 'uhk-usb';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { Subscription } from 'rxjs/Subscription';
|
||||
@@ -73,10 +73,15 @@ export class DeviceService {
|
||||
try {
|
||||
await this.device.waitUntilKeyboardBusy();
|
||||
const result = await this.operations.loadConfigurations();
|
||||
const modules: HardwareModules = {
|
||||
leftModuleInfo: await this.operations.getLeftModuleVersionInfo(),
|
||||
rightModuleInfo: await this.operations.getRightModuleVersionInfo()
|
||||
};
|
||||
|
||||
response = {
|
||||
success: true,
|
||||
...result
|
||||
...result,
|
||||
modules
|
||||
};
|
||||
} catch (error) {
|
||||
response = {
|
||||
|
||||
@@ -10,7 +10,10 @@
|
||||
"url": "git@github.com:UltimateHackingKeyboard/agent.git"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"build": "run-s tsc copy:*",
|
||||
"tsc": "tsc",
|
||||
"copy:scancodes": "copyfiles ./src/config-serializer/config-items/scancodes.json dist",
|
||||
"copy:secondary-roles": "copyfiles ./src/config-serializer/config-items/secondaryRole.json dist",
|
||||
"test": "jasmine-ts --config=jasmine.json",
|
||||
"coverage": "nyc jasmine-ts --config=jasmine.json"
|
||||
},
|
||||
|
||||
@@ -9,3 +9,6 @@ export * from './macro';
|
||||
export * from './module';
|
||||
export * from './module-configuration';
|
||||
export * from './user-configuration';
|
||||
|
||||
export const SCANCODES = require('./scancodes.json');
|
||||
export const SECONDARY_ROLES = require('./secondaryRole.json');
|
||||
|
||||
@@ -7,6 +7,8 @@ import { SwitchLayerAction } from './switch-layer-action';
|
||||
import { SwitchKeymapAction, UnresolvedSwitchKeymapAction } from './switch-keymap-action';
|
||||
import { MouseAction } from './mouse-action';
|
||||
import { PlayMacroAction } from './play-macro-action';
|
||||
import { NoneAction } from './none-action';
|
||||
import { isScancodeExists } from '../scancode-checker';
|
||||
|
||||
export class Helper {
|
||||
|
||||
@@ -25,7 +27,12 @@ export class Helper {
|
||||
buffer.backtrack();
|
||||
|
||||
if (keyActionFirstByte >= KeyActionId.KeystrokeAction && keyActionFirstByte < KeyActionId.LastKeystrokeAction) {
|
||||
return new KeystrokeAction().fromBinary(buffer);
|
||||
const keystrokeAction = new KeystrokeAction().fromBinary(buffer);
|
||||
if (isValidKeystrokeAction(keystrokeAction)) {
|
||||
return keystrokeAction;
|
||||
}
|
||||
|
||||
return new NoneAction();
|
||||
}
|
||||
|
||||
switch (keyActionFirstByte) {
|
||||
@@ -67,8 +74,14 @@ export class Helper {
|
||||
}
|
||||
|
||||
switch (keyAction.keyActionType) {
|
||||
case keyActionType.KeystrokeAction:
|
||||
return new KeystrokeAction().fromJsonObject(keyAction);
|
||||
case keyActionType.KeystrokeAction: {
|
||||
const keystrokeAction = new KeystrokeAction().fromJsonObject(keyAction);
|
||||
if (isValidKeystrokeAction(keystrokeAction)) {
|
||||
return keystrokeAction;
|
||||
}
|
||||
|
||||
return new NoneAction();
|
||||
}
|
||||
case keyActionType.SwitchLayerAction:
|
||||
return new SwitchLayerAction().fromJsonObject(keyAction);
|
||||
case keyActionType.SwitchKeymapAction:
|
||||
@@ -77,8 +90,16 @@ export class Helper {
|
||||
return new MouseAction().fromJsonObject(keyAction);
|
||||
case keyActionType.PlayMacroAction:
|
||||
return new PlayMacroAction().fromJsonObject(keyAction, macros);
|
||||
case keyActionType.NoneAction:
|
||||
return new NoneAction();
|
||||
default:
|
||||
throw `Invalid KeyAction.keyActionType: "${keyAction.keyActionType}"`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function isValidKeystrokeAction(keystrokeAction: KeystrokeAction): boolean {
|
||||
return keystrokeAction.hasSecondaryRoleAction() ||
|
||||
keystrokeAction.hasActiveModifier() ||
|
||||
keystrokeAction.hasScancode() && isScancodeExists(keystrokeAction.scancode);
|
||||
}
|
||||
|
||||
@@ -54,15 +54,12 @@ export class Module {
|
||||
|
||||
const noneAction = new NoneAction();
|
||||
|
||||
const keyActions: KeyAction[] = this.keyActions.map(keyAction => {
|
||||
buffer.writeArray(this.keyActions, (uhkBuffer: UhkBuffer, keyAction: KeyAction) => {
|
||||
if (keyAction) {
|
||||
return keyAction;
|
||||
}
|
||||
return noneAction;
|
||||
});
|
||||
|
||||
buffer.writeArray(keyActions, (uhkBuffer: UhkBuffer, keyAction: KeyAction) => {
|
||||
keyAction.toBinary(uhkBuffer, userConfiguration);
|
||||
} else {
|
||||
noneAction.toBinary(uhkBuffer);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
import { SCANCODES } from './';
|
||||
|
||||
let scancodeMap: Map<number, any>;
|
||||
|
||||
export function isScancodeExists(scancode: number): boolean {
|
||||
if (!scancodeMap) {
|
||||
fillScancodeMap();
|
||||
}
|
||||
|
||||
return scancodeMap.has(scancode);
|
||||
}
|
||||
|
||||
function fillScancodeMap(): void {
|
||||
scancodeMap = new Map<number, any>();
|
||||
|
||||
for (const scanGroup of SCANCODES) {
|
||||
for (const child of scanGroup.children) {
|
||||
if (child.additional && child.additional.scancode) {
|
||||
scancodeMap.set(child.additional.scancode, child);
|
||||
}
|
||||
else {
|
||||
scancodeMap.set(Number.parseInt(child.id), child);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -242,7 +242,7 @@
|
||||
"text": "Delete"
|
||||
},
|
||||
{
|
||||
"id": "118",
|
||||
"id": "101",
|
||||
"text": "Menu"
|
||||
},
|
||||
{
|
||||
@@ -1,6 +1,9 @@
|
||||
import { HardwareModules } from './hardware-modules';
|
||||
|
||||
export interface ConfigurationReply {
|
||||
success: boolean;
|
||||
userConfiguration?: string;
|
||||
hardwareConfiguration?: string;
|
||||
modules?: HardwareModules;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
4
packages/uhk-common/src/models/hardware-module-info.ts
Normal file
4
packages/uhk-common/src/models/hardware-module-info.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export interface HardwareModuleInfo {
|
||||
firmwareVersion?: string;
|
||||
moduleProtocolVersion?: string;
|
||||
}
|
||||
6
packages/uhk-common/src/models/hardware-modules.ts
Normal file
6
packages/uhk-common/src/models/hardware-modules.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { HardwareModuleInfo } from './hardware-module-info';
|
||||
|
||||
export interface HardwareModules {
|
||||
leftModuleInfo?: HardwareModuleInfo;
|
||||
rightModuleInfo?: HardwareModuleInfo;
|
||||
}
|
||||
@@ -5,3 +5,5 @@ export * from './app-start-info';
|
||||
export * from './configuration-reply';
|
||||
export * from './version-information';
|
||||
export * from './device-connection-state';
|
||||
export * from './hardware-modules';
|
||||
export * from './hardware-module-info';
|
||||
|
||||
@@ -22,7 +22,7 @@ export enum UsbCommand {
|
||||
GetDebugBuffer = 0x0b,
|
||||
GetAdcValue = 0x0c,
|
||||
SetLedPwmBrightness = 0x0d,
|
||||
GetModuleProperties = 0x0e
|
||||
GetModuleProperty = 0x0e
|
||||
}
|
||||
|
||||
export enum EepromOperation {
|
||||
@@ -39,7 +39,8 @@ export enum ConfigBufferId {
|
||||
export enum DevicePropertyIds {
|
||||
DeviceProtocolVersion = 0,
|
||||
ProtocolVersions = 1,
|
||||
ConfigSizes = 2
|
||||
ConfigSizes = 2,
|
||||
CurrentKbootCommand = 3
|
||||
}
|
||||
|
||||
export enum EnumerationModes {
|
||||
@@ -80,3 +81,7 @@ export enum KbootCommands {
|
||||
ping = 1,
|
||||
reset = 2
|
||||
}
|
||||
|
||||
export enum ModulePropertyId {
|
||||
protocolVersions = 0
|
||||
}
|
||||
|
||||
@@ -1,11 +1,23 @@
|
||||
import { LogService } from 'uhk-common';
|
||||
import { EnumerationModes, EnumerationNameToProductId, KbootCommands, ModuleSlotToI2cAddress, ModuleSlotToId } from './constants';
|
||||
import { HardwareModuleInfo, LogService, UhkBuffer } from 'uhk-common';
|
||||
import {
|
||||
EnumerationModes,
|
||||
EnumerationNameToProductId,
|
||||
KbootCommands,
|
||||
ModulePropertyId,
|
||||
ModuleSlotToI2cAddress,
|
||||
ModuleSlotToId
|
||||
} from './constants';
|
||||
import * as path from 'path';
|
||||
import * as fs from 'fs';
|
||||
import { UhkBlhost } from './uhk-blhost';
|
||||
import { UhkHidDevice } from './uhk-hid-device';
|
||||
import { snooze } from './util';
|
||||
import { convertBufferToIntArray, getTransferBuffers, DevicePropertyIds, UsbCommand, ConfigBufferId
|
||||
import {
|
||||
convertBufferToIntArray,
|
||||
getTransferBuffers,
|
||||
DevicePropertyIds,
|
||||
UsbCommand,
|
||||
ConfigBufferId
|
||||
} from '../index';
|
||||
import { LoadConfigurationsResult } from './models/load-configurations-result';
|
||||
|
||||
@@ -42,6 +54,13 @@ export class UhkOperations {
|
||||
await snooze(1000);
|
||||
await this.device.jumpToBootloaderModule(ModuleSlotToId.leftHalf);
|
||||
this.device.close();
|
||||
|
||||
const leftModuleBricked = await this.waitForKbootIdle();
|
||||
if (!leftModuleBricked) {
|
||||
this.logService.error('[UhkOperations] Couldn\'t connect to the left keyboard half.');
|
||||
return;
|
||||
}
|
||||
|
||||
await this.device.reenumerate(EnumerationModes.Buspal);
|
||||
this.device.close();
|
||||
await this.blhost.runBlhostCommandRetry([...buspalPrefix, 'get-property', '1']);
|
||||
@@ -161,6 +180,70 @@ export class UhkOperations {
|
||||
}
|
||||
}
|
||||
|
||||
public async waitForKbootIdle(): Promise<boolean> {
|
||||
const timeoutTime = new Date(new Date().getTime() + 30000);
|
||||
|
||||
while (new Date() < timeoutTime) {
|
||||
const buffer = await this.device.write(new Buffer([UsbCommand.GetProperty, DevicePropertyIds.CurrentKbootCommand]));
|
||||
this.device.close();
|
||||
|
||||
if (buffer[1] === 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// tslint:disable-next-line: max-line-length
|
||||
this.logService.info('[DeviceOperation] Cannot ping the bootloader. Please reconnect the left keyboard half. It probably needs several tries, so keep reconnecting until you see this message.');
|
||||
|
||||
await snooze(1000);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public async getLeftModuleVersionInfo(): Promise<HardwareModuleInfo> {
|
||||
try {
|
||||
this.logService.debug('[DeviceOperation] USB[T]: Read left module version information');
|
||||
|
||||
const command = new Buffer([
|
||||
UsbCommand.GetModuleProperty,
|
||||
ModuleSlotToId.leftHalf,
|
||||
ModulePropertyId.protocolVersions
|
||||
]);
|
||||
|
||||
const buffer = await this.device.write(command);
|
||||
const uhkBuffer = UhkBuffer.fromArray(convertBufferToIntArray(buffer));
|
||||
// skip the first 2 byte
|
||||
uhkBuffer.readUInt16();
|
||||
|
||||
return {
|
||||
moduleProtocolVersion: `${uhkBuffer.readUInt16()}.${uhkBuffer.readUInt16()}.${uhkBuffer.readUInt16()}`,
|
||||
firmwareVersion: `${uhkBuffer.readUInt16()}.${uhkBuffer.readUInt16()}.${uhkBuffer.readUInt16()}`
|
||||
};
|
||||
}
|
||||
catch (error) {
|
||||
this.logService.error('[DeviceOperation] Could not read left module version information', error);
|
||||
}
|
||||
|
||||
return {
|
||||
moduleProtocolVersion: '',
|
||||
firmwareVersion: ''
|
||||
};
|
||||
}
|
||||
|
||||
public async getRightModuleVersionInfo(): Promise<HardwareModuleInfo> {
|
||||
this.logService.debug('[DeviceOperation] USB[T]: Read right module version information');
|
||||
|
||||
const command = new Buffer([UsbCommand.GetProperty, DevicePropertyIds.ProtocolVersions]);
|
||||
const buffer = await this.device.write(command);
|
||||
const uhkBuffer = UhkBuffer.fromArray(convertBufferToIntArray(buffer));
|
||||
// skip the first byte
|
||||
uhkBuffer.readUInt8();
|
||||
|
||||
return {
|
||||
firmwareVersion: `${uhkBuffer.readUInt16()}.${uhkBuffer.readUInt16()}.${uhkBuffer.readUInt16()}`
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* IpcMain handler. Send the UserConfiguration to the UHK Device and send a response with the result.
|
||||
* @param {string} json - UserConfiguration in JSON format
|
||||
|
||||
2737
packages/uhk-web/package-lock.json
generated
2737
packages/uhk-web/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -37,6 +37,7 @@
|
||||
"@types/jasmine": "2.5.53",
|
||||
"@types/jasminewd2": "2.0.2",
|
||||
"@types/jquery": "3.2.9",
|
||||
"@types/lodash-es": "4.17.0",
|
||||
"@types/node-hid": "0.5.2",
|
||||
"@types/usb": "1.1.3",
|
||||
"angular-confirmation-popover": "3.2.0",
|
||||
@@ -67,7 +68,7 @@
|
||||
"karma-jasmine": "1.1.0",
|
||||
"karma-jasmine-html-reporter": "0.2.2",
|
||||
"less-loader": "4.0.5",
|
||||
"lodash": "4.17.4",
|
||||
"lodash-es": "4.17.4",
|
||||
"ng2-dragula": "1.5.0",
|
||||
"ng2-nouislider": "^1.7.6",
|
||||
"ng2-select2": "1.0.0-beta.10",
|
||||
|
||||
@@ -9,13 +9,13 @@
|
||||
|
||||
<ul class="list-unstyled btn-list">
|
||||
<li>
|
||||
Download device configuration in
|
||||
<span role="button" class="btn-link" (click)="saveConfigurationInJSONFormat()">JSON</span> or
|
||||
<span role="button" class="btn-link" (click)="saveConfigurationInBINFormat()">binary</span> format.
|
||||
<button class="btn btn-default"
|
||||
(click)="exportUserConfiguration($event)">Export device configuration
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<label class="btn btn-default btn-file">
|
||||
Upload device configuration
|
||||
Import device configuration
|
||||
<input type="file"
|
||||
(change)="changeFile($event)">
|
||||
</label>
|
||||
|
||||
@@ -34,6 +34,14 @@ export class DeviceConfigurationComponent {
|
||||
this.store.dispatch(new SaveUserConfigInBinaryFileAction());
|
||||
}
|
||||
|
||||
exportUserConfiguration(event: MouseEvent) {
|
||||
if (event.shiftKey) {
|
||||
this.saveConfigurationInBINFormat();
|
||||
} else {
|
||||
this.saveConfigurationInJSONFormat();
|
||||
}
|
||||
}
|
||||
|
||||
changeFile(event): void {
|
||||
const files = event.srcElement.files;
|
||||
const fileReader = new FileReader();
|
||||
|
||||
@@ -6,23 +6,36 @@
|
||||
<i class="fa fa-sliders"></i>
|
||||
<span>Firmware</span>
|
||||
</h1>
|
||||
|
||||
<p>
|
||||
Flash firmware {{ (getAgentVersionInfo$ | async).firmwareVersion }} (bundled with Agent)
|
||||
<button class="btn btn-primary"
|
||||
[disabled]="flashFirmwareButtonDisbabled$ | async"
|
||||
(click)="onUpdateFirmware()">Flash firmware
|
||||
</button>
|
||||
Firmware {{ hardwareModules.leftModuleInfo.firmwareVersion }} is running on the left keyboard half.<br>
|
||||
Firmware {{ hardwareModules.rightModuleInfo.firmwareVersion }} is running on the right keyboard half.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<i>
|
||||
Please note that the firmware update process may sometimes fail. If if fails then
|
||||
simply retry until it succeeds. If the left half becomes unresponsive after a failed
|
||||
update then retry and follow the instructions displayed during the update to fix it.
|
||||
We'll make the firmware update process more robust.
|
||||
</i>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Flash firmware file <input id="firmware-file-select"
|
||||
type="file"
|
||||
[disabled]="flashFirmwareButtonDisbabled$ | async"
|
||||
(change)="changeFile($event)">
|
||||
<button class="btn btn-primary"
|
||||
[disabled]="flashFirmwareButtonDisbabled$ | async"
|
||||
(click)="onUpdateFirmwareWithFile()">Flash firmware
|
||||
(click)="onUpdateFirmware()">
|
||||
Flash firmware {{ (getAgentVersionInfo$ | async).firmwareVersion }} (bundled with Agent)
|
||||
</button>
|
||||
<label class="btn btn-primary btn-file"
|
||||
[class.disabled]="flashFirmwareButtonDisbabled$ | async">
|
||||
Choose firmware file and flash it
|
||||
<input id="firmware-file-select"
|
||||
type="file"
|
||||
accept=".tar.bz2"
|
||||
[disabled]="flashFirmwareButtonDisbabled$ | async"
|
||||
(change)="changeFile($event)">
|
||||
</label>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -2,9 +2,16 @@ import { Component, ElementRef, OnDestroy, ViewChild } from '@angular/core';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { Subscription } from 'rxjs/Subscription';
|
||||
import { VersionInformation } from 'uhk-common';
|
||||
import { HardwareModules, VersionInformation } from 'uhk-common';
|
||||
|
||||
import { AppState, firmwareOkButtonDisabled, flashFirmwareButtonDisbabled, getAgentVersionInfo, xtermLog } from '../../../store';
|
||||
import {
|
||||
AppState,
|
||||
firmwareOkButtonDisabled,
|
||||
flashFirmwareButtonDisbabled,
|
||||
getAgentVersionInfo,
|
||||
getHardwareModules,
|
||||
xtermLog
|
||||
} from '../../../store';
|
||||
import { UpdateFirmwareAction, UpdateFirmwareOkButtonAction, UpdateFirmwareWithAction } from '../../../store/actions/device';
|
||||
import { XtermLog } from '../../../models/xterm-log';
|
||||
|
||||
@@ -22,8 +29,9 @@ export class DeviceFirmwareComponent implements OnDestroy {
|
||||
xtermLogSubscription: Subscription;
|
||||
getAgentVersionInfo$: Observable<VersionInformation>;
|
||||
firmwareOkButtonDisabled$: Observable<boolean>;
|
||||
hardwareModulesSubscription: Subscription;
|
||||
hardwareModules: HardwareModules;
|
||||
|
||||
arrayBuffer: Uint8Array;
|
||||
@ViewChild('scrollMe') divElement: ElementRef;
|
||||
|
||||
constructor(private store: Store<AppState>) {
|
||||
@@ -38,24 +46,20 @@ export class DeviceFirmwareComponent implements OnDestroy {
|
||||
});
|
||||
this.getAgentVersionInfo$ = store.select(getAgentVersionInfo);
|
||||
this.firmwareOkButtonDisabled$ = store.select(firmwareOkButtonDisabled);
|
||||
this.hardwareModulesSubscription = store.select(getHardwareModules).subscribe(data => {
|
||||
this.hardwareModules = data;
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.xtermLogSubscription.unsubscribe();
|
||||
this.hardwareModulesSubscription.unsubscribe();
|
||||
}
|
||||
|
||||
onUpdateFirmware(): void {
|
||||
this.store.dispatch(new UpdateFirmwareAction());
|
||||
}
|
||||
|
||||
onUpdateFirmwareWithFile(): void {
|
||||
if (!this.arrayBuffer) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.store.dispatch(new UpdateFirmwareWithAction(Array.prototype.slice.call(this.arrayBuffer)));
|
||||
}
|
||||
|
||||
onOkButtonClick(): void {
|
||||
this.store.dispatch(new UpdateFirmwareOkButtonAction());
|
||||
}
|
||||
@@ -64,14 +68,13 @@ export class DeviceFirmwareComponent implements OnDestroy {
|
||||
const files = event.srcElement.files;
|
||||
|
||||
if (files.length === 0) {
|
||||
this.arrayBuffer = null;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const fileReader = new FileReader();
|
||||
fileReader.onloadend = function () {
|
||||
this.arrayBuffer = new Uint8Array(fileReader.result);
|
||||
const arrayBuffer = new Uint8Array(fileReader.result);
|
||||
this.store.dispatch(new UpdateFirmwareWithAction(Array.prototype.slice.call(arrayBuffer)));
|
||||
}.bind(this);
|
||||
fileReader.readAsArrayBuffer(files[0]);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
<div class="text-center">
|
||||
<span *ngIf="showPlaceholder"
|
||||
class="placeholder">
|
||||
<span (click)="editText()">{{ placeholder }}</span>
|
||||
</span>
|
||||
|
||||
<span *ngIf="showText"
|
||||
class="editable">
|
||||
<span (click)="editText()"
|
||||
[innerHtml]="displayText"></span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div *ngIf="editing">
|
||||
<textarea class="text-editor"
|
||||
[(ngModel)]="text"
|
||||
autofocus
|
||||
(keydown.control.enter)="keydownEnter()"
|
||||
(keydown.alt.enter)="keydownEnter()"></textarea>
|
||||
<div class="pull-right buttons">
|
||||
<button class="btn btn-danger"
|
||||
(click)="cancelEditText()">
|
||||
Cancel
|
||||
</button>
|
||||
<button class="btn btn-primary"
|
||||
(click)="saveText()"
|
||||
[disabled]="isSaveDisabled">
|
||||
Update description
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,26 @@
|
||||
:host {
|
||||
margin-top: 0.5em;
|
||||
|
||||
span.placeholder {
|
||||
color: gray;
|
||||
display: inline-block;
|
||||
|
||||
.glyphicon {
|
||||
color: black;
|
||||
}
|
||||
}
|
||||
|
||||
span.editable,
|
||||
span.placeholder {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
textarea.text-editor {
|
||||
display: block;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.buttons {
|
||||
margin-top: 0.5em;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, forwardRef, Input } from '@angular/core';
|
||||
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
|
||||
|
||||
@Component({
|
||||
selector: 'editable-text',
|
||||
templateUrl: './editable-text.component.html',
|
||||
styleUrls: ['./editable-text.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
providers: [
|
||||
{provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => EditableTextComponent), multi: true}
|
||||
]
|
||||
})
|
||||
export class EditableTextComponent implements ControlValueAccessor {
|
||||
|
||||
@Input() placeholder = 'No editable content';
|
||||
text: string;
|
||||
originalText: string;
|
||||
editing = false;
|
||||
|
||||
get isSaveDisabled(): boolean {
|
||||
return !this.text || this.text.trim().length === 0;
|
||||
}
|
||||
|
||||
get displayText(): string {
|
||||
return this.text && this.text.replace(/\n/g, '<br>');
|
||||
}
|
||||
|
||||
constructor(private cdr: ChangeDetectorRef) {
|
||||
|
||||
}
|
||||
|
||||
writeValue(obj: any): void {
|
||||
if (this.text === obj) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.text = obj;
|
||||
this.cdr.markForCheck();
|
||||
}
|
||||
|
||||
registerOnChange(fn: any): void {
|
||||
this.textChange = fn;
|
||||
}
|
||||
|
||||
registerOnTouched(fn: any): void {
|
||||
}
|
||||
|
||||
saveText(): void {
|
||||
this.originalText = null;
|
||||
this.editing = false;
|
||||
this.textChange(this.text);
|
||||
}
|
||||
|
||||
editText(): void {
|
||||
this.originalText = this.text;
|
||||
this.editing = true;
|
||||
}
|
||||
|
||||
cancelEditText(): void {
|
||||
this.text = this.originalText;
|
||||
this.editing = false;
|
||||
}
|
||||
|
||||
keydownEnter(): void {
|
||||
if (this.isSaveDisabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.saveText();
|
||||
}
|
||||
|
||||
get showPlaceholder(): boolean {
|
||||
return !this.editing && !this.text;
|
||||
}
|
||||
|
||||
get showText(): boolean {
|
||||
return !this.editing && !!this.text;
|
||||
}
|
||||
|
||||
private textChange: any = () => {
|
||||
}
|
||||
}
|
||||
@@ -7,8 +7,11 @@
|
||||
[selectedKey]="selectedKey"
|
||||
[selected]="selectedKey?.layerId === index"
|
||||
[keyboardLayout]="keyboardLayout"
|
||||
[description]="description"
|
||||
[showDescription]="true"
|
||||
(keyClick)="keyClick.emit($event)"
|
||||
(keyHover)="keyHover.emit($event)"
|
||||
(capture)="capture.emit($event)"
|
||||
(descriptionChanged)="descriptionChanged.emit($event)"
|
||||
>
|
||||
</svg-keyboard>
|
||||
|
||||
|
Before Width: | Height: | Size: 659 B After Width: | Height: | Size: 809 B |
@@ -1,4 +1,4 @@
|
||||
import { Component, ChangeDetectionStrategy, Input, Output, EventEmitter, OnChanges, SimpleChanges } from '@angular/core';
|
||||
import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnChanges, Output, SimpleChanges } from '@angular/core';
|
||||
import { animate, keyframes, state, style, transition, trigger } from '@angular/animations';
|
||||
import { Layer } from 'uhk-common';
|
||||
|
||||
@@ -81,11 +81,14 @@ export class KeyboardSliderComponent implements OnChanges {
|
||||
@Input() halvesSplit: boolean;
|
||||
@Input() selectedKey: { layerId: number, moduleId: number, keyId: number };
|
||||
@Input() keyboardLayout = KeyboardLayout.ANSI;
|
||||
@Input() description: string;
|
||||
@Output() keyClick = new EventEmitter();
|
||||
@Output() keyHover = new EventEmitter();
|
||||
@Output() capture = new EventEmitter();
|
||||
@Output() descriptionChanged = new EventEmitter<string>();
|
||||
|
||||
layerAnimationState: AnimationKeyboard[];
|
||||
|
||||
ngOnChanges(changes: SimpleChanges) {
|
||||
if (changes['layers']) {
|
||||
this.layerAnimationState = this.layers.map<AnimationKeyboard>(() => 'initOut');
|
||||
|
||||
@@ -4,7 +4,8 @@
|
||||
(downloadClick)="downloadKeymap()"></keymap-header>
|
||||
<svg-keyboard-wrap [keymap]="keymap$ | async"
|
||||
[halvesSplit]="keyboardSplit"
|
||||
[keyboardLayout]="keyboardLayout$ | async"></svg-keyboard-wrap>
|
||||
[keyboardLayout]="keyboardLayout$ | async"
|
||||
(descriptionChanged)="descriptionChanged($event)"></svg-keyboard-wrap>
|
||||
</ng-template>
|
||||
|
||||
<div *ngIf="!(keymap$ | async)" class="not-found">
|
||||
|
||||
@@ -18,6 +18,8 @@ import { AppState, getKeyboardLayout } from '../../../store';
|
||||
import { getKeymap, getKeymaps, getUserConfiguration } from '../../../store/reducers/user-configuration';
|
||||
import { SvgKeyboardWrapComponent } from '../../svg/wrap';
|
||||
import { KeyboardLayout } from '../../../keyboard/keyboard-layout.enum';
|
||||
import { KeymapActions } from '../../../store/actions';
|
||||
import { ChangeKeymapDescription } from '../../../models/ChangeKeymapDescription';
|
||||
|
||||
@Component({
|
||||
selector: 'keymap-edit',
|
||||
@@ -73,6 +75,10 @@ export class KeymapEditComponent {
|
||||
this.keyboardSplit = !this.keyboardSplit;
|
||||
}
|
||||
|
||||
descriptionChanged(event: ChangeKeymapDescription): void {
|
||||
this.store.dispatch(new KeymapActions.EditDescriptionAction(event));
|
||||
}
|
||||
|
||||
private toExportableJSON(keymap: Keymap): Observable<any> {
|
||||
return this.store
|
||||
.let(getUserConfiguration())
|
||||
|
||||
@@ -13,5 +13,5 @@
|
||||
</ng-template>
|
||||
|
||||
<div *ngIf="!macro" class="not-found">
|
||||
There is no macro with id {{ route.params.select('id') | async }}.
|
||||
There is no macro with id {{ macroId }}.
|
||||
</div>
|
||||
|
||||
@@ -7,7 +7,7 @@ import { Subscription } from 'rxjs/Subscription';
|
||||
import 'rxjs/add/operator/pluck';
|
||||
|
||||
import { MacroActions } from '../../../store/actions';
|
||||
import { AppState } from '../../../store/index';
|
||||
import { AppState } from '../../../store';
|
||||
import { getMacro } from '../../../store/reducers/user-configuration';
|
||||
|
||||
@Component({
|
||||
@@ -21,13 +21,17 @@ import { getMacro } from '../../../store/reducers/user-configuration';
|
||||
export class MacroEditComponent implements OnDestroy {
|
||||
macro: Macro;
|
||||
isNew: boolean;
|
||||
macroId: number;
|
||||
|
||||
private subscription: Subscription;
|
||||
constructor(private store: Store<AppState>, public route: ActivatedRoute) {
|
||||
this.subscription = route
|
||||
.params
|
||||
.pluck<{}, string>('id')
|
||||
.switchMap((id: string) => store.let(getMacro(+id)))
|
||||
.switchMap((id: string) => {
|
||||
this.macroId = +id;
|
||||
return store.let(getMacro(this.macroId));
|
||||
})
|
||||
.subscribe((macro: Macro) => {
|
||||
this.macro = macro;
|
||||
});
|
||||
|
||||
@@ -7,6 +7,10 @@
|
||||
[width]="200"
|
||||
[options]="options"
|
||||
></select2>
|
||||
<icon name="question-circle"
|
||||
data-toggle="tooltip"
|
||||
title="Looking for a non-US character? Just pick the character of the desired key according to the US layout."
|
||||
data-placement="bottom"></icon>
|
||||
<capture-keystroke-button (capture)="onKeysCapture($event)" tabindex="0"></capture-keystroke-button>
|
||||
</div>
|
||||
<div class="modifier-options">
|
||||
|
||||
@@ -10,6 +10,10 @@
|
||||
position: relative;
|
||||
top: 2px;
|
||||
}
|
||||
|
||||
icon {
|
||||
display: inline-block;
|
||||
};
|
||||
}
|
||||
|
||||
.modifier-options {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Component, Input, OnChanges } from '@angular/core';
|
||||
import { Select2OptionData, Select2TemplateFunction } from 'ng2-select2';
|
||||
import { KeyAction, KeystrokeAction, KeystrokeType } from 'uhk-common';
|
||||
import { KeyAction, KeystrokeAction, KeystrokeType, SCANCODES, SECONDARY_ROLES } from 'uhk-common';
|
||||
|
||||
import { Tab } from '../tab';
|
||||
import { MapperService } from '../../../../services/mapper.service';
|
||||
@@ -35,8 +35,8 @@ export class KeypressTabComponent extends Tab implements OnChanges {
|
||||
id: '0',
|
||||
text: 'None'
|
||||
}];
|
||||
this.scanCodeGroups = this.scanCodeGroups.concat(require('./scancodes.json'));
|
||||
this.secondaryRoleGroups = require('./secondaryRole.json');
|
||||
this.scanCodeGroups = this.scanCodeGroups.concat(SCANCODES);
|
||||
this.secondaryRoleGroups = SECONDARY_ROLES;
|
||||
this.leftModifierSelects = Array(this.leftModifiers.length).fill(false);
|
||||
this.rightModifierSelects = Array(this.rightModifiers.length).fill(false);
|
||||
this.selectedScancodeOption = this.scanCodeGroups[0];
|
||||
|
||||
@@ -128,12 +128,14 @@
|
||||
<ul [@toggler]="animation['agent']">
|
||||
<li class="sidebar__level-2--item">
|
||||
<div class="sidebar__level-2" [routerLinkActive]="['active']">
|
||||
<a [routerLink]="['/settings']">Settings</a>
|
||||
<a [routerLink]="['/settings']"
|
||||
[class.disabled]="updatingFirmware$ | async">Settings</a>
|
||||
</div>
|
||||
</li>
|
||||
<li class="sidebar__level-2--item">
|
||||
<div class="sidebar__level-2" [routerLinkActive]="['active']">
|
||||
<a [routerLink]="['/about']">About</a>
|
||||
<a [routerLink]="['/about']"
|
||||
[class.disabled]="updatingFirmware$ | async">About</a>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
@@ -9,6 +9,10 @@
|
||||
|
||||
a {
|
||||
color: #333;
|
||||
|
||||
&.disabled {
|
||||
opacity: 0.65;
|
||||
}
|
||||
}
|
||||
|
||||
// General list styles for the sidebar-menu.
|
||||
@@ -112,6 +116,10 @@ ul {
|
||||
&:focus {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
opacity: 0.65;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -164,12 +172,12 @@ ul {
|
||||
padding: 0;
|
||||
margin: 0 0.25rem;
|
||||
text-overflow: ellipsis;
|
||||
background-color: inherit;
|
||||
background-color: transparent;
|
||||
|
||||
&:focus {
|
||||
box-shadow: 0 0 0 1px #ccc, 0 0 5px 0 #ccc;
|
||||
border-color: transparent;
|
||||
background-color: inherit;
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,17 +56,8 @@ export class SideMenuComponent implements AfterContentInit, OnDestroy {
|
||||
addon: 'active'
|
||||
};
|
||||
|
||||
this.keymaps$ = store.let(getKeymaps())
|
||||
.map(keymaps => keymaps.slice()) // Creating a new array reference, because the sort is working in place
|
||||
.do((keymaps: Keymap[]) => {
|
||||
keymaps.sort((first: Keymap, second: Keymap) => first.name.localeCompare(second.name));
|
||||
});
|
||||
|
||||
this.macros$ = store.let(getMacros())
|
||||
.map(macros => macros.slice()) // Creating a new array reference, because the sort is working in place
|
||||
.do((macros: Macro[]) => {
|
||||
macros.sort((first: Macro, second: Macro) => first.name.localeCompare(second.name));
|
||||
});
|
||||
this.keymaps$ = store.let(getKeymaps());
|
||||
this.macros$ = store.let(getMacros());
|
||||
|
||||
this.showAddonMenu$ = this.store.select(showAddonMenu);
|
||||
this.runInElectron$ = this.store.select(runningInElectron);
|
||||
|
||||
@@ -14,3 +14,7 @@
|
||||
(capture)="onCapture(i, $event.index, $event.captured)"
|
||||
/>
|
||||
</svg>
|
||||
<editable-text *ngIf="showDescription"
|
||||
[ngModel]="description"
|
||||
(ngModelChange)="descriptionChanged.emit($event)"
|
||||
placeholder="No description provided for this keymap."></editable-text>
|
||||
|
||||
|
Before Width: | Height: | Size: 854 B After Width: | Height: | Size: 1.0 KiB |
@@ -1,5 +1,10 @@
|
||||
:host {
|
||||
display: flex;
|
||||
display: block;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
}
|
||||
editable-text {
|
||||
padding-left: 2em;
|
||||
padding-right: 2em;
|
||||
display: block;
|
||||
}
|
||||
|
||||
@@ -31,9 +31,12 @@ export class SvgKeyboardComponent implements OnInit {
|
||||
@Input() selected: boolean;
|
||||
@Input() halvesSplit: boolean;
|
||||
@Input() keyboardLayout = KeyboardLayout.ANSI;
|
||||
@Input() description: string;
|
||||
@Input() showDescription = false;
|
||||
@Output() keyClick = new EventEmitter();
|
||||
@Output() keyHover = new EventEmitter();
|
||||
@Output() capture = new EventEmitter();
|
||||
@Output() descriptionChanged = new EventEmitter<string>();
|
||||
|
||||
modules: SvgModule[];
|
||||
viewBox: string;
|
||||
|
||||
@@ -7,9 +7,11 @@
|
||||
[selectedKey]="selectedKey"
|
||||
[halvesSplit]="halvesSplit"
|
||||
[keyboardLayout]="keyboardLayout"
|
||||
[description]="keymap.description"
|
||||
(keyClick)="onKeyClick($event.moduleId, $event.keyId, $event.keyTarget)"
|
||||
(keyHover)="onKeyHover($event.moduleId, $event.event, $event.over, $event.keyId)"
|
||||
(capture)="onCapture($event.moduleId, $event.keyId, $event.captured)"
|
||||
(descriptionChanged)="onDescriptionChanged($event)"
|
||||
></keyboard-slider>
|
||||
<popover tabindex="0" [visible]="popoverShown" [keyPosition]="keyPosition" [wrapPosition]="wrapPosition" [defaultKeyAction]="popoverInitKeyAction"
|
||||
[currentKeymap]="keymap" [currentLayer]="currentLayer" (cancel)="hidePopover()" (remap)="onRemap($event)"></popover>
|
||||
|
||||
@@ -2,14 +2,16 @@ import {
|
||||
ChangeDetectionStrategy,
|
||||
Component,
|
||||
ElementRef,
|
||||
Renderer,
|
||||
EventEmitter,
|
||||
HostBinding,
|
||||
HostListener,
|
||||
Input,
|
||||
OnChanges,
|
||||
OnInit,
|
||||
ViewChild,
|
||||
SimpleChanges
|
||||
Output,
|
||||
Renderer,
|
||||
SimpleChanges,
|
||||
ViewChild
|
||||
} from '@angular/core';
|
||||
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
@@ -25,10 +27,10 @@ import {
|
||||
KeystrokeAction,
|
||||
Layer,
|
||||
LayerName,
|
||||
SecondaryRoleAction,
|
||||
MouseAction,
|
||||
MouseActionParam,
|
||||
PlayMacroAction,
|
||||
SecondaryRoleAction,
|
||||
SwitchKeymapAction,
|
||||
SwitchLayerAction
|
||||
} from 'uhk-common';
|
||||
@@ -38,6 +40,7 @@ import { AppState } from '../../../store';
|
||||
import { KeymapActions } from '../../../store/actions';
|
||||
import { PopoverComponent } from '../../popover';
|
||||
import { KeyboardLayout } from '../../../keyboard/keyboard-layout.enum';
|
||||
import { ChangeKeymapDescription } from '../../../models/ChangeKeymapDescription';
|
||||
|
||||
interface NameValuePair {
|
||||
name: string;
|
||||
@@ -56,6 +59,7 @@ export class SvgKeyboardWrapComponent implements OnInit, OnChanges {
|
||||
@Input() tooltipEnabled: boolean = false;
|
||||
@Input() halvesSplit: boolean;
|
||||
@Input() keyboardLayout: KeyboardLayout.ANSI;
|
||||
@Output() descriptionChanged = new EventEmitter<ChangeKeymapDescription>();
|
||||
|
||||
@ViewChild(PopoverComponent, { read: ElementRef }) popover: ElementRef;
|
||||
|
||||
@@ -237,6 +241,13 @@ export class SvgKeyboardWrapComponent implements OnInit, OnChanges {
|
||||
return this.currentLayer;
|
||||
}
|
||||
|
||||
onDescriptionChanged(description: string): void {
|
||||
this.descriptionChanged.emit({
|
||||
description,
|
||||
abbr: this.keymap.abbreviation
|
||||
});
|
||||
}
|
||||
|
||||
private getKeyActionContent(keyAction: KeyAction): Observable<NameValuePair[]> {
|
||||
if (keyAction instanceof KeystrokeAction) {
|
||||
const keystrokeAction: KeystrokeAction = keyAction;
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Store } from '@ngrx/store';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
|
||||
import { Notification } from 'uhk-common';
|
||||
import { AppState, getUndoableNotification } from '../../store/index';
|
||||
import { AppState, getUndoableNotification } from '../../store';
|
||||
import { DismissUndoNotificationAction, UndoLastAction } from '../../store/actions/app';
|
||||
|
||||
@Component({
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
import { AfterViewInit, Directive, ElementRef } from '@angular/core';
|
||||
|
||||
@Directive({
|
||||
selector: '[autofocus]'
|
||||
})
|
||||
export class Autofocus implements AfterViewInit {
|
||||
constructor(private el: ElementRef) {
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
}
|
||||
|
||||
ngAfterViewInit() {
|
||||
this.el.nativeElement.focus();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
export interface ChangeKeymapDescription {
|
||||
abbr: string;
|
||||
description: string;
|
||||
}
|
||||
@@ -94,7 +94,7 @@ export class CaptureService {
|
||||
this.mapping.set(88, 27); // X
|
||||
this.mapping.set(89, 28); // Y
|
||||
this.mapping.set(90, 29); // Z
|
||||
this.mapping.set(93, 118); // Menu
|
||||
this.mapping.set(93, 101); // Menu
|
||||
this.mapping.set(96, 98); // Num pad 0
|
||||
this.mapping.set(97, 89); // Num pad 1
|
||||
this.mapping.set(98, 90); // Num pad 2
|
||||
|
||||
@@ -190,6 +190,7 @@ export class MapperService {
|
||||
this.basicScanCodeTextMap.set(98, ['Insert', '0']);
|
||||
this.basicScanCodeTextMap.set(99, ['Del', '.']);
|
||||
this.basicScanCodeTextMap.set(100, ['ISO key', '|']);
|
||||
this.basicScanCodeTextMap.set(101, ['Menu']);
|
||||
this.basicScanCodeTextMap.set(104, ['F13']);
|
||||
this.basicScanCodeTextMap.set(105, ['F14']);
|
||||
this.basicScanCodeTextMap.set(106, ['F15']);
|
||||
@@ -202,7 +203,6 @@ export class MapperService {
|
||||
this.basicScanCodeTextMap.set(113, ['F22']);
|
||||
this.basicScanCodeTextMap.set(114, ['F23']);
|
||||
this.basicScanCodeTextMap.set(115, ['F24']);
|
||||
this.basicScanCodeTextMap.set(118, ['Menu']);
|
||||
this.basicScanCodeTextMap.set(176, ['00']);
|
||||
this.basicScanCodeTextMap.set(177, ['000']);
|
||||
|
||||
@@ -236,7 +236,7 @@ export class MapperService {
|
||||
this.basicScancodeIcons.set(80, 'icon-kbd__mod--arrow-left');
|
||||
this.basicScancodeIcons.set(81, 'icon-kbd__mod--arrow-down');
|
||||
this.basicScancodeIcons.set(82, 'icon-kbd__mod--arrow-up');
|
||||
this.basicScancodeIcons.set(118, 'icon-kbd__mod--menu');
|
||||
this.basicScancodeIcons.set(101, 'icon-kbd__mod--menu');
|
||||
|
||||
this.mediaScancodeIcons = new Map<number, string>();
|
||||
this.mediaScancodeIcons.set(138, 'icon-kbd__fn--browser');
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -101,6 +101,8 @@ 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';
|
||||
import { EditableTextComponent } from './components/editable-text/editable-text.component';
|
||||
import { Autofocus } from './directives/autofocus/autofocus.directive';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
@@ -169,7 +171,9 @@ import { SliderWrapperComponent } from './components/slider-wrapper/slider-wrapp
|
||||
ProgressButtonComponent,
|
||||
LoadingDevicePageComponent,
|
||||
XtermComponent,
|
||||
SliderWrapperComponent
|
||||
SliderWrapperComponent,
|
||||
EditableTextComponent,
|
||||
Autofocus
|
||||
],
|
||||
imports: [
|
||||
CommonModule,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Action } from '@ngrx/store';
|
||||
import { DeviceConnectionState, IpcResponse, type } from 'uhk-common';
|
||||
import { HardwareModules } from '../../../../../uhk-common/src/models';
|
||||
|
||||
const PREFIX = '[device] ';
|
||||
|
||||
@@ -22,7 +23,8 @@ export const ActionTypes = {
|
||||
UPDATE_FIRMWARE_REPLY: type(PREFIX + 'update firmware reply'),
|
||||
UPDATE_FIRMWARE_SUCCESS: type(PREFIX + 'update firmware success'),
|
||||
UPDATE_FIRMWARE_FAILED: type(PREFIX + 'update firmware failed'),
|
||||
UPDATE_FIRMWARE_OK_BUTTON: type(PREFIX + 'update firmware ok button click')
|
||||
UPDATE_FIRMWARE_OK_BUTTON: type(PREFIX + 'update firmware ok button click'),
|
||||
MODULES_INFO_LOADED: type(PREFIX + 'module info loaded')
|
||||
};
|
||||
|
||||
export class SetPrivilegeOnLinuxAction implements Action {
|
||||
@@ -114,6 +116,13 @@ export class ResetMouseSpeedSettingsAction implements Action {
|
||||
type = ActionTypes.RESET_MOUSE_SPEED_SETTINGS;
|
||||
}
|
||||
|
||||
export class HardwareModulesLoadedAction implements Action {
|
||||
type = ActionTypes.MODULES_INFO_LOADED;
|
||||
|
||||
constructor(public payload: HardwareModules) {
|
||||
}
|
||||
}
|
||||
|
||||
export type Actions
|
||||
= SetPrivilegeOnLinuxAction
|
||||
| SetPrivilegeOnLinuxReplyAction
|
||||
@@ -132,4 +141,5 @@ export type Actions
|
||||
| UpdateFirmwareSuccessAction
|
||||
| UpdateFirmwareFailedAction
|
||||
| UpdateFirmwareOkButtonAction
|
||||
| HardwareModulesLoadedAction
|
||||
;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Action } from '@ngrx/store';
|
||||
import { KeyAction, Keymap, Macro } from 'uhk-common';
|
||||
import { UndoUserConfigData } from '../../models/undo-user-config-data';
|
||||
import { ChangeKeymapDescription } from '../../models/ChangeKeymapDescription';
|
||||
|
||||
export type KeymapAction =
|
||||
KeymapActions.AddKeymapAction |
|
||||
@@ -11,7 +12,8 @@ export type KeymapAction =
|
||||
KeymapActions.SetDefaultAction |
|
||||
KeymapActions.RemoveKeymapAction |
|
||||
KeymapActions.SaveKeyAction |
|
||||
KeymapActions.CheckMacroAction;
|
||||
KeymapActions.CheckMacroAction |
|
||||
KeymapActions.EditDescriptionAction;
|
||||
|
||||
export namespace KeymapActions {
|
||||
export const ADD = '[Keymap] Add keymap';
|
||||
@@ -98,6 +100,16 @@ export namespace KeymapActions {
|
||||
payload: UndoUserConfigData
|
||||
};
|
||||
|
||||
export const EDIT_DESCRIPTION = '[Keymap] Edit description';
|
||||
|
||||
export class EditDescriptionAction {
|
||||
type = EDIT_DESCRIPTION;
|
||||
|
||||
constructor(public payload: ChangeKeymapDescription) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
export function loadKeymaps(): Action {
|
||||
return {
|
||||
type: KeymapActions.LOAD_KEYMAPS
|
||||
|
||||
@@ -35,10 +35,12 @@ import { ShowNotificationAction } from '../actions/app';
|
||||
import { AppState } from '../index';
|
||||
import {
|
||||
ActionTypes as UserConfigActions,
|
||||
ApplyUserConfigurationFromFileAction,
|
||||
LoadConfigFromDeviceAction,
|
||||
LoadResetUserConfigurationAction
|
||||
} from '../actions/user-config';
|
||||
import { DefaultUserConfigurationService } from '../../services/default-user-configuration.service';
|
||||
import { DataStorageRepositoryService } from '../../services/datastorage-repository.service';
|
||||
|
||||
@Injectable()
|
||||
export class DeviceEffects {
|
||||
@@ -162,8 +164,16 @@ export class DeviceEffects {
|
||||
});
|
||||
|
||||
@Effect() saveResetUserConfigurationToDevice$ = this.actions$
|
||||
.ofType(UserConfigActions.LOAD_RESET_USER_CONFIGURATION, UserConfigActions.APPLY_USER_CONFIGURATION_FROM_FILE)
|
||||
.switchMap(() => Observable.of(new SaveConfigurationAction()));
|
||||
.ofType<ApplyUserConfigurationFromFileAction
|
||||
| LoadResetUserConfigurationAction>(
|
||||
UserConfigActions.LOAD_RESET_USER_CONFIGURATION,
|
||||
UserConfigActions.APPLY_USER_CONFIGURATION_FROM_FILE)
|
||||
.map(action => action.payload)
|
||||
.switchMap((config: UserConfiguration) => {
|
||||
this.dataStorageRepository.saveConfig(config);
|
||||
|
||||
return Observable.of(new SaveConfigurationAction());
|
||||
});
|
||||
|
||||
@Effect({dispatch: false}) updateFirmware$ = this.actions$
|
||||
.ofType<UpdateFirmwareAction>(ActionTypes.UPDATE_FIRMWARE)
|
||||
@@ -193,6 +203,7 @@ export class DeviceEffects {
|
||||
private router: Router,
|
||||
private deviceRendererService: DeviceRendererService,
|
||||
private store: Store<AppState>,
|
||||
private dataStorageRepository: DataStorageRepositoryService,
|
||||
private defaultUserConfigurationService: DefaultUserConfigurationService) {
|
||||
}
|
||||
|
||||
|
||||
@@ -7,14 +7,17 @@ import { Observable } from 'rxjs/Observable';
|
||||
|
||||
import 'rxjs/add/operator/do';
|
||||
import 'rxjs/add/operator/map';
|
||||
import 'rxjs/add/operator/pairwise';
|
||||
import 'rxjs/add/operator/startWith';
|
||||
import 'rxjs/add/operator/switchMap';
|
||||
import 'rxjs/add/operator/withLatestFrom';
|
||||
import 'rxjs/add/observable/of';
|
||||
|
||||
import { Keymap } from 'uhk-common';
|
||||
import { findNewItem } from '../../util';
|
||||
import { KeymapActions } from '../actions';
|
||||
import { AppState } from '../index';
|
||||
import { getKeymaps } from '../reducers/user-configuration';
|
||||
|
||||
@Injectable()
|
||||
export class KeymapEffects {
|
||||
@@ -32,10 +35,10 @@ export class KeymapEffects {
|
||||
|
||||
@Effect({ dispatch: false }) addOrDuplicate$: any = this.actions$
|
||||
.ofType(KeymapActions.ADD, KeymapActions.DUPLICATE)
|
||||
.withLatestFrom(this.store)
|
||||
.map(latest => latest[1].userConfiguration.keymaps)
|
||||
.do(keymaps => {
|
||||
this.router.navigate(['/keymap', keymaps[keymaps.length - 1].abbreviation]);
|
||||
.withLatestFrom(this.store.let(getKeymaps()).pairwise(), (action, latest) => latest)
|
||||
.do(([prevKeymaps, newKeymaps]) => {
|
||||
const newKeymap = findNewItem(prevKeymaps, newKeymaps);
|
||||
this.router.navigate(['/keymap', newKeymap.abbreviation]);
|
||||
});
|
||||
|
||||
@Effect({ dispatch: false }) remove$: any = this.actions$
|
||||
|
||||
@@ -2,14 +2,18 @@ import { Injectable } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
|
||||
import { Actions, Effect } from '@ngrx/effects';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { Store, Action } from '@ngrx/store';
|
||||
|
||||
import 'rxjs/add/operator/do';
|
||||
import 'rxjs/add/operator/map';
|
||||
import 'rxjs/add/operator/pairwise';
|
||||
import 'rxjs/add/operator/withLatestFrom';
|
||||
|
||||
import { Macro } from 'uhk-common';
|
||||
import { KeymapActions, MacroActions } from '../actions';
|
||||
import { AppState } from '../index';
|
||||
import { getMacros } from '../reducers/user-configuration';
|
||||
import { findNewItem } from '../../util';
|
||||
|
||||
@Injectable()
|
||||
export class MacroEffects {
|
||||
@@ -27,22 +31,16 @@ export class MacroEffects {
|
||||
}
|
||||
});
|
||||
|
||||
@Effect({ dispatch: false }) add$: any = this.actions$
|
||||
.ofType(MacroActions.ADD)
|
||||
.withLatestFrom(this.store)
|
||||
.map(([action, state]) => state.userConfiguration.macros)
|
||||
.map(macros => macros[macros.length - 1])
|
||||
.do(lastMacro => {
|
||||
this.router.navigate(['/macro', lastMacro.id, 'new']);
|
||||
});
|
||||
|
||||
@Effect({ dispatch: false }) duplicate: any = this.actions$
|
||||
.ofType(MacroActions.DUPLICATE)
|
||||
.withLatestFrom(this.store)
|
||||
.map(([action, state]) => state.userConfiguration.macros)
|
||||
.map(macros => macros[macros.length - 1])
|
||||
.do(lastMacro => {
|
||||
this.router.navigate(['/macro', lastMacro.id]);
|
||||
@Effect({ dispatch: false }) addOrDuplicate$: any = this.actions$
|
||||
.ofType(MacroActions.ADD, MacroActions.DUPLICATE)
|
||||
.withLatestFrom(this.store.let(getMacros()).pairwise(), (action, latest) => ([action, latest[0], latest[1]]))
|
||||
.do(([action, prevMacros, newMacros]: [Action, Macro[], Macro[]]) => {
|
||||
const newMacro = findNewItem(prevMacros, newMacros);
|
||||
const commands = ['/macro', newMacro.id];
|
||||
if (action.type === MacroActions.ADD) {
|
||||
commands.push('new');
|
||||
}
|
||||
this.router.navigate(commands);
|
||||
});
|
||||
|
||||
constructor(private actions$: Actions, private router: Router, private store: Store<AppState>) { }
|
||||
|
||||
@@ -43,7 +43,7 @@ import {
|
||||
ShowNotificationAction,
|
||||
UndoLastAction
|
||||
} from '../actions/app';
|
||||
import { ShowSaveToKeyboardButtonAction } from '../actions/device';
|
||||
import { HardwareModulesLoadedAction, 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';
|
||||
@@ -81,7 +81,7 @@ export class UserConfigEffects {
|
||||
@Effect() saveUserConfig$: Observable<Action> = (this.actions$
|
||||
.ofType(
|
||||
KeymapActions.ADD, KeymapActions.DUPLICATE, KeymapActions.EDIT_NAME, KeymapActions.EDIT_ABBR,
|
||||
KeymapActions.SET_DEFAULT, KeymapActions.REMOVE, KeymapActions.SAVE_KEY,
|
||||
KeymapActions.SET_DEFAULT, KeymapActions.REMOVE, KeymapActions.SAVE_KEY, KeymapActions.EDIT_DESCRIPTION,
|
||||
MacroActions.ADD, MacroActions.DUPLICATE, MacroActions.EDIT_NAME, MacroActions.REMOVE, MacroActions.ADD_ACTION,
|
||||
MacroActions.SAVE_ACTION, MacroActions.DELETE_ACTION, MacroActions.REORDER_ACTION,
|
||||
ActionTypes.RENAME_USER_CONFIGURATION, ActionTypes.SET_USER_CONFIGURATION_VALUE) as
|
||||
@@ -173,6 +173,8 @@ export class UserConfigEffects {
|
||||
}));
|
||||
}
|
||||
|
||||
result.push(new HardwareModulesLoadedAction(data.modules));
|
||||
|
||||
this.router.navigate(['/']);
|
||||
|
||||
return result;
|
||||
|
||||
@@ -77,3 +77,4 @@ export const xtermLog = createSelector(deviceState, fromDevice.xtermLog);
|
||||
export const firmwareOkButtonDisabled = createSelector(deviceState, fromDevice.firmwareOkButtonDisabled);
|
||||
// tslint:disable-next-line: max-line-length
|
||||
export const flashFirmwareButtonDisbabled = createSelector(runningInElectron, deviceState, (electron, state: fromDevice.State) => !electron || state.updatingFirmware);
|
||||
export const getHardwareModules = createSelector(deviceState, fromDevice.getHardwareModules);
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { Action } from '@ngrx/store';
|
||||
import { HardwareModules } from 'uhk-common';
|
||||
|
||||
import {
|
||||
ActionTypes,
|
||||
ConnectionStateChangedAction,
|
||||
HardwareModulesLoadedAction,
|
||||
SaveConfigurationAction,
|
||||
UpdateFirmwareFailedAction
|
||||
} from '../actions/device';
|
||||
@@ -16,6 +18,7 @@ export interface State {
|
||||
saveToKeyboard: ProgressButtonState;
|
||||
updatingFirmware: boolean;
|
||||
firmwareUpdateFinished: boolean;
|
||||
modules: HardwareModules;
|
||||
log: Array<XtermLog>;
|
||||
}
|
||||
|
||||
@@ -25,6 +28,15 @@ export const initialState: State = {
|
||||
saveToKeyboard: initProgressButtonState,
|
||||
updatingFirmware: false,
|
||||
firmwareUpdateFinished: false,
|
||||
modules: {
|
||||
leftModuleInfo: {
|
||||
firmwareVersion: '',
|
||||
moduleProtocolVersion: ''
|
||||
},
|
||||
rightModuleInfo: {
|
||||
firmwareVersion: ''
|
||||
}
|
||||
},
|
||||
log: [{message: '', cssClass: XtermCssClass.standard}]
|
||||
};
|
||||
|
||||
@@ -148,6 +160,13 @@ export function reducer(state = initialState, action: Action) {
|
||||
log: [...state.log, logEntry]
|
||||
};
|
||||
}
|
||||
|
||||
case ActionTypes.MODULES_INFO_LOADED:
|
||||
return {
|
||||
...state,
|
||||
modules: (action as HardwareModulesLoadedAction).payload
|
||||
};
|
||||
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
@@ -159,3 +178,4 @@ export const hasDevicePermission = (state: State) => state.hasPermission;
|
||||
export const getSaveToKeyboardState = (state: State) => state.saveToKeyboard;
|
||||
export const xtermLog = (state: State) => state.log;
|
||||
export const firmwareOkButtonDisabled = (state: State) => !state.firmwareUpdateFinished;
|
||||
export const getHardwareModules = (state: State) => state.modules;
|
||||
|
||||
@@ -7,7 +7,7 @@ export const initialState: Keymap[] = [];
|
||||
export function reducer(state = initialState, action: KeymapAction): Keymap[] {
|
||||
switch (action.type) {
|
||||
case KeymapActions.LOAD_KEYMAPS_SUCCESS: {
|
||||
return action.payload;
|
||||
return (action as KeymapActions.LoadKeymapSuccessAction).payload ;
|
||||
}
|
||||
|
||||
default:
|
||||
|
||||
@@ -4,7 +4,18 @@ import { Observable } from 'rxjs/Observable';
|
||||
import 'rxjs/add/observable/of';
|
||||
import 'rxjs/add/operator/map';
|
||||
|
||||
import { KeyAction, KeyActionHelper, Keymap, Layer, Macro, Module, SwitchLayerAction, UserConfiguration } from 'uhk-common';
|
||||
import {
|
||||
KeyAction,
|
||||
KeyActionHelper,
|
||||
Keymap,
|
||||
Layer,
|
||||
Macro,
|
||||
Module,
|
||||
NoneAction,
|
||||
PlayMacroAction,
|
||||
SwitchLayerAction,
|
||||
UserConfiguration
|
||||
} from 'uhk-common';
|
||||
import { KeymapActions, MacroActions } from '../actions';
|
||||
import { AppState } from '../index';
|
||||
import { ActionTypes } from '../actions/user-config';
|
||||
@@ -19,7 +30,12 @@ export function reducer(state = initialState, action: Action & { payload?: any }
|
||||
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);
|
||||
Object.assign(changedUserConfiguration, action.payload);
|
||||
changedUserConfiguration.keymaps = [...changedUserConfiguration.keymaps];
|
||||
changedUserConfiguration.keymaps.sort((first: Keymap, second: Keymap) => first.name.localeCompare(second.name));
|
||||
changedUserConfiguration.macros = [...changedUserConfiguration.macros];
|
||||
changedUserConfiguration.macros.sort((first: Macro, second: Macro) => first.name.localeCompare(second.name));
|
||||
return changedUserConfiguration;
|
||||
}
|
||||
|
||||
case KeymapActions.ADD:
|
||||
@@ -29,7 +45,7 @@ export function reducer(state = initialState, action: Action & { payload?: any }
|
||||
newKeymap.name = generateName(state.keymaps, newKeymap.name);
|
||||
newKeymap.isDefault = (state.keymaps.length === 0);
|
||||
|
||||
changedUserConfiguration.keymaps = state.keymaps.concat(newKeymap);
|
||||
changedUserConfiguration.keymaps = insertItemInNameOrder(state.keymaps, newKeymap);
|
||||
break;
|
||||
}
|
||||
case KeymapActions.EDIT_NAME: {
|
||||
@@ -38,20 +54,27 @@ export function reducer(state = initialState, action: Action & { payload?: any }
|
||||
}
|
||||
|
||||
const name: string = action.payload.name.trim();
|
||||
let keymapToRename: Keymap = null;
|
||||
|
||||
const duplicate = state.keymaps.some((keymap: Keymap) => {
|
||||
if (keymap.abbreviation === action.payload.abbr) {
|
||||
keymapToRename = keymap;
|
||||
}
|
||||
|
||||
return keymap.name === name && keymap.abbreviation !== action.payload.abbr;
|
||||
});
|
||||
|
||||
changedUserConfiguration.keymaps = state.keymaps.map((keymap: Keymap) => {
|
||||
keymap = Object.assign(new Keymap(), keymap);
|
||||
|
||||
if (!duplicate && keymap.abbreviation === action.payload.abbr) {
|
||||
keymap.name = name;
|
||||
if (duplicate) {
|
||||
break;
|
||||
}
|
||||
return keymap;
|
||||
});
|
||||
|
||||
const newKeymap = Object.assign(new Keymap(), keymapToRename, { name });
|
||||
|
||||
changedUserConfiguration.keymaps = insertItemInNameOrder(
|
||||
state.keymaps,
|
||||
newKeymap,
|
||||
keymap => keymap.abbreviation !== newKeymap.abbreviation
|
||||
);
|
||||
break;
|
||||
}
|
||||
case KeymapActions.EDIT_ABBR: {
|
||||
@@ -163,7 +186,7 @@ export function reducer(state = initialState, action: Action & { payload?: any }
|
||||
newMacro.isPrivate = true;
|
||||
newMacro.macroActions = [];
|
||||
|
||||
changedUserConfiguration.macros = state.macros.concat(newMacro);
|
||||
changedUserConfiguration.macros = insertItemInNameOrder(state.macros, newMacro);
|
||||
break;
|
||||
}
|
||||
case MacroActions.DUPLICATE: {
|
||||
@@ -171,7 +194,7 @@ export function reducer(state = initialState, action: Action & { payload?: any }
|
||||
newMacro.name = generateName(state.macros, newMacro.name);
|
||||
newMacro.id = generateMacroId(state.macros);
|
||||
|
||||
changedUserConfiguration.macros = state.macros.concat(newMacro);
|
||||
changedUserConfiguration.macros = insertItemInNameOrder(state.macros, newMacro);
|
||||
break;
|
||||
}
|
||||
case MacroActions.EDIT_NAME: {
|
||||
@@ -180,25 +203,52 @@ export function reducer(state = initialState, action: Action & { payload?: any }
|
||||
}
|
||||
|
||||
const name: string = action.payload.name.trim();
|
||||
let macroToRename: Macro = null;
|
||||
|
||||
const duplicate = state.macros.some((macro: Macro) => {
|
||||
if (macro.id === action.payload.id) {
|
||||
macroToRename = macro;
|
||||
}
|
||||
|
||||
return macro.id !== action.payload.id && macro.name === name;
|
||||
});
|
||||
|
||||
changedUserConfiguration.macros = state.macros.map((macro: Macro) => {
|
||||
macro = Object.assign(new Macro(), macro);
|
||||
if (!duplicate && macro.id === action.payload.id) {
|
||||
macro.name = name;
|
||||
}
|
||||
|
||||
return macro;
|
||||
});
|
||||
|
||||
if (duplicate) {
|
||||
break;
|
||||
}
|
||||
|
||||
const newMacro = Object.assign(new Macro(), macroToRename, { name });
|
||||
changedUserConfiguration.macros = insertItemInNameOrder(state.macros, newMacro, macro => macro.id !== newMacro.id);
|
||||
break;
|
||||
}
|
||||
|
||||
case MacroActions.REMOVE:
|
||||
changedUserConfiguration.macros = state.macros.filter((macro: Macro) => macro.id !== action.payload);
|
||||
const macroId = action.payload;
|
||||
changedUserConfiguration.macros = state.macros.filter((macro: Macro) => macro.id !== macroId);
|
||||
|
||||
for (let k = 0; k < changedUserConfiguration.keymaps.length; k++) {
|
||||
const keymap = changedUserConfiguration.keymaps[k];
|
||||
let hasChanges = false;
|
||||
|
||||
for (const layer of keymap.layers) {
|
||||
for (const module of layer.modules) {
|
||||
for (let ka = 0; ka < module.keyActions.length; ka++) {
|
||||
const keyAction = module.keyActions[ka];
|
||||
|
||||
if (keyAction instanceof PlayMacroAction && keyAction.macroId === macroId) {
|
||||
hasChanges = true;
|
||||
module.keyActions[ka] = new NoneAction();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (hasChanges) {
|
||||
changedUserConfiguration.keymaps[k] = new Keymap(keymap);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case MacroActions.ADD_ACTION:
|
||||
changedUserConfiguration.macros = state.macros.map((macro: Macro) => {
|
||||
if (macro.id === action.payload.id) {
|
||||
@@ -263,6 +313,18 @@ export function reducer(state = initialState, action: Action & { payload?: any }
|
||||
break;
|
||||
}
|
||||
|
||||
case KeymapActions.EDIT_DESCRIPTION: {
|
||||
const data = (action as KeymapActions.EditDescriptionAction).payload;
|
||||
|
||||
changedUserConfiguration.keymaps = state.keymaps.map(keymap => {
|
||||
if (keymap.abbreviation === data.abbr) {
|
||||
keymap.description = data.description;
|
||||
}
|
||||
return keymap;
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -360,6 +422,27 @@ function generateMacroId(macros: Macro[]) {
|
||||
return newId + 1;
|
||||
}
|
||||
|
||||
function insertItemInNameOrder<T extends { name: string }>(
|
||||
items: T[], newItem: T, keepItem: (item: T) => boolean = () => true
|
||||
): T[] {
|
||||
const newItems: T[] = [];
|
||||
let added = false;
|
||||
for (const item of items) {
|
||||
if (!added && item.name.localeCompare(newItem.name) > 0) {
|
||||
newItems.push(newItem);
|
||||
added = true;
|
||||
}
|
||||
if (keepItem(item)) {
|
||||
newItems.push(item);
|
||||
}
|
||||
}
|
||||
if (!added) {
|
||||
newItems.push(newItem);
|
||||
}
|
||||
|
||||
return newItems;
|
||||
}
|
||||
|
||||
function checkExistence(layers: Layer[], property: string, value: any): Layer[] {
|
||||
const keyActionsToClear: {
|
||||
layerIdx: number,
|
||||
|
||||
9
packages/uhk-web/src/app/util/find-new-item.ts
Normal file
9
packages/uhk-web/src/app/util/find-new-item.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
export function findNewItem<T>(oldItems: T[], newItems: T[]): T {
|
||||
for (let i = 0; i < oldItems.length; ++i) {
|
||||
if (oldItems[i] !== newItems[i]) {
|
||||
return newItems[i];
|
||||
}
|
||||
}
|
||||
|
||||
return newItems[newItems.length - 1];
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
export * from './find-new-item';
|
||||
export * from './html-helper';
|
||||
export * from './validators';
|
||||
export * from './version-helper';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { cloneDeep } from 'lodash';
|
||||
import cloneDeep from 'lodash-es/cloneDeep';
|
||||
|
||||
const defaultUserConfig = {
|
||||
userConfigMajorVersion: 3,
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
#!/usr/bin/env node
|
||||
const uhk = require('./uhk');
|
||||
|
||||
(async function() {
|
||||
const device = uhk.getUhkDevice();
|
||||
const sendData = new Buffer([uhk.usbCommands.applyConfig]);
|
||||
device.write(uhk.getTransferData(sendData));
|
||||
const response = Buffer.from(device.readSync());
|
||||
console.log(response);
|
||||
uhk.applyConfig(device);
|
||||
})();
|
||||
|
||||
@@ -9,23 +9,5 @@ if (eepromTransfer === undefined) {
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const device = uhk.getUhkDevice();
|
||||
device.write(uhk.getTransferData(new Buffer([uhk.usbCommands.launchEepromTransfer, eepromTransfer.operation, eepromTransfer.configBuffer])));
|
||||
const buffer = Buffer.from(device.readSync());
|
||||
const responseCode = buffer[0];
|
||||
if (responseCode !== 0) {
|
||||
console.error(`Write user config to eeprom failed. Response code: ${responseCode}`);
|
||||
process.exit(1);
|
||||
}
|
||||
// const buffer = await uhk.writeDevice(device, [uhk.usbCommands.launchEepromTransfer, eepromTransfer.operation, eepromTransfer.configBuffer]);
|
||||
|
||||
function waitUntilKeyboardBusy() {
|
||||
|
||||
device.write(uhk.getTransferData(new Buffer([uhk.usbCommands.getDeviceState])));
|
||||
const keyboardStateBuffer = Buffer.from(device.readSync());
|
||||
|
||||
if (keyboardStateBuffer[1] === 1) {
|
||||
setTimeout(waitUntilKeyboardBusy, 200);
|
||||
}
|
||||
}
|
||||
|
||||
waitUntilKeyboardBusy();
|
||||
|
||||
40
packages/usb/factory-update.js
Executable file
40
packages/usb/factory-update.js
Executable file
@@ -0,0 +1,40 @@
|
||||
#!/usr/bin/env node
|
||||
const fs = require('fs');
|
||||
const program = require('commander');
|
||||
const uhk = require('./uhk')
|
||||
require('shelljs/global');
|
||||
|
||||
(async function() {
|
||||
config.fatal = true;
|
||||
|
||||
program
|
||||
.usage(`firmwarePath`)
|
||||
.parse(process.argv);
|
||||
|
||||
if (program.args.length < 1) {
|
||||
console.error('No firmware path specified.');
|
||||
exit(1);
|
||||
}
|
||||
const firmwarePath = program.args[0];
|
||||
|
||||
if (program.args.length < 2) {
|
||||
console.error('No layout specified.');
|
||||
exit(1);
|
||||
}
|
||||
const layout = program.args[1];
|
||||
if (!['ansi', 'iso'].includes(layout)) {
|
||||
console.error('The specified layout is neither ansi nor iso.');
|
||||
exit(1);
|
||||
}
|
||||
const isIso = layout === 'iso';
|
||||
|
||||
config.verbose = true;
|
||||
await uhk.updateFirmwares(firmwarePath);
|
||||
const device = uhk.getUhkDevice();
|
||||
const configBuffer = fs.readFileSync(`${firmwarePath}/devices/uhk60-right/config.bin`);
|
||||
await uhk.writeUca(device, configBuffer);
|
||||
await uhk.writeHca(device, isIso);
|
||||
await uhk.switchKeymap(device, 'TES');
|
||||
console.log('All done!')
|
||||
config.verbose = false;
|
||||
})();
|
||||
@@ -13,7 +13,8 @@ function getUint16(buffer, offset) {
|
||||
let prevGeneric, prevBasic, prevMedia, prevSystem, prevMouse;
|
||||
function getDebugInfo() {
|
||||
|
||||
const payload = new Buffer([uhk.usbCommands.getDebugInfo]);
|
||||
const payload = new Buffer([uhk.usbCommands.getDebugBuffer]);
|
||||
console.log(payload)
|
||||
console.log('Sending ', uhk.bufferToString(payload));
|
||||
device.write(uhk.getTransferData(payload));
|
||||
const rxBuffer = Buffer.from(device.readSync());
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
#!/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));
|
||||
(async function() {
|
||||
let response = await uhk.writeDevice(device, [uhk.usbCommands.getDeviceProperty, uhk.devicePropertyIds.i2cBaudRate]);
|
||||
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')}`)
|
||||
let i2c0F = response[1].toString(2).padStart(8, '0');
|
||||
console.log(`requestedBaudRate:${requestedBaudRate} | actualBaudRate:${actualBaudRate} | I2C0_F:0b${i2c0F}`)
|
||||
})();
|
||||
|
||||
@@ -51,13 +51,13 @@ function convertMs(milliseconds) {
|
||||
|
||||
const device = uhk.getUhkDevice();
|
||||
|
||||
device.write(uhk.getTransferData(new Buffer(uhk.pushUint32([uhk.usbCommands.getDeviceProperty, uhk.devicePropertyIds.uptime]))));
|
||||
device.write(uhk.getTransferData(new Buffer([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]))));
|
||||
device.write(uhk.getTransferData(new Buffer([uhk.usbCommands.getDeviceProperty, uhk.devicePropertyIds.i2cBaudRate])));
|
||||
response = device.readSync();
|
||||
let requestedBaudRate = uhk.getUint32(response, 2);
|
||||
let actualBaudRate = uhk.getUint32(response, 6);
|
||||
|
||||
@@ -1,14 +1,7 @@
|
||||
#!/usr/bin/env node
|
||||
const uhk = require('./uhk');
|
||||
|
||||
(async function() {
|
||||
const device = uhk.getUhkDevice();
|
||||
|
||||
function getModuleState() {
|
||||
const payload = new Buffer([uhk.usbCommands.getModuleProperty, 1]);
|
||||
console.log('Sending ', uhk.bufferToString(payload));
|
||||
device.write(uhk.getTransferData(payload));
|
||||
const receivedBuffer = device.readSync();
|
||||
console.log('Received', uhk.bufferToString(receivedBuffer));
|
||||
setTimeout(getModuleState, 500)
|
||||
}
|
||||
|
||||
getModuleState();
|
||||
await uhk.getModuleProperty(device, 1, uhk.modulePropertyIds.protocolVersions);
|
||||
})();
|
||||
|
||||
@@ -15,7 +15,7 @@ function convertMs(milliseconds) {
|
||||
return {days, hours, minutes, seconds};
|
||||
}
|
||||
|
||||
let buffer = new Buffer(uhk.pushUint32([uhk.usbCommands.getDeviceProperty, uhk.devicePropertyIds.uptime]));
|
||||
let buffer = new Buffer([uhk.usbCommands.getDeviceProperty, uhk.devicePropertyIds.uptime]);
|
||||
//console.log(buffer);
|
||||
device.write(uhk.getTransferData(buffer));
|
||||
let response = device.readSync();
|
||||
|
||||
@@ -9,6 +9,7 @@ program
|
||||
const moduleSlot = program.args[0];
|
||||
const moduleSlotId = uhk.checkModuleSlot(moduleSlot, uhk.moduleSlotToId);
|
||||
const device = uhk.getUhkDevice();
|
||||
let transfer = new Buffer([uhk.usbCommands.jumpToModuleBootloader, moduleSlotId]);
|
||||
device.write(uhk.getTransferData(transfer));
|
||||
const response = Buffer.from(device.readSync());
|
||||
|
||||
(async function() {
|
||||
await uhk.jumpToModuleBootloader(device, moduleSlotId);
|
||||
})();
|
||||
|
||||
@@ -1,52 +1,10 @@
|
||||
#!/usr/bin/env node
|
||||
const HID = require('node-hid');
|
||||
let uhk = require('./uhk');
|
||||
var program = require('commander');
|
||||
|
||||
program
|
||||
.option('-dt, --polling-timeout <n>', 'Polling timeout (ms)')
|
||||
.option('-bt, --bootloader-timeout <n>', 'Bootloader timeout (ms)')
|
||||
.option('-f, --force', 'Force reenumeration')
|
||||
.parse(process.argv);
|
||||
|
||||
let pollingTimeoutMs = 10000;
|
||||
const pollingIntervalMs = 100;
|
||||
const bootloaderTimeoutMs = 5000;
|
||||
let jumped = false;
|
||||
const uhk = require('./uhk');
|
||||
const program = require('commander');
|
||||
|
||||
program.parse(process.argv);
|
||||
const enumerationMode = program.args[0];
|
||||
const enumerationModeId = uhk.enumerationModes[enumerationMode];
|
||||
|
||||
if (enumerationModeId === undefined) {
|
||||
const enumerationModes = Object.keys(uhk.enumerationModes).join(', ');
|
||||
console.log(`Invalid enumeration mode '${enumerationMode}' is not one of: ${enumerationModes}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log(`Trying to reenumerate as ${enumerationMode}...`);
|
||||
setInterval(() => {
|
||||
pollingTimeoutMs -= pollingIntervalMs;
|
||||
|
||||
const foundDevice = HID.devices().find(device =>
|
||||
device.vendorId === uhk.vendorId && device.productId === uhk.enumerationModeIdToProductId[enumerationModeId]);
|
||||
|
||||
if (foundDevice) {
|
||||
console.log(`${enumerationMode} is up`);
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
if (pollingTimeoutMs <= 0) {
|
||||
console.log(`Couldn't reenumerate as ${enumerationMode}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
let device = uhk.getUhkDevice();
|
||||
if (device && !jumped) {
|
||||
console.log(`UHK found, reenumerating as ${enumerationMode}`);
|
||||
let t = bootloaderTimeoutMs;
|
||||
let message = new Buffer([uhk.usbCommands.reenumerate, enumerationModeId, t&0xff, (t&0xff<<8)>>8, (t&0xff<<16)>>16, (t&0xff<<24)>>24]);
|
||||
device.write(uhk.getTransferData(message));
|
||||
jumped = true;
|
||||
}
|
||||
|
||||
}, pollingIntervalMs);
|
||||
(async function() {
|
||||
await uhk.reenumerate(enumerationMode);
|
||||
})();
|
||||
|
||||
@@ -33,6 +33,6 @@ if (kbootCommand !== 'idle') {
|
||||
}
|
||||
|
||||
const device = uhk.getUhkDevice();
|
||||
let transfer = new Buffer([uhk.usbCommands.sendKbootCommandToModule, kbootCommandId, parseInt(i2cAddress)]);
|
||||
device.write(uhk.getTransferData(transfer));
|
||||
const response = Buffer.from(device.readSync());
|
||||
(async function() {
|
||||
await uhk.sendKbootCommandToModule(device, kbootCommandId, i2cAddress);
|
||||
})();
|
||||
|
||||
@@ -17,8 +17,7 @@ You're free to use any value in between and test the results.`);
|
||||
}
|
||||
|
||||
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));
|
||||
|
||||
(async function() {
|
||||
await uhk.writeDevice(device, [uhk.usbCommands.setI2cBaudRate, ...uhk.uint32ToArray(+bps)]);
|
||||
})();
|
||||
|
||||
@@ -1,65 +0,0 @@
|
||||
require('shelljs/global');
|
||||
|
||||
function checkFirmwareImage(imagePath, extension) {
|
||||
if (!imagePath) {
|
||||
echo('No firmware image specified.');
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (!imagePath.endsWith(extension)) {
|
||||
echo(`Firmware image extension is not ${extension}`);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (!test('-f', imagePath)) {
|
||||
echo('Firmware image does not exist.');
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
function getBlhostCmd(pid) {
|
||||
let blhostPath;
|
||||
switch (process.platform) {
|
||||
case 'linux':
|
||||
const arch = exec('uname -m', {silent:true}).stdout.trim();
|
||||
blhostPath = `linux/${arch}/blhost`;
|
||||
break;
|
||||
case 'darwin':
|
||||
blhostPath = 'mac/blhost';
|
||||
break;
|
||||
case 'win32':
|
||||
blhostPath = 'win/blhost.exe';
|
||||
break;
|
||||
default:
|
||||
echo('Your operating system is not supported.');
|
||||
exit(1);
|
||||
break;
|
||||
}
|
||||
|
||||
return `${__dirname}/blhost/${blhostPath} --usb 0x1d50,0x${pid.toString(16)}`;
|
||||
}
|
||||
|
||||
function execRetry(command) {
|
||||
let firstRun = true;
|
||||
let remainingRetries = 3;
|
||||
let code;
|
||||
do {
|
||||
if (!firstRun) {
|
||||
console.log(`Retrying ${command}`)
|
||||
}
|
||||
config.fatal = !remainingRetries;
|
||||
code = exec(command).code;
|
||||
config.fatal = true;
|
||||
firstRun = false;
|
||||
} while(code && --remainingRetries);
|
||||
}
|
||||
|
||||
const exp = {
|
||||
checkFirmwareImage,
|
||||
getBlhostCmd,
|
||||
execRetry,
|
||||
}
|
||||
|
||||
Object.keys(exp).forEach(function (cmd) {
|
||||
global[cmd] = exp[cmd];
|
||||
});
|
||||
15
packages/usb/switch-keymap.js
Executable file
15
packages/usb/switch-keymap.js
Executable file
@@ -0,0 +1,15 @@
|
||||
#!/usr/bin/env node
|
||||
const uhk = require('./uhk');
|
||||
|
||||
(async function() {
|
||||
const keymapAbbreviation = process.argv[2];
|
||||
|
||||
if (keymapAbbreviation === undefined) {
|
||||
console.log('Usage: switch-keymap.js keymapName');
|
||||
return;
|
||||
}
|
||||
|
||||
const device = uhk.getUhkDevice();
|
||||
const sendData = await uhk.switchKeymap(device, keymapAbbreviation);
|
||||
console.log(sendData)
|
||||
})();
|
||||
@@ -1,6 +1,24 @@
|
||||
const util = require('util');
|
||||
const HID = require('node-hid');
|
||||
// const debug = process.env.DEBUG;
|
||||
const debug = true;
|
||||
const {HardwareConfiguration, UhkBuffer} = require('uhk-common');
|
||||
const {getTransferBuffers, ConfigBufferId, UhkHidDevice, UsbCommand} = require('uhk-usb');
|
||||
const Logger = require('./logger');
|
||||
const debug = process.env.DEBUG;
|
||||
|
||||
function sleep(ms) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
const kbootCommandIdToName = {
|
||||
0: 'idle',
|
||||
1: 'ping',
|
||||
2: 'reset',
|
||||
};
|
||||
|
||||
const eepromOperationIdToName = {
|
||||
0: 'read',
|
||||
1: 'write',
|
||||
}
|
||||
|
||||
function bufferToString(buffer) {
|
||||
let str = '';
|
||||
@@ -22,12 +40,32 @@ 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 uint32ToArray(value) {
|
||||
return [(value >> 0) & 0xff, (value >> 8) & 0xff, (value >> 16) & 0xff, (value >> 24) & 0xff];
|
||||
}
|
||||
|
||||
function writeDevice(device, data, options={}) {
|
||||
const dataBuffer = new Buffer(data);
|
||||
if (!options.noDebug) {
|
||||
writeLog('W: ', dataBuffer);
|
||||
}
|
||||
device.write(getTransferData(dataBuffer));
|
||||
if (options.noRead) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
device.read((err, data) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
if (!options.noDebug) {
|
||||
writeLog('R: ', data);
|
||||
}
|
||||
resolve(data);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function getUhkDevice() {
|
||||
@@ -60,26 +98,332 @@ function getBootloaderDevice() {
|
||||
return foundDevice;
|
||||
}
|
||||
|
||||
let configBufferIds = {
|
||||
function checkFirmwareImage(imagePath, extension) {
|
||||
if (!imagePath) {
|
||||
echo('No firmware image specified.');
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (!imagePath.endsWith(extension)) {
|
||||
echo(`Firmware image extension is not ${extension}`);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (!test('-f', imagePath)) {
|
||||
echo('Firmware image does not exist.');
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
function getBlhostCmd(pid) {
|
||||
let blhostPath;
|
||||
switch (process.platform) {
|
||||
case 'linux':
|
||||
const arch = exec('uname -m', {silent:true}).stdout.trim();
|
||||
blhostPath = `linux/${arch}/blhost`;
|
||||
break;
|
||||
case 'darwin':
|
||||
blhostPath = 'mac/blhost';
|
||||
break;
|
||||
case 'win32':
|
||||
blhostPath = 'win/blhost.exe';
|
||||
break;
|
||||
default:
|
||||
echo('Your operating system is not supported.');
|
||||
exit(1);
|
||||
break;
|
||||
}
|
||||
|
||||
return `${__dirname}/blhost/${blhostPath} --usb 0x1d50,0x${pid.toString(16)}`;
|
||||
}
|
||||
|
||||
function execRetry(command) {
|
||||
let firstRun = true;
|
||||
let remainingRetries = 3;
|
||||
let code;
|
||||
do {
|
||||
if (!firstRun) {
|
||||
console.log(`Retrying ${command}`)
|
||||
}
|
||||
config.fatal = !remainingRetries;
|
||||
code = exec(command).code;
|
||||
config.fatal = true;
|
||||
firstRun = false;
|
||||
} while(code && --remainingRetries);
|
||||
}
|
||||
|
||||
const configBufferIds = {
|
||||
hardwareConfig: 0,
|
||||
stagingUserConfig: 1,
|
||||
validatedUserConfig: 2,
|
||||
};
|
||||
|
||||
const configBufferIdToName = {
|
||||
0: 'hardwareConfig',
|
||||
1: 'stagingUserConfig',
|
||||
2: 'validatedUserConfig',
|
||||
}
|
||||
|
||||
let eepromOperations = {
|
||||
read: 0,
|
||||
write: 1,
|
||||
};
|
||||
|
||||
exports = module.exports = moduleExports = {
|
||||
async function updateDeviceFirmware(firmwareImage, extension) {
|
||||
const usbDir = `${__dirname}`;
|
||||
const blhost = uhk.getBlhostCmd(uhk.enumerationNameToProductId.bootloader);
|
||||
|
||||
uhk.checkFirmwareImage(firmwareImage, extension);
|
||||
config.verbose = true;
|
||||
|
||||
await uhk.reenumerate('bootloader');
|
||||
exec(`${blhost} flash-security-disable 0403020108070605`);
|
||||
exec(`${blhost} flash-erase-region 0xc000 475136`);
|
||||
exec(`${blhost} flash-image ${firmwareImage}`);
|
||||
exec(`${blhost} reset`);
|
||||
|
||||
config.verbose = false;
|
||||
echo('Firmware updated successfully.');
|
||||
};
|
||||
|
||||
// USB commands
|
||||
|
||||
function reenumerate(enumerationMode) {
|
||||
const bootloaderTimeoutMs = 5000;
|
||||
const pollingIntervalMs = 100;
|
||||
let pollingTimeoutMs = 10000;
|
||||
|
||||
let jumped = false;
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const enumerationModeId = exports.enumerationModes[enumerationMode];
|
||||
|
||||
if (enumerationModeId === undefined) {
|
||||
const enumerationModes = Object.keys(exports.enumerationModes).join(', ');
|
||||
console.log(`Invalid enumeration mode '${enumerationMode}' is not one of: ${enumerationModes}`);
|
||||
reject();
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`Trying to reenumerate as ${enumerationMode}...`);
|
||||
const intervalId = setInterval(async function() {
|
||||
pollingTimeoutMs -= pollingIntervalMs;
|
||||
|
||||
const foundDevice = HID.devices().find(device =>
|
||||
device.vendorId === exports.vendorId && device.productId === exports.enumerationModeIdToProductId[enumerationModeId]);
|
||||
|
||||
if (foundDevice) {
|
||||
console.log(`${enumerationMode} is up`);
|
||||
resolve();
|
||||
clearInterval(intervalId);
|
||||
return;
|
||||
}
|
||||
|
||||
if (pollingTimeoutMs <= 0) {
|
||||
console.log(`Couldn't reenumerate as ${enumerationMode}`);
|
||||
reject();
|
||||
clearInterval(intervalId);
|
||||
return;
|
||||
}
|
||||
|
||||
let device = exports.getUhkDevice();
|
||||
if (device && !jumped) {
|
||||
console.log(`UHK found, reenumerating as ${enumerationMode}`);
|
||||
await writeDevice(device, [exports.usbCommands.reenumerate, enumerationModeId, ...uint32ToArray(bootloaderTimeoutMs)], {noRead:true});
|
||||
jumped = true;
|
||||
}
|
||||
|
||||
}, pollingIntervalMs);
|
||||
})
|
||||
};
|
||||
|
||||
async function sendKbootCommandToModule(device, kbootCommandId, i2cAddress) {
|
||||
writeLog(`T: sendKbootCommandToModule kbootCommandId:${kbootCommandIdToName[kbootCommandId]} i2cAddress:${i2cAddress}`);
|
||||
return await uhk.writeDevice(device, [uhk.usbCommands.sendKbootCommandToModule, kbootCommandId, parseInt(i2cAddress)])
|
||||
};
|
||||
|
||||
async function jumpToModuleBootloader(device, moduleSlotId) {
|
||||
writeLog(`T: jumpToModuleBootloader moduleSlotId:${moduleSlotId}`);
|
||||
await uhk.writeDevice(device, [uhk.usbCommands.jumpToModuleBootloader, moduleSlotId]);
|
||||
};
|
||||
|
||||
async function switchKeymap(device, keymapAbbreviation) {
|
||||
writeLog(`T: switchKeymap keymapAbbreviation:${keymapAbbreviation}`);
|
||||
const keymapAbbreviationAscii = keymapAbbreviation.split('').map(char => char.charCodeAt(0));
|
||||
const payload = [uhk.usbCommands.switchKeymap, keymapAbbreviation.length, ...keymapAbbreviationAscii];
|
||||
return await uhk.writeDevice(device, payload);
|
||||
}
|
||||
|
||||
async function waitForKbootIdle(device) {
|
||||
writeLog(`T: waitForKbootIdle`);
|
||||
const intervalMs = 100;
|
||||
const pingMessageInterval = 500;
|
||||
let timeoutMs = 10000;
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const intervalId = setInterval(async function() {
|
||||
const response = await uhk.writeDevice(device, [uhk.usbCommands.getDeviceProperty, uhk.devicePropertyIds.currentKbootCommand]);
|
||||
const currentKbootCommand = response[1];
|
||||
if (currentKbootCommand == 0) {
|
||||
console.log('Bootloader pinged.');
|
||||
resolve();
|
||||
clearInterval(intervalId);
|
||||
return;
|
||||
} else if (timeoutMs % pingMessageInterval === 0) {
|
||||
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.");
|
||||
};
|
||||
|
||||
timeoutMs -= intervalMs;
|
||||
|
||||
if (timeoutMs < 0) {
|
||||
reject();
|
||||
clearInterval(intervalId);
|
||||
return;
|
||||
}
|
||||
}, intervalMs);
|
||||
});
|
||||
}
|
||||
|
||||
async function updateModuleFirmware(i2cAddress, moduleSlotId, firmwareImage) {
|
||||
const usbDir = `${__dirname}`;
|
||||
const blhostUsb = uhk.getBlhostCmd(uhk.enumerationNameToProductId.buspal);
|
||||
const blhostBuspal = `${blhostUsb} --buspal i2c,${i2cAddress}`;
|
||||
|
||||
config.verbose = true;
|
||||
let device = uhk.getUhkDevice();
|
||||
await uhk.sendKbootCommandToModule(device, uhk.kbootCommands.ping, i2cAddress);
|
||||
await uhk.jumpToModuleBootloader(device, moduleSlotId);
|
||||
await uhk.waitForKbootIdle(device);
|
||||
device.close();
|
||||
|
||||
await uhk.reenumerate('buspal');
|
||||
uhk.execRetry(`${blhostBuspal} get-property 1`);
|
||||
exec(`${blhostBuspal} flash-erase-all-unsecure`);
|
||||
exec(`${blhostBuspal} write-memory 0x0 ${firmwareImage}`);
|
||||
exec(`${blhostUsb} reset`);
|
||||
|
||||
await uhk.reenumerate('normalKeyboard');
|
||||
device = uhk.getUhkDevice();
|
||||
await uhk.sendKbootCommandToModule(device, uhk.kbootCommands.reset, i2cAddress);
|
||||
await sleep(1000);
|
||||
await uhk.sendKbootCommandToModule(device, uhk.kbootCommands.idle, i2cAddress);
|
||||
device.close();
|
||||
config.verbose = false;
|
||||
echo('Firmware updated successfully.');
|
||||
};
|
||||
|
||||
async function updateFirmwares(firmwarePath) {
|
||||
console.log('Updating right firmware');
|
||||
await uhk.updateDeviceFirmware(`${firmwarePath}/devices/uhk60-right/firmware.hex`, 'hex');
|
||||
await uhk.reenumerate('normalKeyboard');
|
||||
console.log('Updating left firmware');
|
||||
await uhk.updateModuleFirmware(uhk.moduleSlotToI2cAddress.leftHalf, uhk.moduleSlotToId.leftHalf, `${firmwarePath}/modules/uhk60-left.bin`);
|
||||
}
|
||||
|
||||
async function writeConfig(device, configBuffer, isHardwareConfig) {
|
||||
writeLog(`T: writeConfig isHardwareConfig:${isHardwareConfig}`);
|
||||
const chunkSize = 60;
|
||||
let offset = 0;
|
||||
let chunkSizeToRead;
|
||||
let buffer = await uhk.writeDevice(device, [uhk.usbCommands.getDeviceProperty, uhk.devicePropertyIds.configSizes]);
|
||||
const hardwareConfigMaxSize = getUint16(buffer, 1);
|
||||
const userConfigMaxSize = getUint16(buffer, 3);
|
||||
const configMaxSize = isHardwareConfig ? hardwareConfigMaxSize : userConfigMaxSize;
|
||||
const configSize = Math.min(configMaxSize, configBuffer.length);
|
||||
|
||||
writeLog('WR: ...');
|
||||
while (offset < configSize) {
|
||||
const usbCommand = isHardwareConfig ? uhk.usbCommands.writeHardwareConfig : uhk.usbCommands.writeStagingUserConfig;
|
||||
chunkSizeToRead = Math.min(chunkSize, configSize - offset);
|
||||
|
||||
if (chunkSizeToRead === 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
buffer = [
|
||||
usbCommand, chunkSizeToRead, offset & 0xff, offset >> 8,
|
||||
...configBuffer.slice(offset, offset+chunkSizeToRead)
|
||||
];
|
||||
await uhk.writeDevice(device, buffer, {noDebug:true})
|
||||
offset += chunkSizeToRead;
|
||||
}
|
||||
}
|
||||
|
||||
async function applyConfig(device) {
|
||||
writeLog(`T: applyConfig`);
|
||||
await uhk.writeDevice(device, [uhk.usbCommands.applyConfig]);
|
||||
}
|
||||
|
||||
async function launchEepromTransfer(device, operation, configBufferId) {
|
||||
writeLog(`T: launchEepromTransfer operation:${eepromOperationIdToName[operation]}`);
|
||||
const buffer = await uhk.writeDevice(device, [uhk.usbCommands.launchEepromTransfer, operation, configBufferId]);
|
||||
isBusy = true;
|
||||
writeLog(`T: getDeviceState`);
|
||||
do {
|
||||
const buffer = await uhk.writeDevice(device, [uhk.usbCommands.getDeviceState]);
|
||||
isBusy = buffer[1] === 1;
|
||||
} while (isBusy);
|
||||
};
|
||||
|
||||
async function writeUca(device, configBuffer) {
|
||||
await uhk.writeConfig(device, configBuffer, false);
|
||||
await uhk.applyConfig(device);
|
||||
await uhk.launchEepromTransfer(device, uhk.eepromOperations.write, configBufferIds.validatedUserConfig);
|
||||
}
|
||||
|
||||
async function writeHca(device, isIso) {
|
||||
const hardwareConfig = new HardwareConfiguration();
|
||||
|
||||
hardwareConfig.signature = 'UHK';
|
||||
hardwareConfig.majorVersion = 1;
|
||||
hardwareConfig.minorVersion = 0;
|
||||
hardwareConfig.patchVersion = 0;
|
||||
hardwareConfig.brandId = 0;
|
||||
hardwareConfig.deviceId = 1;
|
||||
hardwareConfig.uniqueId = Math.floor(2**32 * Math.random());
|
||||
hardwareConfig.isVendorModeOn = false;
|
||||
hardwareConfig.isIso = isIso;
|
||||
|
||||
const logger = new Logger();
|
||||
const hardwareBuffer = new UhkBuffer();
|
||||
hardwareConfig.toBinary(hardwareBuffer);
|
||||
const buffer = hardwareBuffer.getBufferContent();
|
||||
|
||||
await uhk.writeConfig(device, buffer, true);
|
||||
await uhk.launchEepromTransfer(device, uhk.eepromOperations.write, configBufferIds.hardwareConfig);
|
||||
}
|
||||
|
||||
async function getModuleProperty(device, slotId, moduleProperty) {
|
||||
await writeDevice(device, [uhk.usbCommands.getModuleProperty, slotId, moduleProperty]);
|
||||
}
|
||||
|
||||
uhk = exports = module.exports = moduleExports = {
|
||||
bufferToString,
|
||||
getUint16,
|
||||
getUint32,
|
||||
pushUint32,
|
||||
uint32ToArray,
|
||||
writeDevice,
|
||||
getUhkDevice,
|
||||
getBootloaderDevice,
|
||||
getTransferData,
|
||||
checkModuleSlot,
|
||||
checkFirmwareImage,
|
||||
getBlhostCmd,
|
||||
execRetry,
|
||||
updateDeviceFirmware,
|
||||
reenumerate,
|
||||
sendKbootCommandToModule,
|
||||
jumpToModuleBootloader,
|
||||
switchKeymap,
|
||||
waitForKbootIdle,
|
||||
updateModuleFirmware,
|
||||
updateFirmwares,
|
||||
writeConfig,
|
||||
applyConfig,
|
||||
launchEepromTransfer,
|
||||
writeUca,
|
||||
writeHca,
|
||||
getModuleProperty,
|
||||
usbCommands: {
|
||||
getDeviceProperty : 0x00,
|
||||
reenumerate : 0x01,
|
||||
@@ -98,6 +442,7 @@ exports = module.exports = moduleExports = {
|
||||
getModuleProperty : 0x0e,
|
||||
getSlaveI2cErrors : 0x0f,
|
||||
setI2cBaudRate : 0x10,
|
||||
switchKeymap : 0x11,
|
||||
},
|
||||
enumerationModes: {
|
||||
bootloader: 0,
|
||||
@@ -131,24 +476,6 @@ exports = module.exports = moduleExports = {
|
||||
},
|
||||
configBufferIds,
|
||||
eepromOperations,
|
||||
eepromTransfer: {
|
||||
readHardwareConfig: {
|
||||
operation: eepromOperations.read,
|
||||
configBuffer: configBufferIds.hardwareConfig,
|
||||
},
|
||||
writeHardwareConfig: {
|
||||
operation: eepromOperations.write,
|
||||
configBuffer:configBufferIds.hardwareConfig,
|
||||
},
|
||||
readUserConfig: {
|
||||
operation: eepromOperations.read,
|
||||
configBuffer: configBufferIds.validatedUserConfig,
|
||||
},
|
||||
writeUserConfig: {
|
||||
operation: eepromOperations.write,
|
||||
configBuffer: configBufferIds.validatedUserConfig,
|
||||
},
|
||||
},
|
||||
kbootCommands: {
|
||||
idle: 0,
|
||||
ping: 1,
|
||||
@@ -175,20 +502,11 @@ function convertBufferToIntArray(buffer) {
|
||||
}
|
||||
|
||||
function getTransferData(buffer) {
|
||||
const data = convertBufferToIntArray(buffer)
|
||||
// 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
|
||||
if (data.length > 0 && data[0] === 0) {
|
||||
// data.unshift(0) // TODO: This has been commented out because it causes bugs on Linux and Mac. Gotta test it on Windows and fully remove it if possible.
|
||||
}
|
||||
|
||||
// From HID API documentation:
|
||||
// http://www.signal11.us/oss/hidapi/hidapi/doxygen/html/group__API.html#gad14ea48e440cf5066df87cc6488493af
|
||||
// The first byte of data[] must contain the Report ID.
|
||||
// For devices which only support a single report, this must be set to 0x0.
|
||||
data.unshift(0)
|
||||
|
||||
return data
|
||||
return [0, ...convertBufferToIntArray(buffer)];
|
||||
}
|
||||
|
||||
function readLog(buffer) {
|
||||
@@ -203,7 +521,11 @@ function writeLog(prefix, buffer) {
|
||||
if (!debug) {
|
||||
return;
|
||||
}
|
||||
if (buffer) {
|
||||
console.log(prefix + bufferToString(buffer))
|
||||
} else {
|
||||
console.log(prefix);
|
||||
}
|
||||
}
|
||||
|
||||
function checkModuleSlot(moduleSlot, mapping) {
|
||||
|
||||
@@ -2,27 +2,13 @@
|
||||
const uhk = require('./uhk');
|
||||
const program = require('commander');
|
||||
require('shelljs/global');
|
||||
require('./shared')
|
||||
|
||||
const extension = '.hex';
|
||||
config.fatal = true;
|
||||
const extension = '.hex';
|
||||
|
||||
program
|
||||
.usage(`firmwareImage${extension}`)
|
||||
.parse(process.argv)
|
||||
|
||||
const firmwareImage = program.args[0];
|
||||
const usbDir = `${__dirname}`;
|
||||
const blhost = getBlhostCmd(uhk.enumerationNameToProductId.bootloader);
|
||||
|
||||
checkFirmwareImage(firmwareImage, extension);
|
||||
|
||||
config.verbose = true;
|
||||
exec(`${usbDir}/reenumerate.js bootloader`);
|
||||
exec(`${blhost} flash-security-disable 0403020108070605`);
|
||||
exec(`${blhost} flash-erase-region 0xc000 475136`);
|
||||
exec(`${blhost} flash-image ${firmwareImage}`);
|
||||
exec(`${blhost} reset`);
|
||||
config.verbose = false;
|
||||
|
||||
echo('Firmware updated successfully.');
|
||||
uhk.updateDeviceFirmware(firmwareImage, extension);
|
||||
|
||||
@@ -18,7 +18,7 @@ const firmwarePath = program.args[0];
|
||||
const layout = program.args[1];
|
||||
|
||||
config.verbose = true;
|
||||
exec(`${__dirname}/update-all-firmwares.js --overwrite-user-config ${firmwarePath}`);
|
||||
exec(`${__dirname}/update-firmwares.js --overwrite-user-config ${firmwarePath}`);
|
||||
exec(`${__dirname}/write-hca.js ${layout}`);
|
||||
config.verbose = false;
|
||||
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
#!/usr/bin/env node
|
||||
const fs = require('fs');
|
||||
const program = require('commander');
|
||||
const tmp = require('tmp');
|
||||
const decompress = require('decompress');
|
||||
const decompressTarbz = require('decompress-tarbz2');
|
||||
const uhk = require('./uhk')
|
||||
require('shelljs/global');
|
||||
|
||||
(async function() {
|
||||
@@ -28,14 +30,14 @@ require('shelljs/global');
|
||||
firmwarePath = tmpObj.name;
|
||||
}
|
||||
config.verbose = true;
|
||||
exec(`${__dirname}/update-device-firmware.js ${firmwarePath}/devices/uhk60-right/firmware.hex`);
|
||||
exec(`${__dirname}/reenumerate.js normalKeyboard`);
|
||||
exec(`${__dirname}/update-module-firmware.js leftHalf ${firmwarePath}/modules/uhk60-left.bin`);
|
||||
await uhk.updateFirmwares(firmwarePath);
|
||||
|
||||
if (program.overwriteUserConfig) {
|
||||
exec(`${__dirname}/write-config.js ${firmwarePath}/devices/uhk60-right/config.bin`);
|
||||
exec(`${__dirname}/apply-config.js`);
|
||||
exec(`${__dirname}/eeprom.js writeUserConfig`);
|
||||
const device = uhk.getUhkDevice();
|
||||
const configBuffer = fs.readFileSync(`${firmwarePath}/devices/uhk60-right/config.bin`);
|
||||
await uhk.writeConfig(device, configBuffer, false);
|
||||
await uhk.applyConfig(device);
|
||||
await uhk.launchEepromTransfer(device, uhk.eepromOperations.write, uhk.eepromTransfer.writeUserConfig);
|
||||
}
|
||||
|
||||
config.verbose = false;
|
||||
@@ -2,7 +2,6 @@
|
||||
const uhk = require('./uhk');
|
||||
const program = require('commander');
|
||||
require('shelljs/global');
|
||||
require('./shared');
|
||||
|
||||
const extension = '.bin';
|
||||
config.fatal = true;
|
||||
@@ -12,27 +11,12 @@ program
|
||||
.parse(process.argv)
|
||||
|
||||
let moduleSlot = program.args[0];
|
||||
const moduleSlotId = uhk.checkModuleSlot(moduleSlot, uhk.moduleSlotToId);
|
||||
const i2cAddress = uhk.checkModuleSlot(moduleSlot, uhk.moduleSlotToI2cAddress);
|
||||
|
||||
const firmwareImage = program.args[1];
|
||||
checkFirmwareImage(firmwareImage, extension);
|
||||
uhk.checkFirmwareImage(firmwareImage, extension);
|
||||
|
||||
const usbDir = `${__dirname}`;
|
||||
const blhostUsb = getBlhostCmd(uhk.enumerationNameToProductId.buspal);
|
||||
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`);
|
||||
exec(`${blhostBuspal} write-memory 0x0 ${firmwareImage}`);
|
||||
exec(`${blhostUsb} reset`);
|
||||
exec(`${usbDir}/reenumerate.js normalKeyboard`);
|
||||
execRetry(`${usbDir}/send-kboot-command-to-module.js reset ${moduleSlot}`);
|
||||
exec(`${usbDir}/send-kboot-command-to-module.js idle`);
|
||||
config.verbose = false;
|
||||
|
||||
echo('Firmware updated successfully.');
|
||||
(async function() {
|
||||
await uhk.updateModuleFirmware(i2cAddress, moduleSlotId, firmwareImage);
|
||||
})();
|
||||
|
||||
@@ -1,22 +1,7 @@
|
||||
#!/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);
|
||||
(async function() {
|
||||
await uhk.waitForKbootIdle(device);
|
||||
})();
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
const program = require('commander');
|
||||
const fs = require('fs');
|
||||
const uhk = require('./uhk');
|
||||
|
||||
(async function() {
|
||||
const device = uhk.getUhkDevice();
|
||||
require('shelljs/global');
|
||||
|
||||
@@ -16,36 +18,9 @@ if (program.args.length == 0) {
|
||||
}
|
||||
|
||||
const configBin = program.args[0];
|
||||
const chunkSize = 60;
|
||||
const isHardwareConfig = program.hardwareConfig;
|
||||
const configTypeString = isHardwareConfig ? 'hardware' : 'user';
|
||||
let offset = 0;
|
||||
let configBuffer = fs.readFileSync(configBin);
|
||||
let chunkSizeToRead;
|
||||
const configBuffer = fs.readFileSync(configBin);
|
||||
|
||||
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);
|
||||
const userConfigMaxSize = buffer[3] + (buffer[4]<<8);
|
||||
const configMaxSize = isHardwareConfig ? hardwareConfigMaxSize : userConfigMaxSize;
|
||||
const configSize = Math.min(configMaxSize, configBuffer.length);
|
||||
|
||||
while (offset < configSize) {
|
||||
const usbCommand = isHardwareConfig ? uhk.usbCommands.writeHardwareConfig : uhk.usbCommands.writeStagingUserConfig;
|
||||
chunkSizeToRead = Math.min(chunkSize, configSize - offset);
|
||||
|
||||
if (chunkSizeToRead === 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
buffer = Buffer.concat([
|
||||
new Buffer([usbCommand, chunkSizeToRead, offset & 0xff, offset >> 8]),
|
||||
configBuffer.slice(offset, offset+chunkSizeToRead)
|
||||
]);
|
||||
|
||||
device.write(uhk.getTransferData(buffer));
|
||||
device.readSync();
|
||||
offset += chunkSizeToRead;
|
||||
}
|
||||
await uhk.writeUserConfig(device, configBuffer, isHardwareConfig);
|
||||
})();
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
#!/usr/bin/env node
|
||||
const {HardwareConfiguration, UhkBuffer} = require('uhk-common');
|
||||
const {EepromTransfer, getTransferBuffers, ConfigBufferId, UhkHidDevice, UsbCommand} = require('uhk-usb');
|
||||
const Logger = require('./logger');
|
||||
const uhk = require('./uhk');
|
||||
|
||||
if (process.argv.length < 2) {
|
||||
console.log(`use: write-hca {iso|ansi}`);
|
||||
@@ -14,36 +12,7 @@ if (layout !== 'iso' && layout !== 'ansi') {
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const hardwareConfig = new HardwareConfiguration();
|
||||
|
||||
hardwareConfig.signature = 'UHK';
|
||||
hardwareConfig.majorVersion = 1;
|
||||
hardwareConfig.minorVersion = 0;
|
||||
hardwareConfig.patchVersion = 0;
|
||||
hardwareConfig.brandId = 0;
|
||||
hardwareConfig.deviceId = 1;
|
||||
hardwareConfig.uniqueId = Math.floor(2**32 * Math.random());
|
||||
hardwareConfig.isVendorModeOn = false;
|
||||
hardwareConfig.isIso = layout === 'iso';
|
||||
|
||||
const logger = new Logger();
|
||||
|
||||
async function writeHca() {
|
||||
const device = new UhkHidDevice(logger);
|
||||
const hardwareBuffer = new UhkBuffer();
|
||||
hardwareConfig.toBinary(hardwareBuffer);
|
||||
const buffer = hardwareBuffer.getBufferContent();
|
||||
const fragments = getTransferBuffers(UsbCommand.WriteHardwareConfig, buffer);
|
||||
logger.debug('USB[T]: Write hardware configuration to keyboard');
|
||||
for (const fragment of fragments) {
|
||||
await device.write(fragment);
|
||||
}
|
||||
|
||||
logger.debug('USB[T]: Write hardware configuration to EEPROM');
|
||||
await device.writeConfigToEeprom(ConfigBufferId.hardwareConfig);
|
||||
}
|
||||
|
||||
writeHca()
|
||||
uhk.writeHca(layout === 'iso')
|
||||
.catch((err)=>{
|
||||
console.error(err);
|
||||
});
|
||||
|
||||
2
scripts/certs/.gitignore
vendored
Normal file
2
scripts/certs/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
windows-cert.p12
|
||||
mac-cert.p12
|
||||
BIN
scripts/certs/mac-cert.p12.enc
Normal file
BIN
scripts/certs/mac-cert.p12.enc
Normal file
Binary file not shown.
BIN
scripts/certs/windows-cert.p12.enc
Normal file
BIN
scripts/certs/windows-cert.p12.enc
Normal file
Binary file not shown.
@@ -42,13 +42,13 @@ if (!isReleaseCommit) {
|
||||
process.exit(0)
|
||||
}
|
||||
|
||||
if (process.platform === 'darwin' && !RUNNING_IN_DEV_MODE) {
|
||||
exec('brew install yarn --without-node');
|
||||
}
|
||||
// if (process.platform === 'darwin' && !RUNNING_IN_DEV_MODE) {
|
||||
// exec('brew install yarn --without-node');
|
||||
// }
|
||||
|
||||
if (!RUNNING_IN_DEV_MODE) {
|
||||
exec("yarn add electron-builder");
|
||||
}
|
||||
// if (!RUNNING_IN_DEV_MODE) {
|
||||
// exec("yarn add electron-builder");
|
||||
// }
|
||||
|
||||
const path = require('path');
|
||||
const builder = require("electron-builder");
|
||||
@@ -82,9 +82,13 @@ if (process.platform === 'darwin') {
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (process.platform === 'darwin') {
|
||||
// TODO: Remove comment when macOS certificates boughted and exported
|
||||
//require('./setup-macos-keychain').registerKeyChain();
|
||||
if (process.platform === 'darwin' && process.env.CI) {
|
||||
const encryptedFile = path.join(__dirname, './certs/mac-cert.p12.enc');
|
||||
const decryptedFile = path.join(__dirname, './certs/mac-cert.p12');
|
||||
exec(`openssl aes-256-cbc -K $CERT_KEY -iv $CERT_IV -in ${encryptedFile} -out ${decryptedFile} -d`);
|
||||
} else if (process.platform === 'win32') {
|
||||
// decrypt windows certificate
|
||||
exec('openssl aes-256-cbc -K %CERT_KEY% -iv %CERT_IV% -in scripts/certs/windows-cert.p12.enc -out scripts/certs/windows-cert.p12 -d')
|
||||
}
|
||||
|
||||
if (TEST_BUILD || gitTag) {
|
||||
@@ -110,14 +114,17 @@ if (TEST_BUILD || gitTag) {
|
||||
directories: {
|
||||
app: electron_build_folder
|
||||
},
|
||||
appId: 'com.ultimategadgetlabs.uhk.agent',
|
||||
appId: 'com.ultimategadgetlabs.agent',
|
||||
productName: 'UHK Agent',
|
||||
mac: {
|
||||
category: 'public.app-category.utilities',
|
||||
extraResources
|
||||
extraResources,
|
||||
identity: 'CMXCBCFHDG',
|
||||
cscLink: path.join(__dirname, 'certs/mac-cert.p12')
|
||||
},
|
||||
win: {
|
||||
extraResources
|
||||
extraResources,
|
||||
certificateFile: path.join(__dirname, 'certs/windows-cert.p12')
|
||||
},
|
||||
linux: {
|
||||
extraResources
|
||||
@@ -136,8 +143,7 @@ if (TEST_BUILD || gitTag) {
|
||||
console.error(`${error}`);
|
||||
process.exit(1);
|
||||
})
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
console.log('No git tag');
|
||||
// TODO: Need it?
|
||||
process.exit(1);
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
'use strict'
|
||||
|
||||
const cp = require('child_process')
|
||||
const path = require('path')
|
||||
|
||||
function registerKeyChain() {
|
||||
const encryptedFile = path.join(__dirname, '../certs/developer-id-cert.p12.enc')
|
||||
const decryptedFile = path.join(__dirname, '../certs/developer-id-cert.p12')
|
||||
cp.execSync(`openssl aes-256-cbc -K $encrypted_04061b49eb95_key -iv $encrypted_04061b49eb95_iv -in ${encryptedFile} -out ${decryptedFile} -d`)
|
||||
|
||||
const keyChain = 'mac-build.keychain'
|
||||
cp.execSync(`security create-keychain -p travis ${keyChain}`)
|
||||
cp.execSync(`security default-keychain -s ${keyChain}`)
|
||||
cp.execSync(`security unlock-keychain -p travis ${keyChain}`)
|
||||
cp.execSync(`security set-keychain-settings -t 3600 -u ${keyChain}`)
|
||||
|
||||
cp.execSync(`security import ${decryptedFile} -k ${keyChain} -P $KEY_PASSWORD -T /usr/bin/codesign`)
|
||||
}
|
||||
|
||||
module.exports.registerKeyChain = registerKeyChain
|
||||
Reference in New Issue
Block a user