58 Commits

Author SHA1 Message Date
László Monda
e84dbf2c15 Bump Agent version to 1.1.2. Bump firmware version to 8.1.4. Update changelog. 2018-03-09 19:02:46 +01:00
Róbert Kiss
990ff8e980 ci: mac code sign (#585)
* ci: register certificate into the mac keychain

* try to not use yarn electron-builder installer on mac

* use -P

* debug electron-osx-sign

* use cscLink

* use xcode xcode9.3beta

* increase package.json version

* revert version to 1.1.1

* delete unused files

* format release.js file

* format release.js file

* format release.js file
2018-03-09 18:31:48 +01:00
Róbert Kiss
1ca8e67e52 try to use xcode 9.2 (#584) 2018-03-08 18:09:17 +01:00
Róbert Kiss
23cb583bf7 chore: upgrade lerna => 2.9.0 (#583) 2018-03-06 00:34:06 +01:00
Róbert Kiss
d5cc735b85 build: Windows code sign (#581)
* build: Add windows certificate

* temporary bump version

* change CSC_LINK settings

* rdp connection

* remove appveyor RDP block
2018-03-01 17:29:55 +01:00
László Monda
1981311136 Merge branch 'master' of github.com:UltimateHackingKeyboard/agent 2018-02-28 00:31:48 +01:00
László Monda
bbb5d4a35b Add a notice regarding the lack of robustness of the firmware update process. 2018-02-28 00:31:06 +01:00
serge-rosov
58ef40fb02 year correction (#582) 2018-02-27 18:11:46 +01:00
Róbert Kiss
b8f35df155 build: switch off mac codesign 2018-02-24 23:33:22 +01:00
Róbert Kiss
c3e712851c build: Sign mac installer 2018-02-24 23:12:01 +01:00
László Monda
2eaa1e0634 Update package-lock.json 2018-02-19 17:11:16 +01:00
László Monda
6ee21bcd7a Fix get-debug-info.js 2018-02-18 03:44:17 +01:00
Róbert Kiss
10ae68ad4b fix(keymap): Fix undefined keymap serialization (#573) 2018-02-17 18:07:33 +01:00
László Monda
02044ae1d0 Bump version to 1.1.1 and update changelog. 2018-02-13 03:54:28 +01:00
László Monda
3f99d47bba Use firmware 8.1.2 2018-02-13 03:40:52 +01:00
László Monda
9beadb4aac Add keymap descriptions. 2018-02-13 03:24:06 +01:00
László Monda
d9fb7a4b42 Merge branch 'master' of github.com:UltimateHackingKeyboard/agent 2018-02-13 03:05:18 +01:00
László Monda
83912ec21f Assign "switch to test keymap" action on all keymaps. 2018-02-13 03:04:36 +01:00
Róbert Kiss
6c7232a5ba feat(device): Make Agent able to unbrick bricked modules (#577) 2018-02-13 02:11:27 +01:00
László Monda
65fc8b5efb Tweak the scancode related help text. 2018-02-12 15:36:39 +01:00
Róbert Kiss
7a64191955 fix(config): Save reset user configuration in web module (#574) 2018-02-11 20:44:08 +01:00
Róbert Kiss
1a413c824e Fix 560 delete bind play macro action when macro delete (#576)
* fix(config): delete KeyAction binding of deleted macro

* refactor: use sorter import

* fix(macro): read the macro id from route params

* fix(keyAction): use NoneAction in keyAction mapping
2018-02-11 20:12:12 +01:00
Róbert Kiss
e545c9d67b feat(popover): Add Non-US help tooltip (#578) 2018-02-11 19:47:41 +01:00
László Monda
8650fef7ae Make reenumerate() more reliable. 2018-01-31 04:26:49 +01:00
László Monda
5c618869a2 Use updateDeviceFirmware(), reenumerate(), and updateModuleFirmware() in update-all-firmwares.js instead of forking processes. 2018-01-31 03:40:46 +01:00
László Monda
1b8d6949e0 Extract updateModuleFirmware() to uhk.js 2018-01-31 03:03:34 +01:00
László Monda
aabc0a8746 Use waitForKbootIdle() in update-module-firmware.js 2018-01-31 02:28:29 +01:00
László Monda
9589398834 Move waitForKbootIdle() to uhk.js 2018-01-31 02:25:37 +01:00
László Monda
933a715ea5 Extract waitForKbootIdle() 2018-01-31 02:22:41 +01:00
László Monda
df14e2d569 Extract uhk.jumpToModuleBootloader() and use it. 2018-01-31 01:17:28 +01:00
László Monda
4f8a0247d3 Call uhk.sendKbootCommandToModule() instead of forking send-kboot-command-to-module.js 2018-01-31 00:57:53 +01:00
László Monda
85ec5f6b6a Make update-module-firmware.js use reenumerate() and sendKbootCommandToModule() instead of forking more processes. 2018-01-31 00:17:27 +01:00
László Monda
739b830f47 Use uhk.writeDevice() in jump-to-module-bootloader.js 2018-01-31 00:09:16 +01:00
László Monda
482cff3d3b Extract sendKbootCommandToModule() from send-kboot-command-to-module.js to uhk.js 2018-01-30 23:44:25 +01:00
László Monda
9284ae5032 Use reenumerate() instead of forking reenumerate.js 2018-01-30 23:32:37 +01:00
László Monda
cac6fdc190 Move updateDeviceFirmware() from update-device-firmware.js to uhk.js 2018-01-30 21:48:42 +01:00
László Monda
ca9bf60a1b Extract updateDeviceFirmware() 2018-01-30 21:46:39 +01:00
László Monda
bb7edb8e4d Remove shared.js 2018-01-30 21:39:05 +01:00
László Monda
0d9c976eb8 Move execRetry() from shared.js to uhk.js 2018-01-30 21:37:27 +01:00
László Monda
288d4f75b6 Move getBlhostCmd() from shared.js to uhk.js 2018-01-30 21:35:35 +01:00
László Monda
73e07eae2d Move checkFirmwareImage() from shared.js to uhk.js 2018-01-30 21:28:00 +01:00
László Monda
8e620caac5 Use reenumerate() in update-device-firmware.js instead of forking reenumerate.js 2018-01-30 20:39:48 +01:00
László Monda
0d4e1acf76 Extract reenumerate() from reenumerate.js to uhk.js 2018-01-30 19:34:29 +01:00
László Monda
88c42d58b1 Extract reenumerate() as an async function in reenumerate.js 2018-01-30 18:36:16 +01:00
László Monda
5099e904fc Use uhk.writeDevice() in set-i2c-baud-rate.js 2018-01-30 17:30:12 +01:00
László Monda
5476f7c3a5 Extract I2C0_F. 2018-01-30 17:25:29 +01:00
László Monda
e0bb0bcca3 Add the async uhk.writeDevice() and use it in get-i2c-baud-rate.js 2018-01-30 17:21:50 +01:00
László Monda
124c3ec29b Simplify reenumerate.js mainly by using uint32ToArray() 2018-01-30 06:09:09 +01:00
László Monda
2310320b8a Replace pushUint32() with uint32ToArray() and don't use it where not needed. 2018-01-30 05:30:32 +01:00
László Monda
67346b4cda Simplify getTransferData() 2018-01-30 05:10:29 +01:00
Róbert Kiss
662ca0152f fix(keymap): Don't show keymap description in the key action popover (#572) 2018-01-29 23:40:41 +01:00
Róbert Kiss
6358528438 ci: use node 8.9.4 (#571)
Node 8.9.4 contain a few security update and use npm 5.6.0 so not need to
install npm in the build process. The build time will a few sec sorter
2018-01-29 23:20:07 +01:00
József Farkas
02f1053d46 feat(popover): sort keymaps and macros alphabetically (#534)
* feat(popover): sort keymaps and macros alphabetically

Closes #512

* small performance refactoring
2018-01-29 23:15:21 +01:00
Róbert Kiss
a44a7dc5f8 chore: use lodash-es version (#569)
lodash-es support tree shaking and the bundle size will near 2MB less
2018-01-29 23:02:17 +01:00
Róbert Kiss
02d57fdabf feat(keymap): add description to keymap (#559)
* feat(keymap): add description to keymaps

* add new feature request

* preserve new lines
2018-01-29 22:54:29 +01:00
Róbert Kiss
38f6688930 chore: upgrade electron => 1.7.11 (#568) 2018-01-29 21:26:32 +01:00
Róbert Kiss
6ca12d0ccd build: set Apple appId => com.ultimategadgetlabs.agent (#566) 2018-01-23 19:19:15 +01:00
László Monda
acd17ac657 Fix the background color of the toplevel device node. Fixes #552 2018-01-22 04:41:43 +01:00
65 changed files with 8241 additions and 6371 deletions

2
.nvmrc
View File

@@ -1 +1 @@
8.9.1
8.9.4

View File

@@ -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:

View File

@@ -4,7 +4,32 @@ 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.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

View File

@@ -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%

2368
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -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.2",
"firmwareVersion": "8.1.4",
"deviceProtocolVersion": "4.2.0",
"userConfigVersion": "4.0.0",
"hardwareConfigVersion": "1.0.0",
@@ -36,18 +36,18 @@
"decompress": "4.2.0",
"decompress-tarbz2": "^4.1.1",
"devtron": "1.4.0",
"electron": "1.7.5",
"electron-builder": "19.45.5",
"electron": "1.7.11",
"electron-builder": "20.4.0",
"electron-debug": "1.4.0",
"electron-devtools-installer": "2.2.0",
"electron-log": "2.2.9",
"electron-rebuild": "1.6.0",
"electron-rebuild": "1.7.3",
"electron-settings": "3.1.2",
"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",

View File

@@ -7,6 +7,7 @@ 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';
export class Helper {
@@ -77,6 +78,8 @@ 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}"`;
}

View File

@@ -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);
}
});
}

View File

@@ -39,7 +39,8 @@ export enum ConfigBufferId {
export enum DevicePropertyIds {
DeviceProtocolVersion = 0,
ProtocolVersions = 1,
ConfigSizes = 2
ConfigSizes = 2,
CurrentKbootCommand = 3
}
export enum EnumerationModes {

View File

@@ -5,7 +5,12 @@ 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 +47,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 +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.
* @param {string} json - UserConfiguration in JSON format

File diff suppressed because it is too large Load Diff

View File

@@ -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",

View File

@@ -6,6 +6,14 @@
<i class="fa fa-sliders"></i>
<span>Firmware</span>
</h1>
<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 {{ (getAgentVersionInfo$ | async).firmwareVersion }} (bundled with Agent)
<button class="btn btn-primary"

View File

@@ -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>

View File

@@ -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;
}
}

View File

@@ -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 = () => {
}
}

View File

@@ -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

View File

@@ -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');

View File

@@ -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">

View File

@@ -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())

View File

@@ -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>

View File

@@ -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;
});

View File

@@ -7,6 +7,10 @@
[width]="200"
[options]="options"
></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>
</div>
<div class="modifier-options">

View File

@@ -10,6 +10,10 @@
position: relative;
top: 2px;
}
icon {
display: inline-block;
};
}
.modifier-options {

View File

@@ -164,12 +164,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;
}
}
}

View File

@@ -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);

View File

@@ -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

View File

@@ -1,5 +1,10 @@
:host {
display: flex;
display: block;
width: 100%;
position: relative;
}
editable-text {
padding-left: 2em;
padding-right: 2em;
display: block;
}

View File

@@ -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;

View File

@@ -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>

View File

@@ -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;

View File

@@ -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({

View File

@@ -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();
}
}

View File

@@ -0,0 +1,4 @@
export interface ChangeKeymapDescription {
abbr: string;
description: string;
}

File diff suppressed because it is too large Load Diff

View File

@@ -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,

View File

@@ -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

View File

@@ -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) {
}

