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

* add write-hca.js

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

* refactor: Move getTransferBuffers into the uhk-usb package

* refactor: delete obsoleted classes

* build: add uhk-usb build command

* refactor: move eeprom transfer to uhk-usb package

* fix: Fix write-hca.js

* feat: load hardware config from the device and

* style: fix ts lint errors

* build: fix rxjs dependency resolve

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

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

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

* feat(usb): Create USB logger

* style: Fix type

* build: Add chalk to dependencies.

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

View File

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

View File

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

Before

Width:  |  Height:  |  Size: 511 B

After

Width:  |  Height:  |  Size: 659 B

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -6,6 +6,7 @@
[capturingEnabled]="popoverEnabled"
[selectedKey]="selectedKey"
[halvesSplit]="halvesSplit"
[keyboardLayout]="keyboardLayout"
(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)"

View File

@@ -17,26 +17,27 @@ import 'rxjs/add/observable/of';
import 'rxjs/add/operator/map';
import { Store } from '@ngrx/store';
import { MapperService } from '../../../services/mapper.service';
import {
camelCaseToSentence,
capitalizeFirstLetter,
KeyAction,
Keymap,
KeystrokeAction,
Layer,
LayerName,
LongPressAction,
MouseAction,
MouseActionParam,
PlayMacroAction,
SwitchKeymapAction,
SwitchLayerAction
} from '../../../config-serializer/config-items/key-action';
import { Keymap } from '../../../config-serializer/config-items/keymap';
import { Layer } from '../../../config-serializer/config-items/layer';
import { LongPressAction } from '../../../config-serializer/config-items/long-press-action';
import { camelCaseToSentence, capitalizeFirstLetter } from 'uhk-common';
} from 'uhk-common';
import { MapperService } from '../../../services/mapper.service';
import { AppState } from '../../../store';
import { KeymapActions } from '../../../store/actions';
import { PopoverComponent } from '../../popover';
import { KeyboardLayout } from '../../../keyboard/keyboard-layout.enum';
interface NameValuePair {
name: string;
@@ -54,6 +55,7 @@ export class SvgKeyboardWrapComponent implements OnInit, OnChanges {
@Input() popoverEnabled: boolean = true;
@Input() tooltipEnabled: boolean = false;
@Input() halvesSplit: boolean;
@Input() keyboardLayout: KeyboardLayout.ANSI;
@ViewChild(PopoverComponent, { read: ElementRef }) popover: ElementRef;

View File

@@ -1,85 +0,0 @@
# Configuration serializer
This directory contains the configuration serializer of Agent.
The configuration of the UHK is unusually complex for a keyboard, and is composed of a number of items of different types, including keymaps, layers, macros, and the like. This is a supposed to be a short guide for the aspiring hacker. Let's get right into it!
## Setup
Given that the development dependencies are installed on your system you should be able to build the configuration serializer tester by executing `npm run build:test` in this directory, then start the test by running `node test-serializer.js`.
## Configuration representations
There are 3 different representations of the configuration, each filling a specific purpose.
The **JavaScript representation** is optimally suited to be serialized as JSON, and saved to the file system, or transmitted over the network. As a plaintext format, it's human-readable and easily editable. See [user-config.json](user-config.json) for an example configuration.
The **TypeScript representation** is structurally similar to the JavaScript representation, but it features strongly typed TypeScript objects instead of typeless JavaScript objects. This representation is meant to be used by Agent. Extensive, per-property [assertion](assert.ts) takes place upon initializing the TypeScript objects to ensure the integrity of the configuration.
The **binary representation** is meant to be written to, and read from the EEPROM of the UHK. It's designed to be very compact in order to maximize the use of the 32kbyte EEPROM space.
## Configuration item types
Each configuration item belongs to a specific type. The following types are available:
**Primitive types** are integers of different sizes, and string. See [UhkBuffer](UhkBuffer.ts) which implements all the primitive types.
**Compound types** are composed of primitive types, and/or compound types. All compound types are saved into the [config-items](config-items) directory.
## Dumping serialization
The serialization of configuration items is a complicated business, and many things can go wrong. That's the exact reason why serialization can be dumped to ease debugging. All you have to do is to set `Serializable.enableDump` to `true`, and you'll see something like the following upon serialization actions:
```
KeyActions.fromJsObject: [{"keyActionType":"none"},{"keyActionType":"keystroke","scancode":110},{"keyActionType":"keystrokeModifiers","modifierMask":33},{"keyActionType":"keystrokeWithM...
NoneAction.fromJsObject: {"keyActionType":"none"} => <NoneAction>
KeystrokeAction.fromJsObject: {"keyActionType":"keystroke","scancode":110} => <KeystrokeAction scancode="110">
KeystrokeModifiersAction.fromJsObject: {"keyActionType":"keystrokeModifiers","modifierMask":33} => <KeystrokeModifiersAction modifierMask="33">
KeystrokeWithModifiersAction.fromJsObject: {"keyActionType":"keystrokeWithModifiers","scancode":120,"modifierMask":16} => <KeystrokeWithModifiersAction scancode="120" modifierMask="16">
SwitchLayerAction.fromJsObject: {"keyActionType":"switchLayer","layer":"fn","toggle":false} => <SwitchLayerAction layer="1" toggle="false">
DualRoleKeystrokeAction.fromJsObject: {"keyActionType":"dualRoleKeystroke","scancode":111,"longPressAction":"mod"} => <DualRoleKeystrokeAction scancode="111" longPressAction="8">
MouseAction.fromJsObject: {"keyActionType":"mouse","mouseAction":"scrollDown"} => <MouseAction mouseAction="8">
PlayMacroAction.fromJsObject: {"keyActionType":"playMacro","macroId":0} => <PlayMacroAction macroId="0">
SwitchKeymapAction.fromJsObject: {"keyActionType":"switchKeymap","keymapId":1} => <SwitchKeymapAction keymapId="1">
KeyActions.toBinary: <KeyActions length="9"> => ['u8(9)]
NoneAction.toBinary: <NoneAction> => ['u8(0)]
KeystrokeAction.toBinary: <KeystrokeAction scancode="110"> => ['u8(1), u8(110)]
KeystrokeModifiersAction.toBinary: <KeystrokeModifiersAction modifierMask="33"> => ['u8(2), u8(33)]
KeystrokeWithModifiersAction.toBinary: <KeystrokeWithModifiersAction scancode="120" modifierMask="16"> => ['u8(3), u8(120), u8(16)]
SwitchLayerAction.toBinary: <SwitchLayerAction layer="1" toggle="false"> => ['u8(5), u8(1)]
DualRoleKeystrokeAction.toBinary: <DualRoleKeystrokeAction scancode="111" longPressAction="8"> => ['u8(4), u8(111), u8(8)]
MouseAction.toBinary: <MouseAction mouseAction="8"> => ['u8(7), u8(8)]
PlayMacroAction.toBinary: <PlayMacroAction macroId="0"> => ['u8(8), u8(0)]
SwitchKeymapAction.toBinary: <SwitchKeymapAction keymapId="1"> => ['u8(6), u8(1)]
KeyActions.fromBinary: [u8(9)]
NoneAction.fromBinary: [u8(0)] => <NoneAction>
KeystrokeAction.fromBinary: [u8(1), u8(110)] => <KeystrokeAction scancode="110">
KeystrokeModifiersAction.fromBinary: [u8(2), u8(33)] => <KeystrokeModifiersAction modifierMask="33">
KeystrokeWithModifiersAction.fromBinary: [u8(3), u8(120), u8(16)] => <KeystrokeWithModifiersAction scancode="120" modifierMask="16">
SwitchLayerAction.fromBinary: [u8(5), u8(1)] => <SwitchLayerAction layer="1" toggle="false">
DualRoleKeystrokeAction.fromBinary: [u8(4), u8(111), u8(8)] => <DualRoleKeystrokeAction scancode="111" longPressAction="8">
MouseAction.fromBinary: [u8(7), u8(8)] => <MouseAction mouseAction="8">
PlayMacroAction.fromBinary: [u8(8), u8(0)] => <PlayMacroAction macroId="0">
SwitchKeymapAction.fromBinary: [u8(6), u8(1)] => <SwitchKeymapAction keymapId="1">
KeyActions.toJsObject: <KeyActions length="9">
NoneAction.toJsObject: <NoneAction> => {"keyActionType":"none"}
KeystrokeAction.toJsObject: <KeystrokeAction scancode="110"> => {"keyActionType":"keystroke","scancode":110}
KeystrokeModifiersAction.toJsObject: <KeystrokeModifiersAction modifierMask="33"> => {"keyActionType":"keystrokeModifiers","modifierMask":33}
KeystrokeWithModifiersAction.toJsObject: <KeystrokeWithModifiersAction scancode="120" modifierMask="16"> => {"keyActionType":"keystrokeWithModifiers","scancode":120,"modifierMask":16}
SwitchLayerAction.toJsObject: <SwitchLayerAction layer="1" toggle="false"> => {"keyActionType":"switchLayer","layer":"fn","toggle":false}
DualRoleKeystrokeAction.toJsObject: <DualRoleKeystrokeAction scancode="111" longPressAction="8"> => {"keyActionType":"dualRoleKeystroke","scancode":111,"longPressAction":"mod"}
MouseAction.toJsObject: <MouseAction mouseAction="8"> => {"keyActionType":"mouse","mouseAction":"scrollDown"}
PlayMacroAction.toJsObject: <PlayMacroAction macroId="0"> => {"keyActionType":"playMacro","macroId":0}
SwitchKeymapAction.toJsObject: <SwitchKeymapAction keymapId="1"> => {"keyActionType":"switchKeymap","keymapId":1}
```
## Testing the serializer
[test-serializer.ts](test-serializer.ts) is designed to test the serializer by taking [user-config.json](user-config.json), and transforming it to TypeScript representation, then to binary representation, then finally back to JavaScript representation. This should exercise every major code path.
If the testing is successful the following should be displayed:
```
JSON configurations are identical.
Binary configurations are identical.
```

View File

@@ -1,78 +0,0 @@
export function assertUInt8(target: any, key: string) {
return assertInteger(target, key, 0, 0xFF);
}
export function assertInt8(target: any, key: string) {
return assertInteger(target, key, -0x80, 0x7F);
}
export function assertUInt16(target: any, key: string) {
return assertInteger(target, key, 0, 0xFFFF);
}
export function assertInt16(target: any, key: string) {
return assertInteger(target, key, -0x8000, 0x7FFF);
}
export function assertUInt32(target: any, key: string) {
return assertInteger(target, key, 0, 0xFFFFFFFF);
}
export function assertInt32(target: any, key: string) {
return assertInteger(target, key, -0x80000000, 0x7FFFFFFF);
}
export function assertCompactLength(target: any, key: string) {
return assertUInt16(target, key);
}
function assertInteger(target: any, key: string, min: number, max: number) {
const priv = '_' + key;
function getter() {
return this[priv];
}
function setter(newVal: any) {
if (this[priv] !== newVal) {
if (newVal < min || newVal > max) {
throw `${target.constructor.name}.${key}: ` +
`Integer ${newVal} is outside the valid [${min}, ${max}] interval`;
}
this[priv] = newVal;
}
}
Object.defineProperty(target, key, {
get: getter,
set: setter,
enumerable: true,
configurable: true
});
}
export function assertEnum<E>(enumerated: E) {
return function(target: any, key: string) {
const priv = '_' + key;
function getter() {
return this[priv];
}
function setter(newVal: any) {
if (this[priv] !== newVal) {
if (enumerated[newVal] === undefined) {
throw `${target.constructor.name}.${key}: ${newVal} is not enum`;
}
this[priv] = newVal;
}
}
Object.defineProperty(target, key, {
get: getter,
set: setter,
enumerable: true,
configurable: true
});
};
}

View File

@@ -1,72 +0,0 @@
import { assertUInt8, assertUInt32 } from '../assert';
import { UhkBuffer } from '../uhk-buffer';
export class HardwareConfiguration {
signature: string;
@assertUInt8
dataModelVersion: number;
@assertUInt8
hardwareId: number;
@assertUInt8
brandId: number;
@assertUInt32
uuid: number;
isIso: boolean;
hasBacklighting: boolean;
fromJsonObject(jsonObject: any): HardwareConfiguration {
this.signature = jsonObject.signature;
this.dataModelVersion = jsonObject.dataModelVersion;
this.hardwareId = jsonObject.hardwareId;
this.brandId = jsonObject.brandId;
this.uuid = jsonObject.uuid;
this.isIso = jsonObject.isIso;
this.hasBacklighting = jsonObject.hasBacklighting;
return this;
}
fromBinary(buffer: UhkBuffer): HardwareConfiguration {
this.signature = buffer.readString();
this.dataModelVersion = buffer.readUInt16();
this.hardwareId = buffer.readUInt8();
this.uuid = buffer.readUInt32();
this.brandId = buffer.readUInt8();
this.isIso = buffer.readBoolean();
this.hasBacklighting = buffer.readBoolean();
return this;
}
toJsonObject(): any {
return {
signature: this.signature,
dataModelVersion: this.dataModelVersion,
hardwareId: this.hardwareId,
brandId: this.brandId,
uuid: this.uuid,
isIso: this.isIso,
hasBacklighting: this.hasBacklighting
};
}
toBinary(buffer: UhkBuffer): void {
buffer.writeString(this.signature);
buffer.writeUInt16(this.dataModelVersion);
buffer.writeUInt8(this.hardwareId);
buffer.writeUInt8(this.brandId);
buffer.writeUInt32(this.uuid);
buffer.writeBoolean(this.isIso);
buffer.writeBoolean(this.hasBacklighting);
}
toString(): string {
return `<HardwareConfiguration signature="${this.signature}">`;
}
}

View File

@@ -1,84 +0,0 @@
import { UhkBuffer } from '../../uhk-buffer';
import { Macro } from '../macro';
import { KeyAction, KeyActionId, keyActionType } from './key-action';
import { KeystrokeAction } from './keystroke-action';
import { SwitchLayerAction } from './switch-layer-action';
import { SwitchKeymapAction, UnresolvedSwitchKeymapAction } from './switch-keymap-action';
import { MouseAction } from './mouse-action';
import { PlayMacroAction } from './play-macro-action';
export class Helper {
static createKeyAction(source: KeyAction | UhkBuffer | any, macros?: Macro[]): KeyAction {
if (source instanceof KeyAction) {
return Helper.fromKeyAction(source);
} else if (source instanceof UhkBuffer) {
return Helper.fromUhkBuffer(source, macros);
} else {
return Helper.fromJSONObject(source, macros);
}
}
private static fromUhkBuffer(buffer: UhkBuffer, macros: Macro[]): KeyAction {
const keyActionFirstByte = buffer.readUInt8();
buffer.backtrack();
if (keyActionFirstByte >= KeyActionId.KeystrokeAction && keyActionFirstByte < KeyActionId.LastKeystrokeAction) {
return new KeystrokeAction().fromBinary(buffer);
}
switch (keyActionFirstByte) {
case KeyActionId.NoneAction:
buffer.readUInt8(); // Read type just to skip it
return undefined;
case KeyActionId.SwitchLayerAction:
return new SwitchLayerAction().fromBinary(buffer);
case KeyActionId.SwitchKeymapAction:
return new UnresolvedSwitchKeymapAction().fromBinary(buffer);
case KeyActionId.MouseAction:
return new MouseAction().fromBinary(buffer);
case KeyActionId.PlayMacroAction:
return new PlayMacroAction().fromBinary(buffer, macros);
default:
throw `Invalid KeyAction first byte: ${keyActionFirstByte}`;
}
}
private static fromKeyAction(keyAction: KeyAction): KeyAction {
let newKeyAction: KeyAction;
if (keyAction instanceof KeystrokeAction) {
newKeyAction = new KeystrokeAction(keyAction);
} else if (keyAction instanceof SwitchLayerAction) {
newKeyAction = new SwitchLayerAction(keyAction);
} else if (keyAction instanceof SwitchKeymapAction) {
newKeyAction = new SwitchKeymapAction(keyAction);
} else if (keyAction instanceof MouseAction) {
newKeyAction = new MouseAction(keyAction);
} else if (keyAction instanceof PlayMacroAction) {
newKeyAction = new PlayMacroAction(keyAction);
}
return newKeyAction;
}
private static fromJSONObject(keyAction: any, macros: Macro[]): KeyAction {
if (!keyAction) {
return;
}
switch (keyAction.keyActionType) {
case keyActionType.KeystrokeAction:
return new KeystrokeAction().fromJsonObject(keyAction);
case keyActionType.SwitchLayerAction:
return new SwitchLayerAction().fromJsonObject(keyAction);
case keyActionType.SwitchKeymapAction:
return new SwitchKeymapAction().fromJsonObject(keyAction);
case keyActionType.MouseAction:
return new MouseAction().fromJsonObject(keyAction);
case keyActionType.PlayMacroAction:
return new PlayMacroAction().fromJsonObject(keyAction, macros);
default:
throw `Invalid KeyAction.keyActionType: "${keyAction.keyActionType}"`;
}
}
}

View File

@@ -1,8 +0,0 @@
export * from './key-action';
export * from './keystroke-action';
export * from './mouse-action';
export * from './none-action';
export * from './play-macro-action';
export * from './switch-keymap-action';
export * from './switch-layer-action';
export * from './helper';

View File

@@ -1,65 +0,0 @@
import { Macro } from '../macro';
import { UhkBuffer } from '../../uhk-buffer';
import { UserConfiguration } from '../user-configuration';
export enum KeyActionId {
NoneAction = 0,
KeystrokeAction = 1,
/*
1 - 31 are reserved for KeystrokeAction
5 bits:
1: Do we have scancode?
2: Do we have modifiers?
3: Do we have longpress?
4-5: What kind of keystroke? (basic, short/long media, system)
*/
LastKeystrokeAction = 31, // TODO: remove this after refactoring the keyActionId check
SwitchLayerAction = 32,
SwitchKeymapAction = 33,
MouseAction = 34,
PlayMacroAction = 35
}
export let keyActionType = {
NoneAction : 'none',
KeystrokeAction : 'keystroke',
SwitchLayerAction : 'switchLayer',
SwitchKeymapAction : 'switchKeymap',
MouseAction : 'mouse',
PlayMacroAction : 'playMacro'
};
export abstract class KeyAction {
assertKeyActionType(jsObject: any): void {
const keyActionClassname: string = this.getName();
const keyActionTypeString: string = keyActionType[keyActionClassname];
if (jsObject.keyActionType !== keyActionTypeString) {
throw `Invalid ${keyActionClassname}.keyActionType: ${jsObject.keyActionType}`;
}
}
readAndAssertKeyActionId(buffer: UhkBuffer): KeyActionId {
const classname: string = this.getName();
const readKeyActionId: number = buffer.readUInt8();
const keyActionId: number = KeyActionId[classname];
if (keyActionId === KeyActionId.KeystrokeAction) {
if (readKeyActionId < KeyActionId.KeystrokeAction || readKeyActionId > KeyActionId.LastKeystrokeAction) {
throw `Invalid ${classname} first byte: ${readKeyActionId}`;
}
} else if (readKeyActionId !== keyActionId) {
throw `Invalid ${classname} first byte: ${readKeyActionId}`;
}
return readKeyActionId;
}
abstract toJsonObject(macros?: Macro[]): any;
abstract toBinary(buffer: UhkBuffer, userConfiguration?: UserConfiguration): void;
abstract getName(): string;
renameKeymap(oldAbbr: string, newAbbr: string): KeyAction {
return this;
}
}

View File

@@ -1,215 +0,0 @@
import { assertEnum, assertUInt8 } from '../../assert';
import { UhkBuffer } from '../../uhk-buffer';
import { KeyModifiers } from '../key-modifiers';
import { LongPressAction } from '../long-press-action';
import { KeyAction, KeyActionId, keyActionType } from './key-action';
import { KeystrokeType } from './keystroke-type';
export enum KeystrokeActionFlag {
scancode = 1 << 0,
modifierMask = 1 << 1,
longPressAction = 1 << 2
}
const KEYSTROKE_ACTION_FLAG_LENGTH = 3;
interface JsonObjectKeystrokeAction {
keyActionType: string;
scancode?: number;
modifierMask?: number;
longPressAction?: string;
type?: string;
}
const MODIFIERS = ['LCtrl', 'LShift', 'LAlt', 'LSuper', 'RCtrl', 'RShift', 'RAlt', 'RSuper'];
export class KeystrokeAction extends KeyAction {
set scancode(scancode: number) {
this._scancode = scancode;
if (this.type !== KeystrokeType.shortMedia && this.type !== KeystrokeType.longMedia) {
return;
}
this.type = scancode < 256 ? KeystrokeType.shortMedia : KeystrokeType.longMedia;
}
get scancode() {
return this._scancode;
}
@assertUInt8
modifierMask: number;
@assertEnum(LongPressAction)
longPressAction: LongPressAction;
set type(type: KeystrokeType) {
if (type === KeystrokeType.shortMedia || type === KeystrokeType.longMedia) {
type = this.scancode < 256 ? KeystrokeType.shortMedia : KeystrokeType.longMedia;
}
this._type = type;
}
get type() {
return this._type;
}
private _scancode: number;
@assertEnum(KeystrokeType)
private _type: KeystrokeType;
constructor(other?: KeystrokeAction) {
super();
if (!other) {
return;
}
this.type = other.type;
this._scancode = other._scancode;
this.modifierMask = other.modifierMask;
this.longPressAction = other.longPressAction;
}
fromJsonObject(jsonObject: JsonObjectKeystrokeAction): KeystrokeAction {
this.assertKeyActionType(jsonObject);
if (jsonObject.type === 'media') {
this.type = jsonObject.scancode < 256 ? KeystrokeType.shortMedia : KeystrokeType.longMedia;
} else {
this.type = KeystrokeType[jsonObject.type];
}
this._scancode = jsonObject.scancode;
this.modifierMask = jsonObject.modifierMask;
this.longPressAction = LongPressAction[jsonObject.longPressAction];
return this;
}
fromBinary(buffer: UhkBuffer): KeystrokeAction {
const keyActionId: KeyActionId = this.readAndAssertKeyActionId(buffer);
const flags: number = keyActionId - KeyActionId.NoneAction; // NoneAction is the same as an empty KeystrokeAction.
this.type = (flags >> 3) & 0b11;
if (flags & KeystrokeActionFlag.scancode) {
this._scancode = this.type === KeystrokeType.longMedia ? buffer.readUInt16() : buffer.readUInt8();
}
if (flags & KeystrokeActionFlag.modifierMask) {
this.modifierMask = buffer.readUInt8();
}
if (flags & KeystrokeActionFlag.longPressAction) {
this.longPressAction = buffer.readUInt8();
}
return this;
}
toJsonObject(): JsonObjectKeystrokeAction {
const jsonObject: JsonObjectKeystrokeAction = {
keyActionType: keyActionType.KeystrokeAction
};
if (this.type === KeystrokeType.shortMedia || this.type === KeystrokeType.longMedia) {
jsonObject.type = 'media';
} else {
jsonObject.type = KeystrokeType[this.type];
}
if (this.hasScancode()) {
jsonObject.scancode = this._scancode;
}
if (this.hasActiveModifier()) {
jsonObject.modifierMask = this.modifierMask;
}
if (this.hasLongPressAction()) {
jsonObject.longPressAction = LongPressAction[this.longPressAction];
}
return jsonObject;
}
toBinary(buffer: UhkBuffer) {
let flags = 0;
const toWrite: {
data: number,
long: boolean
}[] = [];
if (this.hasScancode()) {
flags |= KeystrokeActionFlag.scancode;
toWrite.push({ data: this._scancode, long: this.type === KeystrokeType.longMedia });
}
if (this.hasActiveModifier()) {
flags |= KeystrokeActionFlag.modifierMask;
toWrite.push({ data: this.modifierMask, long: false });
}
if (this.hasLongPressAction()) {
flags |= KeystrokeActionFlag.longPressAction;
toWrite.push({ data: this.longPressAction, long: false });
}
const TYPE_OFFSET = flags + (this.type << KEYSTROKE_ACTION_FLAG_LENGTH);
buffer.writeUInt8(KeyActionId.NoneAction + TYPE_OFFSET); // NoneAction is the same as an empty KeystrokeAction.
for (let i = 0; i < toWrite.length; ++i) {
if (toWrite[i].long) {
buffer.writeUInt16(toWrite[i].data);
} else {
buffer.writeUInt8(toWrite[i].data);
}
}
}
toString(): string {
const properties: string[] = [];
properties.push(`type="${KeystrokeType[this.type]}"`);
if (this.hasScancode()) {
properties.push(`scancode="${this._scancode}"`);
}
if (this.hasActiveModifier()) {
properties.push(`modifierMask="${this.modifierMask}"`);
}
if (this.hasLongPressAction()) {
properties.push(`longPressAction="${this.longPressAction}"`);
}
return `<KeystrokeAction ${properties.join(' ')}>`;
}
isActive(modifier: KeyModifiers): boolean {
return (this.modifierMask & modifier) > 0;
}
hasActiveModifier(): boolean {
return this.modifierMask > 0;
}
hasLongPressAction(): boolean {
return this.longPressAction !== undefined;
}
hasScancode(): boolean {
return !!this._scancode;
}
hasOnlyOneActiveModifier(): boolean {
return this.modifierMask !== 0 && !(this.modifierMask & this.modifierMask - 1);
}
getModifierList(): string[] {
const modifierList: string[] = [];
let modifierMask = this.modifierMask;
for (let i = 0; modifierMask !== 0; ++i, modifierMask >>= 1) {
if (modifierMask & 1) {
modifierList.push(MODIFIERS[i]);
}
}
return modifierList;
}
public getName(): string {
return 'KeystrokeAction';
}
}

View File

@@ -1,6 +0,0 @@
export enum KeystrokeType {
basic,
shortMedia,
longMedia,
system
}

View File

@@ -1,65 +0,0 @@
import { assertEnum } from '../../assert';
import { UhkBuffer } from '../../uhk-buffer';
import { KeyAction, KeyActionId, keyActionType } from './key-action';
export enum MouseActionParam {
leftClick,
middleClick,
rightClick,
moveUp,
moveDown,
moveLeft,
moveRight,
scrollUp,
scrollDown,
scrollLeft,
scrollRight,
accelerate,
decelerate
}
export class MouseAction extends KeyAction {
@assertEnum(MouseActionParam)
mouseAction: MouseActionParam;
constructor(other?: MouseAction) {
super();
if (!other) {
return;
}
this.mouseAction = other.mouseAction;
}
fromJsonObject(jsObject: any): MouseAction {
this.assertKeyActionType(jsObject);
this.mouseAction = MouseActionParam[<string>jsObject.mouseAction];
return this;
}
fromBinary(buffer: UhkBuffer): MouseAction {
this.readAndAssertKeyActionId(buffer);
this.mouseAction = buffer.readUInt8();
return this;
}
toJsonObject(): any {
return {
keyActionType: keyActionType.MouseAction,
mouseAction: MouseActionParam[this.mouseAction]
};
}
toBinary(buffer: UhkBuffer) {
buffer.writeUInt8(KeyActionId.MouseAction);
buffer.writeUInt8(this.mouseAction);
}
toString(): string {
return `<MouseAction mouseAction="${this.mouseAction}">`;
}
public getName(): string {
return 'MouseAction';
}
}

View File

@@ -1,39 +0,0 @@
import { UhkBuffer } from '../../uhk-buffer';
import { KeyAction, KeyActionId, keyActionType } from './key-action';
/**
* NoneAction is only intended for binary serialization of undefined key actions
* DO NOT use it as a real KeyAction
*
*/
export class NoneAction extends KeyAction {
fromJsonObject(jsonObject: any): NoneAction {
this.assertKeyActionType(jsonObject);
return this;
}
fromBinary(buffer: UhkBuffer): NoneAction {
this.readAndAssertKeyActionId(buffer);
return this;
}
toJsonObject(): any {
return {
keyActionType: keyActionType.NoneAction
};
}
toBinary(buffer: UhkBuffer) {
buffer.writeUInt8(KeyActionId.NoneAction);
}
toString(): string {
return '<NoneAction>';
}
public getName(): string {
return 'NoneAction';
}
}

View File

@@ -1,56 +0,0 @@
import { assertUInt8 } from '../../assert';
import { UhkBuffer } from '../../uhk-buffer';
import { Macro } from '../macro';
import { KeyAction, KeyActionId, keyActionType } from './key-action';
import { UserConfiguration } from '../user-configuration';
export class PlayMacroAction extends KeyAction {
@assertUInt8
macroId: number;
constructor(parameter?: PlayMacroAction | Macro) {
super();
if (!parameter) {
return;
}
if (parameter instanceof PlayMacroAction) {
this.macroId = parameter.macroId;
} else {
this.macroId = parameter.id;
}
}
fromJsonObject(jsonObject: any, macros: Macro[]): PlayMacroAction {
this.assertKeyActionType(jsonObject);
this.macroId = macros[jsonObject.macroIndex].id;
return this;
}
fromBinary(buffer: UhkBuffer, macros: Macro[]): PlayMacroAction {
this.readAndAssertKeyActionId(buffer);
const macroIndex = buffer.readUInt8();
this.macroId = macros[macroIndex].id;
return this;
}
toJsonObject(macros: Macro[]): any {
return {
keyActionType: keyActionType.PlayMacroAction,
macroIndex: macros.findIndex(macro => macro.id === this.macroId)
};
}
toBinary(buffer: UhkBuffer, userConfiguration: UserConfiguration) {
buffer.writeUInt8(KeyActionId.PlayMacroAction);
buffer.writeUInt8(userConfiguration.macros.findIndex(macro => macro.id === this.macroId));
}
toString(): string {
return `<PlayMacroAction macroId="${this.macroId}">`;
}
public getName(): string {
return 'PlayMacroAction';
}
}

View File

@@ -1,92 +0,0 @@
import { assertUInt8 } from '../../assert';
import { UhkBuffer } from '../../uhk-buffer';
import { Keymap } from '../keymap';
import { KeyAction, KeyActionId, keyActionType } from './key-action';
import { UserConfiguration } from '../user-configuration';
export class SwitchKeymapAction extends KeyAction {
keymapAbbreviation: string;
constructor(parameter?: SwitchKeymapAction | Keymap | string) {
super();
if (!parameter) {
return;
}
if (parameter instanceof SwitchKeymapAction) {
this.keymapAbbreviation = parameter.keymapAbbreviation;
} else if (parameter instanceof Keymap) {
this.keymapAbbreviation = parameter.abbreviation;
} else {
this.keymapAbbreviation = parameter;
}
}
fromJsonObject(jsonObject: any): SwitchKeymapAction {
this.assertKeyActionType(jsonObject);
this.keymapAbbreviation = jsonObject.keymapAbbreviation;
return this;
}
toJsonObject(): any {
return {
keyActionType: keyActionType.SwitchKeymapAction,
keymapAbbreviation: this.keymapAbbreviation
};
}
toBinary(buffer: UhkBuffer, userConfiguration: UserConfiguration): void {
const keymapIndex = userConfiguration.keymaps.findIndex(keymap => keymap.abbreviation === this.keymapAbbreviation);
buffer.writeUInt8(KeyActionId.SwitchKeymapAction);
buffer.writeUInt8(keymapIndex);
}
toString(): string {
return `<SwitchKeymapAction keymapAbbreviation="${this.keymapAbbreviation}">`;
}
renameKeymap(oldAbbr: string, newAbbr: string): KeyAction {
if (this.keymapAbbreviation !== oldAbbr) {
return this;
}
return new SwitchKeymapAction(newAbbr);
}
public getName(): string {
return 'SwitchKeymapAction';
}
}
export class UnresolvedSwitchKeymapAction extends KeyAction {
@assertUInt8
keymapIndex: number;
constructor(keymapIndex?: number) {
super();
this.keymapIndex = keymapIndex;
}
fromBinary(buffer: UhkBuffer): UnresolvedSwitchKeymapAction {
buffer.readUInt8(); // Skip key action id
this.keymapIndex = buffer.readUInt8();
return this;
}
toBinary(buffer: UhkBuffer) {
buffer.writeUInt8(KeyActionId.SwitchKeymapAction);
buffer.writeUInt8(this.keymapIndex);
}
toJsonObject(): any {
throw new Error('UnresolvedSwitchKeymapAction cannot be serialized directly. Convert it to SwitchKeymapAction first.');
}
resolve(keymaps: Keymap[]): SwitchKeymapAction {
return new SwitchKeymapAction(keymaps[this.keymapIndex]);
}
public getName(): string {
return 'UnresolvedSwitchKeymapAction';
}
}

View File

@@ -1,62 +0,0 @@
import { assertEnum } from '../../assert';
import { UhkBuffer } from '../../uhk-buffer';
import { KeyAction, KeyActionId, keyActionType } from './key-action';
export enum LayerName {
mod,
fn,
mouse
}
export class SwitchLayerAction extends KeyAction {
isLayerToggleable: boolean;
@assertEnum(LayerName)
layer: LayerName;
constructor(other?: SwitchLayerAction) {
super();
if (!other) {
return;
}
this.isLayerToggleable = other.isLayerToggleable;
this.layer = other.layer;
}
fromJsonObject(jsonObject: any): SwitchLayerAction {
this.assertKeyActionType(jsonObject);
this.layer = LayerName[<string>jsonObject.layer];
this.isLayerToggleable = jsonObject.toggle;
return this;
}
fromBinary(buffer: UhkBuffer): SwitchLayerAction {
this.readAndAssertKeyActionId(buffer);
this.layer = buffer.readUInt8();
this.isLayerToggleable = buffer.readBoolean();
return this;
}
toJsonObject(): any {
return {
keyActionType: keyActionType.SwitchLayerAction,
layer: LayerName[this.layer],
toggle: this.isLayerToggleable
};
}
toBinary(buffer: UhkBuffer) {
buffer.writeUInt8(KeyActionId.SwitchLayerAction);
buffer.writeUInt8(this.layer);
buffer.writeBoolean(this.isLayerToggleable);
}
toString(): string {
return `<SwitchLayerAction layer="${this.layer}" toggle="${this.isLayerToggleable}">`;
}
public getName(): string {
return 'SwitchLayerAction';
}
}

View File

@@ -1,10 +0,0 @@
export enum KeyModifiers {
leftCtrl = 1 << 0,
leftShift = 1 << 1,
leftAlt = 1 << 2,
leftGui = 1 << 3,
rightCtrl = 1 << 4,
rightShift = 1 << 5,
rightAlt = 1 << 6,
rightGui = 1 << 7
}

View File

@@ -1,127 +0,0 @@
import { UhkBuffer } from '../uhk-buffer';
import { Layer } from './layer';
import { Macro } from './macro';
import { SwitchLayerAction } from './key-action/switch-layer-action';
import { KeyAction } from './key-action/key-action';
import { ConfigSerializer } from '../config-serializer';
import { UserConfiguration } from './user-configuration';
export class Keymap {
name: string;
description: string;
abbreviation: string;
isDefault: boolean;
layers: Layer[];
constructor(keymap?: Keymap) {
if (!keymap) {
return;
}
this.name = keymap.name;
this.description = keymap.description;
this.abbreviation = keymap.abbreviation;
this.isDefault = keymap.isDefault;
this.layers = keymap.layers.map(layer => new Layer(layer));
}
fromJsonObject(jsonObject: any, macros?: Macro[]): Keymap {
this.isDefault = jsonObject.isDefault;
this.abbreviation = jsonObject.abbreviation;
this.name = jsonObject.name;
this.description = jsonObject.description;
this.layers = jsonObject.layers.map((layer: any) => new Layer().fromJsonObject(layer, macros));
this.normalize();
return this;
}
fromBinary(buffer: UhkBuffer, macros?: Macro[]): Keymap {
this.abbreviation = buffer.readString();
this.isDefault = buffer.readBoolean();
this.name = buffer.readString();
this.description = buffer.readString();
this.layers = buffer.readArray<Layer>(uhkBuffer => {
return new Layer().fromBinary(uhkBuffer, macros);
});
this.normalize();
return this;
}
toJsonObject(macros?: Macro[]): any {
return {
isDefault: this.isDefault,
abbreviation: this.abbreviation,
name: this.name,
description: this.description,
layers: this.layers.map(layer => layer.toJsonObject(macros))
};
}
toBinary(buffer: UhkBuffer, userConfiguration: UserConfiguration): void {
buffer.writeString(this.abbreviation);
buffer.writeBoolean(this.isDefault);
buffer.writeString(this.name);
buffer.writeString(this.description);
buffer.writeArray(this.layers, (uhkBuffer: UhkBuffer, layer: Layer) => {
layer.toBinary(uhkBuffer, userConfiguration);
});
}
toString(): string {
return `<Keymap abbreviation="${this.abbreviation}" name="${this.name}">`;
}
renameKeymap(oldAbbr: string, newAbbr: string): Keymap {
let layers: Layer[];
let layerModified = false;
this.layers.forEach((layer, index) => {
const newLayer = layer.renameKeymap(oldAbbr, newAbbr);
if (newLayer !== layer) {
if (!layerModified) {
layers = this.layers.slice();
layerModified = true;
}
layers[index] = newLayer;
}
});
if (layerModified) {
const newKeymap = Object.assign(new Keymap(), this);
newKeymap.layers = layers;
return newKeymap;
}
return this;
}
private normalize() {
// Removes all the SwitchLayerActions from any non base layer
for (let i = 1; i < this.layers.length; ++i) {
for (const module of this.layers[i].modules) {
module.keyActions = module.keyActions.map(keyAction => {
if (keyAction instanceof SwitchLayerAction) {
return undefined;
}
return keyAction;
});
}
}
// Adds the SwitchLayerActions from the base layer to any none base layer
const baseLayerModules = this.layers[0].modules;
for (let i = 0; i < baseLayerModules.length; ++i) {
baseLayerModules[i].keyActions.forEach((keyAction: KeyAction, keyActionIndex: number) => {
if (keyAction instanceof SwitchLayerAction) {
for (let j = 1; j < this.layers.length; ++j) {
this.layers[j].modules[i].keyActions[keyActionIndex] = new SwitchLayerAction(keyAction);
}
}
});
}
}
}

View File

@@ -1,67 +0,0 @@
import { UhkBuffer } from '../uhk-buffer';
import { Macro } from './macro';
import { Module } from './module';
import { UserConfiguration } from './user-configuration';
import { ConfigSerializer } from '../config-serializer';
export class Layer {
modules: Module[];
constructor(layers?: Layer) {
if (!layers) {
return;
}
this.modules = layers.modules.map(module => new Module(module));
}
fromJsonObject(jsonObject: any, macros?: Macro[]): Layer {
this.modules = jsonObject.modules.map((module: any) => new Module().fromJsonObject(module, macros));
return this;
}
fromBinary(buffer: UhkBuffer, macros?: Macro[]): Layer {
this.modules = buffer.readArray<Module>(uhkBuffer => {
return new Module().fromBinary(uhkBuffer, macros);
});
return this;
}
toJsonObject(macros?: Macro[]): any {
return {
modules: this.modules.map(module => module.toJsonObject(macros))
};
}
toBinary(buffer: UhkBuffer, userConfiguration: UserConfiguration): void {
buffer.writeArray(this.modules, (uhkBuffer: UhkBuffer, module: Module) => {
module.toBinary(uhkBuffer, userConfiguration);
});
}
toString(): string {
return `<Layer>`;
}
renameKeymap(oldAbbr: string, newAbbr: string): Layer {
let modules: Module[];
let moduleModified = false;
this.modules.forEach((module, index) => {
const newModule = module.renameKeymap(oldAbbr, newAbbr);
if (newModule !== module) {
if (!moduleModified) {
modules = this.modules.slice();
moduleModified = true;
}
modules[index] = newModule;
}
});
if (moduleModified) {
const newLayer = Object.assign(new Layer(), this);
newLayer.modules = modules;
return newLayer;
}
return this;
}
}

View File

@@ -1,13 +0,0 @@
export enum LongPressAction {
leftCtrl,
leftShift,
leftAlt,
leftSuper,
rightCtrl,
rightShift,
rightAlt,
rightSuper,
mod,
fn,
mouse
}

View File

@@ -1,49 +0,0 @@
import { assertUInt16 } from '../../assert';
import { UhkBuffer } from '../../uhk-buffer';
import { MacroAction, MacroActionId, macroActionType } from './macro-action';
export class DelayMacroAction extends MacroAction {
@assertUInt16
delay: number;
constructor(other?: DelayMacroAction) {
super();
if (!other) {
return;
}
this.delay = other.delay;
}
fromJsonObject(jsObject: any): DelayMacroAction {
this.assertMacroActionType(jsObject);
this.delay = jsObject.delay;
return this;
}
fromBinary(buffer: UhkBuffer): DelayMacroAction {
this.readAndAssertMacroActionId(buffer);
this.delay = buffer.readUInt16();
return this;
}
toJsonObject(): any {
return {
macroActionType: macroActionType.DelayMacroAction,
delay: this.delay
};
}
toBinary(buffer: UhkBuffer) {
buffer.writeUInt8(MacroActionId.DelayMacroAction);
buffer.writeUInt16(this.delay);
}
toString(): string {
return `<DelayMacroAction delay="${this.delay}">`;
}
public getName(): string {
return 'DelayMacroAction';
}
}

View File

@@ -1,84 +0,0 @@
import { UhkBuffer } from '../../uhk-buffer';
import { MacroAction, MacroActionId, macroActionType } from './macro-action';
import { KeyMacroAction } from './key-macro-action';
import { MouseButtonMacroAction } from './mouse-button-macro-action';
import { MoveMouseMacroAction } from './move-mouse-macro-action';
import { ScrollMouseMacroAction } from './scroll-mouse-macro-action';
import { DelayMacroAction } from './delay-macro-action';
import { TextMacroAction } from './text-macro-action';
export class Helper {
static createMacroAction(source: MacroAction | UhkBuffer | any): MacroAction {
if (source instanceof MacroAction) {
return Helper.fromMacroAction(source);
} else if (source instanceof UhkBuffer) {
return Helper.fromUhkBuffer(source);
} else {
return Helper.fromJSONObject(source);
}
}
private static fromUhkBuffer(buffer: UhkBuffer): MacroAction {
const macroActionFirstByte = buffer.readUInt8();
buffer.backtrack();
if (macroActionFirstByte >= MacroActionId.KeyMacroAction && macroActionFirstByte <= MacroActionId.LastKeyMacroAction) {
return new KeyMacroAction().fromBinary(buffer);
} else if (
macroActionFirstByte >= MacroActionId.MouseButtonMacroAction &&
macroActionFirstByte <= MacroActionId.LastMouseButtonMacroAction
) {
return new MouseButtonMacroAction().fromBinary(buffer);
}
switch (macroActionFirstByte) {
case MacroActionId.MoveMouseMacroAction:
return new MoveMouseMacroAction().fromBinary(buffer);
case MacroActionId.ScrollMouseMacroAction:
return new ScrollMouseMacroAction().fromBinary(buffer);
case MacroActionId.DelayMacroAction:
return new DelayMacroAction().fromBinary(buffer);
case MacroActionId.TextMacroAction:
return new TextMacroAction().fromBinary(buffer);
default:
throw `Invalid MacroAction first byte: ${macroActionFirstByte}`;
}
}
private static fromMacroAction(macroAction: MacroAction): MacroAction {
let newMacroAction: MacroAction;
if (macroAction instanceof KeyMacroAction) {
newMacroAction = new KeyMacroAction(macroAction);
} else if (macroAction instanceof MouseButtonMacroAction) {
newMacroAction = new MouseButtonMacroAction(macroAction);
} else if (macroAction instanceof MoveMouseMacroAction) {
newMacroAction = new MoveMouseMacroAction(macroAction);
} else if (macroAction instanceof ScrollMouseMacroAction) {
newMacroAction = new ScrollMouseMacroAction(macroAction);
} else if (macroAction instanceof DelayMacroAction) {
newMacroAction = new DelayMacroAction(macroAction);
} else if (macroAction instanceof TextMacroAction) {
newMacroAction = new TextMacroAction(macroAction);
}
return newMacroAction;
}
private static fromJSONObject(macroAction: any): MacroAction {
switch (macroAction.macroActionType) {
case macroActionType.KeyMacroAction:
return new KeyMacroAction().fromJsonObject(macroAction);
case macroActionType.MouseButtonMacroAction:
return new MouseButtonMacroAction().fromJsonObject(macroAction);
case macroActionType.MoveMouseMacroAction:
return new MoveMouseMacroAction().fromJsonObject(macroAction);
case macroActionType.ScrollMouseMacroAction:
return new ScrollMouseMacroAction().fromJsonObject(macroAction);
case macroActionType.DelayMacroAction:
return new DelayMacroAction().fromJsonObject(macroAction);
case macroActionType.TextMacroAction:
return new TextMacroAction().fromJsonObject(macroAction);
default:
throw `Invalid MacroAction.macroActionType: "${macroAction.macroActionType}"`;
}
}
}

View File

@@ -1,8 +0,0 @@
export * from './delay-macro-action';
export * from './key-macro-action';
export * from './macro-action';
export * from './move-mouse-macro-action';
export * from './mouse-button-macro-action';
export * from './scroll-mouse-macro-action';
export * from './text-macro-action';
export * from './helper';

View File

@@ -1,139 +0,0 @@
import { assertEnum, assertUInt8 } from '../../assert';
import { UhkBuffer } from '../../uhk-buffer';
import { KeyModifiers } from '../key-modifiers';
import { MacroAction, MacroActionId, MacroSubAction, macroActionType } from './macro-action';
import { KeystrokeType } from '../key-action/keystroke-type';
interface JsObjectKeyMacroAction {
macroActionType: string;
action: string;
type?: string;
scancode?: number;
modifierMask?: number;
}
export class KeyMacroAction extends MacroAction {
@assertEnum(MacroSubAction)
action: MacroSubAction;
@assertEnum(KeystrokeType)
type: KeystrokeType;
@assertUInt8
scancode: number;
@assertUInt8
modifierMask: number;
constructor(other?: KeyMacroAction) {
super();
if (!other) {
return;
}
this.action = other.action;
this.type = other.type;
this.scancode = other.scancode;
this.modifierMask = other.modifierMask;
}
fromJsonObject(jsObject: JsObjectKeyMacroAction): KeyMacroAction {
this.assertMacroActionType(jsObject);
this.action = MacroSubAction[jsObject.action];
if (jsObject.type === 'media') {
this.type = jsObject.scancode < 256 ? KeystrokeType.shortMedia : KeystrokeType.longMedia;
} else {
this.type = KeystrokeType[jsObject.type];
}
this.scancode = jsObject.scancode;
this.modifierMask = jsObject.modifierMask;
return this;
}
fromBinary(buffer: UhkBuffer): KeyMacroAction {
const macroActionId: MacroActionId = this.readAndAssertMacroActionId(buffer);
let keyMacroType: number = macroActionId - MacroActionId.KeyMacroAction;
this.action = keyMacroType & 0b11;
keyMacroType >>= 2;
this.type = keyMacroType & 0b11;
keyMacroType >>= 2;
if (keyMacroType & 0b10) {
this.scancode = buffer.readUInt8();
}
if (keyMacroType & 0b01) {
this.modifierMask = buffer.readUInt8();
}
return this;
}
toJsonObject(): any {
const jsObject: JsObjectKeyMacroAction = {
macroActionType: macroActionType.KeyMacroAction,
action: MacroSubAction[this.action]
};
if (this.hasScancode()) {
if (this.type === KeystrokeType.shortMedia || this.type === KeystrokeType.longMedia) {
jsObject.type = 'media';
} else {
jsObject.type = KeystrokeType[this.type];
}
jsObject.scancode = this.scancode;
}
if (this.hasModifiers()) {
jsObject.modifierMask = this.modifierMask;
}
return jsObject;
}
toBinary(buffer: UhkBuffer) {
let TYPE_OFFSET = 0;
TYPE_OFFSET |= this.action;
TYPE_OFFSET |= this.type << 2;
TYPE_OFFSET |= ((this.hasScancode() ? 2 : 0) + (this.hasModifiers() ? 1 : 0)) << 4;
const keyMacroType: number = MacroActionId.KeyMacroAction + TYPE_OFFSET;
buffer.writeUInt8(keyMacroType);
if (this.hasScancode()) {
buffer.writeUInt8(this.scancode);
}
if (this.hasModifiers()) {
buffer.writeUInt8(this.modifierMask);
}
}
toString(): string {
return `<KeyMacroAction action="${this.action}" scancode="${this.scancode}" modifierMask="${this.modifierMask}">`;
}
isModifierActive(modifier: KeyModifiers): boolean {
return (this.modifierMask & modifier) > 0;
}
hasScancode(): boolean {
return !!this.scancode;
}
hasModifiers(): boolean {
return !!this.modifierMask;
}
isHoldAction(): boolean {
return this.action === MacroSubAction.hold;
}
isPressAction(): boolean {
return this.action === MacroSubAction.press;
}
isReleaseAction(): boolean {
return this.action === MacroSubAction.release;
}
public getName(): string {
return 'KeyMacroAction';
}
}

View File

@@ -1,74 +0,0 @@
import { UhkBuffer } from '../../uhk-buffer';
export enum MacroActionId {
KeyMacroAction = 0,
/*
0 - 63 are reserved for KeyMacroAction
2 bits for: PressKeyMacroAction / HoldKeyMacroAction / ReleaseKeyMacroAction / undefined
2 bits for: with only scancode / only modifiers / both scancode and modifiers / undefined
2 bits for: scancode type basic, short media, long media, system. It should be only used if scancode does exist.
*/
LastKeyMacroAction = 63,
MouseButtonMacroAction = 64,
/*
64 - 66 are reserved for MouseButtonMacroAction
PressMouseButtonsMacroAction = 64,
HoldMouseButtonsMacroAction = 65,
ReleaseMouseButtonsMacroAction = 66,
*/
LastMouseButtonMacroAction = 66,
MoveMouseMacroAction = 67,
ScrollMouseMacroAction = 68,
DelayMacroAction = 69,
TextMacroAction = 70
}
export enum MacroSubAction {
press = 0,
hold = 1,
release = 2
}
export let macroActionType = {
KeyMacroAction : 'key',
MouseButtonMacroAction : 'mouseButton',
MoveMouseMacroAction : 'moveMouse',
ScrollMouseMacroAction : 'scrollMouse',
DelayMacroAction : 'delay',
TextMacroAction : 'text'
};
export abstract class MacroAction {
assertMacroActionType(jsObject: any) {
const macroActionClassname = this.getName();
const macroActionTypeString = macroActionType[macroActionClassname];
if (jsObject.macroActionType !== macroActionTypeString) {
throw `Invalid ${macroActionClassname}.macroActionType: ${jsObject.macroActionType}`;
}
}
readAndAssertMacroActionId(buffer: UhkBuffer): MacroActionId {
const classname: string = this.getName();
const readMacroActionId: MacroActionId = buffer.readUInt8();
const macroActionId: MacroActionId = MacroActionId[classname];
if (macroActionId === MacroActionId.KeyMacroAction) {
if (readMacroActionId < MacroActionId.KeyMacroAction || readMacroActionId > MacroActionId.LastKeyMacroAction) {
throw `Invalid ${classname} first byte: ${readMacroActionId}`;
}
} else if (macroActionId === MacroActionId.MouseButtonMacroAction) {
if (readMacroActionId < MacroActionId.MouseButtonMacroAction ||
readMacroActionId > MacroActionId.LastMouseButtonMacroAction) {
throw `Invalid ${classname} first byte: ${readMacroActionId}`;
}
} else if (readMacroActionId !== macroActionId) {
throw `Invalid ${classname} first byte: ${readMacroActionId}`;
}
return readMacroActionId;
}
abstract toJsonObject(): any;
abstract toBinary(buffer: UhkBuffer): void;
abstract getName(): string;
}

View File

@@ -1,99 +0,0 @@
import { assertEnum, assertUInt8 } from '../../assert';
import { UhkBuffer } from '../../uhk-buffer';
import { MacroAction, MacroActionId, MacroSubAction, macroActionType } from './macro-action';
export enum MouseButtons {
Left = 1 << 0,
Middle = 1 << 1,
Right = 1 << 2
}
interface JsObjectMouseButtonMacroAction {
macroActionType: string;
action: string;
mouseButtonsMask?: number;
}
export class MouseButtonMacroAction extends MacroAction {
@assertEnum(MacroSubAction)
action: MacroSubAction;
@assertUInt8
mouseButtonsMask: number;
constructor(other?: MouseButtonMacroAction) {
super();
if (!other) {
return;
}
this.action = other.action;
this.mouseButtonsMask = other.mouseButtonsMask;
}
fromJsonObject(jsObject: JsObjectMouseButtonMacroAction): MouseButtonMacroAction {
this.assertMacroActionType(jsObject);
this.action = MacroSubAction[jsObject.action];
this.mouseButtonsMask = jsObject.mouseButtonsMask;
return this;
}
fromBinary(buffer: UhkBuffer): MouseButtonMacroAction {
const macroActionId: MacroActionId = this.readAndAssertMacroActionId(buffer);
this.action = macroActionId - MacroActionId.MouseButtonMacroAction;
this.mouseButtonsMask = buffer.readUInt8();
return this;
}
toJsonObject(): any {
return {
macroActionType: macroActionType.MouseButtonMacroAction,
action: MacroSubAction[this.action],
mouseButtonsMask: this.mouseButtonsMask
};
}
toBinary(buffer: UhkBuffer): void {
buffer.writeUInt8(MacroActionId.MouseButtonMacroAction + this.action);
buffer.writeUInt8(this.mouseButtonsMask);
}
setMouseButtons(buttonStates: boolean[]): void {
let bitmask = 0;
for (let i = 0; i < buttonStates.length; i++) {
bitmask |= Number(buttonStates[i]) << i;
}
this.mouseButtonsMask = bitmask;
}
getMouseButtons(): boolean[] {
const enabledMouseButtons: boolean[] = [];
for (let bitmask = this.mouseButtonsMask; bitmask; bitmask >>>= 1) {
enabledMouseButtons.push(Boolean(bitmask & 1));
}
return enabledMouseButtons;
}
toString(): string {
return `<MouseButtonMacroAction mouseButtonsMask="${this.mouseButtonsMask}">`;
}
hasButtons(): boolean {
return this.mouseButtonsMask !== 0;
}
isOnlyHoldAction(): boolean {
return this.action === MacroSubAction.hold;
}
isOnlyPressAction(): boolean {
return this.action === MacroSubAction.press;
}
isOnlyReleaseAction(): boolean {
return this.action === MacroSubAction.release;
}
public getName(): string {
return 'MouseButtonMacroAction';
}
}

View File

@@ -1,57 +0,0 @@
import { assertInt16 } from '../../assert';
import { UhkBuffer } from '../../uhk-buffer';
import { MacroAction, MacroActionId, macroActionType } from './macro-action';
export class MoveMouseMacroAction extends MacroAction {
@assertInt16
x: number;
@assertInt16
y: number;
constructor(other?: MoveMouseMacroAction) {
super();
if (!other) {
return;
}
this.x = other.x;
this.y = other.y;
}
fromJsonObject(jsObject: any): MoveMouseMacroAction {
this.assertMacroActionType(jsObject);
this.x = jsObject.x;
this.y = jsObject.y;
return this;
}
fromBinary(buffer: UhkBuffer): MoveMouseMacroAction {
this.readAndAssertMacroActionId(buffer);
this.x = buffer.readInt16();
this.y = buffer.readInt16();
return this;
}
toJsonObject(): any {
return {
macroActionType: macroActionType.MoveMouseMacroAction,
x: this.x,
y: this.y
};
}
toBinary(buffer: UhkBuffer) {
buffer.writeUInt8(MacroActionId.MoveMouseMacroAction);
buffer.writeInt16(this.x);
buffer.writeInt16(this.y);
}
toString(): string {
return `<MoveMouseMacroAction pos="(${this.x},${this.y})">`;
}
public getName(): string {
return 'MoveMouseMacroAction';
}
}

View File

@@ -1,57 +0,0 @@
import { assertInt16 } from '../../assert';
import { UhkBuffer } from '../../uhk-buffer';
import { MacroAction, MacroActionId, macroActionType } from './macro-action';
export class ScrollMouseMacroAction extends MacroAction {
@assertInt16
x: number;
@assertInt16
y: number;
constructor(other?: ScrollMouseMacroAction) {
super();
if (!other) {
return;
}
this.x = other.x;
this.y = other.y;
}
fromJsonObject(jsObject: any): ScrollMouseMacroAction {
this.assertMacroActionType(jsObject);
this.x = jsObject.x;
this.y = jsObject.y;
return this;
}
fromBinary(buffer: UhkBuffer): ScrollMouseMacroAction {
this.readAndAssertMacroActionId(buffer);
this.x = buffer.readInt16();
this.y = buffer.readInt16();
return this;
}
toJsonObject(): any {
return {
macroActionType: macroActionType.ScrollMouseMacroAction,
x: this.x,
y: this.y
};
}
toBinary(buffer: UhkBuffer) {
buffer.writeUInt8(MacroActionId.ScrollMouseMacroAction);
buffer.writeInt16(this.x);
buffer.writeInt16(this.y);
}
toString(): string {
return `<ScrollMouseMacroAction pos="(${this.x},${this.y})">`;
}
public getName(): string {
return 'ScrollMouseMacroAction';
}
}

View File

@@ -1,47 +0,0 @@
import { UhkBuffer } from '../../uhk-buffer';
import { MacroAction, MacroActionId, macroActionType } from './macro-action';
export class TextMacroAction extends MacroAction {
text: string;
constructor(other?: TextMacroAction) {
super();
if (!other) {
return;
}
this.text = other.text;
}
fromJsonObject(jsObject: any): TextMacroAction {
this.assertMacroActionType(jsObject);
this.text = jsObject.text;
return this;
}
fromBinary(buffer: UhkBuffer): TextMacroAction {
this.readAndAssertMacroActionId(buffer);
this.text = buffer.readString();
return this;
}
toJsonObject(): any {
return {
macroActionType: macroActionType.TextMacroAction,
text: this.text
};
}
toBinary(buffer: UhkBuffer) {
buffer.writeUInt8(MacroActionId.TextMacroAction);
buffer.writeString(this.text);
}
toString(): string {
return `<TextMacroAction text="${this.text}">`;
}
public getName(): string {
return 'TextMacroAction';
}
}

View File

@@ -1,68 +0,0 @@
import { assertUInt8 } from '../assert';
import { UhkBuffer } from '../uhk-buffer';
import { Helper as MacroActionHelper, MacroAction } from './macro-action';
export class Macro {
@assertUInt8
id: number;
isLooped: boolean;
isPrivate: boolean;
name: string;
macroActions: MacroAction[];
constructor(other?: Macro) {
if (!other) {
return;
}
this.id = other.id;
this.isLooped = other.isLooped;
this.isPrivate = other.isPrivate;
this.name = other.name;
this.macroActions = other.macroActions.map(macroAction => MacroActionHelper.createMacroAction(macroAction));
}
fromJsonObject(jsonObject: any): Macro {
this.isLooped = jsonObject.isLooped;
this.isPrivate = jsonObject.isPrivate;
this.name = jsonObject.name;
this.macroActions = jsonObject.macroActions.map((macroAction: any) => MacroActionHelper.createMacroAction(macroAction));
return this;
}
fromBinary(buffer: UhkBuffer): Macro {
this.isLooped = buffer.readBoolean();
this.isPrivate = buffer.readBoolean();
this.name = buffer.readString();
const macroActionsLength: number = buffer.readCompactLength();
this.macroActions = [];
for (let i = 0; i < macroActionsLength; ++i) {
this.macroActions.push(MacroActionHelper.createMacroAction(buffer));
}
return this;
}
toJsonObject(): any {
return {
isLooped: this.isLooped,
isPrivate: this.isPrivate,
name: this.name,
macroActions: this.macroActions.map(macroAction => macroAction.toJsonObject())
};
}
toBinary(buffer: UhkBuffer): void {
buffer.writeBoolean(this.isLooped);
buffer.writeBoolean(this.isPrivate);
buffer.writeString(this.name);
buffer.writeArray(this.macroActions);
}
toString(): string {
return `<Macro id="${this.id}" name="${this.name}">`;
}
}

View File

@@ -1,58 +0,0 @@
import { assertUInt8 } from '../assert';
import { UhkBuffer } from '../uhk-buffer';
export class ModuleConfiguration {
/*
* module id enumeration is a separate story
*/
@assertUInt8
id: number;
@assertUInt8
initialPointerSpeed: number;
@assertUInt8
pointerAcceleration: number;
@assertUInt8
maxPointerSpeed: number;
fromJsonObject(jsonObject: any): ModuleConfiguration {
this.id = jsonObject.id;
this.initialPointerSpeed = jsonObject.initialPointerSpeed;
this.pointerAcceleration = jsonObject.pointerAcceleration;
this.maxPointerSpeed = jsonObject.maxPointerSpeed;
return this;
}
fromBinary(buffer: UhkBuffer): ModuleConfiguration {
this.id = buffer.readUInt8();
this.initialPointerSpeed = buffer.readUInt8();
this.pointerAcceleration = buffer.readUInt8();
this.maxPointerSpeed = buffer.readUInt8();
return this;
}
toJsonObject(): any {
return {
id: this.id,
initialPointerSpeed: this.initialPointerSpeed,
pointerAcceleration: this.pointerAcceleration,
maxPointerSpeed: this.maxPointerSpeed
};
}
toBinary(buffer: UhkBuffer): void {
buffer.writeUInt8(this.id);
buffer.writeUInt8(this.initialPointerSpeed);
buffer.writeUInt8(this.pointerAcceleration);
buffer.writeUInt8(this.maxPointerSpeed);
}
toString(): string {
return `<ModuleConfiguration id="${this.id}" >`;
}
}

View File

@@ -1,108 +0,0 @@
import { assertEnum, assertUInt8 } from '../assert';
import { UhkBuffer } from '../uhk-buffer';
import { Helper as KeyActionHelper, KeyAction, NoneAction, PlayMacroAction, SwitchKeymapAction } from './key-action';
import { Macro } from './macro';
import { UserConfiguration } from './user-configuration';
enum PointerRole {
none,
move,
scroll
}
export class Module {
@assertUInt8
id: number;
keyActions: KeyAction[];
@assertEnum(PointerRole)
pointerRole: PointerRole;
constructor(other?: Module) {
if (!other) {
return;
}
this.id = other.id;
this.keyActions = other.keyActions.map(keyAction => KeyActionHelper.createKeyAction(keyAction));
this.pointerRole = other.pointerRole;
}
fromJsonObject(jsonObject: any, macros?: Macro[]): Module {
this.id = jsonObject.id;
this.pointerRole = PointerRole[<string>jsonObject.pointerRole];
this.keyActions = jsonObject.keyActions.map((keyAction: any) => {
return KeyActionHelper.createKeyAction(keyAction, macros);
});
return this;
}
fromBinary(buffer: UhkBuffer, macros?: Macro[]): Module {
this.id = buffer.readUInt8();
this.pointerRole = buffer.readUInt8();
const keyActionsLength: number = buffer.readCompactLength();
this.keyActions = [];
for (let i = 0; i < keyActionsLength; ++i) {
this.keyActions.push(KeyActionHelper.createKeyAction(buffer, macros));
}
return this;
}
toJsonObject(macros?: Macro[]): any {
return {
id: this.id,
pointerRole: PointerRole[this.pointerRole],
keyActions: this.keyActions.map(keyAction => {
if (keyAction && (macros || !(keyAction instanceof PlayMacroAction || keyAction instanceof SwitchKeymapAction))) {
return keyAction.toJsonObject(macros);
}
})
};
}
toBinary(buffer: UhkBuffer, userConfiguration: UserConfiguration): void {
buffer.writeUInt8(this.id);
buffer.writeUInt8(this.pointerRole);
const noneAction = new NoneAction();
const keyActions: KeyAction[] = this.keyActions.map(keyAction => {
if (keyAction) {
return keyAction;
}
return noneAction;
});
buffer.writeArray(keyActions, (uhkBuffer: UhkBuffer, keyAction: KeyAction) => {
keyAction.toBinary(uhkBuffer, userConfiguration);
});
}
toString(): string {
return `<Module id="${this.id}" pointerRole="${this.pointerRole}">`;
}
renameKeymap(oldAbbr: string, newAbbr: string): Module {
let keyActions: KeyAction[];
let keyActionModified = false;
this.keyActions.forEach((keyAction, index) => {
if (!keyAction) { return; }
const newKeyAction = keyAction.renameKeymap(oldAbbr, newAbbr);
if (newKeyAction !== keyAction) {
if (!keyActionModified) {
keyActions = this.keyActions.slice();
keyActionModified = true;
}
keyActions[index] = newKeyAction;
}
});
if (keyActionModified) {
const newModule = Object.assign(new Module(), this);
newModule.keyActions = keyActions;
return newModule;
}
return this;
}
}

View File

@@ -1,78 +0,0 @@
import { assertUInt16 } from '../assert';
import { UhkBuffer } from '../uhk-buffer';
import { Keymap } from './keymap';
import { Macro } from './macro';
import { ModuleConfiguration } from './module-configuration';
import { ConfigSerializer } from '../config-serializer';
export class UserConfiguration {
@assertUInt16
dataModelVersion: number;
moduleConfigurations: ModuleConfiguration[] = [];
keymaps: Keymap[] = [];
macros: Macro[] = [];
fromJsonObject(jsonObject: any): UserConfiguration {
this.dataModelVersion = jsonObject.dataModelVersion;
this.moduleConfigurations = jsonObject.moduleConfigurations.map((moduleConfiguration: any) => {
return new ModuleConfiguration().fromJsonObject(moduleConfiguration);
});
this.macros = jsonObject.macros.map((macroJsonObject: any, index: number) => {
const macro = new Macro().fromJsonObject(macroJsonObject);
macro.id = index;
return macro;
});
this.keymaps = jsonObject.keymaps.map((keymap: any) => new Keymap().fromJsonObject(keymap, this.macros));
return this;
}
fromBinary(buffer: UhkBuffer): UserConfiguration {
this.dataModelVersion = buffer.readUInt16();
this.moduleConfigurations = buffer.readArray<ModuleConfiguration>(uhkBuffer => {
return new ModuleConfiguration().fromBinary(uhkBuffer);
});
this.macros = buffer.readArray<Macro>((uhkBuffer, index) => {
const macro = new Macro().fromBinary(uhkBuffer);
macro.id = index;
return macro;
});
this.keymaps = buffer.readArray<Keymap>(uhkBuffer => new Keymap().fromBinary(uhkBuffer, this.macros));
ConfigSerializer.resolveSwitchKeymapActions(this.keymaps);
return this;
}
toJsonObject(): any {
return {
dataModelVersion: this.dataModelVersion,
moduleConfigurations: this.moduleConfigurations.map(moduleConfiguration => moduleConfiguration.toJsonObject()),
keymaps: this.keymaps.map(keymap => keymap.toJsonObject(this.macros)),
macros: this.macros.map(macro => macro.toJsonObject())
};
}
toBinary(buffer: UhkBuffer): void {
buffer.writeUInt16(this.dataModelVersion);
buffer.writeArray(this.moduleConfigurations);
buffer.writeArray(this.macros);
buffer.writeArray(this.keymaps, (uhkBuffer: UhkBuffer, keymap: Keymap) => {
keymap.toBinary(uhkBuffer, this);
});
}
toString(): string {
return `<UserConfiguration dataModelVersion="${this.dataModelVersion}">`;
}
getKeymap(keymapAbbreviation: string): Keymap {
return this.keymaps.find(keymap => keymapAbbreviation === keymap.abbreviation);
}
getMacro(macroId: number): Macro {
return this.macros.find(macro => macroId === macro.id);
}
}

View File

@@ -1,20 +0,0 @@
import { Keymap } from './config-items/keymap';
import { UnresolvedSwitchKeymapAction } from './config-items/key-action';
export namespace ConfigSerializer {
export function resolveSwitchKeymapActions(keymaps: Keymap[]) {
for (const keymap of keymaps) {
for (const layer of keymap.layers) {
for (const module of layer.modules) {
for (let i = 0; i < module.keyActions.length; ++i) {
const keyAction = module.keyActions[i];
if (keyAction instanceof UnresolvedSwitchKeymapAction) {
module.keyActions[i] = keyAction.resolve(keymaps);
}
}
}
}
}
}
}

View File

@@ -1,214 +0,0 @@
export class UhkBuffer {
static simpleElementWriter<T>(buffer: UhkBuffer, element: T): void {
(<any>element).toBinary(buffer); // TODO: Remove any
}
private static eepromSize = 32 * 1024;
private static maxCompactLength = 0xFFFF;
private static longCompactLengthPrefix = 0xFF;
private static stringEncoding = 'utf8';
private static isFirstElementToDump = false;
offset: number;
private _enableDump = false;
private buffer: Buffer;
private bytesToBacktrack: number;
constructor() {
this.offset = 0;
this.bytesToBacktrack = 0;
this.buffer = new Buffer(UhkBuffer.eepromSize);
this.buffer.fill(0);
}
readInt8(): number {
const value = this.buffer.readInt8(this.offset);
this.dump(`i8(${value})`);
this.bytesToBacktrack = 1;
this.offset += this.bytesToBacktrack;
return value;
}
writeInt8(value: number): void {
this.dump(`i8(${value})`);
this.buffer.writeInt8(value, this.offset);
this.offset += 1;
}
readUInt8(): number {
const value = this.buffer.readUInt8(this.offset);
this.dump(`u8(${value})`);
this.bytesToBacktrack = 1;
this.offset += this.bytesToBacktrack;
return value;
}
writeUInt8(value: number): void {
this.dump(`u8(${value})`);
this.buffer.writeUInt8(value, this.offset);
this.offset += 1;
}
readInt16(): number {
const value = this.buffer.readInt16LE(this.offset);
this.dump(`i16(${value})`);
this.bytesToBacktrack = 2;
this.offset += this.bytesToBacktrack;
return value;
}
writeInt16(value: number): void {
this.dump(`i16(${value})`);
this.buffer.writeInt16LE(value, this.offset);
this.offset += 2;
}
readUInt16(): number {
const value = this.buffer.readUInt16LE(this.offset);
this.dump(`u16(${value})`);
this.bytesToBacktrack = 2;
this.offset += this.bytesToBacktrack;
return value;
}
writeUInt16(value: number): void {
this.dump(`u16(${value})`);
this.buffer.writeUInt16LE(value, this.offset);
this.offset += 2;
}
readInt32(): number {
const value = this.buffer.readInt32LE(this.offset);
this.dump(`i32(${value})`);
this.bytesToBacktrack = 4;
this.offset += this.bytesToBacktrack;
return value;
}
writeInt32(value: number): void {
this.dump(`i32(${value})`);
this.buffer.writeInt32LE(value, this.offset);
this.offset += 4;
}
readUInt32(): number {
const value = this.buffer.readUInt32LE(this.offset);
this.dump(`u32(${value})`);
this.bytesToBacktrack = 4;
this.offset += this.bytesToBacktrack;
return value;
}
writeUInt32(value: number): void {
this.dump(`u32(${value})`);
this.buffer.writeUInt32LE(value, this.offset);
this.offset += 4;
}
readCompactLength(): number {
let length = this.readUInt8();
if (length === UhkBuffer.longCompactLengthPrefix) {
length = this.readUInt16();
}
return length;
}
writeCompactLength(length: number) {
if (length >= UhkBuffer.longCompactLengthPrefix) {
this.writeUInt8(UhkBuffer.longCompactLengthPrefix);
this.writeUInt16(length);
} else {
this.writeUInt8(length);
}
}
readString(): string {
const stringByteLength = this.readCompactLength();
const str = this.buffer.toString(UhkBuffer.stringEncoding, this.offset, this.offset + stringByteLength);
this.dump(`${UhkBuffer.stringEncoding}(${str})`);
this.bytesToBacktrack = stringByteLength;
this.offset += stringByteLength;
return str;
}
writeString(str: string): void {
const stringByteLength = Buffer.byteLength(str, UhkBuffer.stringEncoding);
if (stringByteLength > UhkBuffer.maxCompactLength) {
throw `Cannot serialize string: ${stringByteLength} bytes is larger
than the maximum allowed length of ${UhkBuffer.maxCompactLength} bytes`;
}
this.writeCompactLength(stringByteLength);
this.dump(`${UhkBuffer.stringEncoding}(${str})`);
this.buffer.write(str, this.offset, stringByteLength, UhkBuffer.stringEncoding);
this.offset += stringByteLength;
}
readBoolean(): boolean {
return this.readUInt8() !== 0;
}
writeBoolean(bool: boolean) {
this.writeUInt8(bool ? 1 : 0);
}
readArray<T>(elementReader: (buffer: UhkBuffer, index?: number) => T): T[] {
const array: T[] = [];
const length = this.readCompactLength();
for (let i = 0; i < length; ++i) {
array.push(elementReader(this, i));
}
return array;
}
writeArray<T>(
array: T[],
elementWriter: (buffer: UhkBuffer, element: T, index?: number) => void = UhkBuffer.simpleElementWriter
): void {
const length = array.length;
this.writeCompactLength(length);
for (let i = 0; i < length; ++i) {
elementWriter(this, array[i], i);
}
}
backtrack(): void {
this.offset -= this.bytesToBacktrack;
this.bytesToBacktrack = 0;
}
getBufferContent(): Buffer {
return this.buffer.slice(0, this.offset);
}
get enableDump() {
return this._enableDump;
}
set enableDump(value) {
if (value) {
UhkBuffer.isFirstElementToDump = true;
}
this._enableDump = value;
}
dump(value: any) {
if (!this.enableDump) {
return;
}
if (!UhkBuffer.isFirstElementToDump) {
process.stdout.write(', ');
}
process.stdout.write(value);
if (UhkBuffer.isFirstElementToDump) {
UhkBuffer.isFirstElementToDump = false;
}
}
}

View File

@@ -1,6 +1,6 @@
import { Injectable } from '@angular/core';
import { UserConfiguration } from 'uhk-common';
import { UserConfiguration } from '../config-serializer/config-items/user-configuration';
import { AutoUpdateSettings } from '../models/auto-update-settings';
@Injectable()

View File

@@ -1,5 +1,5 @@
import { Injectable } from '@angular/core';
import { UserConfiguration } from '../config-serializer/config-items/user-configuration';
import { UserConfiguration } from 'uhk-common';
@Injectable()
export class DefaultUserConfigurationService {
@@ -7,7 +7,7 @@ export class DefaultUserConfigurationService {
constructor() {
this._defaultConfig = new UserConfiguration()
.fromJsonObject(require('../config-serializer/user-config.json'));
.fromJsonObject(require('./user-config.json'));
}
getDefault(): UserConfiguration {

View File

@@ -9,7 +9,7 @@ import {
SaveConfigurationReplyAction,
SetPrivilegeOnLinuxReplyAction
} from '../store/actions/device';
import { LoadUserConfigFromDeviceReplyAction } from '../store/actions/user-config';
import { LoadConfigFromDeviceReplyAction } from '../store/actions/user-config';
@Injectable()
export class DeviceRendererService {
@@ -29,8 +29,8 @@ export class DeviceRendererService {
this.ipcRenderer.send(IpcEvents.device.saveUserConfiguration, JSON.stringify(buffer));
}
loadUserConfiguration(): void {
this.ipcRenderer.send(IpcEvents.device.loadUserConfiguration);
loadConfigurationFromKeyboard(): void {
this.ipcRenderer.send(IpcEvents.device.loadConfigurations);
}
private registerEvents(): void {
@@ -46,8 +46,8 @@ export class DeviceRendererService {
this.dispachStoreAction(new SaveConfigurationReplyAction(response));
});
this.ipcRenderer.on(IpcEvents.device.loadUserConfigurationReply, (event: string, response: string) => {
this.dispachStoreAction(new LoadUserConfigFromDeviceReplyAction(JSON.parse(response)));
this.ipcRenderer.on(IpcEvents.device.loadConfigurationReply, (event: string, response: string) => {
this.dispachStoreAction(new LoadConfigFromDeviceReplyAction(JSON.parse(response)));
});
}

View File

@@ -1,5 +1,5 @@
import { Injectable } from '@angular/core';
import { KeystrokeType } from '../config-serializer/config-items/key-action/keystroke-type';
import { KeystrokeType } from 'uhk-common';
@Injectable()
export class MapperService {

View File

@@ -1,8 +1,7 @@
import { Action } from '@ngrx/store';
import { type } from 'uhk-common';
import { Notification, CommandLineArgs } from 'uhk-common';
import { AppStartInfo } from '../../../../../uhk-common/src/models/app-start-info';
import { AppStartInfo, HardwareConfiguration, Notification } from 'uhk-common';
const PREFIX = '[app] ';
@@ -15,7 +14,8 @@ export const ActionTypes = {
APP_PROCESS_START_INFO: type(PREFIX + 'process start info'),
UNDO_LAST: type(PREFIX + 'undo last action'),
UNDO_LAST_SUCCESS: type(PREFIX + 'undo last action success'),
DISMISS_UNDO_NOTIFICATION: type(PREFIX + 'dismiss notification action')
DISMISS_UNDO_NOTIFICATION: type(PREFIX + 'dismiss notification action'),
LOAD_HARDWARE_CONFIGURATION_SUCCESS: type(PREFIX + 'load hardware configuration success')
};
export class AppBootsrappedAction implements Action {
@@ -58,6 +58,12 @@ export class DismissUndoNotificationAction implements Action {
type = ActionTypes.DISMISS_UNDO_NOTIFICATION;
}
export class LoadHardwareConfigurationSuccessAction implements Action {
type = ActionTypes.LOAD_HARDWARE_CONFIGURATION_SUCCESS;
constructor(public payload: HardwareConfiguration) {}
}
export type Actions
= AppStartedAction
| AppBootsrappedAction
@@ -66,4 +72,6 @@ export type Actions
| ProcessAppStartInfoAction
| UndoLastAction
| UndoLastSuccessAction
| DismissUndoNotificationAction;
| DismissUndoNotificationAction
| LoadHardwareConfigurationSuccessAction
;

View File

@@ -1,8 +1,5 @@
import { Action } from '@ngrx/store';
import { KeyAction } from '../../config-serializer/config-items/key-action';
import { Keymap } from '../../config-serializer/config-items/keymap';
import { Macro } from '../../config-serializer/config-items/macro';
import { KeyAction, Keymap, Macro } from 'uhk-common';
export namespace KeymapActions {
export const PREFIX = '[Keymap] ';

View File

@@ -1,7 +1,5 @@
import { Action } from '@ngrx/store';
import { Macro } from '../../config-serializer/config-items/macro';
import { MacroAction } from '../../config-serializer/config-items/macro-action';
import { Macro, MacroAction } from 'uhk-common';
export namespace MacroActions {
export const PREFIX = '[Macro] ';

View File

@@ -1,15 +1,13 @@
import { Action } from '@ngrx/store';
import { type } from 'uhk-common';
import { UserConfiguration } from '../../config-serializer/config-items/user-configuration';
import { type, UserConfiguration, ConfigurationReply } from 'uhk-common';
const PREFIX = '[user-config] ';
// tslint:disable-next-line:variable-name
export const ActionTypes = {
LOAD_USER_CONFIG: type(PREFIX + 'Load User Config'),
LOAD_USER_CONFIG_FROM_DEVICE: type(PREFIX + 'Load User Config from Device'),
LOAD_USER_CONFIG_FROM_DEVICE_REPLY: type(PREFIX + 'Load User Config from Device reply'),
LOAD_CONFIG_FROM_DEVICE: type(PREFIX + 'Load User Config from Device'),
LOAD_CONFIG_FROM_DEVICE_REPLY: type(PREFIX + 'Load User Config from Device reply'),
LOAD_USER_CONFIG_SUCCESS: type(PREFIX + 'Load User Config Success'),
SAVE_USER_CONFIG_SUCCESS: type(PREFIX + 'Save User Config Success')
};
@@ -18,14 +16,14 @@ export class LoadUserConfigAction implements Action {
type = ActionTypes.LOAD_USER_CONFIG;
}
export class LoadUserConfigFromDeviceAction implements Action {
type = ActionTypes.LOAD_USER_CONFIG_FROM_DEVICE;
export class LoadConfigFromDeviceAction implements Action {
type = ActionTypes.LOAD_CONFIG_FROM_DEVICE;
}
export class LoadUserConfigFromDeviceReplyAction implements Action {
type = ActionTypes.LOAD_USER_CONFIG_FROM_DEVICE_REPLY;
export class LoadConfigFromDeviceReplyAction implements Action {
type = ActionTypes.LOAD_CONFIG_FROM_DEVICE_REPLY;
constructor(public payload: Array<number>) { }
constructor(public payload: ConfigurationReply) { }
}
export class LoadUserConfigSuccessAction implements Action {
@@ -43,4 +41,7 @@ export class SaveUserConfigSuccessAction implements Action {
export type Actions
= LoadUserConfigAction
| LoadUserConfigSuccessAction
| SaveUserConfigSuccessAction;
| SaveUserConfigSuccessAction
| LoadConfigFromDeviceAction
| LoadConfigFromDeviceReplyAction
;

View File

@@ -12,7 +12,7 @@ import 'rxjs/add/operator/map';
import 'rxjs/add/operator/mergeMap';
import 'rxjs/add/operator/withLatestFrom';
import { NotificationType, IpcResponse } from 'uhk-common';
import { NotificationType, IpcResponse, UhkBuffer, UserConfiguration } from 'uhk-common';
import {
ActionTypes,
ConnectionStateChangedAction,
@@ -24,9 +24,7 @@ import {
import { DeviceRendererService } from '../../services/device-renderer.service';
import { ShowNotificationAction } from '../actions/app';
import { AppState } from '../index';
import { UserConfiguration } from '../../config-serializer/config-items/user-configuration';
import { UhkBuffer } from '../../config-serializer/uhk-buffer';
import { LoadUserConfigFromDeviceAction } from '../actions/user-config';
import { LoadConfigFromDeviceAction } from '../actions/user-config';
@Injectable()
export class DeviceEffects {
@@ -44,7 +42,7 @@ export class DeviceEffects {
})
.switchMap((connected: boolean) => {
if (connected) {
return Observable.of(new LoadUserConfigFromDeviceAction());
return Observable.of(new LoadConfigFromDeviceAction());
}
return Observable.empty();

View File

@@ -12,11 +12,10 @@ import 'rxjs/add/operator/switchMap';
import 'rxjs/add/operator/withLatestFrom';
import 'rxjs/add/observable/of';
import { Keymap } from 'uhk-common';
import { KeymapActions } from '../actions';
import { AppState } from '../index';
import { Keymap } from '../../config-serializer/config-items/keymap';
@Injectable()
export class KeymapEffects {

View File

@@ -11,7 +11,14 @@ import 'rxjs/add/operator/withLatestFrom';
import 'rxjs/add/operator/mergeMap';
import 'rxjs/add/observable/of';
import { LogService, NotificationType } from 'uhk-common';
import {
ConfigurationReply,
HardwareConfiguration,
LogService,
NotificationType,
UhkBuffer,
UserConfiguration
} from 'uhk-common';
import {
ActionTypes,
@@ -20,21 +27,42 @@ import {
SaveUserConfigSuccessAction
} from '../actions/user-config';
import { UserConfiguration } from '../../config-serializer/config-items/user-configuration';
import { DataStorageRepositoryService } from '../../services/datastorage-repository.service';
import { DefaultUserConfigurationService } from '../../services/default-user-configuration.service';
import { AppState, getPrevUserConfiguration, getUserConfiguration } from '../index';
import { KeymapActions } from '../actions/keymap';
import { MacroActions } from '../actions/macro';
import { UndoUserConfigData } from '../../models/undo-user-config-data';
import { ShowNotificationAction, DismissUndoNotificationAction } from '../actions/app';
import { ShowNotificationAction, DismissUndoNotificationAction, LoadHardwareConfigurationSuccessAction } from '../actions/app';
import { ShowSaveToKeyboardButtonAction } from '../actions/device';
import { DeviceRendererService } from '../../services/device-renderer.service';
import { UhkBuffer } from '../../config-serializer/uhk-buffer';
@Injectable()
export class UserConfigEffects {
private static getUserConfigFromDeviceResponse(json: string): UserConfiguration {
const data = JSON.parse(json);
const userConfig = new UserConfiguration();
userConfig.fromBinary(UhkBuffer.fromArray(data));
if (userConfig.dataModelVersion > 0) {
return userConfig;
}
return null;
}
private static getHardwareConfigFromDeviceResponse(json: string): HardwareConfiguration {
const data = JSON.parse(json);
const hardwareConfig = new HardwareConfiguration();
hardwareConfig.fromBinary(UhkBuffer.fromArray(data));
if (hardwareConfig.uuid > 0) {
return hardwareConfig;
}
return null;
}
@Effect() loadUserConfig$: Observable<Action> = this.actions$
.ofType(ActionTypes.LOAD_USER_CONFIG)
.startWith(new LoadUserConfigAction())
@@ -88,38 +116,36 @@ export class UserConfigEffects {
return [new LoadUserConfigSuccessAction(config), go(payload.path)];
});
@Effect({dispatch: false}) loadUserConfigFromDevice$ = this.actions$
.ofType(ActionTypes.LOAD_USER_CONFIG_FROM_DEVICE)
.do(() => this.deviceRendererService.loadUserConfiguration());
@Effect({dispatch: false}) loadConfigFromDevice$ = this.actions$
.ofType(ActionTypes.LOAD_CONFIG_FROM_DEVICE)
.do(() => this.deviceRendererService.loadConfigurationFromKeyboard());
@Effect() loadUserConfigFromDeviceReply$ = this.actions$
.ofType(ActionTypes.LOAD_USER_CONFIG_FROM_DEVICE_REPLY)
.map(action => action.payload)
.switchMap((data: Array<number>) => {
try {
let userConfig;
if (data.length > 0) {
const uhkBuffer = new UhkBuffer();
let hasNonZeroValue = false;
for (const num of data) {
if (num > 0) {
hasNonZeroValue = true;
}
uhkBuffer.writeUInt8(num);
}
uhkBuffer.offset = 0;
userConfig = new UserConfiguration();
userConfig.fromBinary(uhkBuffer);
if (hasNonZeroValue) {
return Observable.of(new LoadUserConfigSuccessAction(userConfig));
}
}
} catch (err) {
this.logService.error('Eeprom parse error:', err);
@Effect() loadConfigFromDeviceReply$ = this.actions$
.ofType(ActionTypes.LOAD_CONFIG_FROM_DEVICE_REPLY)
.map(toPayload)
.mergeMap((data: ConfigurationReply): any => {
if (!data.success) {
return [new ShowNotificationAction({
type: NotificationType.Error,
message: data.error
})];
}
return Observable.empty();
try {
const userConfig = UserConfigEffects.getUserConfigFromDeviceResponse(data.userConfiguration);
const hardwareConfig = UserConfigEffects.getHardwareConfigFromDeviceResponse(data.hardwareConfiguration);
return [
new LoadUserConfigSuccessAction(userConfig),
new LoadHardwareConfigurationSuccessAction(hardwareConfig)
];
} catch (err) {
this.logService.error('Eeprom parse error:', err);
return [new ShowNotificationAction({
type: NotificationType.Error,
message: err.message
})];
}
});
constructor(private actions$: Actions,
@@ -147,4 +173,5 @@ export class UserConfigEffects {
return config;
}
}

View File

@@ -3,11 +3,10 @@ import { compose } from '@ngrx/core/compose';
import { ActionReducer, combineReducers } from '@ngrx/store';
import { RouterState, routerReducer } from '@ngrx/router-store';
import { storeFreeze } from 'ngrx-store-freeze';
import { Keymap, UserConfiguration } from 'uhk-common';
import userConfigurationReducer from './reducers/user-configuration';
import presetReducer from './reducers/preset';
import { Keymap } from '../config-serializer/config-items/keymap';
import { UserConfiguration } from '../config-serializer/config-items/user-configuration';
import * as fromAppUpdate from './reducers/app-update.reducer';
import * as autoUpdateSettings from './reducers/auto-update-settings';
import * as fromApp from './reducers/app.reducer';
@@ -53,6 +52,8 @@ export const showAddonMenu = createSelector(appState, fromApp.showAddonMenu);
export const getUndoableNotification = createSelector(appState, fromApp.getUndoableNotification);
export const getPrevUserConfiguration = createSelector(appState, fromApp.getPrevUserConfiguration);
export const runningInElectron = createSelector(appState, fromApp.runningInElectron);
export const getHardwareConfiguration = createSelector(appState, fromApp.getHardwareConfiguration);
export const getKeyboardLayout = createSelector(appState, fromApp.getKeyboardLayout);
export const appUpdateState = (state: AppState) => state.appUpdate;
export const getShowAppUpdateAvailable = createSelector(appUpdateState, fromAppUpdate.getShowAppUpdateAvailable);

View File

@@ -1,10 +1,10 @@
import { routerActions } from '@ngrx/router-store';
import { Action } from '@ngrx/store';
import { runInElectron, Notification, NotificationType } from 'uhk-common';
import { HardwareConfiguration, runInElectron, Notification, NotificationType, UserConfiguration } from 'uhk-common';
import { ActionTypes, ShowNotificationAction } from '../actions/app';
import { ActionTypes as UserConfigActionTypes } from '../actions/user-config';
import { UserConfiguration } from '../../config-serializer/config-items/user-configuration';
import { KeyboardLayout } from '../../keyboard/keyboard-layout.enum';
export interface State {
started: boolean;
@@ -13,7 +13,8 @@ export interface State {
navigationCountAfterNotification: number;
prevUserConfig?: UserConfiguration;
runningInElectron: boolean;
userConfigLoading: boolean;
configLoading: boolean;
hardwareConfig?: HardwareConfiguration;
}
const initialState: State = {
@@ -21,7 +22,7 @@ const initialState: State = {
showAddonMenu: false,
navigationCountAfterNotification: 0,
runningInElectron: runInElectron(),
userConfigLoading: true
configLoading: true
};
export function reducer(state = initialState, action: Action) {
@@ -56,7 +57,7 @@ export function reducer(state = initialState, action: Action) {
// When deleted a keymap or macro the app automaticaly navigate to other keymap, or macro, so
// so we have to count the navigations and when reach the 2nd then remove the dialog.
case routerActions.UPDATE_LOCATION: {
const newState = { ...state };
const newState = {...state};
newState.navigationCountAfterNotification++;
if (newState.navigationCountAfterNotification > 1) {
@@ -79,18 +80,24 @@ export function reducer(state = initialState, action: Action) {
return {
...state,
prevUserConfig: action.payload,
userConfigLoading: false
configLoading: false
};
}
case UserConfigActionTypes.LOAD_USER_CONFIG_FROM_DEVICE:
case UserConfigActionTypes.LOAD_CONFIG_FROM_DEVICE:
case UserConfigActionTypes.LOAD_USER_CONFIG: {
return {
...state,
userConfigLoading: true
configLoading: true
};
}
case ActionTypes.LOAD_HARDWARE_CONFIGURATION_SUCCESS:
return {
...state,
hardwareConfig: action.payload
};
default:
return state;
}
@@ -100,3 +107,11 @@ export const showAddonMenu = (state: State) => state.showAddonMenu;
export const getUndoableNotification = (state: State) => state.undoableNotification;
export const getPrevUserConfiguration = (state: State) => state.prevUserConfig;
export const runningInElectron = (state: State) => state.runningInElectron;
export const getHardwareConfiguration = (state: State) => state.hardwareConfig;
export const getKeyboardLayout = (state: State): KeyboardLayout => {
if (state.hardwareConfig && state.hardwareConfig.isIso) {
return KeyboardLayout.ISO;
}
return KeyboardLayout.ANSI;
};

View File

@@ -1,6 +1,6 @@
import { Action } from '@ngrx/store';
import { Keymap } from 'uhk-common';
import { Keymap } from '../../config-serializer/config-items/keymap';
import { KeymapActions } from '../actions/keymap';
const initialState: Keymap[] = [];

View File

@@ -5,12 +5,7 @@ import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/of';
import 'rxjs/add/operator/map';
import { Helper as KeyActionHelper } from '../../config-serializer/config-items/key-action';
import { Keymap } from '../../config-serializer/config-items/keymap';
import { Macro } from '../../config-serializer/config-items/macro';
import { UserConfiguration } from '../../config-serializer/config-items/user-configuration';
import { Layer } from '../../config-serializer/config-items/layer';
import { Module } from '../../config-serializer/config-items/module';
import { Keymap, KeyActionHelper, Layer, Macro, Module, UserConfiguration } from 'uhk-common';
import { KeymapActions, MacroActions } from '../actions';
import { AppState } from '../index';
import { ActionTypes } from '../actions/user-config';

View File

@@ -1,6 +1,6 @@
import * as storage from 'electron-settings';
import { UserConfiguration } from '../../app/config-serializer/config-items/user-configuration';
import { UserConfiguration } from 'uhk-common';
import { DataStorageRepositoryService } from '../../app/services/datastorage-repository.service';
import { AutoUpdateSettings } from '../../app/models/auto-update-settings';

View File

@@ -1,25 +1,19 @@
import { Injectable } from '@angular/core';
import * as log from 'electron-log';
import * as util from 'util';
import { LogService } from 'uhk-common';
const transferRegExp = /USB\[T]:/;
const writeRegExp = /USB\[W]:/;
const readRegExp = /USB\[R]: 00/;
const errorRegExp = /(?:(USB\[R]: ([^0]|0[^0])))/;
import { LogService, LogRegExps } from 'uhk-common';
// https://github.com/megahertz/electron-log/issues/44
// console.debug starting with Chromium 58 this method is a no-op on Chromium browsers.
if (console.debug) {
console.debug = (...args: any[]): void => {
if (writeRegExp.test(args[0])) {
if (LogRegExps.writeRegExp.test(args[0])) {
console.log('%c' + args[0], 'color:blue');
} else if (readRegExp.test(args[0])) {
} else if (LogRegExps.readRegExp.test(args[0])) {
console.log('%c' + args[0], 'color:green');
} else if (errorRegExp.test(args[0])) {
} else if (LogRegExps.errorRegExp.test(args[0])) {
console.log('%c' + args[0], 'color:red');
}else if (transferRegExp.test(args[0])) {
}else if (LogRegExps.transferRegExp.test(args[0])) {
console.log('%c' + args[0], 'color:orange');
} else {
console.log(...args);

View File

@@ -1,17 +0,0 @@
import { LogService } from 'uhk-common';
import { UhkDeviceService } from './uhk-device.service';
import { UhkHidApiService } from './uhk-hid-api.service';
export function uhkDeviceFactory(logService: LogService): UhkDeviceService {
// HID API officially support MAC, WIN and linux x64 platform
// https://github.com/node-hid/node-hid#platform-support
if (process.platform === 'darwin' ||
process.platform === 'win32' ||
(process.platform === 'linux' && process.arch === 'x64')) {
return new UhkHidApiService(logService);
}
// On other platform use libUsb, but we try to test on all platform
// return new UhkLibUsbApiService(logService);
return new UhkHidApiService(logService);
}

View File

@@ -1,106 +0,0 @@
import { OnDestroy } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import { Subject } from 'rxjs/Subject';
import { Observer } from 'rxjs/Observer';
import { Subscriber } from 'rxjs/Subscriber';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
import { Subscription } from 'rxjs/Subscription';
import { Constants, LogService } from 'uhk-common';
import { SenderMessage } from '../models/sender-message';
enum Command {
UploadConfig = 8,
ApplyConfig = 9
}
export abstract class UhkDeviceService implements OnDestroy {
protected connected$: BehaviorSubject<boolean>;
protected initialized$: BehaviorSubject<boolean>;
protected deviceOpened$: BehaviorSubject<boolean>;
protected outSubscription: Subscription;
protected messageIn$: Observable<Buffer>;
protected messageOut$: Subject<SenderMessage>;
constructor(protected logService: LogService) {
this.messageOut$ = new Subject<SenderMessage>();
this.initialized$ = new BehaviorSubject(false);
this.connected$ = new BehaviorSubject(false);
this.deviceOpened$ = new BehaviorSubject(false);
this.outSubscription = Subscription.EMPTY;
}
ngOnDestroy() {
this.disconnect();
this.initialized$.unsubscribe();
this.connected$.unsubscribe();
this.deviceOpened$.unsubscribe();
}
sendConfig(configBuffer: Buffer): Observable<Buffer> {
return Observable.create((subscriber: Subscriber<Buffer>) => {
this.logService.info('Sending...', configBuffer);
const fragments: Buffer[] = [];
const MAX_SENDING_PAYLOAD_SIZE = Constants.MAX_PAYLOAD_SIZE - 4;
for (let offset = 0; offset < configBuffer.length; offset += MAX_SENDING_PAYLOAD_SIZE) {
const length = offset + MAX_SENDING_PAYLOAD_SIZE < configBuffer.length
? MAX_SENDING_PAYLOAD_SIZE
: configBuffer.length - offset;
const header = new Buffer([Command.UploadConfig, length, offset & 0xFF, offset >> 8]);
fragments.push(Buffer.concat([header, configBuffer.slice(offset, offset + length)]));
}
const buffers: Buffer[] = [];
const observer: Observer<Buffer> = {
next: (buffer: Buffer) => buffers.push(buffer),
error: error => subscriber.error(error),
complete: () => {
if (buffers.length === fragments.length) {
subscriber.next(Buffer.concat(buffers));
subscriber.complete();
this.logService.info('Sending finished');
}
}
};
fragments
.map<SenderMessage>(fragment => ({ buffer: fragment, observer }))
.forEach(senderPackage => this.messageOut$.next(senderPackage));
});
}
applyConfig(): Observable<Buffer> {
return Observable.create((subscriber: Subscriber<Buffer>) => {
this.logService.info('Applying configuration');
this.messageOut$.next({
buffer: new Buffer([Command.ApplyConfig]),
observer: subscriber
});
});
}
isInitialized(): Observable<boolean> {
return this.initialized$.asObservable();
}
isConnected(): Observable<boolean> {
return this.connected$.asObservable();
}
isOpened(): Observable<boolean> {
return this.deviceOpened$.asObservable();
}
disconnect() {
this.outSubscription.unsubscribe();
this.messageIn$ = undefined;
this.initialized$.next(false);
this.deviceOpened$.next(false);
this.connected$.next(false);
}
abstract initialize(): void;
abstract hasPermissions(): Observable<boolean>;
}

View File

@@ -1,136 +0,0 @@
import { Injectable, OnDestroy } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import { Subscriber } from 'rxjs/Subscriber';
import { Device, devices, HID } from 'node-hid';
import 'rxjs/add/observable/empty';
import 'rxjs/add/observable/interval';
import 'rxjs/add/operator/catch';
import 'rxjs/add/operator/concat';
import 'rxjs/add/operator/combineLatest';
import 'rxjs/add/operator/concatMap';
import 'rxjs/add/operator/publish';
import 'rxjs/add/operator/do';
import 'rxjs/add/operator/startWith';
import 'rxjs/add/operator/map';
import { Constants, LogService } from 'uhk-common';
import { UhkDeviceService } from './uhk-device.service';
@Injectable()
export class UhkHidApiService extends UhkDeviceService implements OnDestroy {
private device: HID;
constructor(protected logService: LogService) {
super(logService);
}
ngOnDestroy() {
super.ngOnDestroy();
}
initialize(): void {
if (this.initialized$.getValue()) {
return;
}
this.device = this.getDevice();
if (!this.device) {
return;
}
this.deviceOpened$.next(true);
this.messageIn$ = Observable.create((subscriber: Subscriber<Buffer>) => {
this.logService.info('Try to read');
this.device.read((error: any, data: any = []) => {
if (error) {
this.logService.error('reading error', error);
subscriber.error(error);
} else {
this.logService.info('read data', data);
subscriber.next(data);
subscriber.complete();
}
});
});
const outSending = this.messageOut$.concatMap(senderPackage => {
return (<Observable<void>>Observable.create((subscriber: Subscriber<void>) => {
this.logService.info('transfering', senderPackage.buffer);
const data = Array.prototype.slice.call(senderPackage.buffer, 0);
// 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);
}
// 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);
try {
this.device.write(data);
this.logService.info('transfering finished');
subscriber.complete();
}
catch (error) {
this.logService.error('transfering errored', error);
subscriber.error(error);
}
})).concat(this.messageIn$)
.do(buffer => senderPackage.observer.next(buffer) && senderPackage.observer.complete())
.catch((error: string) => {
senderPackage.observer.error(error);
return Observable.empty<void>();
});
}).publish();
this.outSubscription = outSending.connect();
this.initialized$.next(true);
}
hasPermissions(): Observable<boolean> {
return this.isConnected()
.combineLatest(this.deviceOpened$)
.map((latest: boolean[]) => {
const connected = latest[0];
const opened = latest[1];
if (!connected) {
return false;
} else if (opened) {
return true;
}
return true;
});
}
/**
* Return the 0 interface of the keyboard.
* @returns {HID}
*/
getDevice(): HID {
try {
const devs = devices();
this.logService.info('Available devices:', devs);
const dev = devs.find((x: Device) =>
x.vendorId === Constants.VENDOR_ID &&
x.productId === Constants.PRODUCT_ID &&
((x.usagePage === 128 && x.usage === 129) || x.interface === 0));
const device = new HID(dev.path);
this.logService.info('Used device:', dev);
return device;
}
catch (err) {
this.logService.error('Can not create device:', err);
}
return null;
}
}