Compare commits
30 Commits
fix-firmwa
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b53751b408 | ||
|
|
8a7f30dbb1 | ||
|
|
3b67e4d71d | ||
|
|
d0626405a9 | ||
|
|
df040fb78e | ||
|
|
ea4ca7c39c | ||
|
|
c477f9bcdc | ||
|
|
abe740cf61 | ||
|
|
1003e7b14b | ||
|
|
fef24613e4 | ||
|
|
9844645409 | ||
|
|
6814c8e126 | ||
|
|
d87d770042 | ||
|
|
5dc2e6f47b | ||
|
|
9f8926e34b | ||
|
|
0bd1152a32 | ||
|
|
4a1e88ed83 | ||
|
|
9db719a54b | ||
|
|
49d31f90f7 | ||
|
|
d364ac85a6 | ||
|
|
6867ef45f6 | ||
|
|
bfa8343aa5 | ||
|
|
a8c2866f95 | ||
|
|
d15e08430f | ||
|
|
fa5f5cdc5d | ||
|
|
f65bf80c74 | ||
|
|
b691f866c5 | ||
|
|
4e09f95653 | ||
|
|
37a67805ce | ||
|
|
735aae03d9 |
@@ -13,7 +13,9 @@ Firmware: 8.5.**4** [[release](https://github.com/UltimateHackingKeyboard/firmwa
|
||||
- Implement the Kinetis bootloader protocol natively instead of relying on blhost.
|
||||
- Fix device recovery mode.
|
||||
- Correctly display whether the UHK is detected.
|
||||
- Animate keyboard splitting, merging, and the presence of the left half.
|
||||
- Don't disable input in the key action popover after adding a layer switch action, deleting it, and trying to edit it on its layer.
|
||||
- Provide reasonable default mouse settings for Macs.
|
||||
- Don't change tab immediately upon closing the key action popover.
|
||||
- Fix UI glitch that occurrs when hitting Tab after updating keymap description.
|
||||
- Make the Agent icon slightly smaller to be consistent with most application icons.
|
||||
|
||||
@@ -12,7 +12,7 @@ Agent is the configuration application of the [Ultimate Hacking Keyboard](https:
|
||||
|
||||
### Step 1: Build Dependencies
|
||||
|
||||
You'll need Node.js LTS. Use your OS package manager to install it. [Check the NodeJS site for more info.](https://nodejs.org/en/download/package-manager/ "Installing Node.js via package manager") Mac OS users can simply `brew install node` to get both. Should you need multiple Node.js versions on the same computer, use Node Version Manager for [Mac/Linux](https://github.com/creationix/nvm) or for [Windows](https://github.com/coreybutler/nvm-windows)
|
||||
You'll need Node.js 12. Use your OS package manager to install it. [Check the NodeJS site for more info.](https://nodejs.org/en/download/package-manager/ "Installing Node.js via package manager") Mac OS users can simply `brew install node` to get both. Should you need multiple Node.js versions on the same computer, use Node Version Manager for [Mac/Linux](https://github.com/creationix/nvm) or for [Windows](https://github.com/coreybutler/nvm-windows)
|
||||
|
||||
You'll also need `libusb`.
|
||||
On debian-based linux distros, `apt-get install libusb-dev libudev-dev g++` is sufficient.
|
||||
|
||||
3632
package-lock.json
generated
3632
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
30
package.json
30
package.json
@@ -4,9 +4,9 @@
|
||||
"author": "Ultimate Gadget Laboratories",
|
||||
"main": "electron/dist/electron-main.js",
|
||||
"version": "1.2.13",
|
||||
"firmwareVersion": "8.5.4",
|
||||
"deviceProtocolVersion": "4.4.0",
|
||||
"userConfigVersion": "4.0.1",
|
||||
"firmwareVersion": "8.6.0",
|
||||
"deviceProtocolVersion": "4.5.0",
|
||||
"userConfigVersion": "4.1.1",
|
||||
"hardwareConfigVersion": "1.0.0",
|
||||
"description": "Agent is the configuration application of the Ultimate Hacking Keyboard.",
|
||||
"repository": {
|
||||
@@ -27,47 +27,42 @@
|
||||
"@types/jasmine": "3.3.12",
|
||||
"@types/jasminewd2": "2.0.3",
|
||||
"@types/jquery": "3.3.29",
|
||||
"@types/jsonfile": "4.0.1",
|
||||
"@types/jsonfile": "5.0.0",
|
||||
"@types/lodash": "4.14.136",
|
||||
"@types/node": "8.0.53",
|
||||
"@types/node-hid": "0.7.0",
|
||||
"@types/node-hid": "0.7.2",
|
||||
"@types/request": "2.0.8",
|
||||
"@types/semver": "5.5.0",
|
||||
"@types/tmp": "0.0.33",
|
||||
"autoprefixer": "6.5.3",
|
||||
"buffer": "5.0.6",
|
||||
"check-node-version": "^3.2.0",
|
||||
"check-node-version": "4.0.1",
|
||||
"copy-webpack-plugin": "5.0.0",
|
||||
"copyfiles": "^2.0.0",
|
||||
"copyfiles": "2.1.1",
|
||||
"core-js": "2.4.1",
|
||||
"cross-env": "5.0.5",
|
||||
"decompress": "4.2.0",
|
||||
"decompress-tarbz2": "4.1.1",
|
||||
"devtron": "1.4.0",
|
||||
"electron": "5.0.9",
|
||||
"electron-builder": "20.34.0",
|
||||
"electron-builder": "20.44.4",
|
||||
"electron-debug": "1.5.0",
|
||||
"electron-devtools-installer": "2.2.3",
|
||||
"electron-log": "2.2.16",
|
||||
"electron-rebuild": "1.8.5",
|
||||
"electron-rebuild": "1.8.6",
|
||||
"electron-settings": "3.1.4",
|
||||
"electron-updater": "2.21.4",
|
||||
"exports-loader": "0.6.3",
|
||||
"file-loader": "0.10.0",
|
||||
"electron-updater": "4.1.2",
|
||||
"fs-extra": "8.1.0",
|
||||
"gh-pages": "2.0.1",
|
||||
"html-webpack-plugin": "3.2.0",
|
||||
"jasmine": "3.4.0",
|
||||
"jasmine-core": "3.4.0",
|
||||
"jasmine-node": "3.0.0",
|
||||
"jasmine-ts": "0.3.0",
|
||||
"jsonfile": "4.0.0",
|
||||
"jsonfile": "5.0.0",
|
||||
"lerna": "3.16.4",
|
||||
"lodash": "4.17.15",
|
||||
"node-hid": "0.7.9",
|
||||
"npm-run-all": "4.0.2",
|
||||
"npm-run-all": "4.1.5",
|
||||
"nrf-intel-hex": "1.3.0",
|
||||
"postcss-url": "8.0.0",
|
||||
"pre-commit": "1.2.2",
|
||||
"request": "2.88.0",
|
||||
"rimraf": "2.6.1",
|
||||
@@ -96,6 +91,7 @@
|
||||
"server:electron": "lerna exec --scope uhk-web npm run server:renderer",
|
||||
"electron": "lerna exec --scope uhk-agent npm start",
|
||||
"electron:spe": "lerna exec --scope uhk-agent npm run electron:spe",
|
||||
"electron:kboot": "lerna exec --scope uhk-agent npm run electron:kboot",
|
||||
"pack": "node ./scripts/release.js",
|
||||
"sprites": "node ./scripts/generate-svg-sprites",
|
||||
"release": "node ./scripts/release.js",
|
||||
|
||||
@@ -12,15 +12,19 @@ export class KBoot {
|
||||
}
|
||||
|
||||
open(): void {
|
||||
logger('Open peripheral');
|
||||
this.peripheral.open();
|
||||
}
|
||||
|
||||
close(): void {
|
||||
logger('Close peripheral');
|
||||
this.peripheral.close();
|
||||
}
|
||||
|
||||
// ================= Read properties ==================
|
||||
async getProperty(property: Properties, memoryId = MemoryIds.Internal): Promise<CommandResponse> {
|
||||
logger('Start read memory %o', { property, memoryId });
|
||||
|
||||
const command: CommandOption = {
|
||||
command: Commands.GetProperty,
|
||||
params: [
|
||||
@@ -32,14 +36,17 @@ export class KBoot {
|
||||
const response = await this.peripheral.sendCommand(command);
|
||||
|
||||
if (response.tag !== ResponseTags.Property) {
|
||||
logger('Response tag is not property response: %d', property);
|
||||
throw new Error('Response tag is not property response');
|
||||
}
|
||||
|
||||
if (response.code === ResponseCodes.UnknownProperty) {
|
||||
logger('Unknown property %d', response.code);
|
||||
throw new Error('Unknown property!');
|
||||
}
|
||||
|
||||
if (response.code !== ResponseCodes.Success) {
|
||||
logger('Unknown error %d', response.code);
|
||||
throw new Error(`Unknown error. Error code:${response.code}`);
|
||||
}
|
||||
|
||||
@@ -47,6 +54,8 @@ export class KBoot {
|
||||
}
|
||||
|
||||
async getBootloaderVersion(): Promise<BootloaderVersion> {
|
||||
logger('Start to read Bootloader Version');
|
||||
|
||||
const response = await this.getProperty(Properties.BootloaderVersion);
|
||||
|
||||
const version: BootloaderVersion = {
|
||||
@@ -64,7 +73,9 @@ export class KBoot {
|
||||
// ================= End read properties ==================
|
||||
|
||||
async flashSecurityDisable(key: number[]): Promise<void> {
|
||||
logger('Start flash security disable %o', { key });
|
||||
if (key.length !== 8) {
|
||||
logger('Error: Flash security key must be 8 byte. %o', key);
|
||||
throw new Error('Flash security key must be 8 byte');
|
||||
}
|
||||
|
||||
@@ -76,15 +87,18 @@ export class KBoot {
|
||||
const response = await this.peripheral.sendCommand(command);
|
||||
|
||||
if (response.tag !== ResponseTags.Generic) {
|
||||
logger('Response tag is not generic response: %d', response.tag);
|
||||
throw new Error('Response tag is not generic response');
|
||||
}
|
||||
|
||||
if (response.code !== ResponseCodes.Success) {
|
||||
logger('Can not disable flash security: %d', response.code);
|
||||
throw new Error(`Can not disable flash security`);
|
||||
}
|
||||
}
|
||||
|
||||
async flashEraseRegion(startAddress: number, count: number): Promise<void> {
|
||||
logger('Start flash erase region');
|
||||
const command: CommandOption = {
|
||||
command: Commands.FlashEraseRegion,
|
||||
params: [
|
||||
@@ -96,15 +110,18 @@ export class KBoot {
|
||||
const response = await this.peripheral.sendCommand(command);
|
||||
|
||||
if (response.tag !== ResponseTags.Generic) {
|
||||
logger('Response tag is not generic response: %d', response.tag);
|
||||
throw new Error('Response tag is not generic response');
|
||||
}
|
||||
|
||||
if (response.code !== ResponseCodes.Success) {
|
||||
throw new Error(`Can not disable flash security`);
|
||||
logger('Can not flash erase region: %d', response.code);
|
||||
throw new Error(`Can not flash erase region`);
|
||||
}
|
||||
}
|
||||
|
||||
async flashEraseAllUnsecure(): Promise<void> {
|
||||
logger('Start flash erase all unsecure');
|
||||
const command: CommandOption = {
|
||||
command: Commands.FlashEraseAllUnsecure,
|
||||
params: []
|
||||
@@ -113,19 +130,23 @@ export class KBoot {
|
||||
const response = await this.peripheral.sendCommand(command);
|
||||
|
||||
if (response.tag !== ResponseTags.Generic) {
|
||||
logger('Response tag is not generic response: %d', response.tag);
|
||||
throw new Error('Response tag is not generic response');
|
||||
}
|
||||
|
||||
if (response.code !== ResponseCodes.Success) {
|
||||
throw new Error(`Can not disable flash security`);
|
||||
logger('Can not flash erase all unsecure: %d', response.code);
|
||||
throw new Error(`Can not flash erase all unsecure`);
|
||||
}
|
||||
}
|
||||
|
||||
async readMemory(startAddress: number, count: number): Promise<any> {
|
||||
logger('Start read memory %o', { startAddress, count });
|
||||
return this.peripheral.readMemory(startAddress, count);
|
||||
}
|
||||
|
||||
async writeMemory(options: DataOption): Promise<void> {
|
||||
logger('Start write memory %o', { options });
|
||||
return this.peripheral.writeMemory(options);
|
||||
}
|
||||
|
||||
@@ -133,6 +154,7 @@ export class KBoot {
|
||||
* Reset the bootloader
|
||||
*/
|
||||
async reset(): Promise<void> {
|
||||
logger('Start reset the bootloader');
|
||||
const command: CommandOption = {
|
||||
command: Commands.Reset,
|
||||
params: []
|
||||
@@ -154,10 +176,12 @@ export class KBoot {
|
||||
}
|
||||
|
||||
if (response.tag !== ResponseTags.Generic) {
|
||||
logger('Response tag is not generic response: %d', response.tag);
|
||||
throw new Error('Response tag is not generic response');
|
||||
}
|
||||
|
||||
if (response.code !== ResponseCodes.Success) {
|
||||
logger('Unknown error %d', response.code);
|
||||
throw new Error(`Unknown error. Error code:${response.code}`);
|
||||
}
|
||||
}
|
||||
@@ -168,8 +192,9 @@ export class KBoot {
|
||||
* @param [speed=64] - Speed of the I2C
|
||||
*/
|
||||
async configureI2c(address: number, speed = 64): Promise<void> {
|
||||
|
||||
logger('Start configure I2C', { address, speed });
|
||||
if (address > 127) {
|
||||
logger('Only 7-bit i2c address is supported');
|
||||
throw new Error('Only 7-bit i2c address is supported');
|
||||
}
|
||||
|
||||
@@ -184,10 +209,12 @@ export class KBoot {
|
||||
const response = await this.peripheral.sendCommand(command);
|
||||
|
||||
if (response.tag !== ResponseTags.Generic) {
|
||||
logger('Response tag is not generic response: %d', response.tag);
|
||||
throw new Error('Response tag is not generic response');
|
||||
}
|
||||
|
||||
if (response.code !== ResponseCodes.Success) {
|
||||
logger('Unknown error %d', response.code);
|
||||
throw new Error(`Unknown error. Error code:${response.code}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,6 +55,8 @@ export class UsbPeripheral implements Peripheral {
|
||||
close(): void {
|
||||
if (this._device) {
|
||||
this._device.close();
|
||||
this._device.removeAllListeners('data');
|
||||
this._device.removeAllListeners('error');
|
||||
this._device = undefined;
|
||||
}
|
||||
}
|
||||
@@ -222,6 +224,7 @@ export class UsbPeripheral implements Peripheral {
|
||||
}
|
||||
|
||||
private _readFromBuffer(bufferName: string, byte: number, timeout: number): Promise<Buffer> {
|
||||
logger('start read from buffer %o', { bufferName, byte, timeout });
|
||||
return new Promise<Buffer>(async (resolve, reject) => {
|
||||
const startTime = new Date();
|
||||
while (startTime.getTime() + timeout > new Date().getTime()) {
|
||||
@@ -266,6 +269,7 @@ export class UsbPeripheral implements Peripheral {
|
||||
}
|
||||
|
||||
private async _getNextCommandResponse(): Promise<CommandResponse> {
|
||||
logger('Start read next command response');
|
||||
const response = await this._readFromCommandStream();
|
||||
const commandResponse = decodeCommandResponse(response);
|
||||
logger('next command response: %o', commandResponse);
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
"scripts": {
|
||||
"start": "cross-env DEBUG=kboot* electron ./dist/electron-main.js",
|
||||
"electron:spe": "electron ./dist/electron-main.js --spe",
|
||||
"electron:kboot": "cross-env DEBUG=kboot* electron ./dist/electron-main.js --useKboot",
|
||||
"build": "webpack && npm run install:build-deps && npm run build:usb && npm run download-firmware && npm run copy-to-tmp-folder",
|
||||
"build:usb": "electron-rebuild -w node-hid -p -m ./dist",
|
||||
"lint": "tslint --project tsconfig.json",
|
||||
|
||||
@@ -11,6 +11,7 @@ import * as commandLineArgs from 'command-line-args';
|
||||
import { UhkHidDevice, UhkOperations } from 'uhk-usb';
|
||||
// import { ElectronDataStorageRepositoryService } from './services/electron-datastorage-repository.service';
|
||||
import { CommandLineArgs, LogRegExps } from 'uhk-common';
|
||||
import { UhkBlhost } from 'uhk-usb';
|
||||
import { DeviceService } from './services/device.service';
|
||||
import { logger } from './services/logger.service';
|
||||
import { AppUpdateService } from './services/app-update.service';
|
||||
@@ -22,7 +23,8 @@ import { loadWindowState, saveWindowState } from './util/window';
|
||||
|
||||
const optionDefinitions = [
|
||||
{name: 'addons', type: Boolean},
|
||||
{name: 'spe', type: Boolean} // simulate privilege escalation error
|
||||
{name: 'spe', type: Boolean}, // simulate privilege escalation error
|
||||
{name: 'useKboot', type: Boolean} // If it is true use kboot package instead of blhost for firmware upgrade
|
||||
];
|
||||
|
||||
const options: CommandLineArgs = commandLineArgs(optionDefinitions);
|
||||
@@ -36,6 +38,7 @@ let win: Electron.BrowserWindow;
|
||||
autoUpdater.logger = logger;
|
||||
|
||||
let deviceService: DeviceService;
|
||||
let uhkBlhost: UhkBlhost;
|
||||
let uhkHidDeviceService: UhkHidDevice;
|
||||
let uhkOperations: UhkOperations;
|
||||
let appUpdateService: AppUpdateService;
|
||||
@@ -100,8 +103,9 @@ function createWindow() {
|
||||
|
||||
setMenu(win);
|
||||
uhkHidDeviceService = new UhkHidDevice(logger, options, packagesDir);
|
||||
uhkOperations = new UhkOperations(logger, uhkHidDeviceService, packagesDir);
|
||||
deviceService = new DeviceService(logger, win, uhkHidDeviceService, uhkOperations, packagesDir);
|
||||
uhkBlhost = new UhkBlhost(logger, packagesDir);
|
||||
uhkOperations = new UhkOperations(logger, uhkBlhost, uhkHidDeviceService, packagesDir);
|
||||
deviceService = new DeviceService(logger, win, uhkHidDeviceService, uhkOperations, packagesDir, options);
|
||||
appUpdateService = new AppUpdateService(logger, win, app);
|
||||
appService = new AppService(logger, win, deviceService, options, uhkHidDeviceService);
|
||||
sudoService = new SudoService(logger, options);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { ipcMain } from 'electron';
|
||||
import { isEqual } from 'lodash';
|
||||
import {
|
||||
CommandLineArgs,
|
||||
ConfigurationReply,
|
||||
DeviceConnectionState,
|
||||
FirmwareUpgradeIpcResponse,
|
||||
@@ -41,7 +42,8 @@ export class DeviceService {
|
||||
private win: Electron.BrowserWindow,
|
||||
private device: UhkHidDevice,
|
||||
private operations: UhkOperations,
|
||||
private rootDir: string) {
|
||||
private rootDir: string,
|
||||
private options: CommandLineArgs) {
|
||||
this.startPollUhkDevice();
|
||||
this.uhkDevicePoller()
|
||||
.catch(error => {
|
||||
@@ -182,15 +184,25 @@ export class DeviceService {
|
||||
const packageJson = await getPackageJsonFromPathAsync(firmwarePathData.packageJsonPath);
|
||||
this.logService.debug('New firmware version:', packageJson.firmwareVersion);
|
||||
|
||||
await this.operations.updateRightFirmware(firmwarePathData.rightFirmwarePath);
|
||||
await this.operations.updateLeftModule(firmwarePathData.leftFirmwarePath);
|
||||
if (this.options.useKboot) {
|
||||
await this.operations.updateRightFirmwareWithKboot(firmwarePathData.rightFirmwarePath);
|
||||
await this.operations.updateLeftModuleWithKboot(firmwarePathData.leftFirmwarePath);
|
||||
} else {
|
||||
await this.operations.updateRightFirmwareWithBlhost(firmwarePathData.rightFirmwarePath);
|
||||
await this.operations.updateLeftModuleWithBlhost(firmwarePathData.leftFirmwarePath);
|
||||
}
|
||||
} else {
|
||||
const packageJsonPath = path.join(this.rootDir, 'packages/firmware/package.json');
|
||||
const packageJson = await getPackageJsonFromPathAsync(packageJsonPath);
|
||||
this.logService.debug('New firmware version:', packageJson.firmwareVersion);
|
||||
|
||||
await this.operations.updateRightFirmware();
|
||||
await this.operations.updateLeftModule();
|
||||
if (this.options.useKboot) {
|
||||
await this.operations.updateRightFirmwareWithKboot();
|
||||
await this.operations.updateLeftModuleWithKboot();
|
||||
} else {
|
||||
await this.operations.updateRightFirmwareWithBlhost();
|
||||
await this.operations.updateLeftModuleWithBlhost();
|
||||
}
|
||||
}
|
||||
|
||||
response.success = true;
|
||||
@@ -220,7 +232,11 @@ export class DeviceService {
|
||||
try {
|
||||
await this.stopPollUhkDevice();
|
||||
|
||||
await this.operations.updateRightFirmware();
|
||||
if (this.options.useKboot) {
|
||||
await this.operations.updateRightFirmwareWithKboot();
|
||||
} else {
|
||||
await this.operations.updateRightFirmwareWithBlhost();
|
||||
}
|
||||
|
||||
response.modules = await this.getHardwareModules(false);
|
||||
response.success = true;
|
||||
@@ -270,19 +286,21 @@ export class DeviceService {
|
||||
|
||||
while (true) {
|
||||
if (this._pollerAllowed) {
|
||||
|
||||
this._uhkDevicePolling = true;
|
||||
try {
|
||||
|
||||
const state = await this.device.getDeviceConnectionStateAsync();
|
||||
if (!isEqual(state, savedState)) {
|
||||
savedState = state;
|
||||
this.win.webContents.send(IpcEvents.device.deviceConnectionStateChanged, state);
|
||||
this.logService.info('[DeviceService] Device connection state changed to:', state);
|
||||
const state = await this.device.getDeviceConnectionStateAsync();
|
||||
if (!isEqual(state, savedState)) {
|
||||
savedState = state;
|
||||
this.win.webContents.send(IpcEvents.device.deviceConnectionStateChanged, state);
|
||||
this.logService.info('[DeviceService] Device connection state changed to:', state);
|
||||
}
|
||||
} catch (err) {
|
||||
this.logService.error('[DeviceService] Device connection state query error', err);
|
||||
}
|
||||
|
||||
this._uhkDevicePolling = false;
|
||||
}
|
||||
|
||||
this._uhkDevicePolling = false;
|
||||
await snooze(250);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,12 @@ export enum MouseActionParam {
|
||||
scrollLeft,
|
||||
scrollRight,
|
||||
accelerate,
|
||||
decelerate
|
||||
decelerate,
|
||||
button4,
|
||||
button5,
|
||||
button6,
|
||||
button7,
|
||||
button8
|
||||
}
|
||||
|
||||
export class MouseAction extends KeyAction {
|
||||
|
||||
@@ -7,4 +7,8 @@ export interface CommandLineArgs {
|
||||
* simulate privilege escalation error
|
||||
*/
|
||||
spe?: boolean;
|
||||
/**
|
||||
* If it is true use kboot package instead of blhost for firmware upgrade
|
||||
*/
|
||||
useKboot?: boolean;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { HardwareModuleInfo } from './hardware-module-info';
|
||||
import { LeftModuleInfo } from './left-module-info';
|
||||
import { RightModuleInfo } from './right-module-info';
|
||||
|
||||
export interface HardwareModules {
|
||||
leftModuleInfo?: HardwareModuleInfo;
|
||||
rightModuleInfo?: HardwareModuleInfo;
|
||||
leftModuleInfo?: LeftModuleInfo;
|
||||
rightModuleInfo?: RightModuleInfo;
|
||||
}
|
||||
|
||||
@@ -6,8 +6,9 @@ export * from './app-start-info';
|
||||
export * from './configuration-reply';
|
||||
export * from './version-information';
|
||||
export * from './device-connection-state';
|
||||
export * from './left-module-info';
|
||||
export * from './hardware-modules';
|
||||
export * from './hardware-module-info';
|
||||
export * from './right-module-info';
|
||||
export * from './save-user-configuration-data';
|
||||
export * from './udev-rules-info';
|
||||
export * from './update-firmware-data';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export interface HardwareModuleInfo {
|
||||
export interface LeftModuleInfo {
|
||||
firmwareVersion?: string;
|
||||
moduleProtocolVersion?: string;
|
||||
}
|
||||
7
packages/uhk-common/src/models/right-module-info.ts
Normal file
7
packages/uhk-common/src/models/right-module-info.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
export interface RightModuleInfo {
|
||||
deviceProtocolVersion?: string;
|
||||
hardwareConfigVersion?: string;
|
||||
firmwareVersion?: string;
|
||||
moduleProtocolVersion?: string;
|
||||
userConfigVersion?: string;
|
||||
}
|
||||
@@ -71,9 +71,9 @@ export enum EnumerationNameToProductId {
|
||||
}
|
||||
|
||||
export enum ModuleSlotToI2cAddress {
|
||||
leftHalf = 0x10,
|
||||
leftModule = 0x20,
|
||||
rightModule = 0x30
|
||||
leftHalf = '0x10',
|
||||
leftModule = '0x20',
|
||||
rightModule = '0x30'
|
||||
}
|
||||
|
||||
export enum ModuleSlotToId {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
export * from './constants';
|
||||
export * from './uhk-blhost';
|
||||
export * from './uhk-hid-device';
|
||||
export * from './uhk-operations';
|
||||
export * from './util';
|
||||
|
||||
89
packages/uhk-usb/src/uhk-blhost.ts
Normal file
89
packages/uhk-usb/src/uhk-blhost.ts
Normal file
@@ -0,0 +1,89 @@
|
||||
import * as path from 'path';
|
||||
import { spawn } from 'child_process';
|
||||
import { LogService } from 'uhk-common';
|
||||
import { retry } from './util';
|
||||
|
||||
export class UhkBlhost {
|
||||
private blhostPath: string;
|
||||
|
||||
constructor(private logService: LogService,
|
||||
private rootDir: string) {
|
||||
}
|
||||
|
||||
public async runBlhostCommand(params: Array<string>): Promise<void> {
|
||||
const self = this;
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
const blhostPath = this.getBlhostPath();
|
||||
self.logService.debug(`[blhost] RUN: ${blhostPath} ${params.join(' ')}`);
|
||||
const childProcess = spawn(`"${blhostPath}"`, params, {shell: true});
|
||||
let finished = false;
|
||||
|
||||
childProcess.stdout.on('data', data => {
|
||||
self.logService.debug(`[blhost] STDOUT: ${data}`);
|
||||
});
|
||||
|
||||
childProcess.stderr.on('data', data => {
|
||||
self.logService.error(`[blhost] STDERR: ${data}`);
|
||||
});
|
||||
|
||||
childProcess.on('close', code => {
|
||||
self.logService.debug(`[blhost] CLOSE_CODE: ${code}`);
|
||||
finish(code);
|
||||
});
|
||||
|
||||
childProcess.on('exit', code => {
|
||||
self.logService.debug(`[blhost] EXIT_CODE: ${code}`);
|
||||
finish(code);
|
||||
});
|
||||
|
||||
childProcess.on('error', err => {
|
||||
self.logService.debug(`[blhost] ERROR: ${err}`);
|
||||
});
|
||||
|
||||
function finish(code) {
|
||||
if (finished) {
|
||||
return;
|
||||
}
|
||||
|
||||
finished = true;
|
||||
|
||||
self.logService.debug(`[blhost] FINISHED: ${code}`);
|
||||
|
||||
if (code !== 0) {
|
||||
return reject(new Error(`blhost error code:${code}`));
|
||||
}
|
||||
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public async runBlhostCommandRetry(params: Array<string>, maxTry = 100): Promise<void> {
|
||||
return await retry(async () => await this.runBlhostCommand(params), maxTry, this.logService);
|
||||
}
|
||||
|
||||
private getBlhostPath(): string {
|
||||
if (this.blhostPath) {
|
||||
return this.blhostPath;
|
||||
}
|
||||
|
||||
let blhostPath;
|
||||
switch (process.platform) {
|
||||
case 'linux':
|
||||
blhostPath = 'linux/x86_64/blhost';
|
||||
break;
|
||||
case 'darwin':
|
||||
blhostPath = 'mac/blhost';
|
||||
break;
|
||||
case 'win32':
|
||||
blhostPath = 'win/blhost.exe';
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Could not find blhost path. Unknown platform:${process.platform}`);
|
||||
}
|
||||
|
||||
this.blhostPath = path.join(this.rootDir, `packages/blhost/${blhostPath}`);
|
||||
|
||||
return this.blhostPath;
|
||||
}
|
||||
}
|
||||
@@ -208,7 +208,7 @@ export class UhkHidDevice {
|
||||
|
||||
while (new Date().getTime() - startTime.getTime() < 20000) {
|
||||
const devs = devices();
|
||||
this.logService.silly('[UhkHidDevice] reenumeration devices', devs);
|
||||
this.logService.debug('[UhkHidDevice] reenumeration devices', devs);
|
||||
|
||||
const inBootloaderMode = devs.some((x: Device) =>
|
||||
x.vendorId === Constants.VENDOR_ID &&
|
||||
@@ -219,7 +219,7 @@ export class UhkHidDevice {
|
||||
return;
|
||||
}
|
||||
|
||||
this.logService.silly(`[UhkHidDevice] Could not find reenumerated device: ${reenumMode}. Waiting...`);
|
||||
this.logService.debug(`[UhkHidDevice] Could not find reenumerated device: ${reenumMode}. Waiting...`);
|
||||
await snooze(100);
|
||||
|
||||
if (!jumped) {
|
||||
@@ -232,7 +232,7 @@ export class UhkHidDevice {
|
||||
device.close();
|
||||
jumped = true;
|
||||
} else {
|
||||
this.logService.silly(`[UhkHidDevice] USB[T]: Enumerate device is not ready yet}`);
|
||||
this.logService.debug(`[UhkHidDevice] USB[T]: Enumerate device is not ready yet}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -249,7 +249,7 @@ export class UhkHidDevice {
|
||||
if (command === KbootCommands.idle) {
|
||||
transfer = Buffer.from([UsbCommand.SendKbootCommandToModule, command]);
|
||||
} else {
|
||||
transfer = Buffer.from([UsbCommand.SendKbootCommandToModule, command, module]);
|
||||
transfer = Buffer.from([UsbCommand.SendKbootCommandToModule, command, Number.parseInt(module, 16)]);
|
||||
}
|
||||
await retry(async () => await this.write(transfer), maxTry, this.logService);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { HardwareModuleInfo, LogService, UhkBuffer } from 'uhk-common';
|
||||
import { LeftModuleInfo, LogService, RightModuleInfo, UhkBuffer } from 'uhk-common';
|
||||
import { DataOption, KBoot, Properties, UsbPeripheral } from 'kboot';
|
||||
|
||||
import {
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
import * as path from 'path';
|
||||
import * as fs from 'fs';
|
||||
import * as os from 'os';
|
||||
import { UhkBlhost } from './uhk-blhost';
|
||||
import { UhkHidDevice } from './uhk-hid-device';
|
||||
import { readBootloaderFirmwareFromHexFileAsync, snooze, waitForDevice } from './util';
|
||||
import { ConfigBufferId, convertBufferToIntArray, DevicePropertyIds, getTransferBuffers, UsbCommand } from '../index';
|
||||
@@ -20,11 +21,67 @@ import { LoadConfigurationsResult } from './models/load-configurations-result';
|
||||
|
||||
export class UhkOperations {
|
||||
constructor(private logService: LogService,
|
||||
private blhost: UhkBlhost,
|
||||
private device: UhkHidDevice,
|
||||
private rootDir: string) {
|
||||
}
|
||||
|
||||
public async updateRightFirmware(firmwarePath = this.getFirmwarePath()) {
|
||||
public async updateRightFirmwareWithBlhost(firmwarePath = this.getFirmwarePath()) {
|
||||
this.logService.debug(`[UhkOperations] Operating system: ${os.type()} ${os.release()} ${os.arch()}`);
|
||||
this.logService.debug('[UhkOperations] Start flashing right firmware');
|
||||
const prefix = [`--usb 0x1d50,0x${EnumerationNameToProductId.bootloader.toString(16)}`];
|
||||
|
||||
await this.device.reenumerate(EnumerationModes.Bootloader);
|
||||
this.device.close();
|
||||
await this.blhost.runBlhostCommand([...prefix, 'flash-security-disable', '0403020108070605']);
|
||||
await this.blhost.runBlhostCommand([...prefix, 'flash-erase-region', '0xc000', '475136']);
|
||||
await this.blhost.runBlhostCommand([...prefix, 'flash-image', `"${firmwarePath}"`]);
|
||||
await this.blhost.runBlhostCommand([...prefix, 'reset']);
|
||||
this.logService.debug('[UhkOperations] Right firmware successfully flashed');
|
||||
}
|
||||
|
||||
public async updateLeftModuleWithBlhost(firmwarePath = this.getLeftModuleFirmwarePath()) {
|
||||
this.logService.debug('[UhkOperations] Start flashing left module firmware');
|
||||
|
||||
const prefix = [`--usb 0x1d50,0x${EnumerationNameToProductId.buspal.toString(16)}`];
|
||||
const buspalPrefix = [...prefix, `--buspal i2c,${ModuleSlotToI2cAddress.leftHalf}`];
|
||||
|
||||
await this.device.reenumerate(EnumerationModes.NormalKeyboard);
|
||||
this.device.close();
|
||||
await snooze(1000);
|
||||
await this.device.sendKbootCommandToModule(ModuleSlotToI2cAddress.leftHalf, KbootCommands.ping, 100);
|
||||
await snooze(1000);
|
||||
await this.device.jumpToBootloaderModule(ModuleSlotToId.leftHalf);
|
||||
this.device.close();
|
||||
|
||||
const leftModuleBricked = await this.waitForKbootIdle();
|
||||
if (!leftModuleBricked) {
|
||||
const msg = '[UhkOperations] Couldn\'t connect to the left keyboard half.';
|
||||
this.logService.error(msg);
|
||||
throw new Error(msg);
|
||||
}
|
||||
|
||||
await this.device.reenumerate(EnumerationModes.Buspal);
|
||||
this.device.close();
|
||||
await this.blhost.runBlhostCommandRetry([...buspalPrefix, 'get-property', '1']);
|
||||
await this.blhost.runBlhostCommand([...buspalPrefix, 'flash-erase-all-unsecure']);
|
||||
await this.blhost.runBlhostCommand([...buspalPrefix, 'write-memory', '0x0', `"${firmwarePath}"`]);
|
||||
await this.blhost.runBlhostCommand([...prefix, 'reset']);
|
||||
await snooze(1000);
|
||||
await this.device.reenumerate(EnumerationModes.NormalKeyboard);
|
||||
this.device.close();
|
||||
await snooze(1000);
|
||||
await this.device.sendKbootCommandToModule(ModuleSlotToI2cAddress.leftHalf, KbootCommands.reset, 100);
|
||||
this.device.close();
|
||||
await snooze(1000);
|
||||
await this.device.sendKbootCommandToModule(ModuleSlotToI2cAddress.leftHalf, KbootCommands.idle);
|
||||
this.device.close();
|
||||
|
||||
this.logService.debug('[UhkOperations] Left firmware successfully flashed');
|
||||
this.logService.debug('[UhkOperations] Both left and right firmwares successfully flashed');
|
||||
}
|
||||
|
||||
public async updateRightFirmwareWithKboot(firmwarePath = this.getFirmwarePath()) {
|
||||
this.logService.debug(`[UhkOperations] Operating system: ${os.type()} ${os.release()} ${os.arch()}`);
|
||||
this.logService.debug('[UhkOperations] Start flashing right firmware');
|
||||
|
||||
@@ -58,9 +115,10 @@ export class UhkOperations {
|
||||
this.logService.debug('[UhkOperations] Right firmware successfully flashed');
|
||||
}
|
||||
|
||||
public async updateLeftModule(firmwarePath = this.getLeftModuleFirmwarePath()) {
|
||||
public async updateLeftModuleWithKboot(firmwarePath = this.getLeftModuleFirmwarePath()) {
|
||||
this.logService.debug('[UhkOperations] Start flashing left module firmware');
|
||||
|
||||
const i2cAddressOfLeftModule = Number.parseInt(ModuleSlotToI2cAddress.leftHalf, 16);
|
||||
await this.device.reenumerate(EnumerationModes.NormalKeyboard);
|
||||
this.device.close();
|
||||
await snooze(1000);
|
||||
@@ -86,28 +144,31 @@ export class UhkOperations {
|
||||
while (true) {
|
||||
try {
|
||||
this.logService.debug('[UhkOperations] Try to connect to the LEFT keyboard');
|
||||
await kboot.configureI2c(ModuleSlotToI2cAddress.leftHalf);
|
||||
await kboot.configureI2c(i2cAddressOfLeftModule);
|
||||
await kboot.getProperty(Properties.BootloaderVersion);
|
||||
break;
|
||||
} catch {
|
||||
if (tryCount > 100) {
|
||||
throw new Error('Can not connect to the LEFT keyboard');
|
||||
}
|
||||
} finally {
|
||||
kboot.close();
|
||||
await snooze(2000);
|
||||
}
|
||||
await snooze(100);
|
||||
tryCount++;
|
||||
}
|
||||
|
||||
// https://github.com/node-hid/node-hid/issues/230
|
||||
this.logService.debug('[UhkOperations] Wait 1 sec to prevent node-hid race condition');
|
||||
await snooze(1000);
|
||||
|
||||
this.logService.debug('[UhkOperations] Flash erase all on LEFT keyboard');
|
||||
await kboot.configureI2c(ModuleSlotToI2cAddress.leftHalf);
|
||||
await kboot.configureI2c(i2cAddressOfLeftModule);
|
||||
await kboot.flashEraseAllUnsecure();
|
||||
|
||||
this.logService.debug('[UhkOperations] Read LEFT firmware from file');
|
||||
const configData = fs.readFileSync(firmwarePath);
|
||||
|
||||
this.logService.debug('[UhkOperations] Write memory');
|
||||
await kboot.configureI2c(ModuleSlotToI2cAddress.leftHalf);
|
||||
await kboot.configureI2c(i2cAddressOfLeftModule);
|
||||
await kboot.writeMemory({ startAddress: 0, data: configData });
|
||||
|
||||
this.logService.debug('[UhkOperations] Reset LEFT keyboard');
|
||||
@@ -248,7 +309,7 @@ export class UhkOperations {
|
||||
return false;
|
||||
}
|
||||
|
||||
public async getLeftModuleVersionInfo(): Promise<HardwareModuleInfo> {
|
||||
public async getLeftModuleVersionInfo(): Promise<LeftModuleInfo> {
|
||||
try {
|
||||
this.logService.debug('[DeviceOperation] USB[T]: Read left module version information');
|
||||
|
||||
@@ -277,7 +338,7 @@ export class UhkOperations {
|
||||
};
|
||||
}
|
||||
|
||||
public async getRightModuleVersionInfo(): Promise<HardwareModuleInfo> {
|
||||
public async getRightModuleVersionInfo(): Promise<RightModuleInfo> {
|
||||
this.logService.debug('[DeviceOperation] USB[T]: Read right module version information');
|
||||
|
||||
const command = Buffer.from([UsbCommand.GetProperty, DevicePropertyIds.ProtocolVersions]);
|
||||
@@ -287,7 +348,11 @@ export class UhkOperations {
|
||||
uhkBuffer.readUInt8();
|
||||
|
||||
return {
|
||||
firmwareVersion: `${uhkBuffer.readUInt16()}.${uhkBuffer.readUInt16()}.${uhkBuffer.readUInt16()}`
|
||||
firmwareVersion: `${uhkBuffer.readUInt16()}.${uhkBuffer.readUInt16()}.${uhkBuffer.readUInt16()}`,
|
||||
deviceProtocolVersion: `${uhkBuffer.readUInt16()}.${uhkBuffer.readUInt16()}.${uhkBuffer.readUInt16()}`,
|
||||
moduleProtocolVersion: `${uhkBuffer.readUInt16()}.${uhkBuffer.readUInt16()}.${uhkBuffer.readUInt16()}`,
|
||||
userConfigVersion: `${uhkBuffer.readUInt16()}.${uhkBuffer.readUInt16()}.${uhkBuffer.readUInt16()}`,
|
||||
hardwareConfigVersion: `${uhkBuffer.readUInt16()}.${uhkBuffer.readUInt16()}.${uhkBuffer.readUInt16()}`
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Device, devices } from 'node-hid';
|
||||
import { readFile } from 'fs-extra';
|
||||
import { EOL } from 'os';
|
||||
import MemoryMap from 'nrf-intel-hex';
|
||||
import * as MemoryMap from 'nrf-intel-hex';
|
||||
import { LogService } from 'uhk-common';
|
||||
|
||||
import { Constants, UsbCommand } from './constants';
|
||||
@@ -126,7 +126,9 @@ export const getFileContentAsync = async (filePath: string): Promise<Array<strin
|
||||
|
||||
export const readBootloaderFirmwareFromHexFileAsync = async (hexFilePath: string): Promise<Map<any, any>> => {
|
||||
const fileContent = await readFile(hexFilePath, { encoding: 'utf8' });
|
||||
const memoryMap = MemoryMap.fromHex(fileContent);
|
||||
const fromHex = MemoryMap.fromHex ? MemoryMap.fromHex : MemoryMap.default.fromHex;
|
||||
|
||||
const memoryMap = fromHex(fileContent);
|
||||
|
||||
return memoryMap;
|
||||
};
|
||||
|
||||
7422
packages/uhk-web/package-lock.json
generated
7422
packages/uhk-web/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -19,22 +19,22 @@
|
||||
},
|
||||
"private": true,
|
||||
"devDependencies": {
|
||||
"@angular/animations": "8.2.2",
|
||||
"@angular-builders/custom-webpack": "8.1.0",
|
||||
"@angular/cli": "8.2.1",
|
||||
"@angular/common": "8.2.2",
|
||||
"@angular/compiler": "8.2.2",
|
||||
"@angular/compiler-cli": "8.2.2",
|
||||
"@angular/core": "8.2.2",
|
||||
"@angular-devkit/build-angular": "0.802.1",
|
||||
"@angular-devkit/build-optimizer": "0.802.1",
|
||||
"@angular-devkit/core": "8.2.1",
|
||||
"@angular/forms": "8.2.2",
|
||||
"@angular/language-service": "8.2.2",
|
||||
"@angular/platform-browser": "8.2.2",
|
||||
"@angular/platform-browser-dynamic": "8.2.2",
|
||||
"@angular/router": "8.2.2",
|
||||
"@ngtools/webpack": "8.2.1",
|
||||
"@angular/animations": "8.2.6",
|
||||
"@angular-builders/custom-webpack": "8.2.0",
|
||||
"@angular/cli": "8.3.4",
|
||||
"@angular/common": "8.2.6",
|
||||
"@angular/compiler": "8.2.6",
|
||||
"@angular/compiler-cli": "8.2.6",
|
||||
"@angular/core": "8.2.6",
|
||||
"@angular-devkit/build-angular": "0.803.4",
|
||||
"@angular-devkit/build-optimizer": "0.803.4",
|
||||
"@angular-devkit/core": "8.3.4",
|
||||
"@angular/forms": "8.2.6",
|
||||
"@angular/language-service": "8.2.6",
|
||||
"@angular/platform-browser": "8.2.6",
|
||||
"@angular/platform-browser-dynamic": "8.2.6",
|
||||
"@angular/router": "8.2.6",
|
||||
"@ngtools/webpack": "8.3.4",
|
||||
"@ngrx/effects": "8.2.0",
|
||||
"@ngrx/router-store": "8.2.0",
|
||||
"@ngrx/store": "8.2.0",
|
||||
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
} from './store';
|
||||
import { ProgressButtonState } from './store/reducers/progress-button-state';
|
||||
import { UpdateInfo } from './models/update-info';
|
||||
import { KeyUpAction, KeyDownAction } from './store/actions/app';
|
||||
|
||||
@Component({
|
||||
selector: 'main-app',
|
||||
@@ -95,6 +96,13 @@ export class MainAppComponent implements OnDestroy {
|
||||
this.enableUsbStackTest();
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
this.store.dispatch(new KeyDownAction(event));
|
||||
}
|
||||
|
||||
@HostListener('document:keyup', ['$event'])
|
||||
onKeyUp(event: KeyboardEvent) {
|
||||
this.store.dispatch(new KeyUpAction(event));
|
||||
}
|
||||
|
||||
updateApp() {
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
<li>Right click on a key: Capture key</li>
|
||||
<li>Hold Shift while clicking on a key: Remap on all keymaps</li>
|
||||
<li>Hold Alt while clicking on a key: Remap on all layers</li>
|
||||
<li>Hold Alt to see macro reference counts in the side menu</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -38,6 +38,7 @@
|
||||
></layer-tab>
|
||||
<mouse-tab #tab *ngSwitchCase="tabName.Mouse" class="popover-content"
|
||||
[defaultKeyAction]="defaultKeyAction"
|
||||
[extraMouseButtonsSupported]="extraMouseButtonsSupported$ | async"
|
||||
(validAction)="setKeyActionValidState($event)"
|
||||
></mouse-tab>
|
||||
<macro-tab #tab *ngSwitchCase="tabName.Macro" class="popover-content"
|
||||
|
||||
@@ -30,7 +30,7 @@ import {
|
||||
|
||||
import { Tab } from './tab';
|
||||
|
||||
import { AppState, getKeymaps, macroPlaybackSupported } from '../../store';
|
||||
import { AppState, extraMouseButtonsSupported, getKeymaps, macroPlaybackSupported } from '../../store';
|
||||
import { KeyActionRemap } from '../../models/key-action-remap';
|
||||
import { RemapInfo } from '../../models/remap-info';
|
||||
|
||||
@@ -145,6 +145,7 @@ export class PopoverComponent implements OnChanges {
|
||||
}
|
||||
];
|
||||
macroPlaybackSupported$: Observable<boolean>;
|
||||
extraMouseButtonsSupported$: Observable<boolean>;
|
||||
|
||||
private readonly currentKeymap$ = new BehaviorSubject<Keymap>(undefined);
|
||||
|
||||
@@ -158,6 +159,7 @@ export class PopoverComponent implements OnChanges {
|
||||
keymaps.filter((keymap: Keymap) => currentKeymap.abbreviation !== keymap.abbreviation))
|
||||
);
|
||||
this.macroPlaybackSupported$ = store.select(macroPlaybackSupported);
|
||||
this.extraMouseButtonsSupported$ = store.select(extraMouseButtonsSupported);
|
||||
}
|
||||
|
||||
ngOnChanges(change: SimpleChanges) {
|
||||
|
||||
@@ -80,6 +80,27 @@
|
||||
[class.btn-primary]="mouseActionParam === MouseActionParam.rightClick"
|
||||
(click)="setMouseActionParam(MouseActionParam.rightClick)">Right</button>
|
||||
</div>
|
||||
<div class="additional-keys" *ngIf="extraMouseButtonsSupported">
|
||||
<div class="btn-group col-xs-12" role="group">
|
||||
<button type="button" class="btn btn-default col-xs-4"
|
||||
[class.btn-primary]="mouseActionParam === MouseActionParam.button4"
|
||||
(click)="setMouseActionParam(MouseActionParam.button4)">Button 4</button>
|
||||
<button type="button" class="btn btn-default col-xs-4"
|
||||
[class.btn-primary]="mouseActionParam === MouseActionParam.button5"
|
||||
(click)="setMouseActionParam(MouseActionParam.button5)">Button 5</button>
|
||||
<button type="button" class="btn btn-default col-xs-4"
|
||||
[class.btn-primary]="mouseActionParam === MouseActionParam.button6"
|
||||
(click)="setMouseActionParam(MouseActionParam.button6)">Button 6</button>
|
||||
</div>
|
||||
<div class="btn-group col-xs-12" role="group">
|
||||
<button type="button" class="btn btn-default col-xs-6"
|
||||
[class.btn-primary]="mouseActionParam === MouseActionParam.button7"
|
||||
(click)="setMouseActionParam(MouseActionParam.button7)">Button 7</button>
|
||||
<button type="button" class="btn btn-default col-xs-6"
|
||||
[class.btn-primary]="mouseActionParam === MouseActionParam.button8"
|
||||
(click)="setMouseActionParam(MouseActionParam.button8)">Button 8</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div *ngSwitchCase="3" class="mouse__config mouse__config--speed text-center">
|
||||
<div class="help-text--mouse-speed text-left">
|
||||
@@ -104,4 +125,4 @@
|
||||
</div>
|
||||
<div *ngSwitchDefault>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -54,6 +54,32 @@
|
||||
}
|
||||
}
|
||||
|
||||
.mouse__config--click {
|
||||
.additional-keys {
|
||||
display: inline-block;
|
||||
margin-top: 1rem;
|
||||
|
||||
.btn-group:first-child {
|
||||
.btn {
|
||||
border-bottom-left-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.btn-group:last-child {
|
||||
.btn {
|
||||
border-top: 0;
|
||||
border-top-left-radius: 0;
|
||||
border-top-right-radius: 0;
|
||||
|
||||
&:last-child {
|
||||
right: 1px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.mouse__config--speed {
|
||||
.btn-default {
|
||||
font-size: 25px;
|
||||
|
||||
@@ -11,6 +11,7 @@ import { Tab } from '../tab';
|
||||
})
|
||||
export class MouseTabComponent extends Tab implements OnChanges {
|
||||
@Input() defaultKeyAction: KeyAction;
|
||||
@Input() extraMouseButtonsSupported: boolean;
|
||||
|
||||
/* tslint:disable:variable-name: It is an enum type. So it can start with uppercase. */
|
||||
MouseActionParam = MouseActionParam;
|
||||
@@ -62,6 +63,11 @@ export class MouseTabComponent extends Tab implements OnChanges {
|
||||
case MouseActionParam.leftClick:
|
||||
case MouseActionParam.middleClick:
|
||||
case MouseActionParam.rightClick:
|
||||
case MouseActionParam.button4:
|
||||
case MouseActionParam.button5:
|
||||
case MouseActionParam.button6:
|
||||
case MouseActionParam.button7:
|
||||
case MouseActionParam.button8:
|
||||
this.selectedPageIndex = 2;
|
||||
break;
|
||||
case MouseActionParam.decelerate:
|
||||
|
||||
@@ -91,6 +91,12 @@
|
||||
<div class="sidebar__level-2" [routerLinkActive]="['active']">
|
||||
<a [routerLink]="['/macro', macro.id]"
|
||||
[class.disabled]="state.updatingFirmware">{{macro.name}}</a>
|
||||
<span *ngIf="state.macroUsageCountVisible"
|
||||
class="sidebar__macro_count badge"
|
||||
title="This is the number of times the macro is used across all keymaps."
|
||||
data-toggle="tooltip"
|
||||
data-placement="bottom"
|
||||
data-container="body">{{ macro.usageCount }}</span>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
@@ -148,6 +148,12 @@ ul {
|
||||
right: 19px;
|
||||
top: 3px;
|
||||
}
|
||||
|
||||
&__macro_count {
|
||||
position: absolute;
|
||||
right: 11px;
|
||||
top: 1px;
|
||||
}
|
||||
}
|
||||
|
||||
.menu--bottom {
|
||||
|
||||
@@ -28,6 +28,26 @@ export class SvgMouseKeyComponent implements OnChanges {
|
||||
this.type = 'click';
|
||||
this.param = 'Middle';
|
||||
break;
|
||||
case MouseActionParam.button4:
|
||||
this.type = 'click';
|
||||
this.param = 'Button 4';
|
||||
break;
|
||||
case MouseActionParam.button5:
|
||||
this.type = 'click';
|
||||
this.param = 'Button 5';
|
||||
break;
|
||||
case MouseActionParam.button6:
|
||||
this.type = 'click';
|
||||
this.param = 'Button 6';
|
||||
break;
|
||||
case MouseActionParam.button7:
|
||||
this.type = 'click';
|
||||
this.param = 'Button 7';
|
||||
break;
|
||||
case MouseActionParam.button8:
|
||||
this.type = 'click';
|
||||
this.param = 'Button 8';
|
||||
break;
|
||||
case MouseActionParam.scrollDown:
|
||||
this.type = 'scroll';
|
||||
this.param = 'down';
|
||||
|
||||
@@ -1 +1,3 @@
|
||||
export * from './last-edited-key';
|
||||
export * from './macro-menu-item';
|
||||
export * from './side-menu-page-state';
|
||||
|
||||
5
packages/uhk-web/src/app/models/macro-menu-item.ts
Normal file
5
packages/uhk-web/src/app/models/macro-menu-item.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export interface MacroMenuItem {
|
||||
id: number;
|
||||
name: string;
|
||||
usageCount: number;
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Keymap, Macro } from 'uhk-common';
|
||||
import { MacroMenuItem } from './macro-menu-item';
|
||||
|
||||
export interface SideMenuPageState {
|
||||
showAddonMenu: boolean;
|
||||
@@ -6,6 +7,7 @@ export interface SideMenuPageState {
|
||||
updatingFirmware: boolean;
|
||||
deviceName: string;
|
||||
keymaps: Keymap[];
|
||||
macros: Macro[];
|
||||
macros: MacroMenuItem[];
|
||||
restoreUserConfiguration: boolean;
|
||||
macroUsageCountVisible: boolean;
|
||||
}
|
||||
|
||||
@@ -19,7 +19,9 @@ export enum ActionTypes {
|
||||
SetupPermissionError = '[app] Setup permission error',
|
||||
LoadAppStartInfo = '[app] Load app start info',
|
||||
StartKeypressCapturing = '[app] Start keypress capturing',
|
||||
StopKeypressCapturing = '[app] Stop keypress capturing'
|
||||
StopKeypressCapturing = '[app] Stop keypress capturing',
|
||||
KeyDown = '[app] Key down',
|
||||
KeyUp = '[app] Key up'
|
||||
}
|
||||
|
||||
export class AppBootstrappedAction implements Action {
|
||||
@@ -110,6 +112,18 @@ export class StopKeypressCapturingAction implements Action {
|
||||
type = ActionTypes.StopKeypressCapturing;
|
||||
}
|
||||
|
||||
export class KeyDownAction implements Action {
|
||||
readonly type = ActionTypes.KeyDown;
|
||||
|
||||
constructor(public payload: KeyboardEvent) {}
|
||||
}
|
||||
|
||||
export class KeyUpAction implements Action {
|
||||
readonly type = ActionTypes.KeyUp;
|
||||
|
||||
constructor(public payload: KeyboardEvent) {}
|
||||
}
|
||||
|
||||
export type Actions
|
||||
= AppStartedAction
|
||||
| AppBootstrappedAction
|
||||
@@ -127,4 +141,6 @@ export type Actions
|
||||
| LoadAppStartInfoAction
|
||||
| StartKeypressCapturingAction
|
||||
| StopKeypressCapturingAction
|
||||
| KeyDownAction
|
||||
| KeyUpAction
|
||||
;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { ActionReducerMap, createSelector, MetaReducer } from '@ngrx/store';
|
||||
import { routerReducer, RouterReducerState } from '@ngrx/router-store';
|
||||
import { storeFreeze } from 'ngrx-store-freeze';
|
||||
import { HardwareModules, Keymap, UserConfiguration } from 'uhk-common';
|
||||
import { HardwareModules, Keymap, UserConfiguration, PlayMacroAction } from 'uhk-common';
|
||||
|
||||
import * as fromUserConfig from './reducers/user-configuration';
|
||||
import * as fromPreset from './reducers/preset';
|
||||
@@ -16,6 +16,7 @@ import { environment } from '../../environments/environment';
|
||||
import { RouterStateUrl } from './router-util';
|
||||
import { PrivilagePageSate } from '../models/privilage-page-sate';
|
||||
import { isVersionGte } from '../util';
|
||||
import { SideMenuPageState, MacroMenuItem } from '../models';
|
||||
|
||||
// State interface for the application
|
||||
export interface AppState {
|
||||
@@ -67,6 +68,7 @@ export const deviceConfigurationLoaded = createSelector(appState, fromApp.device
|
||||
export const getAgentVersionInfo = createSelector(appState, fromApp.getAgentVersionInfo);
|
||||
export const getOperatingSystem = createSelector(appState, fromSelectors.getOperatingSystem);
|
||||
export const keypressCapturing = createSelector(appState, fromApp.keypressCapturing);
|
||||
export const getMacroUsageCountVisible = createSelector(appState, fromApp.macroUsageCountVisible);
|
||||
export const runningOnNotSupportedWindows = createSelector(appState, fromApp.runningOnNotSupportedWindows);
|
||||
export const contributors = (state: AppState) => state.contributors;
|
||||
export const firmwareUpgradeAllowed = createSelector(runningOnNotSupportedWindows, notSupportedOs => !notSupportedOs);
|
||||
@@ -123,25 +125,57 @@ export const getPrivilegePageState = createSelector(appState, getUpdateUdevRules
|
||||
};
|
||||
});
|
||||
|
||||
export const getMacroMenuItems = (userConfiguration: UserConfiguration): MacroMenuItem[] => {
|
||||
const macroMap = userConfiguration.macros.reduce((map, macro) => {
|
||||
return map.set(macro.id, {
|
||||
id: macro.id,
|
||||
name: macro.name,
|
||||
usageCount: 0
|
||||
});
|
||||
}, new Map<number, MacroMenuItem>());
|
||||
|
||||
for (const keymap of userConfiguration.keymaps) {
|
||||
for (const layer of keymap.layers) {
|
||||
for (const module of layer.modules) {
|
||||
for (const keyAction of module.keyActions) {
|
||||
if (!(keyAction instanceof PlayMacroAction)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const menuItem = macroMap.get(keyAction.macroId);
|
||||
menuItem.usageCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Array
|
||||
.from(macroMap.values())
|
||||
.sort((first: MacroMenuItem, second: MacroMenuItem) => first.name.localeCompare(second.name));
|
||||
};
|
||||
|
||||
export const getSideMenuPageState = createSelector(
|
||||
showAddonMenu,
|
||||
runningInElectron,
|
||||
updatingFirmware,
|
||||
getUserConfiguration,
|
||||
getRestoreUserConfiguration,
|
||||
getMacroUsageCountVisible,
|
||||
(showAddonMenuValue: boolean,
|
||||
runningInElectronValue: boolean,
|
||||
updatingFirmwareValue: boolean,
|
||||
userConfiguration: UserConfiguration,
|
||||
restoreUserConfiguration: boolean) => {
|
||||
restoreUserConfiguration: boolean,
|
||||
macroUsageCountVisible): SideMenuPageState => {
|
||||
return {
|
||||
showAddonMenu: showAddonMenuValue,
|
||||
runInElectron: runningInElectronValue,
|
||||
updatingFirmware: updatingFirmwareValue,
|
||||
deviceName: userConfiguration.deviceName,
|
||||
keymaps: userConfiguration.keymaps,
|
||||
macros: userConfiguration.macros,
|
||||
restoreUserConfiguration
|
||||
macros: getMacroMenuItems(userConfiguration),
|
||||
restoreUserConfiguration,
|
||||
macroUsageCountVisible
|
||||
};
|
||||
}
|
||||
);
|
||||
@@ -157,3 +191,6 @@ export const layerDoubleTapSupported = createSelector(
|
||||
return isVersionGte(hardwareModules.rightModuleInfo.firmwareVersion, '8.4.3');
|
||||
}
|
||||
);
|
||||
export const extraMouseButtonsSupported = createSelector(getHardwareModules, (hardwareModules: HardwareModules): boolean => {
|
||||
return isVersionGte(hardwareModules.rightModuleInfo.userConfigVersion, '4.1.1');
|
||||
});
|
||||
|
||||
@@ -30,6 +30,7 @@ export interface State {
|
||||
platform?: string;
|
||||
osVersion?: string;
|
||||
keypressCapturing: boolean;
|
||||
macroUsageCountVisible: boolean;
|
||||
}
|
||||
|
||||
export const initialState: State = {
|
||||
@@ -40,7 +41,8 @@ export const initialState: State = {
|
||||
configLoading: true,
|
||||
agentVersionInfo: getVersions(),
|
||||
privilegeWhatWillThisDoClicked: false,
|
||||
keypressCapturing: false
|
||||
keypressCapturing: false,
|
||||
macroUsageCountVisible: false
|
||||
};
|
||||
|
||||
export function reducer(
|
||||
@@ -156,7 +158,8 @@ export function reducer(
|
||||
case App.ActionTypes.StartKeypressCapturing:
|
||||
return {
|
||||
...state,
|
||||
keypressCapturing: true
|
||||
keypressCapturing: true,
|
||||
macroUsageCountVisible: false
|
||||
};
|
||||
|
||||
case App.ActionTypes.StopKeypressCapturing:
|
||||
@@ -165,6 +168,24 @@ export function reducer(
|
||||
keypressCapturing: false
|
||||
};
|
||||
|
||||
case App.ActionTypes.KeyDown: {
|
||||
const event = (action as App.KeyDownAction).payload;
|
||||
|
||||
return {
|
||||
...state,
|
||||
macroUsageCountVisible: !state.keypressCapturing && !event.defaultPrevented && event.altKey
|
||||
};
|
||||
}
|
||||
|
||||
case App.ActionTypes.KeyUp: {
|
||||
const event = (action as App.KeyDownAction).payload;
|
||||
|
||||
return {
|
||||
...state,
|
||||
macroUsageCountVisible: event.altKey
|
||||
};
|
||||
}
|
||||
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
@@ -197,3 +218,4 @@ export const runningOnNotSupportedWindows = (state: State): boolean => {
|
||||
};
|
||||
|
||||
export const keypressCapturing = (state: State): boolean => state.keypressCapturing;
|
||||
export const macroUsageCountVisible = (state: State): boolean => state.macroUsageCountVisible;
|
||||
|
||||
@@ -7,9 +7,18 @@ const rootDir = path.join(__dirname, '../../tmp');
|
||||
const uhkHidDevice = new UhkHidDevice(logService, {}, rootDir);
|
||||
const uhkOperations = new UhkOperations(logService, uhkHidDevice, rootDir);
|
||||
|
||||
process.on('uncaughtException', error => {
|
||||
console.error('uncaughtException', error);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
process.on('unhandledRejection', (reason: any, promise: Promise<any>): void => {
|
||||
console.error('unhandledRejection', { reason, promise });
|
||||
});
|
||||
|
||||
uhkOperations
|
||||
.updateRightFirmware()
|
||||
.then(() => uhkOperations.updateLeftModule())
|
||||
.updateRightFirmwareWithKboot()
|
||||
.then(() => uhkOperations.updateLeftModuleWithKboot())
|
||||
.then(() => console.log('Firmware upgrade finished'))
|
||||
.catch(error => {
|
||||
console.error(error);
|
||||
|
||||
@@ -8,6 +8,13 @@ const copyOptions = {
|
||||
|
||||
const promises = [];
|
||||
|
||||
promises.push(
|
||||
fse.copy(
|
||||
path.join(__dirname, '../packages/usb/blhost'),
|
||||
path.join(__dirname, '../tmp/packages/blhost'),
|
||||
copyOptions)
|
||||
);
|
||||
|
||||
promises.push(
|
||||
fse.copy(
|
||||
path.join(__dirname, '../rules'),
|
||||
|
||||
@@ -96,7 +96,7 @@ if (TEST_BUILD || gitTag) {
|
||||
const rootJson = require('../package.json');
|
||||
update2ndPackageJson(rootJson);
|
||||
|
||||
// Add firmware to extra resources
|
||||
// Add firmware and blhost to extra resources
|
||||
const extractedFirmwareDir = path.join(__dirname, '../tmp/packages');
|
||||
extraResources.push({from: extractedFirmwareDir, to: 'packages/'});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user