Compare commits
45 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 |
@@ -45,7 +45,6 @@ addons:
|
|||||||
|
|
||||||
install:
|
install:
|
||||||
- nvm install
|
- nvm install
|
||||||
- npm i -g npm@5.6.0
|
|
||||||
- npm install
|
- npm install
|
||||||
|
|
||||||
before_script:
|
before_script:
|
||||||
|
|||||||
16
CHANGELOG.md
16
CHANGELOG.md
@@ -4,6 +4,22 @@ All notable changes to this project will be documented in this file.
|
|||||||
|
|
||||||
The format is loosely based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/).
|
The format is loosely based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/).
|
||||||
|
|
||||||
|
Every Agent version includes the most recent firmware version. See the [firmware changelog](https://github.com/UltimateHackingKeyboard/firmware/blob/master/CHANGELOG.md).
|
||||||
|
|
||||||
|
## [1.1.1] - 2017-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] - 2017-01-15
|
## [1.1.0] - 2017-01-15
|
||||||
|
|
||||||
Firmware: 8.**1**.0 [[release](https://github.com/UltimateHackingKeyboard/firmware/releases/tag/v8.1.0)] | Device Protocol: 4.2.0 | User Config: 4.0.0 | Hardware Config: 1.0.0
|
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
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ shallow_clone: true
|
|||||||
|
|
||||||
install:
|
install:
|
||||||
- ps: Install-Product node $env:nodejs_version
|
- ps: Install-Product node $env:nodejs_version
|
||||||
- npm i -g npm@5.6.0
|
|
||||||
- choco install chromium
|
- choco install chromium
|
||||||
- set CI=true
|
- set CI=true
|
||||||
- set PATH=%APPDATA%\npm;%PATH%
|
- set PATH=%APPDATA%\npm;%PATH%
|
||||||
|
|||||||
391
package-lock.json
generated
391
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -3,8 +3,8 @@
|
|||||||
"private": true,
|
"private": true,
|
||||||
"author": "Ultimate Gadget Laboratories",
|
"author": "Ultimate Gadget Laboratories",
|
||||||
"main": "electron/dist/electron-main.js",
|
"main": "electron/dist/electron-main.js",
|
||||||
"version": "1.1.0",
|
"version": "1.1.1",
|
||||||
"firmwareVersion": "8.1.0",
|
"firmwareVersion": "8.1.2",
|
||||||
"deviceProtocolVersion": "4.2.0",
|
"deviceProtocolVersion": "4.2.0",
|
||||||
"userConfigVersion": "4.0.0",
|
"userConfigVersion": "4.0.0",
|
||||||
"hardwareConfigVersion": "1.0.0",
|
"hardwareConfigVersion": "1.0.0",
|
||||||
@@ -36,7 +36,7 @@
|
|||||||
"decompress": "4.2.0",
|
"decompress": "4.2.0",
|
||||||
"decompress-tarbz2": "^4.1.1",
|
"decompress-tarbz2": "^4.1.1",
|
||||||
"devtron": "1.4.0",
|
"devtron": "1.4.0",
|
||||||
"electron": "1.7.5",
|
"electron": "1.7.11",
|
||||||
"electron-builder": "19.45.5",
|
"electron-builder": "19.45.5",
|
||||||
"electron-debug": "1.4.0",
|
"electron-debug": "1.4.0",
|
||||||
"electron-devtools-installer": "2.2.0",
|
"electron-devtools-installer": "2.2.0",
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { SwitchLayerAction } from './switch-layer-action';
|
|||||||
import { SwitchKeymapAction, UnresolvedSwitchKeymapAction } from './switch-keymap-action';
|
import { SwitchKeymapAction, UnresolvedSwitchKeymapAction } from './switch-keymap-action';
|
||||||
import { MouseAction } from './mouse-action';
|
import { MouseAction } from './mouse-action';
|
||||||
import { PlayMacroAction } from './play-macro-action';
|
import { PlayMacroAction } from './play-macro-action';
|
||||||
|
import { NoneAction } from './none-action';
|
||||||
|
|
||||||
export class Helper {
|
export class Helper {
|
||||||
|
|
||||||
@@ -77,6 +78,8 @@ export class Helper {
|
|||||||
return new MouseAction().fromJsonObject(keyAction);
|
return new MouseAction().fromJsonObject(keyAction);
|
||||||
case keyActionType.PlayMacroAction:
|
case keyActionType.PlayMacroAction:
|
||||||
return new PlayMacroAction().fromJsonObject(keyAction, macros);
|
return new PlayMacroAction().fromJsonObject(keyAction, macros);
|
||||||
|
case keyActionType.NoneAction:
|
||||||
|
return new NoneAction();
|
||||||
default:
|
default:
|
||||||
throw `Invalid KeyAction.keyActionType: "${keyAction.keyActionType}"`;
|
throw `Invalid KeyAction.keyActionType: "${keyAction.keyActionType}"`;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,7 +39,8 @@ export enum ConfigBufferId {
|
|||||||
export enum DevicePropertyIds {
|
export enum DevicePropertyIds {
|
||||||
DeviceProtocolVersion = 0,
|
DeviceProtocolVersion = 0,
|
||||||
ProtocolVersions = 1,
|
ProtocolVersions = 1,
|
||||||
ConfigSizes = 2
|
ConfigSizes = 2,
|
||||||
|
CurrentKbootCommand = 3
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum EnumerationModes {
|
export enum EnumerationModes {
|
||||||
|
|||||||
@@ -5,8 +5,13 @@ import * as fs from 'fs';
|
|||||||
import { UhkBlhost } from './uhk-blhost';
|
import { UhkBlhost } from './uhk-blhost';
|
||||||
import { UhkHidDevice } from './uhk-hid-device';
|
import { UhkHidDevice } from './uhk-hid-device';
|
||||||
import { snooze } from './util';
|
import { snooze } from './util';
|
||||||
import { convertBufferToIntArray, getTransferBuffers, DevicePropertyIds, UsbCommand, ConfigBufferId
|
import {
|
||||||
} from '../index';
|
convertBufferToIntArray,
|
||||||
|
getTransferBuffers,
|
||||||
|
DevicePropertyIds,
|
||||||
|
UsbCommand,
|
||||||
|
ConfigBufferId
|
||||||
|
} from '../index';
|
||||||
import { LoadConfigurationsResult } from './models/load-configurations-result';
|
import { LoadConfigurationsResult } from './models/load-configurations-result';
|
||||||
|
|
||||||
export class UhkOperations {
|
export class UhkOperations {
|
||||||
@@ -42,6 +47,13 @@ export class UhkOperations {
|
|||||||
await snooze(1000);
|
await snooze(1000);
|
||||||
await this.device.jumpToBootloaderModule(ModuleSlotToId.leftHalf);
|
await this.device.jumpToBootloaderModule(ModuleSlotToId.leftHalf);
|
||||||
this.device.close();
|
this.device.close();
|
||||||
|
|
||||||
|
const leftModuleBricked = await this.waitForKbootIdle();
|
||||||
|
if (!leftModuleBricked) {
|
||||||
|
this.logService.error('[UhkOperations] Couldn\'t connect to the left keyboard half.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
await this.device.reenumerate(EnumerationModes.Buspal);
|
await this.device.reenumerate(EnumerationModes.Buspal);
|
||||||
this.device.close();
|
this.device.close();
|
||||||
await this.blhost.runBlhostCommandRetry([...buspalPrefix, 'get-property', '1']);
|
await this.blhost.runBlhostCommandRetry([...buspalPrefix, 'get-property', '1']);
|
||||||
@@ -161,6 +173,26 @@ 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;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* IpcMain handler. Send the UserConfiguration to the UHK Device and send a response with the result.
|
* IpcMain handler. Send the UserConfiguration to the UHK Device and send a response with the result.
|
||||||
* @param {string} json - UserConfiguration in JSON format
|
* @param {string} json - UserConfiguration in JSON format
|
||||||
|
|||||||
2747
packages/uhk-web/package-lock.json
generated
2747
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/jasmine": "2.5.53",
|
||||||
"@types/jasminewd2": "2.0.2",
|
"@types/jasminewd2": "2.0.2",
|
||||||
"@types/jquery": "3.2.9",
|
"@types/jquery": "3.2.9",
|
||||||
|
"@types/lodash-es": "4.17.0",
|
||||||
"@types/node-hid": "0.5.2",
|
"@types/node-hid": "0.5.2",
|
||||||
"@types/usb": "1.1.3",
|
"@types/usb": "1.1.3",
|
||||||
"angular-confirmation-popover": "3.2.0",
|
"angular-confirmation-popover": "3.2.0",
|
||||||
@@ -67,7 +68,7 @@
|
|||||||
"karma-jasmine": "1.1.0",
|
"karma-jasmine": "1.1.0",
|
||||||
"karma-jasmine-html-reporter": "0.2.2",
|
"karma-jasmine-html-reporter": "0.2.2",
|
||||||
"less-loader": "4.0.5",
|
"less-loader": "4.0.5",
|
||||||
"lodash": "4.17.4",
|
"lodash-es": "4.17.4",
|
||||||
"ng2-dragula": "1.5.0",
|
"ng2-dragula": "1.5.0",
|
||||||
"ng2-nouislider": "^1.7.6",
|
"ng2-nouislider": "^1.7.6",
|
||||||
"ng2-select2": "1.0.0-beta.10",
|
"ng2-select2": "1.0.0-beta.10",
|
||||||
|
|||||||
@@ -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"
|
[selectedKey]="selectedKey"
|
||||||
[selected]="selectedKey?.layerId === index"
|
[selected]="selectedKey?.layerId === index"
|
||||||
[keyboardLayout]="keyboardLayout"
|
[keyboardLayout]="keyboardLayout"
|
||||||
|
[description]="description"
|
||||||
|
[showDescription]="true"
|
||||||
(keyClick)="keyClick.emit($event)"
|
(keyClick)="keyClick.emit($event)"
|
||||||
(keyHover)="keyHover.emit($event)"
|
(keyHover)="keyHover.emit($event)"
|
||||||
(capture)="capture.emit($event)"
|
(capture)="capture.emit($event)"
|
||||||
|
(descriptionChanged)="descriptionChanged.emit($event)"
|
||||||
>
|
>
|
||||||
</svg-keyboard>
|
</svg-keyboard>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 659 B After Width: | Height: | Size: 809 B |
@@ -1,4 +1,4 @@
|
|||||||
import { Component, ChangeDetectionStrategy, Input, Output, EventEmitter, OnChanges, SimpleChanges } from '@angular/core';
|
import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnChanges, Output, SimpleChanges } from '@angular/core';
|
||||||
import { animate, keyframes, state, style, transition, trigger } from '@angular/animations';
|
import { animate, keyframes, state, style, transition, trigger } from '@angular/animations';
|
||||||
import { Layer } from 'uhk-common';
|
import { Layer } from 'uhk-common';
|
||||||
|
|
||||||
@@ -81,11 +81,14 @@ export class KeyboardSliderComponent implements OnChanges {
|
|||||||
@Input() halvesSplit: boolean;
|
@Input() halvesSplit: boolean;
|
||||||
@Input() selectedKey: { layerId: number, moduleId: number, keyId: number };
|
@Input() selectedKey: { layerId: number, moduleId: number, keyId: number };
|
||||||
@Input() keyboardLayout = KeyboardLayout.ANSI;
|
@Input() keyboardLayout = KeyboardLayout.ANSI;
|
||||||
|
@Input() description: string;
|
||||||
@Output() keyClick = new EventEmitter();
|
@Output() keyClick = new EventEmitter();
|
||||||
@Output() keyHover = new EventEmitter();
|
@Output() keyHover = new EventEmitter();
|
||||||
@Output() capture = new EventEmitter();
|
@Output() capture = new EventEmitter();
|
||||||
|
@Output() descriptionChanged = new EventEmitter<string>();
|
||||||
|
|
||||||
layerAnimationState: AnimationKeyboard[];
|
layerAnimationState: AnimationKeyboard[];
|
||||||
|
|
||||||
ngOnChanges(changes: SimpleChanges) {
|
ngOnChanges(changes: SimpleChanges) {
|
||||||
if (changes['layers']) {
|
if (changes['layers']) {
|
||||||
this.layerAnimationState = this.layers.map<AnimationKeyboard>(() => 'initOut');
|
this.layerAnimationState = this.layers.map<AnimationKeyboard>(() => 'initOut');
|
||||||
|
|||||||
@@ -4,7 +4,8 @@
|
|||||||
(downloadClick)="downloadKeymap()"></keymap-header>
|
(downloadClick)="downloadKeymap()"></keymap-header>
|
||||||
<svg-keyboard-wrap [keymap]="keymap$ | async"
|
<svg-keyboard-wrap [keymap]="keymap$ | async"
|
||||||
[halvesSplit]="keyboardSplit"
|
[halvesSplit]="keyboardSplit"
|
||||||
[keyboardLayout]="keyboardLayout$ | async"></svg-keyboard-wrap>
|
[keyboardLayout]="keyboardLayout$ | async"
|
||||||
|
(descriptionChanged)="descriptionChanged($event)"></svg-keyboard-wrap>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
|
||||||
<div *ngIf="!(keymap$ | async)" class="not-found">
|
<div *ngIf="!(keymap$ | async)" class="not-found">
|
||||||
|
|||||||
@@ -18,6 +18,8 @@ import { AppState, getKeyboardLayout } from '../../../store';
|
|||||||
import { getKeymap, getKeymaps, getUserConfiguration } from '../../../store/reducers/user-configuration';
|
import { getKeymap, getKeymaps, getUserConfiguration } from '../../../store/reducers/user-configuration';
|
||||||
import { SvgKeyboardWrapComponent } from '../../svg/wrap';
|
import { SvgKeyboardWrapComponent } from '../../svg/wrap';
|
||||||
import { KeyboardLayout } from '../../../keyboard/keyboard-layout.enum';
|
import { KeyboardLayout } from '../../../keyboard/keyboard-layout.enum';
|
||||||
|
import { KeymapActions } from '../../../store/actions';
|
||||||
|
import { ChangeKeymapDescription } from '../../../models/ChangeKeymapDescription';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'keymap-edit',
|
selector: 'keymap-edit',
|
||||||
@@ -64,7 +66,7 @@ export class KeymapEditComponent {
|
|||||||
const keymap = latest[0];
|
const keymap = latest[0];
|
||||||
const exportableJSON = latest[1];
|
const exportableJSON = latest[1];
|
||||||
const fileName = keymap.name + '_keymap.json';
|
const fileName = keymap.name + '_keymap.json';
|
||||||
saveAs(new Blob([exportableJSON], { type: 'application/json' }), fileName);
|
saveAs(new Blob([exportableJSON], {type: 'application/json'}), fileName);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,6 +75,10 @@ export class KeymapEditComponent {
|
|||||||
this.keyboardSplit = !this.keyboardSplit;
|
this.keyboardSplit = !this.keyboardSplit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
descriptionChanged(event: ChangeKeymapDescription): void {
|
||||||
|
this.store.dispatch(new KeymapActions.EditDescriptionAction(event));
|
||||||
|
}
|
||||||
|
|
||||||
private toExportableJSON(keymap: Keymap): Observable<any> {
|
private toExportableJSON(keymap: Keymap): Observable<any> {
|
||||||
return this.store
|
return this.store
|
||||||
.let(getUserConfiguration())
|
.let(getUserConfiguration())
|
||||||
|
|||||||
@@ -13,5 +13,5 @@
|
|||||||
</ng-template>
|
</ng-template>
|
||||||
|
|
||||||
<div *ngIf="!macro" class="not-found">
|
<div *ngIf="!macro" class="not-found">
|
||||||
There is no macro with id {{ route.params.select('id') | async }}.
|
There is no macro with id {{ macroId }}.
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { Subscription } from 'rxjs/Subscription';
|
|||||||
import 'rxjs/add/operator/pluck';
|
import 'rxjs/add/operator/pluck';
|
||||||
|
|
||||||
import { MacroActions } from '../../../store/actions';
|
import { MacroActions } from '../../../store/actions';
|
||||||
import { AppState } from '../../../store/index';
|
import { AppState } from '../../../store';
|
||||||
import { getMacro } from '../../../store/reducers/user-configuration';
|
import { getMacro } from '../../../store/reducers/user-configuration';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@@ -21,13 +21,17 @@ import { getMacro } from '../../../store/reducers/user-configuration';
|
|||||||
export class MacroEditComponent implements OnDestroy {
|
export class MacroEditComponent implements OnDestroy {
|
||||||
macro: Macro;
|
macro: Macro;
|
||||||
isNew: boolean;
|
isNew: boolean;
|
||||||
|
macroId: number;
|
||||||
|
|
||||||
private subscription: Subscription;
|
private subscription: Subscription;
|
||||||
constructor(private store: Store<AppState>, public route: ActivatedRoute) {
|
constructor(private store: Store<AppState>, public route: ActivatedRoute) {
|
||||||
this.subscription = route
|
this.subscription = route
|
||||||
.params
|
.params
|
||||||
.pluck<{}, string>('id')
|
.pluck<{}, string>('id')
|
||||||
.switchMap((id: string) => store.let(getMacro(+id)))
|
.switchMap((id: string) => {
|
||||||
|
this.macroId = +id;
|
||||||
|
return store.let(getMacro(this.macroId));
|
||||||
|
})
|
||||||
.subscribe((macro: Macro) => {
|
.subscribe((macro: Macro) => {
|
||||||
this.macro = macro;
|
this.macro = macro;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -7,6 +7,10 @@
|
|||||||
[width]="200"
|
[width]="200"
|
||||||
[options]="options"
|
[options]="options"
|
||||||
></select2>
|
></select2>
|
||||||
|
<icon name="question-circle"
|
||||||
|
data-toggle="tooltip"
|
||||||
|
title="Looking for a non-US character, but can't find it? Please note that USB keyboards send scancodes, not characters to your computer. Then your operating system translates the scancodes to characters according to your current OS keyboard layout. This means that you have to select the US-equivalent character of the desired key in Agent."
|
||||||
|
data-placement="bottom"></icon>
|
||||||
<capture-keystroke-button (capture)="onKeysCapture($event)" tabindex="0"></capture-keystroke-button>
|
<capture-keystroke-button (capture)="onKeysCapture($event)" tabindex="0"></capture-keystroke-button>
|
||||||
</div>
|
</div>
|
||||||
<div class="modifier-options">
|
<div class="modifier-options">
|
||||||
|
|||||||
@@ -10,6 +10,10 @@
|
|||||||
position: relative;
|
position: relative;
|
||||||
top: 2px;
|
top: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
icon {
|
||||||
|
display: inline-block;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
.modifier-options {
|
.modifier-options {
|
||||||
|
|||||||
@@ -164,12 +164,12 @@ ul {
|
|||||||
padding: 0;
|
padding: 0;
|
||||||
margin: 0 0.25rem;
|
margin: 0 0.25rem;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
background-color: inherit;
|
background-color: transparent;
|
||||||
|
|
||||||
&:focus {
|
&:focus {
|
||||||
box-shadow: 0 0 0 1px #ccc, 0 0 5px 0 #ccc;
|
box-shadow: 0 0 0 1px #ccc, 0 0 5px 0 #ccc;
|
||||||
border-color: transparent;
|
border-color: transparent;
|
||||||
background-color: inherit;
|
background-color: transparent;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,17 +56,8 @@ export class SideMenuComponent implements AfterContentInit, OnDestroy {
|
|||||||
addon: 'active'
|
addon: 'active'
|
||||||
};
|
};
|
||||||
|
|
||||||
this.keymaps$ = store.let(getKeymaps())
|
this.keymaps$ = store.let(getKeymaps());
|
||||||
.map(keymaps => keymaps.slice()) // Creating a new array reference, because the sort is working in place
|
this.macros$ = store.let(getMacros());
|
||||||
.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.showAddonMenu$ = this.store.select(showAddonMenu);
|
this.showAddonMenu$ = this.store.select(showAddonMenu);
|
||||||
this.runInElectron$ = this.store.select(runningInElectron);
|
this.runInElectron$ = this.store.select(runningInElectron);
|
||||||
|
|||||||
@@ -1,16 +1,20 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" [attr.viewBox]="viewBox" height="100%" width="100%">
|
<svg xmlns="http://www.w3.org/2000/svg" [attr.viewBox]="viewBox" height="100%" width="100%">
|
||||||
<svg:g svg-module *ngFor="let module of modules; let i = index"
|
<svg:g svg-module *ngFor="let module of modules; let i = index"
|
||||||
[coverages]="module.coverages"
|
[coverages]="module.coverages"
|
||||||
[keyboardKeys]="module.keyboardKeys"
|
[keyboardKeys]="module.keyboardKeys"
|
||||||
[keybindAnimationEnabled]="keybindAnimationEnabled"
|
[keybindAnimationEnabled]="keybindAnimationEnabled"
|
||||||
[capturingEnabled]="capturingEnabled"
|
[capturingEnabled]="capturingEnabled"
|
||||||
[attr.transform]="module.attributes.transform"
|
[attr.transform]="module.attributes.transform"
|
||||||
[keyActions]="moduleConfig[i].keyActions"
|
[keyActions]="moduleConfig[i].keyActions"
|
||||||
[selectedKey]="selectedKey"
|
[selectedKey]="selectedKey"
|
||||||
[@split]="moduleAnimationStates[i]"
|
[@split]="moduleAnimationStates[i]"
|
||||||
[selected]="selectedKey?.moduleId === i"
|
[selected]="selectedKey?.moduleId === i"
|
||||||
(keyClick)="onKeyClick(i, $event.index, $event.keyTarget)"
|
(keyClick)="onKeyClick(i, $event.index, $event.keyTarget)"
|
||||||
(keyHover)="onKeyHover($event.index, $event.event, $event.over, i)"
|
(keyHover)="onKeyHover($event.index, $event.event, $event.over, i)"
|
||||||
(capture)="onCapture(i, $event.index, $event.captured)"
|
(capture)="onCapture(i, $event.index, $event.captured)"
|
||||||
/>
|
/>
|
||||||
</svg>
|
</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 {
|
:host {
|
||||||
display: flex;
|
display: block;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
position: relative;
|
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() selected: boolean;
|
||||||
@Input() halvesSplit: boolean;
|
@Input() halvesSplit: boolean;
|
||||||
@Input() keyboardLayout = KeyboardLayout.ANSI;
|
@Input() keyboardLayout = KeyboardLayout.ANSI;
|
||||||
|
@Input() description: string;
|
||||||
|
@Input() showDescription = false;
|
||||||
@Output() keyClick = new EventEmitter();
|
@Output() keyClick = new EventEmitter();
|
||||||
@Output() keyHover = new EventEmitter();
|
@Output() keyHover = new EventEmitter();
|
||||||
@Output() capture = new EventEmitter();
|
@Output() capture = new EventEmitter();
|
||||||
|
@Output() descriptionChanged = new EventEmitter<string>();
|
||||||
|
|
||||||
modules: SvgModule[];
|
modules: SvgModule[];
|
||||||
viewBox: string;
|
viewBox: string;
|
||||||
|
|||||||
@@ -7,9 +7,11 @@
|
|||||||
[selectedKey]="selectedKey"
|
[selectedKey]="selectedKey"
|
||||||
[halvesSplit]="halvesSplit"
|
[halvesSplit]="halvesSplit"
|
||||||
[keyboardLayout]="keyboardLayout"
|
[keyboardLayout]="keyboardLayout"
|
||||||
|
[description]="keymap.description"
|
||||||
(keyClick)="onKeyClick($event.moduleId, $event.keyId, $event.keyTarget)"
|
(keyClick)="onKeyClick($event.moduleId, $event.keyId, $event.keyTarget)"
|
||||||
(keyHover)="onKeyHover($event.moduleId, $event.event, $event.over, $event.keyId)"
|
(keyHover)="onKeyHover($event.moduleId, $event.event, $event.over, $event.keyId)"
|
||||||
(capture)="onCapture($event.moduleId, $event.keyId, $event.captured)"
|
(capture)="onCapture($event.moduleId, $event.keyId, $event.captured)"
|
||||||
|
(descriptionChanged)="onDescriptionChanged($event)"
|
||||||
></keyboard-slider>
|
></keyboard-slider>
|
||||||
<popover tabindex="0" [visible]="popoverShown" [keyPosition]="keyPosition" [wrapPosition]="wrapPosition" [defaultKeyAction]="popoverInitKeyAction"
|
<popover tabindex="0" [visible]="popoverShown" [keyPosition]="keyPosition" [wrapPosition]="wrapPosition" [defaultKeyAction]="popoverInitKeyAction"
|
||||||
[currentKeymap]="keymap" [currentLayer]="currentLayer" (cancel)="hidePopover()" (remap)="onRemap($event)"></popover>
|
[currentKeymap]="keymap" [currentLayer]="currentLayer" (cancel)="hidePopover()" (remap)="onRemap($event)"></popover>
|
||||||
|
|||||||
@@ -2,14 +2,16 @@ import {
|
|||||||
ChangeDetectionStrategy,
|
ChangeDetectionStrategy,
|
||||||
Component,
|
Component,
|
||||||
ElementRef,
|
ElementRef,
|
||||||
Renderer,
|
EventEmitter,
|
||||||
HostBinding,
|
HostBinding,
|
||||||
HostListener,
|
HostListener,
|
||||||
Input,
|
Input,
|
||||||
OnChanges,
|
OnChanges,
|
||||||
OnInit,
|
OnInit,
|
||||||
ViewChild,
|
Output,
|
||||||
SimpleChanges
|
Renderer,
|
||||||
|
SimpleChanges,
|
||||||
|
ViewChild
|
||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
|
|
||||||
import { Observable } from 'rxjs/Observable';
|
import { Observable } from 'rxjs/Observable';
|
||||||
@@ -25,10 +27,10 @@ import {
|
|||||||
KeystrokeAction,
|
KeystrokeAction,
|
||||||
Layer,
|
Layer,
|
||||||
LayerName,
|
LayerName,
|
||||||
SecondaryRoleAction,
|
|
||||||
MouseAction,
|
MouseAction,
|
||||||
MouseActionParam,
|
MouseActionParam,
|
||||||
PlayMacroAction,
|
PlayMacroAction,
|
||||||
|
SecondaryRoleAction,
|
||||||
SwitchKeymapAction,
|
SwitchKeymapAction,
|
||||||
SwitchLayerAction
|
SwitchLayerAction
|
||||||
} from 'uhk-common';
|
} from 'uhk-common';
|
||||||
@@ -38,6 +40,7 @@ import { AppState } from '../../../store';
|
|||||||
import { KeymapActions } from '../../../store/actions';
|
import { KeymapActions } from '../../../store/actions';
|
||||||
import { PopoverComponent } from '../../popover';
|
import { PopoverComponent } from '../../popover';
|
||||||
import { KeyboardLayout } from '../../../keyboard/keyboard-layout.enum';
|
import { KeyboardLayout } from '../../../keyboard/keyboard-layout.enum';
|
||||||
|
import { ChangeKeymapDescription } from '../../../models/ChangeKeymapDescription';
|
||||||
|
|
||||||
interface NameValuePair {
|
interface NameValuePair {
|
||||||
name: string;
|
name: string;
|
||||||
@@ -56,6 +59,7 @@ export class SvgKeyboardWrapComponent implements OnInit, OnChanges {
|
|||||||
@Input() tooltipEnabled: boolean = false;
|
@Input() tooltipEnabled: boolean = false;
|
||||||
@Input() halvesSplit: boolean;
|
@Input() halvesSplit: boolean;
|
||||||
@Input() keyboardLayout: KeyboardLayout.ANSI;
|
@Input() keyboardLayout: KeyboardLayout.ANSI;
|
||||||
|
@Output() descriptionChanged = new EventEmitter<ChangeKeymapDescription>();
|
||||||
|
|
||||||
@ViewChild(PopoverComponent, { read: ElementRef }) popover: ElementRef;
|
@ViewChild(PopoverComponent, { read: ElementRef }) popover: ElementRef;
|
||||||
|
|
||||||
@@ -237,6 +241,13 @@ export class SvgKeyboardWrapComponent implements OnInit, OnChanges {
|
|||||||
return this.currentLayer;
|
return this.currentLayer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onDescriptionChanged(description: string): void {
|
||||||
|
this.descriptionChanged.emit({
|
||||||
|
description,
|
||||||
|
abbr: this.keymap.abbreviation
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private getKeyActionContent(keyAction: KeyAction): Observable<NameValuePair[]> {
|
private getKeyActionContent(keyAction: KeyAction): Observable<NameValuePair[]> {
|
||||||
if (keyAction instanceof KeystrokeAction) {
|
if (keyAction instanceof KeystrokeAction) {
|
||||||
const keystrokeAction: KeystrokeAction = keyAction;
|
const keystrokeAction: KeystrokeAction = keyAction;
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { Store } from '@ngrx/store';
|
|||||||
import { Observable } from 'rxjs/Observable';
|
import { Observable } from 'rxjs/Observable';
|
||||||
|
|
||||||
import { Notification } from 'uhk-common';
|
import { Notification } from 'uhk-common';
|
||||||
import { AppState, getUndoableNotification } from '../../store/index';
|
import { AppState, getUndoableNotification } from '../../store';
|
||||||
import { DismissUndoNotificationAction, UndoLastAction } from '../../store/actions/app';
|
import { DismissUndoNotificationAction, UndoLastAction } from '../../store/actions/app';
|
||||||
|
|
||||||
@Component({
|
@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;
|
||||||
|
}
|
||||||
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 { UhkDeviceLoadedGuard } from './services/uhk-device-loaded.guard';
|
||||||
import { XtermComponent } from './components/xterm/xterm.component';
|
import { XtermComponent } from './components/xterm/xterm.component';
|
||||||
import { SliderWrapperComponent } from './components/slider-wrapper/slider-wrapper.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({
|
@NgModule({
|
||||||
declarations: [
|
declarations: [
|
||||||
@@ -169,7 +171,9 @@ import { SliderWrapperComponent } from './components/slider-wrapper/slider-wrapp
|
|||||||
ProgressButtonComponent,
|
ProgressButtonComponent,
|
||||||
LoadingDevicePageComponent,
|
LoadingDevicePageComponent,
|
||||||
XtermComponent,
|
XtermComponent,
|
||||||
SliderWrapperComponent
|
SliderWrapperComponent,
|
||||||
|
EditableTextComponent,
|
||||||
|
Autofocus
|
||||||
],
|
],
|
||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { Action } from '@ngrx/store';
|
import { Action } from '@ngrx/store';
|
||||||
import { KeyAction, Keymap, Macro } from 'uhk-common';
|
import { KeyAction, Keymap, Macro } from 'uhk-common';
|
||||||
import { UndoUserConfigData } from '../../models/undo-user-config-data';
|
import { UndoUserConfigData } from '../../models/undo-user-config-data';
|
||||||
|
import { ChangeKeymapDescription } from '../../models/ChangeKeymapDescription';
|
||||||
|
|
||||||
export type KeymapAction =
|
export type KeymapAction =
|
||||||
KeymapActions.AddKeymapAction |
|
KeymapActions.AddKeymapAction |
|
||||||
@@ -11,7 +12,8 @@ export type KeymapAction =
|
|||||||
KeymapActions.SetDefaultAction |
|
KeymapActions.SetDefaultAction |
|
||||||
KeymapActions.RemoveKeymapAction |
|
KeymapActions.RemoveKeymapAction |
|
||||||
KeymapActions.SaveKeyAction |
|
KeymapActions.SaveKeyAction |
|
||||||
KeymapActions.CheckMacroAction;
|
KeymapActions.CheckMacroAction |
|
||||||
|
KeymapActions.EditDescriptionAction;
|
||||||
|
|
||||||
export namespace KeymapActions {
|
export namespace KeymapActions {
|
||||||
export const ADD = '[Keymap] Add keymap';
|
export const ADD = '[Keymap] Add keymap';
|
||||||
@@ -98,6 +100,16 @@ export namespace KeymapActions {
|
|||||||
payload: UndoUserConfigData
|
payload: UndoUserConfigData
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const EDIT_DESCRIPTION = '[Keymap] Edit description';
|
||||||
|
|
||||||
|
export class EditDescriptionAction {
|
||||||
|
type = EDIT_DESCRIPTION;
|
||||||
|
|
||||||
|
constructor(public payload: ChangeKeymapDescription) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function loadKeymaps(): Action {
|
export function loadKeymaps(): Action {
|
||||||
return {
|
return {
|
||||||
type: KeymapActions.LOAD_KEYMAPS
|
type: KeymapActions.LOAD_KEYMAPS
|
||||||
|
|||||||
@@ -35,10 +35,12 @@ import { ShowNotificationAction } from '../actions/app';
|
|||||||
import { AppState } from '../index';
|
import { AppState } from '../index';
|
||||||
import {
|
import {
|
||||||
ActionTypes as UserConfigActions,
|
ActionTypes as UserConfigActions,
|
||||||
|
ApplyUserConfigurationFromFileAction,
|
||||||
LoadConfigFromDeviceAction,
|
LoadConfigFromDeviceAction,
|
||||||
LoadResetUserConfigurationAction
|
LoadResetUserConfigurationAction
|
||||||
} from '../actions/user-config';
|
} from '../actions/user-config';
|
||||||
import { DefaultUserConfigurationService } from '../../services/default-user-configuration.service';
|
import { DefaultUserConfigurationService } from '../../services/default-user-configuration.service';
|
||||||
|
import { DataStorageRepositoryService } from '../../services/datastorage-repository.service';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class DeviceEffects {
|
export class DeviceEffects {
|
||||||
@@ -162,8 +164,16 @@ export class DeviceEffects {
|
|||||||
});
|
});
|
||||||
|
|
||||||
@Effect() saveResetUserConfigurationToDevice$ = this.actions$
|
@Effect() saveResetUserConfigurationToDevice$ = this.actions$
|
||||||
.ofType(UserConfigActions.LOAD_RESET_USER_CONFIGURATION, UserConfigActions.APPLY_USER_CONFIGURATION_FROM_FILE)
|
.ofType<ApplyUserConfigurationFromFileAction
|
||||||
.switchMap(() => Observable.of(new SaveConfigurationAction()));
|
| 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$
|
@Effect({dispatch: false}) updateFirmware$ = this.actions$
|
||||||
.ofType<UpdateFirmwareAction>(ActionTypes.UPDATE_FIRMWARE)
|
.ofType<UpdateFirmwareAction>(ActionTypes.UPDATE_FIRMWARE)
|
||||||
@@ -193,6 +203,7 @@ export class DeviceEffects {
|
|||||||
private router: Router,
|
private router: Router,
|
||||||
private deviceRendererService: DeviceRendererService,
|
private deviceRendererService: DeviceRendererService,
|
||||||
private store: Store<AppState>,
|
private store: Store<AppState>,
|
||||||
|
private dataStorageRepository: DataStorageRepositoryService,
|
||||||
private defaultUserConfigurationService: DefaultUserConfigurationService) {
|
private defaultUserConfigurationService: DefaultUserConfigurationService) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,14 +7,17 @@ import { Observable } from 'rxjs/Observable';
|
|||||||
|
|
||||||
import 'rxjs/add/operator/do';
|
import 'rxjs/add/operator/do';
|
||||||
import 'rxjs/add/operator/map';
|
import 'rxjs/add/operator/map';
|
||||||
|
import 'rxjs/add/operator/pairwise';
|
||||||
import 'rxjs/add/operator/startWith';
|
import 'rxjs/add/operator/startWith';
|
||||||
import 'rxjs/add/operator/switchMap';
|
import 'rxjs/add/operator/switchMap';
|
||||||
import 'rxjs/add/operator/withLatestFrom';
|
import 'rxjs/add/operator/withLatestFrom';
|
||||||
import 'rxjs/add/observable/of';
|
import 'rxjs/add/observable/of';
|
||||||
|
|
||||||
import { Keymap } from 'uhk-common';
|
import { Keymap } from 'uhk-common';
|
||||||
|
import { findNewItem } from '../../util';
|
||||||
import { KeymapActions } from '../actions';
|
import { KeymapActions } from '../actions';
|
||||||
import { AppState } from '../index';
|
import { AppState } from '../index';
|
||||||
|
import { getKeymaps } from '../reducers/user-configuration';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class KeymapEffects {
|
export class KeymapEffects {
|
||||||
@@ -32,10 +35,10 @@ export class KeymapEffects {
|
|||||||
|
|
||||||
@Effect({ dispatch: false }) addOrDuplicate$: any = this.actions$
|
@Effect({ dispatch: false }) addOrDuplicate$: any = this.actions$
|
||||||
.ofType(KeymapActions.ADD, KeymapActions.DUPLICATE)
|
.ofType(KeymapActions.ADD, KeymapActions.DUPLICATE)
|
||||||
.withLatestFrom(this.store)
|
.withLatestFrom(this.store.let(getKeymaps()).pairwise(), (action, latest) => latest)
|
||||||
.map(latest => latest[1].userConfiguration.keymaps)
|
.do(([prevKeymaps, newKeymaps]) => {
|
||||||
.do(keymaps => {
|
const newKeymap = findNewItem(prevKeymaps, newKeymaps);
|
||||||
this.router.navigate(['/keymap', keymaps[keymaps.length - 1].abbreviation]);
|
this.router.navigate(['/keymap', newKeymap.abbreviation]);
|
||||||
});
|
});
|
||||||
|
|
||||||
@Effect({ dispatch: false }) remove$: any = this.actions$
|
@Effect({ dispatch: false }) remove$: any = this.actions$
|
||||||
|
|||||||
@@ -2,14 +2,18 @@ import { Injectable } from '@angular/core';
|
|||||||
import { Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
|
|
||||||
import { Actions, Effect } from '@ngrx/effects';
|
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/do';
|
||||||
import 'rxjs/add/operator/map';
|
import 'rxjs/add/operator/map';
|
||||||
|
import 'rxjs/add/operator/pairwise';
|
||||||
import 'rxjs/add/operator/withLatestFrom';
|
import 'rxjs/add/operator/withLatestFrom';
|
||||||
|
|
||||||
|
import { Macro } from 'uhk-common';
|
||||||
import { KeymapActions, MacroActions } from '../actions';
|
import { KeymapActions, MacroActions } from '../actions';
|
||||||
import { AppState } from '../index';
|
import { AppState } from '../index';
|
||||||
|
import { getMacros } from '../reducers/user-configuration';
|
||||||
|
import { findNewItem } from '../../util';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class MacroEffects {
|
export class MacroEffects {
|
||||||
@@ -27,23 +31,17 @@ export class MacroEffects {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@Effect({ dispatch: false }) add$: any = this.actions$
|
@Effect({ dispatch: false }) addOrDuplicate$: any = this.actions$
|
||||||
.ofType(MacroActions.ADD)
|
.ofType(MacroActions.ADD, MacroActions.DUPLICATE)
|
||||||
.withLatestFrom(this.store)
|
.withLatestFrom(this.store.let(getMacros()).pairwise(), (action, latest) => ([action, latest[0], latest[1]]))
|
||||||
.map(([action, state]) => state.userConfiguration.macros)
|
.do(([action, prevMacros, newMacros]: [Action, Macro[], Macro[]]) => {
|
||||||
.map(macros => macros[macros.length - 1])
|
const newMacro = findNewItem(prevMacros, newMacros);
|
||||||
.do(lastMacro => {
|
const commands = ['/macro', newMacro.id];
|
||||||
this.router.navigate(['/macro', lastMacro.id, 'new']);
|
if (action.type === MacroActions.ADD) {
|
||||||
|
commands.push('new');
|
||||||
|
}
|
||||||
|
this.router.navigate(commands);
|
||||||
});
|
});
|
||||||
|
|
||||||
@Effect({ dispatch: false }) duplicate: any = this.actions$
|
constructor(private actions$: Actions, private router: Router, private store: Store<AppState>) { }
|
||||||
.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]);
|
|
||||||
});
|
|
||||||
|
|
||||||
constructor(private actions$: Actions, private router: Router, private store: Store<AppState>) {}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ export class UserConfigEffects {
|
|||||||
@Effect() saveUserConfig$: Observable<Action> = (this.actions$
|
@Effect() saveUserConfig$: Observable<Action> = (this.actions$
|
||||||
.ofType(
|
.ofType(
|
||||||
KeymapActions.ADD, KeymapActions.DUPLICATE, KeymapActions.EDIT_NAME, KeymapActions.EDIT_ABBR,
|
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.ADD, MacroActions.DUPLICATE, MacroActions.EDIT_NAME, MacroActions.REMOVE, MacroActions.ADD_ACTION,
|
||||||
MacroActions.SAVE_ACTION, MacroActions.DELETE_ACTION, MacroActions.REORDER_ACTION,
|
MacroActions.SAVE_ACTION, MacroActions.DELETE_ACTION, MacroActions.REORDER_ACTION,
|
||||||
ActionTypes.RENAME_USER_CONFIGURATION, ActionTypes.SET_USER_CONFIGURATION_VALUE) as
|
ActionTypes.RENAME_USER_CONFIGURATION, ActionTypes.SET_USER_CONFIGURATION_VALUE) as
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ export const initialState: Keymap[] = [];
|
|||||||
export function reducer(state = initialState, action: KeymapAction): Keymap[] {
|
export function reducer(state = initialState, action: KeymapAction): Keymap[] {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case KeymapActions.LOAD_KEYMAPS_SUCCESS: {
|
case KeymapActions.LOAD_KEYMAPS_SUCCESS: {
|
||||||
return action.payload;
|
return (action as KeymapActions.LoadKeymapSuccessAction).payload ;
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -4,7 +4,18 @@ import { Observable } from 'rxjs/Observable';
|
|||||||
import 'rxjs/add/observable/of';
|
import 'rxjs/add/observable/of';
|
||||||
import 'rxjs/add/operator/map';
|
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 { KeymapActions, MacroActions } from '../actions';
|
||||||
import { AppState } from '../index';
|
import { AppState } from '../index';
|
||||||
import { ActionTypes } from '../actions/user-config';
|
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.APPLY_USER_CONFIGURATION_FROM_FILE:
|
||||||
case ActionTypes.LOAD_RESET_USER_CONFIGURATION:
|
case ActionTypes.LOAD_RESET_USER_CONFIGURATION:
|
||||||
case ActionTypes.LOAD_USER_CONFIG_SUCCESS: {
|
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:
|
case KeymapActions.ADD:
|
||||||
@@ -29,7 +45,7 @@ export function reducer(state = initialState, action: Action & { payload?: any }
|
|||||||
newKeymap.name = generateName(state.keymaps, newKeymap.name);
|
newKeymap.name = generateName(state.keymaps, newKeymap.name);
|
||||||
newKeymap.isDefault = (state.keymaps.length === 0);
|
newKeymap.isDefault = (state.keymaps.length === 0);
|
||||||
|
|
||||||
changedUserConfiguration.keymaps = state.keymaps.concat(newKeymap);
|
changedUserConfiguration.keymaps = insertItemInNameOrder(state.keymaps, newKeymap);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case KeymapActions.EDIT_NAME: {
|
case KeymapActions.EDIT_NAME: {
|
||||||
@@ -38,20 +54,27 @@ export function reducer(state = initialState, action: Action & { payload?: any }
|
|||||||
}
|
}
|
||||||
|
|
||||||
const name: string = action.payload.name.trim();
|
const name: string = action.payload.name.trim();
|
||||||
|
let keymapToRename: Keymap = null;
|
||||||
|
|
||||||
const duplicate = state.keymaps.some((keymap: Keymap) => {
|
const duplicate = state.keymaps.some((keymap: Keymap) => {
|
||||||
|
if (keymap.abbreviation === action.payload.abbr) {
|
||||||
|
keymapToRename = keymap;
|
||||||
|
}
|
||||||
|
|
||||||
return keymap.name === name && keymap.abbreviation !== action.payload.abbr;
|
return keymap.name === name && keymap.abbreviation !== action.payload.abbr;
|
||||||
});
|
});
|
||||||
|
|
||||||
changedUserConfiguration.keymaps = state.keymaps.map((keymap: Keymap) => {
|
if (duplicate) {
|
||||||
keymap = Object.assign(new Keymap(), keymap);
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
if (!duplicate && keymap.abbreviation === action.payload.abbr) {
|
const newKeymap = Object.assign(new Keymap(), keymapToRename, { name });
|
||||||
keymap.name = name;
|
|
||||||
}
|
|
||||||
return keymap;
|
|
||||||
});
|
|
||||||
|
|
||||||
|
changedUserConfiguration.keymaps = insertItemInNameOrder(
|
||||||
|
state.keymaps,
|
||||||
|
newKeymap,
|
||||||
|
keymap => keymap.abbreviation !== newKeymap.abbreviation
|
||||||
|
);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case KeymapActions.EDIT_ABBR: {
|
case KeymapActions.EDIT_ABBR: {
|
||||||
@@ -163,7 +186,7 @@ export function reducer(state = initialState, action: Action & { payload?: any }
|
|||||||
newMacro.isPrivate = true;
|
newMacro.isPrivate = true;
|
||||||
newMacro.macroActions = [];
|
newMacro.macroActions = [];
|
||||||
|
|
||||||
changedUserConfiguration.macros = state.macros.concat(newMacro);
|
changedUserConfiguration.macros = insertItemInNameOrder(state.macros, newMacro);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case MacroActions.DUPLICATE: {
|
case MacroActions.DUPLICATE: {
|
||||||
@@ -171,7 +194,7 @@ export function reducer(state = initialState, action: Action & { payload?: any }
|
|||||||
newMacro.name = generateName(state.macros, newMacro.name);
|
newMacro.name = generateName(state.macros, newMacro.name);
|
||||||
newMacro.id = generateMacroId(state.macros);
|
newMacro.id = generateMacroId(state.macros);
|
||||||
|
|
||||||
changedUserConfiguration.macros = state.macros.concat(newMacro);
|
changedUserConfiguration.macros = insertItemInNameOrder(state.macros, newMacro);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case MacroActions.EDIT_NAME: {
|
case MacroActions.EDIT_NAME: {
|
||||||
@@ -180,25 +203,52 @@ export function reducer(state = initialState, action: Action & { payload?: any }
|
|||||||
}
|
}
|
||||||
|
|
||||||
const name: string = action.payload.name.trim();
|
const name: string = action.payload.name.trim();
|
||||||
|
let macroToRename: Macro = null;
|
||||||
|
|
||||||
const duplicate = state.macros.some((macro: Macro) => {
|
const duplicate = state.macros.some((macro: Macro) => {
|
||||||
|
if (macro.id === action.payload.id) {
|
||||||
|
macroToRename = macro;
|
||||||
|
}
|
||||||
|
|
||||||
return macro.id !== action.payload.id && macro.name === name;
|
return macro.id !== action.payload.id && macro.name === name;
|
||||||
});
|
});
|
||||||
|
|
||||||
changedUserConfiguration.macros = state.macros.map((macro: Macro) => {
|
if (duplicate) {
|
||||||
macro = Object.assign(new Macro(), macro);
|
break;
|
||||||
if (!duplicate && macro.id === action.payload.id) {
|
}
|
||||||
macro.name = name;
|
|
||||||
}
|
|
||||||
|
|
||||||
return macro;
|
|
||||||
});
|
|
||||||
|
|
||||||
|
const newMacro = Object.assign(new Macro(), macroToRename, { name });
|
||||||
|
changedUserConfiguration.macros = insertItemInNameOrder(state.macros, newMacro, macro => macro.id !== newMacro.id);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case MacroActions.REMOVE:
|
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;
|
break;
|
||||||
|
|
||||||
case MacroActions.ADD_ACTION:
|
case MacroActions.ADD_ACTION:
|
||||||
changedUserConfiguration.macros = state.macros.map((macro: Macro) => {
|
changedUserConfiguration.macros = state.macros.map((macro: Macro) => {
|
||||||
if (macro.id === action.payload.id) {
|
if (macro.id === action.payload.id) {
|
||||||
@@ -263,6 +313,18 @@ export function reducer(state = initialState, action: Action & { payload?: any }
|
|||||||
break;
|
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:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -360,6 +422,27 @@ function generateMacroId(macros: Macro[]) {
|
|||||||
return newId + 1;
|
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[] {
|
function checkExistence(layers: Layer[], property: string, value: any): Layer[] {
|
||||||
const keyActionsToClear: {
|
const keyActionsToClear: {
|
||||||
layerIdx: number,
|
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 './html-helper';
|
||||||
export * from './validators';
|
export * from './validators';
|
||||||
export * from './version-helper';
|
export * from './version-helper';
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { cloneDeep } from 'lodash';
|
import cloneDeep from 'lodash-es/cloneDeep';
|
||||||
|
|
||||||
const defaultUserConfig = {
|
const defaultUserConfig = {
|
||||||
userConfigMajorVersion: 3,
|
userConfigMajorVersion: 3,
|
||||||
|
|||||||
@@ -1,13 +1,11 @@
|
|||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
const path = require('path');
|
|
||||||
const uhk = require('./uhk');
|
const uhk = require('./uhk');
|
||||||
const device = uhk.getUhkDevice();
|
const device = uhk.getUhkDevice();
|
||||||
|
|
||||||
let buffer = new Buffer(uhk.pushUint32([uhk.usbCommands.getDeviceProperty, uhk.devicePropertyIds.i2cBaudRate]));
|
(async function() {
|
||||||
//console.log(buffer);
|
let response = await uhk.writeDevice(device, [uhk.usbCommands.getDeviceProperty, uhk.devicePropertyIds.i2cBaudRate]);
|
||||||
device.write(uhk.getTransferData(buffer));
|
let requestedBaudRate = uhk.getUint32(response, 2);
|
||||||
let response = device.readSync();
|
let actualBaudRate = uhk.getUint32(response, 6);
|
||||||
//console.log(Buffer.from(response));
|
let i2c0F = response[1].toString(2).padStart(8, '0');
|
||||||
let requestedBaudRate = uhk.getUint32(response, 2);
|
console.log(`requestedBaudRate:${requestedBaudRate} | actualBaudRate:${actualBaudRate} | I2C0_F:0b${i2c0F}`)
|
||||||
let actualBaudRate = uhk.getUint32(response, 6);
|
})();
|
||||||
console.log(`requestedBaudRate:${requestedBaudRate} | actualBaudRate:${actualBaudRate} | I2C0_F:0b${response[1].toString(2).padStart(8, '0')}`)
|
|
||||||
|
|||||||
@@ -51,13 +51,13 @@ function convertMs(milliseconds) {
|
|||||||
|
|
||||||
const device = uhk.getUhkDevice();
|
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 response = device.readSync();
|
||||||
let uptimeMs = uhk.getUint32(response, 1);
|
let uptimeMs = uhk.getUint32(response, 1);
|
||||||
let uptime = convertMs(uptimeMs);
|
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')}`)
|
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();
|
response = device.readSync();
|
||||||
let requestedBaudRate = uhk.getUint32(response, 2);
|
let requestedBaudRate = uhk.getUint32(response, 2);
|
||||||
let actualBaudRate = uhk.getUint32(response, 6);
|
let actualBaudRate = uhk.getUint32(response, 6);
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ function convertMs(milliseconds) {
|
|||||||
return {days, hours, minutes, seconds};
|
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);
|
//console.log(buffer);
|
||||||
device.write(uhk.getTransferData(buffer));
|
device.write(uhk.getTransferData(buffer));
|
||||||
let response = device.readSync();
|
let response = device.readSync();
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ program
|
|||||||
const moduleSlot = program.args[0];
|
const moduleSlot = program.args[0];
|
||||||
const moduleSlotId = uhk.checkModuleSlot(moduleSlot, uhk.moduleSlotToId);
|
const moduleSlotId = uhk.checkModuleSlot(moduleSlot, uhk.moduleSlotToId);
|
||||||
const device = uhk.getUhkDevice();
|
const device = uhk.getUhkDevice();
|
||||||
let transfer = new Buffer([uhk.usbCommands.jumpToModuleBootloader, moduleSlotId]);
|
|
||||||
device.write(uhk.getTransferData(transfer));
|
(async function() {
|
||||||
const response = Buffer.from(device.readSync());
|
await uhk.jumpToModuleBootloader(device, moduleSlotId);
|
||||||
|
})();
|
||||||
|
|||||||
@@ -1,52 +1,10 @@
|
|||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
const HID = require('node-hid');
|
const uhk = require('./uhk');
|
||||||
let uhk = require('./uhk');
|
const program = require('commander');
|
||||||
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;
|
|
||||||
|
|
||||||
|
program.parse(process.argv);
|
||||||
const enumerationMode = program.args[0];
|
const enumerationMode = program.args[0];
|
||||||
const enumerationModeId = uhk.enumerationModes[enumerationMode];
|
|
||||||
|
|
||||||
if (enumerationModeId === undefined) {
|
(async function() {
|
||||||
const enumerationModes = Object.keys(uhk.enumerationModes).join(', ');
|
await uhk.reenumerate(enumerationMode);
|
||||||
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);
|
|
||||||
|
|||||||
@@ -33,6 +33,6 @@ if (kbootCommand !== 'idle') {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const device = uhk.getUhkDevice();
|
const device = uhk.getUhkDevice();
|
||||||
let transfer = new Buffer([uhk.usbCommands.sendKbootCommandToModule, kbootCommandId, parseInt(i2cAddress)]);
|
(async function() {
|
||||||
device.write(uhk.getTransferData(transfer));
|
await uhk.sendKbootCommandToModule(device, kbootCommandId, i2cAddress);
|
||||||
const response = Buffer.from(device.readSync());
|
})();
|
||||||
|
|||||||
@@ -17,8 +17,7 @@ You're free to use any value in between and test the results.`);
|
|||||||
}
|
}
|
||||||
|
|
||||||
let bps = process.argv[2];
|
let bps = process.argv[2];
|
||||||
let buffer = new Buffer(uhk.pushUint32([uhk.usbCommands.setI2cBaudRate], +bps));
|
|
||||||
//console.log(buffer);
|
(async function() {
|
||||||
device.write(uhk.getTransferData(buffer));
|
await uhk.writeDevice(device, [uhk.usbCommands.setI2cBaudRate, ...uhk.uint32ToArray(+bps)]);
|
||||||
let response = device.readSync();
|
})();
|
||||||
//console.log(Buffer.from(response));
|
|
||||||
|
|||||||
@@ -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];
|
|
||||||
});
|
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
const util = require('util');
|
||||||
const HID = require('node-hid');
|
const HID = require('node-hid');
|
||||||
// const debug = process.env.DEBUG;
|
// const debug = process.env.DEBUG;
|
||||||
const debug = true;
|
const debug = true;
|
||||||
@@ -22,12 +23,16 @@ function getUint32(buffer, offset) {
|
|||||||
return (buffer[offset]) + (buffer[offset+1] * 2**8) + (buffer[offset+2] * 2**16) + (buffer[offset+3] * 2**24);
|
return (buffer[offset]) + (buffer[offset+1] * 2**8) + (buffer[offset+2] * 2**16) + (buffer[offset+3] * 2**24);
|
||||||
}
|
}
|
||||||
|
|
||||||
function pushUint32(array, value) {
|
function uint32ToArray(value) {
|
||||||
array.push((value >> 0) & 0xff);
|
return [(value >> 0) & 0xff, (value >> 8) & 0xff, (value >> 16) & 0xff, (value >> 24) & 0xff];
|
||||||
array.push((value >> 8) & 0xff);
|
}
|
||||||
array.push((value >> 16) & 0xff);
|
|
||||||
array.push((value >> 24) & 0xff);
|
function writeDevice(device, data, options={}) {
|
||||||
return array;
|
device.write(getTransferData(new Buffer(data)));
|
||||||
|
if (options.noRead) {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
return util.promisify(device.read.bind(device))();
|
||||||
}
|
}
|
||||||
|
|
||||||
function getUhkDevice() {
|
function getUhkDevice() {
|
||||||
@@ -60,6 +65,60 @@ function getBootloaderDevice() {
|
|||||||
return foundDevice;
|
return foundDevice;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
let configBufferIds = {
|
let configBufferIds = {
|
||||||
hardwareConfig: 0,
|
hardwareConfig: 0,
|
||||||
stagingUserConfig: 1,
|
stagingUserConfig: 1,
|
||||||
@@ -71,15 +130,157 @@ let eepromOperations = {
|
|||||||
write: 1,
|
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) {
|
||||||
|
return await uhk.writeDevice(device, [uhk.usbCommands.sendKbootCommandToModule, kbootCommandId, parseInt(i2cAddress)])
|
||||||
|
};
|
||||||
|
|
||||||
|
async function jumpToModuleBootloader(device, moduleSlotId) {
|
||||||
|
await uhk.writeDevice(device, [uhk.usbCommands.jumpToModuleBootloader, moduleSlotId]);
|
||||||
|
};
|
||||||
|
|
||||||
|
async function waitForKbootIdle(device) {
|
||||||
|
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 uhk.sendKbootCommandToModule(device, uhk.kbootCommands.idle, i2cAddress);
|
||||||
|
device.close();
|
||||||
|
config.verbose = false;
|
||||||
|
echo('Firmware updated successfully.');
|
||||||
|
};
|
||||||
|
|
||||||
|
uhk = exports = module.exports = moduleExports = {
|
||||||
bufferToString,
|
bufferToString,
|
||||||
getUint16,
|
getUint16,
|
||||||
getUint32,
|
getUint32,
|
||||||
pushUint32,
|
uint32ToArray,
|
||||||
|
writeDevice,
|
||||||
getUhkDevice,
|
getUhkDevice,
|
||||||
getBootloaderDevice,
|
getBootloaderDevice,
|
||||||
getTransferData,
|
getTransferData,
|
||||||
checkModuleSlot,
|
checkModuleSlot,
|
||||||
|
checkFirmwareImage,
|
||||||
|
getBlhostCmd,
|
||||||
|
execRetry,
|
||||||
|
updateDeviceFirmware,
|
||||||
|
reenumerate,
|
||||||
|
sendKbootCommandToModule,
|
||||||
|
jumpToModuleBootloader,
|
||||||
|
waitForKbootIdle,
|
||||||
|
updateModuleFirmware,
|
||||||
usbCommands: {
|
usbCommands: {
|
||||||
getDeviceProperty : 0x00,
|
getDeviceProperty : 0x00,
|
||||||
reenumerate : 0x01,
|
reenumerate : 0x01,
|
||||||
@@ -175,20 +376,11 @@ function convertBufferToIntArray(buffer) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getTransferData(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:
|
// From HID API documentation:
|
||||||
// http://www.signal11.us/oss/hidapi/hidapi/doxygen/html/group__API.html#gad14ea48e440cf5066df87cc6488493af
|
// http://www.signal11.us/oss/hidapi/hidapi/doxygen/html/group__API.html#gad14ea48e440cf5066df87cc6488493af
|
||||||
// The first byte of data[] must contain the Report ID.
|
// The first byte of data[] must contain the Report ID.
|
||||||
// For devices which only support a single report, this must be set to 0x0.
|
// For devices which only support a single report, this must be set to 0x0.
|
||||||
data.unshift(0)
|
return [0, ...convertBufferToIntArray(buffer)];
|
||||||
|
|
||||||
return data
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function readLog(buffer) {
|
function readLog(buffer) {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ const program = require('commander');
|
|||||||
const tmp = require('tmp');
|
const tmp = require('tmp');
|
||||||
const decompress = require('decompress');
|
const decompress = require('decompress');
|
||||||
const decompressTarbz = require('decompress-tarbz2');
|
const decompressTarbz = require('decompress-tarbz2');
|
||||||
|
const uhk = require('./uhk')
|
||||||
require('shelljs/global');
|
require('shelljs/global');
|
||||||
|
|
||||||
(async function() {
|
(async function() {
|
||||||
@@ -28,9 +29,11 @@ require('shelljs/global');
|
|||||||
firmwarePath = tmpObj.name;
|
firmwarePath = tmpObj.name;
|
||||||
}
|
}
|
||||||
config.verbose = true;
|
config.verbose = true;
|
||||||
exec(`${__dirname}/update-device-firmware.js ${firmwarePath}/devices/uhk60-right/firmware.hex`);
|
console.log('Updating right firmware');
|
||||||
exec(`${__dirname}/reenumerate.js normalKeyboard`);
|
await uhk.updateDeviceFirmware(`${firmwarePath}/devices/uhk60-right/firmware.hex`, 'hex');
|
||||||
exec(`${__dirname}/update-module-firmware.js leftHalf ${firmwarePath}/modules/uhk60-left.bin`);
|
await uhk.reenumerate('normalKeyboard');
|
||||||
|
console.log('Updating left firmware');
|
||||||
|
await uhk.updateModuleFirmware(uhk.moduleSlotToI2cAddress.leftHalf, uhk.moduleSlotToId.leftHalf, `${firmwarePath}/modules/uhk60-left.bin`);
|
||||||
|
|
||||||
if (program.overwriteUserConfig) {
|
if (program.overwriteUserConfig) {
|
||||||
exec(`${__dirname}/write-config.js ${firmwarePath}/devices/uhk60-right/config.bin`);
|
exec(`${__dirname}/write-config.js ${firmwarePath}/devices/uhk60-right/config.bin`);
|
||||||
@@ -39,7 +42,7 @@ require('shelljs/global');
|
|||||||
}
|
}
|
||||||
|
|
||||||
config.verbose = false;
|
config.verbose = false;
|
||||||
} catch(exception) {
|
} catch (exception) {
|
||||||
console.error(exception.message);
|
console.error(exception.message);
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,27 +2,13 @@
|
|||||||
const uhk = require('./uhk');
|
const uhk = require('./uhk');
|
||||||
const program = require('commander');
|
const program = require('commander');
|
||||||
require('shelljs/global');
|
require('shelljs/global');
|
||||||
require('./shared')
|
|
||||||
|
|
||||||
const extension = '.hex';
|
|
||||||
config.fatal = true;
|
config.fatal = true;
|
||||||
|
const extension = '.hex';
|
||||||
|
|
||||||
program
|
program
|
||||||
.usage(`firmwareImage${extension}`)
|
.usage(`firmwareImage${extension}`)
|
||||||
.parse(process.argv)
|
.parse(process.argv)
|
||||||
|
|
||||||
const firmwareImage = program.args[0];
|
const firmwareImage = program.args[0];
|
||||||
const usbDir = `${__dirname}`;
|
uhk.updateDeviceFirmware(firmwareImage, extension);
|
||||||
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.');
|
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
const uhk = require('./uhk');
|
const uhk = require('./uhk');
|
||||||
const program = require('commander');
|
const program = require('commander');
|
||||||
require('shelljs/global');
|
require('shelljs/global');
|
||||||
require('./shared');
|
|
||||||
|
|
||||||
const extension = '.bin';
|
const extension = '.bin';
|
||||||
config.fatal = true;
|
config.fatal = true;
|
||||||
@@ -12,27 +11,12 @@ program
|
|||||||
.parse(process.argv)
|
.parse(process.argv)
|
||||||
|
|
||||||
let moduleSlot = program.args[0];
|
let moduleSlot = program.args[0];
|
||||||
|
const moduleSlotId = uhk.checkModuleSlot(moduleSlot, uhk.moduleSlotToId);
|
||||||
const i2cAddress = uhk.checkModuleSlot(moduleSlot, uhk.moduleSlotToI2cAddress);
|
const i2cAddress = uhk.checkModuleSlot(moduleSlot, uhk.moduleSlotToI2cAddress);
|
||||||
|
|
||||||
const firmwareImage = program.args[1];
|
const firmwareImage = program.args[1];
|
||||||
checkFirmwareImage(firmwareImage, extension);
|
uhk.checkFirmwareImage(firmwareImage, extension);
|
||||||
|
|
||||||
const usbDir = `${__dirname}`;
|
(async function() {
|
||||||
const blhostUsb = getBlhostCmd(uhk.enumerationNameToProductId.buspal);
|
await uhk.updateModuleFirmware(i2cAddress, moduleSlotId, firmwareImage);
|
||||||
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.');
|
|
||||||
|
|||||||
@@ -1,22 +1,7 @@
|
|||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
const uhk = require('./uhk');
|
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();
|
const device = uhk.getUhkDevice();
|
||||||
|
|
||||||
getCurrentKbootCommand();
|
(async function() {
|
||||||
|
await uhk.waitForKbootIdle(device);
|
||||||
setInterval(() => {
|
})();
|
||||||
getCurrentKbootCommand();
|
|
||||||
}, 500);
|
|
||||||
|
|||||||
@@ -110,7 +110,7 @@ if (TEST_BUILD || gitTag) {
|
|||||||
directories: {
|
directories: {
|
||||||
app: electron_build_folder
|
app: electron_build_folder
|
||||||
},
|
},
|
||||||
appId: 'com.ultimategadgetlabs.uhk.agent',
|
appId: 'com.ultimategadgetlabs.agent',
|
||||||
productName: 'UHK Agent',
|
productName: 'UHK Agent',
|
||||||
mac: {
|
mac: {
|
||||||
category: 'public.app-category.utilities',
|
category: 'public.app-category.utilities',
|
||||||
|
|||||||
Reference in New Issue
Block a user