View File

@@ -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$

View File

@@ -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>) { }

View File

@@ -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

View File

@@ -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:

View File

@@ -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,

View 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];
}

View File

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

View File

@@ -1,4 +1,4 @@
import { cloneDeep } from 'lodash';
import cloneDeep from 'lodash-es/cloneDeep';
const defaultUserConfig = {
userConfigMajorVersion: 3,

View File

@@ -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());

View File

@@ -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}`)
})();

View File

@@ -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);

View File

@@ -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();

View File

@@ -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);
})();

View File

@@ -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);
})();

View File

@@ -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);
})();

View File

@@ -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)]);
})();

View File

@@ -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];
});

View File

@@ -1,3 +1,4 @@
const util = require('util');
const HID = require('node-hid');
// const debug = process.env.DEBUG;
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);
}
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={}) {
device.write(getTransferData(new Buffer(data)));
if (options.noRead) {
return Promise.resolve();
}
return util.promisify(device.read.bind(device))();
}
function getUhkDevice() {
@@ -60,6 +65,60 @@ function getBootloaderDevice() {
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 = {
hardwareConfig: 0,
stagingUserConfig: 1,
@@ -71,15 +130,157 @@ let eepromOperations = {
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,
getUint16,
getUint32,
pushUint32,
uint32ToArray,
writeDevice,
getUhkDevice,
getBootloaderDevice,
getTransferData,
checkModuleSlot,
checkFirmwareImage,
getBlhostCmd,
execRetry,
updateDeviceFirmware,
reenumerate,
sendKbootCommandToModule,
jumpToModuleBootloader,
waitForKbootIdle,
updateModuleFirmware,
usbCommands: {
getDeviceProperty : 0x00,
reenumerate : 0x01,
@@ -175,20 +376,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) {

View File

@@ -3,6 +3,7 @@ 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,9 +29,11 @@ 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`);
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`);
if (program.overwriteUserConfig) {
exec(`${__dirname}/write-config.js ${firmwarePath}/devices/uhk60-right/config.bin`);

View File

@@ -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);

View File

@@ -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);
})();

View File

@@ -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
scripts/certs/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
windows-cert.p12
mac-cert.p12

Binary file not shown.

Binary file not shown.

View File

@@ -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);

View File

@@ -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