51 Commits

Author SHA1 Message Date
László Monda
1d4bb6113c Bump version to 1.1.5 in package.json and update the changelog. 2018-04-10 11:42:34 +02:00
Róbert Kiss
136120b831 feat: not allow run multiple Agent (#603) 2018-04-09 21:09:09 +02:00
Róbert Kiss
cd299c06d6 feat: not allow run multiple Agent (#604) 2018-04-09 20:48:22 +02:00
László Monda
e152a36ad7 Add agent-app-icon.png which I forgot to add. 2018-04-09 14:16:09 +02:00
László Monda
e90544db33 Bump Agent version to 1.1.4 in package.json and update the changelog. 2018-04-09 13:59:38 +02:00
László Monda
7ceca202b4 Reposition the ISO key in the scancode list. 2018-04-09 12:48:44 +02:00
László Monda
ddc65aa54b Replace application icon with a diagonal gradient based icon that should look better on the desktop. 2018-04-09 12:20:46 +02:00
Róbert Kiss
13ec617d58 Make saving the configuration more robust (#594)
* feat: Make saving the configuration more robust

* parse backup user config before return

* fix some bug

* Add write-userconfig.js and invalid-config.bin

* throw exception if failed user config parsing

* Merge branch 'master' into feat-467-make-save-more-robust

* hide keymaps and macros if agent in restore mode

* fix Device name settings
2018-04-09 10:11:26 +02:00
Róbert Kiss
00c5b69129 fix: display agent icon when user use ALT + TAB (#600) 2018-04-07 23:17:50 +02:00
Róbert Kiss
6ccf005750 feat: Handle privilege escalation gracefully even without PolicyKit (#599)
* feat: Handle privilege escalation gracefully even without PolicyKit

* build: upgrade tslint => 5.9.1

* build: add uhk-agent/package-lock.json

* feat: add error animation

* fix: display agent icon when user use ALT + TAB
2018-04-07 23:09:47 +02:00
László Monda
6e1f0ded9e Bump Agent version to 1.1.3 and update changelog. 2018-04-06 16:26:21 +02:00
László Monda
d58386ef4b Reference firmware 8.1.5 2018-04-04 15:51:04 +02:00
László Monda
179c982bfb Make the Fn+Backspace shortcut of the QWERTY for PC keymap switch to the TES keymap for testing purposes. 2018-04-03 23:25:30 +02:00
László Monda
a7d07dbf4c Make factory-update.js allow the layout to be set. 2018-04-03 21:04:47 +02:00
László Monda
0ca922d24a Sleep 1s between sending reset and idle kboot commands. Hopefully this will always make the left half resume after firmware updates. 2018-04-03 00:49:51 +02:00
László Monda
a6f1aa15a5 Supply the correct configBufferId values to launchEepromTransfer() and get rid of the obsoleted eepromTransfer mapping. 2018-04-02 23:54:48 +02:00
László Monda
fc2d025cc4 Don't display the buffer related USB transfers of writeConfig() because they generate too much noise. 2018-04-02 23:32:13 +02:00
László Monda
8b5ae106bd Display kboot command names instead of numberic ids. 2018-04-02 23:20:07 +02:00
László Monda
44639bbf53 Make apply-config.js and get-module-state.js use read() instead of readSync() 2018-04-02 22:56:28 +02:00
László Monda
9fcce9234a Make getDeviceState await. Dump transfer descriptions before the actual transfers when the debug mode is on. 2018-04-02 21:35:47 +02:00
László Monda
b26fecfc7a Make factory-update.js switch keymap. 2018-04-02 18:28:12 +02:00
László Monda
1b15911783 Extract code to uhk.writeUca() 2018-04-02 17:31:27 +02:00
László Monda
148dd8d361 Rewrite writeHca() using the JavaScript USB API instead of TypeScript because the latter couldn't reopen the USB device. 2018-04-02 17:25:35 +02:00
László Monda
0d9ac50999 Move writeHca() to uhk.js 2018-04-02 14:47:48 +02:00
László Monda
e19e4bc5a4 Move the gist of write-hca.js into writeHca() 2018-04-02 14:43:11 +02:00
László Monda
bdd79a5a9a Clean up writeConfig() a bit. 2018-04-02 14:32:45 +02:00
László Monda
fb4e05fdc4 Dump USB reads and writes via writeDevice() 2018-04-02 00:30:03 +02:00
László Monda
01fcf9053a Strip down factory-update.js to its essentials. 2018-04-02 00:11:35 +02:00
László Monda
533c2f13d2 Copy update-firmwares.js as factory-update.js 2018-04-01 23:58:13 +02:00
László Monda
b9c32b46a9 Extract uhk.applyConfig() and uhk.launchEepromTransfer() 2018-04-01 23:55:40 +02:00
László Monda
f9b7260be6 Extract uhk.writeUserConfig() 2018-04-01 01:23:50 +02:00
László Monda
847694d590 Rewrite write-config.js using async/await. 2018-04-01 01:13:17 +02:00
László Monda
58178a5c7b Extract uhk.updateFirmwares() 2018-03-31 23:50:02 +02:00
László Monda
9b93b4dac5 Rename update-all-firmwares.js to update-firmwares.js 2018-03-31 23:24:34 +02:00
Róbert Kiss
478dac0621 build: extract electron dependencies to the root package.json (#593) 2018-03-31 22:11:41 +02:00
László Monda
f196fcdaa2 Make switch-keymap.js accept a keymap abbreviation. 2018-03-30 19:17:42 +02:00
László Monda
0b420ff516 Extract uhk.switchKeymap() 2018-03-30 18:59:50 +02:00
László Monda
7656af76e4 Add switch-keymap.js 2018-03-30 12:48:28 +02:00
László Monda
beed546ae4 Remove switch keymap actions that point to the TES keymap. 2018-03-29 19:09:00 +02:00
László Monda
bf94370f2f Add the "Export device configuration" button instead of the export link. Make the button export JSON by default and BIN when pressed with Shift. 2018-03-29 18:44:09 +02:00
László Monda
2476049681 Out of play, play/pause, stop, and pause only leave play/pause in the default configuration. 2018-03-29 18:12:08 +02:00
László Monda
f8d8b6d213 Remove Launch Browser, Launch Calculator, and Eject Disk key actions from the default configurations because they don't work reliably across OSes and very rarely used. 2018-03-29 14:51:06 +02:00
László Monda
05bbce1d50 Remove redundant stop media playback actions. 2018-03-29 13:27:13 +02:00
László Monda
32494fa228 Remove relative switch keymap actions. 2018-03-29 13:12:27 +02:00
László Monda
e0ce38988e Remove shut down key actions, only keep sleep key actions, and bind them to the \ key. 2018-03-29 12:54:51 +02:00
László Monda
5c660c549d Update the test keymap. This one should be the final version. 2018-03-28 20:56:56 +02:00
László Monda
510b914e26 Change terminology from download / upload to export / import for greater clarity. 2018-03-28 18:27:27 +02:00
László Monda
cf64fc0c08 Make the tooltip text regarding non-US characters easier to understand. 2018-03-25 22:24:33 +02:00
Róbert Kiss
b25bc9d81d feat: show firmware version of the device/modules on firmware page (#589) 2018-03-23 07:10:02 +01:00
Róbert Kiss
2f00a5eaf4 feat: enhance device firmware page (#588)
* feat: enhance device firmware page

* remove confirmation dialog from firmware upgrade buttons
2018-03-15 12:20:35 +01:00
László Monda
e8fe0f8d3e Fix menu scancode. (#586)
* Fix menu scancode.

* Change the old menu key scancode 118 to 101.

* validate scancodes
2018-03-11 22:56:12 +01:00
96 changed files with 2826 additions and 4246 deletions

View File

@@ -6,6 +6,31 @@ The format is loosely based on [Keep a Changelog](http://keepachangelog.com/en/1
Every Agent version includes the most recent firmware version. See the [firmware changelog](https://github.com/UltimateHackingKeyboard/firmware/blob/master/CHANGELOG.md).
## [1.1.5] - 2018-04-10
Firmware: 8.1.5 [[release](https://github.com/UltimateHackingKeyboard/firmware/releases/tag/v8.1.5)] | Device Protocol: 4.2.0 | User Config: 4.0.0 | Hardware Config: 1.0.0
- Don't allow to run multiple instances of Agent at the same time, but rather focus the already existing Agent window.
## [1.1.4] - 2018-04-09
Firmware: 8.1.5 [[release](https://github.com/UltimateHackingKeyboard/firmware/releases/tag/v8.1.5)] | Device Protocol: 4.2.0 | User Config: 4.0.0 | Hardware Config: 1.0.0
- Handle privilege escalation gracefully on Linux even without PolicyKit.
- Fix application icon path.
- Replace application icon with a diagonal gradient based icon that should look better on desktop.
- Make saving the configuration more robust, and add a configuration recovery screen.
- Reposition the ISO key in the scancode list.
## [1.1.3] - 2018-04-06
Firmware: 8.1.**5** [[release](https://github.com/UltimateHackingKeyboard/firmware/releases/tag/v8.1.5)] | Device Protocol: 4.2.0 | User Config: 4.0.0 | Hardware Config: 1.0.0
- Show the firmware versions of the left and right keyboard halves on the firmware page.
- Fix menu scancode.
- Make the tooltip text regarding non-US characters easier to understand.
- On the Device Configuration page change terminology from download/upload to export/import for greater clarity.
## [1.1.2] - 2018-03-09
Firmware: 8.1.**4** [[release](https://github.com/UltimateHackingKeyboard/firmware/releases/tag/v8.1.4)] | Device Protocol: 4.2.0 | User Config: 4.0.0 | Hardware Config: 1.0.0

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 392 B

After

Width:  |  Height:  |  Size: 499 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 546 B

After

Width:  |  Height:  |  Size: 735 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 KiB

After

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 660 B

After

Width:  |  Height:  |  Size: 913 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 966 B

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

950
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -3,8 +3,8 @@
"private": true,
"author": "Ultimate Gadget Laboratories",
"main": "electron/dist/electron-main.js",
"version": "1.1.2",
"firmwareVersion": "8.1.4",
"version": "1.1.5",
"firmwareVersion": "8.1.5",
"deviceProtocolVersion": "4.2.0",
"userConfigVersion": "4.0.0",
"hardwareConfigVersion": "1.0.0",
@@ -21,7 +21,7 @@
"devDependencies": {
"@types/electron-devtools-installer": "2.0.2",
"@types/electron-settings": "3.0.0",
"@types/fs-extra": "4.0.5",
"@types/fs-extra": "5.0.1",
"@types/jasmine": "2.6.0",
"@types/jsonfile": "4.0.1",
"@types/node": "8.0.53",
@@ -30,25 +30,28 @@
"@types/usb": "1.1.3",
"autoprefixer": "6.5.3",
"buffer": "5.0.6",
"copyfiles": "^2.0.0",
"copy-webpack-plugin": "4.0.1",
"core-js": "2.4.1",
"cross-env": "5.0.5",
"decompress": "4.2.0",
"decompress-tarbz2": "^4.1.1",
"devtron": "1.4.0",
"electron": "1.7.11",
"electron-builder": "20.4.0",
"electron-debug": "1.4.0",
"electron-devtools-installer": "2.2.0",
"electron-log": "2.2.9",
"electron": "1.8.4",
"electron-builder": "20.8.1",
"electron-debug": "1.5.0",
"electron-devtools-installer": "2.2.3",
"electron-log": "2.2.14",
"electron-rebuild": "1.7.3",
"electron-settings": "3.1.2",
"electron-settings": "3.1.4",
"electron-updater": "2.21.4",
"exports-loader": "0.6.3",
"file-loader": "0.10.0",
"fs-extra": "4.0.2",
"fs-extra": "5.0.0",
"jsonfile": "4.0.0",
"lerna": "2.9.0",
"mkdirp": "0.5.1",
"node-hid": "0.5.7",
"npm-run-all": "4.0.2",
"pre-commit": "1.2.2",
"request": "2.83.0",
@@ -58,7 +61,7 @@
"svg-sprite": "1.3.7",
"ts-loader": "2.3.1",
"ts-node": "3.0.4",
"tslint": "5.5.0",
"tslint": "5.9.1",
"typescript": "2.5.2",
"webpack": "2.4.1"
},
@@ -74,11 +77,11 @@
"test:uhk-web": "lerna exec --scope uhk-web npm test",
"lint": "run-s -scn lint:ts lint:style",
"lint:ts": "run-p -sn lint:ts:electron-main lint:ts:electron-renderer lint:ts:web lint:ts:test-serializer lint:ts:uhk-usb",
"lint:ts:electron-main": "tslint --type-check --project ./packages/uhk-agent/tsconfig.json",
"lint:ts:electron-renderer": "tslint --type-check --project ./packages/uhk-web/src/tsconfig.renderer.json",
"lint:ts:web": "tslint --type-check --project ./packages/uhk-web/src/tsconfig.app.json",
"lint:ts:test-serializer": "tslint --type-check --project ./packages/test-serializer/tsconfig.json",
"lint:ts:uhk-usb": "tslint --type-check --project ./packages/uhk-usb/tsconfig.json",
"lint:ts:electron-main": "tslint --project ./packages/uhk-agent/tsconfig.json",
"lint:ts:electron-renderer": "tslint --project ./packages/uhk-web/src/tsconfig.renderer.json",
"lint:ts:web": "tslint --project ./packages/uhk-web/src/tsconfig.app.json",
"lint:ts:test-serializer": "tslint --project ./packages/test-serializer/tsconfig.json",
"lint:ts:uhk-usb": "tslint --project ./packages/uhk-usb/tsconfig.json",
"lint:style": "stylelint \"packages/uhk-agent/src/**/*.scss\" \"packages/uhk-web/src/**/*.scss\" --syntax scss",
"build": "run-s build:common build:usb build:web build:electron",
"build:web": "lerna exec --scope uhk-web npm run build",
@@ -90,6 +93,7 @@
"server:web": "lerna exec --scope uhk-web npm start",
"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",
"standard-version": "standard-version",
"pack": "node ./scripts/release.js",
"sprites": "node ./scripts/generate-svg-sprites",

File diff suppressed because it is too large Load Diff

View File

@@ -17,13 +17,7 @@
"command-line-args": "4.0.7",
"decompress": "4.2.0",
"decompress-bzip2": "4.0.0",
"electron": "1.7.9",
"electron-is-dev": "0.1.2",
"electron-log": "2.2.9",
"electron-rebuild": "1.6.0",
"electron-settings": "3.1.2",
"electron-updater": "2.15.0",
"node-hid": "0.5.4",
"node-hid": "0.5.7",
"sudo-prompt": "7.0.0",
"tmp": "0.0.33",
"uhk-common": "^1.0.0",
@@ -36,6 +30,7 @@
},
"scripts": {
"start": "electron ./dist/electron-main.js",
"electron:spe": "electron ./dist/electron-main.js --spe",
"build": "webpack && npm run install:build-deps && npm run build:usb && npm run download-firmware && npm run copy-blhost",
"build:usb": "electron-rebuild -w node-hid -p -m ./dist",
"install:build-deps": "cd ./dist && npm i",

View File

@@ -2,7 +2,7 @@
/// <reference path="./custom_types/command-line-args.d.ts"/>
import './polyfills';
import { app, BrowserWindow, ipcMain } from 'electron';
import { app, BrowserWindow } from 'electron';
import { autoUpdater } from 'electron-updater';
import * as path from 'path';
@@ -10,7 +10,7 @@ import * as url from 'url';
import * as commandLineArgs from 'command-line-args';
import { UhkHidDevice, UhkOperations } from 'uhk-usb';
// import { ElectronDataStorageRepositoryService } from './services/electron-datastorage-repository.service';
import { LogRegExps } from 'uhk-common';
import { CommandLineArgs, LogRegExps } from 'uhk-common';
import { DeviceService } from './services/device.service';
import { logger } from './services/logger.service';
import { AppUpdateService } from './services/app-update.service';
@@ -18,13 +18,13 @@ import { AppService } from './services/app.service';
import { SudoService } from './services/sudo.service';
import { UhkBlhost } from '../../uhk-usb/src';
import * as isDev from 'electron-is-dev';
import { CommandLineInputs } from './models/command-line-inputs';
const optionDefinitions = [
{name: 'addons', type: Boolean}
{name: 'addons', type: Boolean},
{name: 'spe', type: Boolean} // simulate privilege escalation error
];
const options: CommandLineInputs = commandLineArgs(optionDefinitions);
const options: CommandLineArgs = commandLineArgs(optionDefinitions);
// import './dev-extension';
// require('electron-debug')({ showDevTools: true, enabled: true });
@@ -60,7 +60,25 @@ if (console.debug) {
};
}
const isSecondInstance = app.makeSingleInstance(function (commandLine, workingDirectory) {
// Someone tried to run a second instance, we should focus our window.
if (win) {
if (win.isMinimized()) {
win.restore();
}
win.focus();
}
});
if (isSecondInstance) {
app.quit();
}
function createWindow() {
if (isSecondInstance) {
return;
}
logger.info('[Electron Main] Create new window.');
let packagesDir;
if (isDev) {
@@ -79,17 +97,17 @@ function createWindow() {
webPreferences: {
nodeIntegration: true
},
icon: 'assets/images/agent-icon.png'
icon: path.join(__dirname, 'renderer/assets/images/agent-app-icon.png')
});
win.setMenuBarVisibility(false);
win.maximize();
uhkHidDeviceService = new UhkHidDevice(logger);
uhkHidDeviceService = new UhkHidDevice(logger, options);
uhkBlhost = new UhkBlhost(logger, packagesDir);
uhkOperations = new UhkOperations(logger, uhkBlhost, uhkHidDeviceService, packagesDir);
deviceService = new DeviceService(logger, win, uhkHidDeviceService, uhkOperations);
appUpdateService = new AppUpdateService(logger, win, app);
appService = new AppService(logger, win, deviceService, options, uhkHidDeviceService);
sudoService = new SudoService(logger);
sudoService = new SudoService(logger, options);
// and load the index.html of the app.
win.loadURL(url.format({
@@ -133,13 +151,13 @@ app.on('ready', createWindow);
// Quit when all windows are closed.
app.on('window-all-closed', () => {
app.quit();
});
app.on('will-quit', () => {
if (appUpdateService) {
appUpdateService.saveFirtsRun();
}
app.exit();
});
app.on('will-quit', () => {
});
app.on('activate', () => {

View File

@@ -1,3 +1,10 @@
export interface CommandLineInputs {
/**
* addons menu visible or not
*/
addons?: boolean;
/**
* simulate privilege escalation error
*/
spe?: boolean;
}

View File

@@ -1,5 +1,15 @@
import { ipcMain } from 'electron';
import { ConfigurationReply, DeviceConnectionState, IpcEvents, IpcResponse, LogService } from 'uhk-common';
import {
ConfigurationReply,
DeviceConnectionState,
getHardwareConfigFromDeviceResponse,
HardwareModules,
IpcEvents,
IpcResponse,
LogService,
mapObjectToUserConfigBinaryBuffer,
SaveUserConfigurationData
} from 'uhk-common';
import { snooze, UhkHidDevice, UhkOperations } from 'uhk-usb';
import { Observable } from 'rxjs/Observable';
import { Subscription } from 'rxjs/Subscription';
@@ -14,6 +24,7 @@ import 'rxjs/add/operator/distinctUntilChanged';
import { saveTmpFirmware } from '../util/save-extract-firmware';
import { TmpFirmware } from '../models/tmp-firmware';
import { QueueManager } from './queue-manager';
import { backupUserConfiguration, getBackupUserConfigurationContent } from '../util/backup-user-confoguration';
/**
* IpcMain pair of the UHK Communication
@@ -73,10 +84,19 @@ export class DeviceService {
try {
await this.device.waitUntilKeyboardBusy();
const result = await this.operations.loadConfigurations();
const modules: HardwareModules = {
leftModuleInfo: await this.operations.getLeftModuleVersionInfo(),
rightModuleInfo: await this.operations.getRightModuleVersionInfo()
};
const hardwareConfig = getHardwareConfigFromDeviceResponse(result.hardwareConfiguration);
const uniqueId = hardwareConfig.uniqueId;
response = {
success: true,
...result
...result,
modules,
backupConfiguration: await getBackupUserConfigurationContent(this.logService, uniqueId)
};
} catch (error) {
response = {
@@ -157,10 +177,13 @@ export class DeviceService {
private async saveUserConfiguration(event: Electron.Event, args: Array<string>): Promise<void> {
const response = new IpcResponse();
const json = args[0];
const data: SaveUserConfigurationData = JSON.parse(args[0]);
try {
await this.operations.saveUserConfiguration(json);
await backupUserConfiguration(data);
const buffer = mapObjectToUserConfigBinaryBuffer(data.configuration);
await this.operations.saveUserConfiguration(buffer);
response.success = true;
}

View File

@@ -5,12 +5,13 @@ import * as sudo from 'sudo-prompt';
import { dirSync } from 'tmp';
import { emptyDir, copy } from 'fs-extra';
import { IpcEvents, LogService, IpcResponse } from 'uhk-common';
import { CommandLineArgs, IpcEvents, LogService, IpcResponse } from 'uhk-common';
export class SudoService {
private rootDir: string;
constructor(private logService: LogService) {
constructor(private logService: LogService,
private options: CommandLineArgs) {
if (isDev) {
this.rootDir = path.join(path.join(process.cwd(), process.argv[1]), '../../../../');
} else {
@@ -21,6 +22,19 @@ export class SudoService {
}
private async setPrivilege(event: Electron.Event) {
if (this.options.spe) {
const error = new Error('No polkit authentication agent found.');
this.logService.error('[SudoService] Simulate privilege escalation error ', error);
const response = new IpcResponse();
response.success = false;
response.error = {message: error.message};
event.sender.send(IpcEvents.device.setPrivilegeOnLinuxReply, response);
return;
}
switch (process.platform) {
case 'linux':
await this.setPrivilegeOnLinux(event);
@@ -55,7 +69,7 @@ export class SudoService {
if (error) {
this.logService.error('[SudoService] Error when set privilege: ', error);
response.success = false;
response.error = error;
response.error = {message: error.message};
} else {
response.success = true;
}

View File

@@ -0,0 +1,32 @@
import { app } from 'electron';
import { LogService, UserConfiguration, SaveUserConfigurationData } from 'uhk-common';
import * as path from 'path';
import * as fs from 'fs-extra';
export const getBackupUserConfigurationPath = (uniqueId: number): string => {
const appDataDir = app.getPath('userData');
return path.join(appDataDir, `${uniqueId}.json`);
};
export const backupUserConfiguration = (data: SaveUserConfigurationData): Promise<void> => {
const backupFilePath = getBackupUserConfigurationPath(data.uniqueId);
return fs.writeJSON(backupFilePath, data.configuration, {spaces: 2});
};
export const getBackupUserConfigurationContent = async (logService: LogService, uniqueId: number): Promise<UserConfiguration> => {
try {
const backupFilePath = getBackupUserConfigurationPath(uniqueId);
if (await fs.pathExists(backupFilePath)) {
const json = await fs.readJSON(backupFilePath);
new UserConfiguration().fromJsonObject(json);
return json;
}
return null;
} catch (error) {
logService.error('Can not load backup user configuration for device', {uniqueId, error});
}
};

View File

@@ -10,7 +10,10 @@
"url": "git@github.com:UltimateHackingKeyboard/agent.git"
},
"scripts": {
"build": "tsc",
"build": "run-s tsc copy:*",
"tsc": "tsc",
"copy:scancodes": "copyfiles ./src/config-serializer/config-items/scancodes.json dist",
"copy:secondary-roles": "copyfiles ./src/config-serializer/config-items/secondaryRole.json dist",
"test": "jasmine-ts --config=jasmine.json",
"coverage": "nyc jasmine-ts --config=jasmine.json"
},

View File

@@ -9,3 +9,6 @@ export * from './macro';
export * from './module';
export * from './module-configuration';
export * from './user-configuration';
export const SCANCODES = require('./scancodes.json');
export const SECONDARY_ROLES = require('./secondaryRole.json');

View File

@@ -8,6 +8,7 @@ import { SwitchKeymapAction, UnresolvedSwitchKeymapAction } from './switch-keyma
import { MouseAction } from './mouse-action';
import { PlayMacroAction } from './play-macro-action';
import { NoneAction } from './none-action';
import { isScancodeExists } from '../scancode-checker';
export class Helper {
@@ -26,7 +27,12 @@ export class Helper {
buffer.backtrack();
if (keyActionFirstByte >= KeyActionId.KeystrokeAction && keyActionFirstByte < KeyActionId.LastKeystrokeAction) {
return new KeystrokeAction().fromBinary(buffer);
const keystrokeAction = new KeystrokeAction().fromBinary(buffer);
if (isValidKeystrokeAction(keystrokeAction)) {
return keystrokeAction;
}
return new NoneAction();
}
switch (keyActionFirstByte) {
@@ -68,8 +74,14 @@ export class Helper {
}
switch (keyAction.keyActionType) {
case keyActionType.KeystrokeAction:
return new KeystrokeAction().fromJsonObject(keyAction);
case keyActionType.KeystrokeAction: {
const keystrokeAction = new KeystrokeAction().fromJsonObject(keyAction);
if (isValidKeystrokeAction(keystrokeAction)) {
return keystrokeAction;
}
return new NoneAction();
}
case keyActionType.SwitchLayerAction:
return new SwitchLayerAction().fromJsonObject(keyAction);
case keyActionType.SwitchKeymapAction:
@@ -85,3 +97,9 @@ export class Helper {
}
}
}
function isValidKeystrokeAction(keystrokeAction: KeystrokeAction): boolean {
return keystrokeAction.hasSecondaryRoleAction() ||
keystrokeAction.hasActiveModifier() ||
keystrokeAction.hasScancode() && isScancodeExists(keystrokeAction.scancode);
}

View File

@@ -0,0 +1,26 @@
import { SCANCODES } from './';
let scancodeMap: Map<number, any>;
export function isScancodeExists(scancode: number): boolean {
if (!scancodeMap) {
fillScancodeMap();
}
return scancodeMap.has(scancode);
}
function fillScancodeMap(): void {
scancodeMap = new Map<number, any>();
for (const scanGroup of SCANCODES) {
for (const child of scanGroup.children) {
if (child.additional && child.additional.scancode) {
scancodeMap.set(child.additional.scancode, child);
}
else {
scancodeMap.set(Number.parseInt(child.id), child);
}
}
}
}

View File

@@ -105,6 +105,10 @@
{
"id": "29",
"text": "Z"
},
{
"id": "100",
"text": "| ISO"
}
]
},
@@ -242,7 +246,7 @@
"text": "Delete"
},
{
"id": "118",
"id": "101",
"text": "Menu"
},
{
@@ -314,10 +318,6 @@
"id": "69",
"text": "F12"
},
{
"id": "100",
"text": "| ISO"
},
{
"id": "104",
"text": "F13"

View File

@@ -1,3 +1,10 @@
export interface CommandLineArgs {
addons: boolean;
/**
* addons menu visible or not
*/
addons?: boolean;
/**
* simulate privilege escalation error
*/
spe?: boolean;
}

View File

@@ -1,6 +1,11 @@
import { HardwareModules } from './hardware-modules';
import { UserConfiguration } from '../config-serializer/config-items';
export interface ConfigurationReply {
success: boolean;
userConfiguration?: string;
hardwareConfiguration?: string;
modules?: HardwareModules;
error?: string;
backupConfiguration?: UserConfiguration;
}

View File

@@ -0,0 +1,4 @@
export interface HardwareModuleInfo {
firmwareVersion?: string;
moduleProtocolVersion?: string;
}

View File

@@ -0,0 +1,6 @@
import { HardwareModuleInfo } from './hardware-module-info';
export interface HardwareModules {
leftModuleInfo?: HardwareModuleInfo;
rightModuleInfo?: HardwareModuleInfo;
}

View File

@@ -5,3 +5,6 @@ export * from './app-start-info';
export * from './configuration-reply';
export * from './version-information';
export * from './device-connection-state';
export * from './hardware-modules';
export * from './hardware-module-info';
export * from './save-user-configuration-data';

View File

@@ -0,0 +1,4 @@
export interface SaveUserConfigurationData {
uniqueId: number;
configuration: string;
}

View File

@@ -0,0 +1,33 @@
import { HardwareConfiguration, UhkBuffer, UserConfiguration } from '../../index';
export const getHardwareConfigFromDeviceResponse = (json: string): HardwareConfiguration => {
const data = JSON.parse(json);
const hardwareConfig = new HardwareConfiguration();
hardwareConfig.fromBinary(UhkBuffer.fromArray(data));
if (hardwareConfig.uniqueId > 0) {
return hardwareConfig;
}
return null;
};
export const getUserConfigFromDeviceResponse = (json: string): UserConfiguration => {
const data = JSON.parse(json);
const userConfig = new UserConfiguration();
userConfig.fromBinary(UhkBuffer.fromArray(data));
if (userConfig.userConfigMajorVersion > 0) {
return userConfig;
}
throw Error('Invalid user configuration');
};
export const mapObjectToUserConfigBinaryBuffer = (obj: any): Buffer => {
const configuration = new UserConfiguration();
configuration.fromJsonObject(obj);
const buffer = new UhkBuffer();
configuration.toBinary(buffer);
return buffer.getBufferContent();
};

View File

@@ -1,6 +1,7 @@
export { IpcEvents } from './ipcEvents';
export * from './log';
export * from './constants';
export * from './helpers';
// Source: http://stackoverflow.com/questions/13720256/javascript-regex-camelcase-to-sentence
export function camelCaseToSentence(camelCasedText: string): string {

View File

@@ -23,7 +23,7 @@
"integrity": "sha1-u13KOCu5TwXhUZQ3PRb9O6HKEQ0=",
"requires": {
"delegates": "1.0.0",
"readable-stream": "2.3.3"
"readable-stream": "2.3.6"
}
},
"bindings": {
@@ -32,11 +32,12 @@
"integrity": "sha512-DpLh5EzMR2kzvX1KIlVC0VkC3iZtHKTgdtZ0a3pglBZdaQFjt5S9g9xd1lE+YvXyfd6mtCeRnrUfOLYiTMlNSw=="
},
"bl": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/bl/-/bl-1.2.1.tgz",
"integrity": "sha1-ysMo977kVzDUBLaSID/LWQ4XLV4=",
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/bl/-/bl-1.2.2.tgz",
"integrity": "sha512-e8tQYnZodmebYDWGH7KMRvtzKXaJHx3BbilrgZCfvyLUYdKpK1t5PSPmpkny/SgiTSCnjfLW7v5rlONXVFkQEA==",
"requires": {
"readable-stream": "2.3.3"
"readable-stream": "2.3.6",
"safe-buffer": "5.1.1"
}
},
"chownr": {
@@ -59,6 +60,14 @@
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
"integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
},
"decompress-response": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz",
"integrity": "sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M=",
"requires": {
"mimic-response": "1.0.0"
}
},
"deep-extend": {
"version": "0.4.2",
"resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.4.2.tgz",
@@ -69,10 +78,15 @@
"resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
"integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o="
},
"detect-libc": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz",
"integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups="
},
"end-of-stream": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.0.tgz",
"integrity": "sha1-epDYM+/abPpurA9JSduw+tOmMgY=",
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz",
"integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==",
"requires": {
"once": "1.4.0"
}
@@ -113,9 +127,9 @@
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
},
"ini": {
"version": "1.3.4",
"resolved": "https://registry.npmjs.org/ini/-/ini-1.3.4.tgz",
"integrity": "sha1-BTfLedr1m1mhpRff9wbIbsA5Fi4="
"version": "1.3.5",
"resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz",
"integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw=="
},
"is-fullwidth-code-point": {
"version": "1.0.0",
@@ -130,6 +144,11 @@
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
"integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
},
"mimic-response": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.0.tgz",
"integrity": "sha1-3z02Uqc/3ta5sLJBRub9BSNTRY4="
},
"minimist": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
@@ -151,14 +170,17 @@
}
},
"nan": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/nan/-/nan-2.7.0.tgz",
"integrity": "sha1-2Vv3IeyHfgjbJ27T/G63j5CDrUY="
"version": "2.10.0",
"resolved": "https://registry.npmjs.org/nan/-/nan-2.10.0.tgz",
"integrity": "sha512-bAdJv7fBLhWC+/Bls0Oza+mvTaNQtP+1RyhhhvD95pgUJz6XM5IzgmxOkItJ9tkoCiplvAnXI1tNmmUD/eScyA=="
},
"node-abi": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/node-abi/-/node-abi-2.1.1.tgz",
"integrity": "sha512-6oxV13poCOv7TfGvhsSz6XZWpXeKkdGVh72++cs33OfMh3KAX8lN84dCvmqSETyDXAFcUHtV7eJrgFBoOqZbNQ=="
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/node-abi/-/node-abi-2.3.0.tgz",
"integrity": "sha512-zwm6vU3SsVgw3e9fu48JBaRBCJGIvAgysDsqtf5+vEexFE71bEOtaMWb5zr/zODZNzTPtQlqUUpC79k68Hspow==",
"requires": {
"semver": "5.5.0"
}
},
"node-hid": {
"version": "0.5.7",
@@ -166,8 +188,8 @@
"integrity": "sha512-dwwpOetL2+MGYgivbO22ML+45ieCGbueWv1rYxRgBoEc2QMp6UF6ZucEkYts1IA3YPWJNkmpGh6dqQ85n19szw==",
"requires": {
"bindings": "1.3.0",
"nan": "2.7.0",
"prebuild-install": "2.3.0"
"nan": "2.10.0",
"prebuild-install": "2.5.1"
}
},
"noop-logger": {
@@ -210,62 +232,63 @@
"integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M="
},
"prebuild-install": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-2.3.0.tgz",
"integrity": "sha512-gzjq2oHB8oMbzJSsSh9MQ64zrXZGt092/uT4TLZlz2qnrPxpWqp4vYB7LZrDxnlxf5RfbCjkgDI/z0EIVuYzAw==",
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-2.5.1.tgz",
"integrity": "sha512-3DX9L6pzwc1m1ksMkW3Ky2WLgPQUBiySOfXVl3WZyAeJSyJb4wtoH9OmeRGcubAWsMlLiL8BTHbwfm/jPQE9Ag==",
"requires": {
"detect-libc": "1.0.3",
"expand-template": "1.1.0",
"github-from-package": "0.0.0",
"minimist": "1.2.0",
"mkdirp": "0.5.1",
"node-abi": "2.1.1",
"node-abi": "2.3.0",
"noop-logger": "0.1.1",
"npmlog": "4.1.2",
"os-homedir": "1.0.2",
"pump": "1.0.2",
"rc": "1.2.2",
"simple-get": "1.4.3",
"pump": "2.0.1",
"rc": "1.2.6",
"simple-get": "2.7.0",
"tar-fs": "1.16.0",
"tunnel-agent": "0.6.0",
"xtend": "4.0.1"
"which-pm-runs": "1.0.0"
}
},
"process-nextick-args": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz",
"integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M="
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz",
"integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw=="
},
"pump": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/pump/-/pump-1.0.2.tgz",
"integrity": "sha1-Oz7mUS+U8OV1U4wXmV+fFpkKXVE=",
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz",
"integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==",
"requires": {
"end-of-stream": "1.4.0",
"end-of-stream": "1.4.1",
"once": "1.4.0"
}
},
"rc": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/rc/-/rc-1.2.2.tgz",
"integrity": "sha1-2M6ctX6NZNnHut2YdsfDTL48cHc=",
"version": "1.2.6",
"resolved": "https://registry.npmjs.org/rc/-/rc-1.2.6.tgz",
"integrity": "sha1-6xiYnG1PTxYsOZ953dKfODVWgJI=",
"requires": {
"deep-extend": "0.4.2",
"ini": "1.3.4",
"ini": "1.3.5",
"minimist": "1.2.0",
"strip-json-comments": "2.0.1"
}
},
"readable-stream": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz",
"integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==",
"version": "2.3.6",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
"integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
"requires": {
"core-util-is": "1.0.2",
"inherits": "2.0.3",
"isarray": "1.0.0",
"process-nextick-args": "1.0.7",
"process-nextick-args": "2.0.0",
"safe-buffer": "5.1.1",
"string_decoder": "1.0.3",
"string_decoder": "1.1.1",
"util-deprecate": "1.0.2"
}
},
@@ -274,6 +297,11 @@
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz",
"integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg=="
},
"semver": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz",
"integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA=="
},
"set-blocking": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
@@ -284,14 +312,19 @@
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz",
"integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0="
},
"simple-concat": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.0.tgz",
"integrity": "sha1-c0TLuLbib7J9ZrL8hvn21Zl1IcY="
},
"simple-get": {
"version": "1.4.3",
"resolved": "https://registry.npmjs.org/simple-get/-/simple-get-1.4.3.tgz",
"integrity": "sha1-6XVe2kB+ltpAxeUVjJ6jezO+y+s=",
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/simple-get/-/simple-get-2.7.0.tgz",
"integrity": "sha512-RkE9rGPHcxYZ/baYmgJtOSM63vH0Vyq+ma5TijBcLla41SWlh8t6XYIGMR/oeZcmr+/G8k+zrClkkVrtnQ0esg==",
"requires": {
"decompress-response": "3.3.0",
"once": "1.4.0",
"unzip-response": "1.0.2",
"xtend": "4.0.1"
"simple-concat": "1.0.0"
}
},
"string-width": {
@@ -305,9 +338,9 @@
}
},
"string_decoder": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz",
"integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==",
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
"requires": {
"safe-buffer": "5.1.1"
}
@@ -332,18 +365,29 @@
"requires": {
"chownr": "1.0.1",
"mkdirp": "0.5.1",
"pump": "1.0.2",
"tar-stream": "1.5.4"
"pump": "1.0.3",
"tar-stream": "1.5.5"
},
"dependencies": {
"pump": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/pump/-/pump-1.0.3.tgz",
"integrity": "sha512-8k0JupWme55+9tCVE+FS5ULT3K6AbgqrGa58lTT49RpyfwwcGedHqaC5LlQNdEAumn/wFsu6aPwkuPMioy8kqw==",
"requires": {
"end-of-stream": "1.4.1",
"once": "1.4.0"
}
}
}
},
"tar-stream": {
"version": "1.5.4",
"resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-1.5.4.tgz",
"integrity": "sha1-NlSc8E7RrumyowwBQyUiONr5QBY=",
"version": "1.5.5",
"resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-1.5.5.tgz",
"integrity": "sha512-mQdgLPc/Vjfr3VWqWbfxW8yQNiJCbAZ+Gf6GDu1Cy0bdb33ofyiNGBtAY96jHFhDuivCwgW1H9DgTON+INiXgg==",
"requires": {
"bl": "1.2.1",
"end-of-stream": "1.4.0",
"readable-stream": "2.3.3",
"bl": "1.2.2",
"end-of-stream": "1.4.1",
"readable-stream": "2.3.6",
"xtend": "4.0.1"
}
},
@@ -355,16 +399,16 @@
"safe-buffer": "5.1.1"
}
},
"unzip-response": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/unzip-response/-/unzip-response-1.0.2.tgz",
"integrity": "sha1-uYTwh3/AqJwsdzzB73tbIytbBv4="
},
"util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
"integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
},
"which-pm-runs": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/which-pm-runs/-/which-pm-runs-1.0.0.tgz",
"integrity": "sha1-Zws6+8VS4LVd9rd4DKdGFfI60cs="
},
"wide-align": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.2.tgz",

View File

@@ -22,7 +22,7 @@ export enum UsbCommand {
GetDebugBuffer = 0x0b,
GetAdcValue = 0x0c,
SetLedPwmBrightness = 0x0d,
GetModuleProperties = 0x0e
GetModuleProperty = 0x0e
}
export enum EepromOperation {
@@ -81,3 +81,7 @@ export enum KbootCommands {
ping = 1,
reset = 2
}
export enum ModulePropertyId {
protocolVersions = 0
}

View File

@@ -1,5 +1,5 @@
import { Device, devices, HID } from 'node-hid';
import { LogService } from 'uhk-common';
import { CommandLineArgs, LogService } from 'uhk-common';
import {
ConfigBufferId,
@@ -27,7 +27,8 @@ export class UhkHidDevice {
private _device: HID;
private _hasPermission = false;
constructor(private logService: LogService) {
constructor(private logService: LogService,
private options: CommandLineArgs) {
}
/**
@@ -38,6 +39,10 @@ export class UhkHidDevice {
* @returns {boolean}
*/
public hasPermission(): boolean {
if (this.options.spe) {
return false;
}
try {
if (this._hasPermission) {
return true;

View File

@@ -1,5 +1,12 @@
import { LogService } from 'uhk-common';
import { EnumerationModes, EnumerationNameToProductId, KbootCommands, ModuleSlotToI2cAddress, ModuleSlotToId } from './constants';
import { HardwareModuleInfo, LogService, UhkBuffer } from 'uhk-common';
import {
EnumerationModes,
EnumerationNameToProductId,
KbootCommands,
ModulePropertyId,
ModuleSlotToI2cAddress,
ModuleSlotToId
} from './constants';
import * as path from 'path';
import * as fs from 'fs';
import { UhkBlhost } from './uhk-blhost';
@@ -98,8 +105,6 @@ export class UhkOperations {
* @returns {Promise<Buffer>}
*/
public async loadConfiguration(configBufferId: ConfigBufferId): Promise<string> {
let response = [];
const configBufferIdToName = ['HardwareConfig', 'StagingUserConfig', 'ValidatedUserConfig'];
const configName = configBufferIdToName[configBufferId];
@@ -133,7 +138,8 @@ export class UhkOperations {
}
}
}
response = convertBufferToIntArray(configBuffer);
const response = convertBufferToIntArray(configBuffer);
return Promise.resolve(JSON.stringify(response));
} catch (error) {
const errMsg = `[DeviceOperation] ${configName} from eeprom error`;
@@ -158,10 +164,10 @@ export class UhkOperations {
return configSize;
}
public async saveUserConfiguration(json: string): Promise<void> {
public async saveUserConfiguration(buffer: Buffer): Promise<void> {
try {
this.logService.debug('[DeviceOperation] USB[T]: Write user configuration to keyboard');
await this.sendUserConfigToKeyboard(json);
await this.sendUserConfigToKeyboard(buffer);
this.logService.debug('[DeviceOperation] USB[T]: Write user configuration to EEPROM');
await this.device.writeConfigToEeprom(ConfigBufferId.validatedUserConfig);
}
@@ -193,14 +199,57 @@ export class UhkOperations {
return false;
}
public async getLeftModuleVersionInfo(): Promise<HardwareModuleInfo> {
try {
this.logService.debug('[DeviceOperation] USB[T]: Read left module version information');
const command = new Buffer([
UsbCommand.GetModuleProperty,
ModuleSlotToId.leftHalf,
ModulePropertyId.protocolVersions
]);
const buffer = await this.device.write(command);
const uhkBuffer = UhkBuffer.fromArray(convertBufferToIntArray(buffer));
// skip the first 2 byte
uhkBuffer.readUInt16();
return {
moduleProtocolVersion: `${uhkBuffer.readUInt16()}.${uhkBuffer.readUInt16()}.${uhkBuffer.readUInt16()}`,
firmwareVersion: `${uhkBuffer.readUInt16()}.${uhkBuffer.readUInt16()}.${uhkBuffer.readUInt16()}`
};
}
catch (error) {
this.logService.error('[DeviceOperation] Could not read left module version information', error);
}
return {
moduleProtocolVersion: '',
firmwareVersion: ''
};
}
public async getRightModuleVersionInfo(): Promise<HardwareModuleInfo> {
this.logService.debug('[DeviceOperation] USB[T]: Read right module version information');
const command = new Buffer([UsbCommand.GetProperty, DevicePropertyIds.ProtocolVersions]);
const buffer = await this.device.write(command);
const uhkBuffer = UhkBuffer.fromArray(convertBufferToIntArray(buffer));
// skip the first byte
uhkBuffer.readUInt8();
return {
firmwareVersion: `${uhkBuffer.readUInt16()}.${uhkBuffer.readUInt16()}.${uhkBuffer.readUInt16()}`
};
}
/**
* IpcMain handler. Send the UserConfiguration to the UHK Device and send a response with the result.
* @param {string} json - UserConfiguration in JSON format
* @param {Buffer} buffer - UserConfiguration buffer
* @returns {Promise<void>}
* @private
*/
private async sendUserConfigToKeyboard(json: string): Promise<void> {
const buffer: Buffer = new Buffer(JSON.parse(json).data);
private async sendUserConfigToKeyboard(buffer: Buffer): Promise<void> {
const fragments = getTransferBuffers(UsbCommand.WriteStagingUserConfig, buffer);
for (const fragment of fragments) {
await this.device.write(fragment);

File diff suppressed because it is too large Load Diff

View File

@@ -38,7 +38,6 @@
"@types/jasminewd2": "2.0.2",
"@types/jquery": "3.2.9",
"@types/lodash-es": "4.17.0",
"@types/node-hid": "0.5.2",
"@types/usb": "1.1.3",
"angular-confirmation-popover": "3.2.0",
"angular-notifier": "2.0.0",
@@ -72,8 +71,8 @@
"ng2-dragula": "1.5.0",
"ng2-nouislider": "^1.7.6",
"ng2-select2": "1.0.0-beta.10",
"ngx-clipboard": "8.0.0",
"ngrx-store-freeze": "0.1.9",
"node-hid": "0.5.4",
"nouislider": "^10.1.0",
"postcss-loader": "1.3.3",
"postcss-url": "5.1.2",

View File

@@ -1,4 +1,4 @@
import { Component, HostListener, ViewEncapsulation } from '@angular/core';
import { Component, ViewEncapsulation } from '@angular/core';
import { animate, style, transition, trigger } from '@angular/animations';
import { Observable } from 'rxjs/Observable';
import { Action, Store } from '@ngrx/store';
@@ -14,7 +14,6 @@ import {
saveToKeyboardState
} from './store';
import { ProgressButtonState } from './store/reducers/progress-button-state';
import { SaveUserConfigInBinaryFileAction, SaveUserConfigInJsonFileAction } from './store/actions/user-config';
@Component({
selector: 'main-app',

View File

@@ -9,13 +9,13 @@
<ul class="list-unstyled btn-list">
<li>
Download device configuration in
<span role="button" class="btn-link" (click)="saveConfigurationInJSONFormat()">JSON</span> or
<span role="button" class="btn-link" (click)="saveConfigurationInBINFormat()">binary</span> format.
<button class="btn btn-default"
(click)="exportUserConfiguration($event)">Export device configuration
</button>
</li>
<li>
<label class="btn btn-default btn-file">
Upload device configuration
Import device configuration
<input type="file"
(change)="changeFile($event)">
</label>

View File

@@ -34,6 +34,14 @@ export class DeviceConfigurationComponent {
this.store.dispatch(new SaveUserConfigInBinaryFileAction());
}
exportUserConfiguration(event: MouseEvent) {
if (event.shiftKey) {
this.saveConfigurationInBINFormat();
} else {
this.saveConfigurationInJSONFormat();
}
}
changeFile(event): void {
const files = event.srcElement.files;
const fileReader = new FileReader();

View File

@@ -4,6 +4,7 @@ import { DeviceConfigurationComponent } from './configuration/device-configurati
import { DeviceFirmwareComponent } from './firmware/device-firmware.component';
import { MouseSpeedComponent } from './mouse-speed/mouse-speed.component';
import { LEDBrightnessComponent } from './led-brightness/led-brightness.component';
import { RestoreConfigurationComponent } from './restore-configuration/restore-configuration.component';
export const deviceRoutes: Routes = [
{
@@ -29,6 +30,10 @@ export const deviceRoutes: Routes = [
{
path: 'firmware',
component: DeviceFirmwareComponent
},
{
path: 'restore-user-configuration',
component: RestoreConfigurationComponent
}
]
}

View File

@@ -7,30 +7,35 @@
<span>Firmware</span>
</h1>
<p><i>
<p>
Firmware {{ hardwareModules.leftModuleInfo.firmwareVersion }} is running on the left keyboard half.<br>
Firmware {{ hardwareModules.rightModuleInfo.firmwareVersion }} is running on the right keyboard half.
</p>
<p>
<i>
Please note that the firmware update process may sometimes fail. If if fails then
simply retry until it succeeds. If the left half becomes unresponsive after a failed
update then retry and follow the instructions displayed during the update to fix it.
We'll make the firmware update process more robust.
</i></p>
<p>
Flash firmware {{ (getAgentVersionInfo$ | async).firmwareVersion }} (bundled with Agent)
<button class="btn btn-primary"
[disabled]="flashFirmwareButtonDisbabled$ | async"
(click)="onUpdateFirmware()">Flash firmware
</button>
</i>
</p>
<p>
Flash firmware file <input id="firmware-file-select"
type="file"
[disabled]="flashFirmwareButtonDisbabled$ | async"
(change)="changeFile($event)">
<button class="btn btn-primary"
[disabled]="flashFirmwareButtonDisbabled$ | async"
(click)="onUpdateFirmwareWithFile()">Flash firmware
(click)="onUpdateFirmware()">
Flash firmware {{ (getAgentVersionInfo$ | async).firmwareVersion }} (bundled with Agent)
</button>
<label class="btn btn-primary btn-file"
[class.disabled]="flashFirmwareButtonDisbabled$ | async">
Choose firmware file and flash it
<input id="firmware-file-select"
type="file"
accept=".tar.bz2"
[disabled]="flashFirmwareButtonDisbabled$ | async"
(change)="changeFile($event)">
</label>
</p>
</div>

View File

@@ -2,9 +2,16 @@ import { Component, ElementRef, OnDestroy, ViewChild } from '@angular/core';
import { Store } from '@ngrx/store';
import { Observable } from 'rxjs/Observable';
import { Subscription } from 'rxjs/Subscription';
import { VersionInformation } from 'uhk-common';
import { HardwareModules, VersionInformation } from 'uhk-common';
import { AppState, firmwareOkButtonDisabled, flashFirmwareButtonDisbabled, getAgentVersionInfo, xtermLog } from '../../../store';
import {
AppState,
firmwareOkButtonDisabled,
flashFirmwareButtonDisbabled,
getAgentVersionInfo,
getHardwareModules,
xtermLog
} from '../../../store';
import { UpdateFirmwareAction, UpdateFirmwareOkButtonAction, UpdateFirmwareWithAction } from '../../../store/actions/device';
import { XtermLog } from '../../../models/xterm-log';
@@ -22,8 +29,9 @@ export class DeviceFirmwareComponent implements OnDestroy {
xtermLogSubscription: Subscription;
getAgentVersionInfo$: Observable<VersionInformation>;
firmwareOkButtonDisabled$: Observable<boolean>;
hardwareModulesSubscription: Subscription;
hardwareModules: HardwareModules;
arrayBuffer: Uint8Array;
@ViewChild('scrollMe') divElement: ElementRef;
constructor(private store: Store<AppState>) {
@@ -38,24 +46,20 @@ export class DeviceFirmwareComponent implements OnDestroy {
});
this.getAgentVersionInfo$ = store.select(getAgentVersionInfo);
this.firmwareOkButtonDisabled$ = store.select(firmwareOkButtonDisabled);
this.hardwareModulesSubscription = store.select(getHardwareModules).subscribe(data => {
this.hardwareModules = data;
});
}
ngOnDestroy(): void {
this.xtermLogSubscription.unsubscribe();
this.hardwareModulesSubscription.unsubscribe();
}
onUpdateFirmware(): void {
this.store.dispatch(new UpdateFirmwareAction());
}
onUpdateFirmwareWithFile(): void {
if (!this.arrayBuffer) {
return;
}
this.store.dispatch(new UpdateFirmwareWithAction(Array.prototype.slice.call(this.arrayBuffer)));
}
onOkButtonClick(): void {
this.store.dispatch(new UpdateFirmwareOkButtonAction());
}
@@ -64,14 +68,13 @@ export class DeviceFirmwareComponent implements OnDestroy {
const files = event.srcElement.files;
if (files.length === 0) {
this.arrayBuffer = null;
return;
}
const fileReader = new FileReader();
fileReader.onloadend = function () {
this.arrayBuffer = new Uint8Array(fileReader.result);
const arrayBuffer = new Uint8Array(fileReader.result);
this.store.dispatch(new UpdateFirmwareWithAction(Array.prototype.slice.call(arrayBuffer)));
}.bind(this);
fileReader.readAsArrayBuffer(files[0]);
}

View File

@@ -2,4 +2,5 @@ export * from './configuration/device-configuration.component';
export * from './firmware/device-firmware.component';
export * from './mouse-speed/mouse-speed.component';
export * from './led-brightness/led-brightness.component';
export * from './restore-configuration/restore-configuration.component';
export * from './device.routes';

View File

@@ -0,0 +1,19 @@
<h1>
<i class="fa fa-exclamation-circle"></i>
<span>Fix configuration</span>
</h1>
<p>
Your on-board device configuration is invalid.
</p>
<button class="btn btn-primary"
*ngIf="state.hasBackupUserConfiguration"
[disabled]="state.restoringUserConfiguration"
(click)="restoreUserConfiguration()"> Restore the last valid device configuration
</button>
<button class="btn btn-danger"
*ngIf="!state.hasBackupUserConfiguration"
[disabled]="state.restoringUserConfiguration"
(click)="resetUserConfiguration()">Reset device configuration
</button>

View File

@@ -0,0 +1,10 @@
:host {
overflow-y: auto;
display: block;
height: 100%;
width: 100%;
p {
margin: 1.5rem 0;
}
}

View File

@@ -0,0 +1,48 @@
import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core';
import { Store } from '@ngrx/store';
import { Subscription } from 'rxjs/Subscription';
import { AppState, getBackupUserConfigurationState } from '../../../store';
import { ResetUserConfigurationAction, RestoreUserConfigurationFromBackupAction } from '../../../store/actions/device';
import { RestoreConfigurationState } from '../../../models/restore-configuration-state';
@Component({
selector: 'restore-configuration',
templateUrl: './restore-configuration.component.html',
styleUrls: ['./restore-configuration.component.scss'],
host: {
'class': 'container-fluid'
}
})
export class RestoreConfigurationComponent implements OnInit, OnDestroy {
state: RestoreConfigurationState;
private stateSubscription: Subscription;
constructor(private store: Store<AppState>,
private cdRef: ChangeDetectorRef) {
}
ngOnDestroy(): void {
if (this.stateSubscription) {
this.stateSubscription.unsubscribe();
}
}
ngOnInit(): void {
this.stateSubscription = this.store
.select(getBackupUserConfigurationState)
.subscribe(data => {
this.state = data;
this.cdRef.markForCheck();
});
}
resetUserConfiguration() {
this.store.dispatch(new ResetUserConfigurationAction());
}
restoreUserConfiguration(): void {
this.store.dispatch(new RestoreUserConfigurationFromBackupAction());
}
}

View File

@@ -9,7 +9,7 @@
></select2>
<icon name="question-circle"
data-toggle="tooltip"
title="Looking for a non-US character, but can't find it? Please note that USB keyboards send scancodes, not characters to your computer. Then your operating system translates the scancodes to characters according to your current OS keyboard layout. This means that you have to select the US-equivalent character of the desired key in Agent."
title="Looking for a non-US character? Just pick the character of the desired key according to the US layout."
data-placement="bottom"></icon>
<capture-keystroke-button (capture)="onKeysCapture($event)" tabindex="0"></capture-keystroke-button>
</div>

View File

@@ -1,6 +1,6 @@
import { Component, Input, OnChanges } from '@angular/core';
import { Select2OptionData, Select2TemplateFunction } from 'ng2-select2';
import { KeyAction, KeystrokeAction, KeystrokeType } from 'uhk-common';
import { KeyAction, KeystrokeAction, KeystrokeType, SCANCODES, SECONDARY_ROLES } from 'uhk-common';
import { Tab } from '../tab';
import { MapperService } from '../../../../services/mapper.service';
@@ -35,8 +35,8 @@ export class KeypressTabComponent extends Tab implements OnChanges {
id: '0',
text: 'None'
}];
this.scanCodeGroups = this.scanCodeGroups.concat(require('./scancodes.json'));
this.secondaryRoleGroups = require('./secondaryRole.json');
this.scanCodeGroups = this.scanCodeGroups.concat(SCANCODES);
this.secondaryRoleGroups = SECONDARY_ROLES;
this.leftModifierSelects = Array(this.leftModifiers.length).fill(false);
this.rightModifierSelects = Array(this.rightModifiers.length).fill(false);
this.selectedScancodeOption = this.scanCodeGroups[0];

View File

@@ -1,4 +1,39 @@
<span class="privilege-checker-wrapper">
<uhk-message header="Cannot talk to your UHK" subtitle="Your UHK has been detected, but its permissions are not set up yet, so Agent can't talk to it."></uhk-message>
<button class="btn btn-default btn-lg btn-primary" (click)="setUpPermissions()"> Set up permissions </button>
</span>
<div class="privilege-checker-wrapper">
<uhk-message header="Cannot talk to your UHK"
subtitle="Your UHK has been detected, but its permissions are not set up yet, so Agent can't talk to it."></uhk-message>
<button class="btn btn-default btn-lg btn-primary"
(click)="setUpPermissions()"> Set up permissions
</button>
<div class="mt-10">
<a class="link-inline"
*ngIf="state.showWhatWillThisDo"
(click)="whatWillThisDo()">What will this do?
</a>
<div>
<p class="privilege-error"
#privilegeError
*ngIf="state.permissionSetupFailed">
Agent wasn't able to set up permissions via PolicyKit. This is most likely because the
<code>polkit</code> package is not installed on your system.
</p>
<div *ngIf="state.showWhatWillThisDoContent">
Agent uses the following script to set up permissions. You can run it manually as root, then
<a class="link-inline"
(click)="retry()">retry</a>.
<div class="copy-container">
<span class="fa fa-2x fa-copy"
ngxClipboard
[cbContent]="command"
title="Copy to clipboard"
data-toggle="tooltip"
data-placement="top"></span>
<pre><code>{{ command }}</code></pre>
</div>
</div>
</div>
</div>
</div>

View File

@@ -9,3 +9,19 @@
uhk-message {
max-width: 50%;
}
.privilege-error {
animation: error-fade-in 2s;
}
@keyframes error-fade-in {
0% {
color: white;
background-color: red;
}
100% {
color: inherit;
background-color: inherit;
}
}

View File

@@ -1,26 +1,61 @@
import { Component } from '@angular/core';
import { Router } from '@angular/router';
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core';
import { Store } from '@ngrx/store';
import 'rxjs/add/observable/of';
import 'rxjs/add/observable/throw';
import 'rxjs/add/operator/distinctUntilChanged';
import 'rxjs/add/operator/ignoreElements';
import 'rxjs/add/operator/takeWhile';
import { Subscription } from 'rxjs/Subscription';
import { AppState } from '../../store/index';
import { AppState, getPrivilegePageState } from '../../store';
import { SetPrivilegeOnLinuxAction } from '../../store/actions/device';
import { LoadAppStartInfoAction, PrivilegeWhatWillThisDoAction } from '../../store/actions/app';
import { PrivilagePageSate } from '../../models/privilage-page-sate';
@Component({
selector: 'privilege-checker',
changeDetection: ChangeDetectionStrategy.OnPush,
templateUrl: './privilege-checker.component.html',
styleUrls: ['./privilege-checker.component.scss']
})
export class PrivilegeCheckerComponent {
constructor(protected store: Store<AppState>) {
export class PrivilegeCheckerComponent implements OnInit, OnDestroy {
state: PrivilagePageSate;
command = `cat <<EOF >/etc/udev/rules.d/50-uhk60.rules
# Ultimate Hacking Keyboard rules
# These are the udev rules for accessing the USB interfaces of the UHK as non-root users.
# Copy this file to /etc/udev/rules.d and physically reconnect the UHK afterwards.
SUBSYSTEMS=="usb", ATTRS{idVendor}=="1d50", ATTRS{idProduct}=="612[0-7]", MODE:="0666"
EOF
udevadm trigger
udevadm settle`;
private stateSubscription: Subscription;
constructor(private store: Store<AppState>,
private cdRef: ChangeDetectorRef) {
}
ngOnInit(): void {
this.stateSubscription = this.store.select(getPrivilegePageState)
.subscribe(state => {
this.state = state;
this.cdRef.markForCheck();
});
}
ngOnDestroy(): void {
if (this.stateSubscription) {
this.stateSubscription.unsubscribe();
}
}
setUpPermissions(): void {
this.store.dispatch(new SetPrivilegeOnLinuxAction());
}
whatWillThisDo(): void {
this.store.dispatch(new PrivilegeWhatWillThisDoAction());
}
retry(): void {
this.store.dispatch(new LoadAppStartInfoAction());
}
}

View File

@@ -5,6 +5,7 @@
<input #deviceName cancelable
class="pane-title__name"
type="text"
[readonly]="state.restoreUserConfiguration"
(change)="editDeviceName($event.target.value)"
(keyup.enter)="deviceName.blur()"
(keyup)="calculateHeaderTextWidth($event.target.value)">
@@ -17,33 +18,43 @@
<i class="fa fa-chevron-up pull-right" (click)="toggleHide($event, 'configuration')"></i>
</div>
<ul [@toggler]="animation['configuration']">
<li class="sidebar__level-2--item">
<li class="sidebar__level-2--item"
*ngIf="!state.restoreUserConfiguration">
<div class="sidebar__level-2" [routerLinkActive]="['active']">
<a [routerLink]="['/device/mouse-speed']"
[class.disabled]="updatingFirmware$ | async">Mouse speed</a>
[class.disabled]="state.updatingFirmware">Mouse speed</a>
</div>
</li>
<li class="sidebar__level-2--item">
<li class="sidebar__level-2--item"
*ngIf="!state.restoreUserConfiguration">
<div class="sidebar__level-2" [routerLinkActive]="['active']">
<a [routerLink]="['/device/led-brightness']"
[class.disabled]="updatingFirmware$ | async">LED brightness</a>
[class.disabled]="state.updatingFirmware">LED brightness</a>
</div>
</li>
<li class="sidebar__level-2--item">
<li class="sidebar__level-2--item"
*ngIf="!state.restoreUserConfiguration">
<div class="sidebar__level-2" [routerLinkActive]="['active']">
<a [routerLink]="['/device/configuration']"
[class.disabled]="updatingFirmware$ | async">Configuration</a>
[class.disabled]="state.updatingFirmware">Configuration</a>
</div>
</li>
<li class="sidebar__level-2--item"
*ngIf="state.restoreUserConfiguration">
<div class="sidebar__level-2" [routerLinkActive]="['active']">
<a [routerLink]="['/device/restore-user-configuration']">Fix configuration</a>
</div>
</li>
<li class="sidebar__level-2--item">
<div class="sidebar__level-2" [routerLinkActive]="['active']">
<a [routerLink]="['/device/firmware']"
[class.disabled]="updatingFirmware$ | async">Firmware</a>
[class.disabled]="state.updatingFirmware">Firmware</a>
</div>
</li>
</ul>
</li>
<li class="sidebar__level-1--item">
<li class="sidebar__level-1--item"
*ngIf="!state.restoreUserConfiguration">
<div class="sidebar__level-1">
<i class="fa fa-keyboard-o"></i> Keymaps
<!--a [routerLink]="['/keymap/add']"
@@ -55,10 +66,10 @@
(click)="toggleHide($event, 'keymap')"></i>
</div>
<ul [@toggler]="animation['keymap']">
<li *ngFor="let keymap of keymaps$ | async" class="sidebar__level-2--item">
<li *ngFor="let keymap of state.keymaps" class="sidebar__level-2--item">
<div class="sidebar__level-2" [routerLinkActive]="['active']">
<a [routerLink]="['/keymap', keymap.abbreviation]"
[class.disabled]="updatingFirmware$ | async">{{keymap.name}}</a>
[class.disabled]="state.updatingFirmware">{{keymap.name}}</a>
<i *ngIf="keymap.isDefault" class="fa fa-star sidebar__fav"
title="This is the default keymap which gets activated when powering the keyboard."
data-toggle="tooltip" data-placement="bottom"></i>
@@ -66,26 +77,27 @@
</li>
</ul>
</li>
<li class="sidebar__level-1--item">
<li class="sidebar__level-1--item"
*ngIf="!state.restoreUserConfiguration">
<div class="sidebar__level-1">
<i class="fa fa-play"></i> Macros
<a (click)="addMacro()"
class="btn btn-default pull-right btn-sm"
[class.disabled]="updatingFirmware$ | async">
[class.disabled]="state.updatingFirmware">
<i class="fa fa-plus"></i>
</a>
<i class="fa fa-chevron-up pull-right" (click)="toggleHide($event, 'macro')"></i>
</div>
<ul [@toggler]="animation['macro']">
<li *ngFor="let macro of macros$ | async" class="sidebar__level-2--item">
<li *ngFor="let macro of state.macros" class="sidebar__level-2--item">
<div class="sidebar__level-2" [routerLinkActive]="['active']">
<a [routerLink]="['/macro', macro.id]"
[class.disabled]="updatingFirmware$ | async">{{macro.name}}</a>
[class.disabled]="state.updatingFirmware">{{macro.name}}</a>
</div>
</li>
</ul>
</li>
<li class="sidebar__level-1--item" *ngIf="showAddonMenu$ | async">
<li class="sidebar__level-1--item" *ngIf="state.showAddonMenu">
<div class="sidebar__level-1">
<i class="fa fa-puzzle-piece"></i> Add-on modules
<i class="fa fa-chevron-up pull-right" (click)="toggleHide($event, 'addon')"></i>
@@ -94,25 +106,25 @@
<li class="sidebar__level-2--item" data-name="Key cluster" data-abbrev="">
<div class="sidebar__level-2" [routerLinkActive]="['active']">
<a [routerLink]="['/add-on', 'Key cluster']"
[class.disabled]="updatingFirmware$ | async">Key cluster</a>
[class.disabled]="state.updatingFirmware">Key cluster</a>
</div>
</li>
<li class="sidebar__level-2--item" data-name="Trackball" data-abbrev="">
<div class="sidebar__level-2" [routerLinkActive]="['active']">
<a [routerLink]="['/add-on', 'Trackball']"
[class.disabled]="updatingFirmware$ | async">Trackball</a>
[class.disabled]="state.updatingFirmware">Trackball</a>
</div>
</li>
<li class="sidebar__level-2--item" data-name="Toucpad" data-abbrev="">
<div class="sidebar__level-2" [routerLinkActive]="['active']">
<a [routerLink]="['/add-on', 'Touchpad']"
[class.disabled]="updatingFirmware$ | async">Touchpad</a>
[class.disabled]="state.updatingFirmware">Touchpad</a>
</div>
</li>
<li class="sidebar__level-2--item" data-name="Trackpoint" data-abbrev="">
<div class="sidebar__level-2" [routerLinkActive]="['active']">
<a [routerLink]="['/add-on', 'Trackpoint']"
[class.disabled]="updatingFirmware$ | async">Trackpoint</a>
[class.disabled]="state.updatingFirmware">Trackpoint</a>
</div>
</li>
</ul>
@@ -128,12 +140,14 @@
<ul [@toggler]="animation['agent']">
<li class="sidebar__level-2--item">
<div class="sidebar__level-2" [routerLinkActive]="['active']">
<a [routerLink]="['/settings']">Settings</a>
<a [routerLink]="['/settings']"
[class.disabled]="state.updatingFirmware">Settings</a>
</div>
</li>
<li class="sidebar__level-2--item">
<div class="sidebar__level-2" [routerLinkActive]="['active']">
<a [routerLink]="['/about']">About</a>
<a [routerLink]="['/about']"
[class.disabled]="state.updatingFirmware">About</a>
</div>
</li>
</ul>

View File

@@ -9,6 +9,10 @@
a {
color: #333;
&.disabled {
opacity: 0.65;
}
}
// General list styles for the sidebar-menu.
@@ -112,6 +116,10 @@ ul {
&:focus {
text-decoration: none;
}
&.disabled {
opacity: 0.65;
}
}
}
}

View File

@@ -1,20 +1,27 @@
import { AfterContentInit, Component, ElementRef, OnDestroy, Renderer2, ViewChild } from '@angular/core';
import {
AfterContentInit,
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
ElementRef,
OnDestroy, OnInit,
Renderer2,
ViewChild
} from '@angular/core';
import { animate, state, style, transition, trigger } from '@angular/animations';
import { Keymap, Macro } from 'uhk-common';
import { Store } from '@ngrx/store';
import { Observable } from 'rxjs/Observable';
import { Subscription } from 'rxjs/Subscription';
import 'rxjs/add/operator/do';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/let';
import { AppState, getDeviceName, runningInElectron, showAddonMenu, updatingFirmware } from '../../store';
import { AppState, getSideMenuPageState } from '../../store';
import { MacroActions } from '../../store/actions';
import { getKeymaps, getMacros } from '../../store/reducers/user-configuration';
import * as util from '../../util';
import { RenameUserConfigurationAction } from '../../store/actions/user-config';
import { SideMenuPageState } from '../../models/side-menu-page-state';
@Component({
animations: [
@@ -30,24 +37,19 @@ import { RenameUserConfigurationAction } from '../../store/actions/user-config';
],
selector: 'side-menu',
templateUrl: './side-menu.component.html',
styleUrls: ['./side-menu.component.scss']
styleUrls: ['./side-menu.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class SideMenuComponent implements AfterContentInit, OnDestroy {
showAddonMenu$: Observable<boolean>;
runInElectron$: Observable<boolean>;
updatingFirmware$: Observable<boolean>;
deviceName$: Observable<string>;
deviceNameSubscription: Subscription;
keymaps$: Observable<Keymap[]>;
macros$: Observable<Macro[]>;
export class SideMenuComponent implements AfterContentInit, OnInit, OnDestroy {
state: SideMenuPageState;
animation: { [key: string]: 'active' | 'inactive' };
deviceNameValue: string;
updatingFirmware = false;
updatingFirmwareSubscription: Subscription;
@ViewChild('deviceName') deviceName: ElementRef;
constructor(private store: Store<AppState>, private renderer: Renderer2) {
private stateSubscription: Subscription;
constructor(private store: Store<AppState>,
private renderer: Renderer2,
private cdRef: ChangeDetectorRef) {
this.animation = {
device: 'active',
configuration: 'active',
@@ -55,20 +57,13 @@ export class SideMenuComponent implements AfterContentInit, OnDestroy {
macro: 'active',
addon: 'active'
};
}
this.keymaps$ = store.let(getKeymaps());
this.macros$ = store.let(getMacros());
this.showAddonMenu$ = this.store.select(showAddonMenu);
this.runInElectron$ = this.store.select(runningInElectron);
this.deviceName$ = store.select(getDeviceName);
this.deviceNameSubscription = this.deviceName$.subscribe(name => {
this.deviceNameValue = name;
ngOnInit(): void {
this.stateSubscription = this.store.select(getSideMenuPageState).subscribe(data => {
this.state = data;
this.setDeviceName();
});
this.updatingFirmware$ = store.select(updatingFirmware);
this.updatingFirmwareSubscription = this.updatingFirmware$.subscribe(updating => {
this.updatingFirmware = updating;
this.cdRef.markForCheck();
});
}
@@ -77,12 +72,13 @@ export class SideMenuComponent implements AfterContentInit, OnDestroy {
}
ngOnDestroy(): void {
this.deviceNameSubscription.unsubscribe();
this.updatingFirmwareSubscription.unsubscribe();
if (this.stateSubscription) {
this.stateSubscription.unsubscribe();
}
}
toggleHide(event: Event, type: string) {
if (this.updatingFirmware) {
if (this.state.updatingFirmware) {
return;
}
@@ -110,7 +106,7 @@ export class SideMenuComponent implements AfterContentInit, OnDestroy {
}
editDeviceName(name: string): void {
if (!util.isValidName(name) || name.trim() === this.deviceNameValue) {
if (!util.isValidName(name) || name.trim() === this.state.deviceName) {
this.setDeviceName();
return;
}
@@ -126,7 +122,7 @@ export class SideMenuComponent implements AfterContentInit, OnDestroy {
private setDeviceName(): void {
if (this.deviceName) {
this.renderer.setProperty(this.deviceName.nativeElement, 'value', this.deviceNameValue);
this.renderer.setProperty(this.deviceName.nativeElement, 'value', this.state.deviceName);
this.calculateHeaderTextWidth(this.deviceName.nativeElement.value);
}
}

View File

@@ -0,0 +1,5 @@
export interface PrivilagePageSate {
showWhatWillThisDo: boolean;
showWhatWillThisDoContent: boolean;
permissionSetupFailed: boolean;
}

View File

@@ -0,0 +1,4 @@
export interface RestoreConfigurationState {
restoringUserConfiguration: boolean;
hasBackupUserConfiguration: boolean;
}

View File

@@ -0,0 +1,11 @@
import { Keymap, Macro } from 'uhk-common';
export interface SideMenuPageState {
showAddonMenu: boolean;
runInElectron: boolean;
updatingFirmware: boolean;
deviceName: string;
keymaps: Keymap[];
macros: Macro[];
restoreUserConfiguration: boolean;
}

View File

@@ -94,7 +94,7 @@ export class CaptureService {
this.mapping.set(88, 27); // X
this.mapping.set(89, 28); // Y
this.mapping.set(90, 29); // Z
this.mapping.set(93, 118); // Menu
this.mapping.set(93, 101); // Menu
this.mapping.set(96, 98); // Num pad 0
this.mapping.set(97, 89); // Num pad 1
this.mapping.set(98, 90); // Num pad 2

View File

@@ -1,7 +1,7 @@
import { Injectable, NgZone } from '@angular/core';
import { Action, Store } from '@ngrx/store';
import { DeviceConnectionState, IpcEvents, IpcResponse, LogService } from 'uhk-common';
import { DeviceConnectionState, IpcEvents, IpcResponse, LogService, SaveUserConfigurationData } from 'uhk-common';
import { AppState } from '../store';
import { IpcCommonRenderer } from './ipc-common-renderer';
import {
@@ -26,8 +26,8 @@ export class DeviceRendererService {
this.ipcRenderer.send(IpcEvents.device.setPrivilegeOnLinux);
}
saveUserConfiguration(buffer: Buffer): void {
this.ipcRenderer.send(IpcEvents.device.saveUserConfiguration, JSON.stringify(buffer));
saveUserConfiguration(data: SaveUserConfigurationData): void {
this.ipcRenderer.send(IpcEvents.device.saveUserConfiguration, JSON.stringify(data));
}
loadConfigurationFromKeyboard(): void {

View File

@@ -190,6 +190,7 @@ export class MapperService {
this.basicScanCodeTextMap.set(98, ['Insert', '0']);
this.basicScanCodeTextMap.set(99, ['Del', '.']);
this.basicScanCodeTextMap.set(100, ['ISO key', '|']);
this.basicScanCodeTextMap.set(101, ['Menu']);
this.basicScanCodeTextMap.set(104, ['F13']);
this.basicScanCodeTextMap.set(105, ['F14']);
this.basicScanCodeTextMap.set(106, ['F15']);
@@ -202,7 +203,6 @@ export class MapperService {
this.basicScanCodeTextMap.set(113, ['F22']);
this.basicScanCodeTextMap.set(114, ['F23']);
this.basicScanCodeTextMap.set(115, ['F24']);
this.basicScanCodeTextMap.set(118, ['Menu']);
this.basicScanCodeTextMap.set(176, ['00']);
this.basicScanCodeTextMap.set(177, ['000']);
@@ -236,7 +236,7 @@ export class MapperService {
this.basicScancodeIcons.set(80, 'icon-kbd__mod--arrow-left');
this.basicScancodeIcons.set(81, 'icon-kbd__mod--arrow-down');
this.basicScancodeIcons.set(82, 'icon-kbd__mod--arrow-up');
this.basicScancodeIcons.set(118, 'icon-kbd__mod--menu');
this.basicScancodeIcons.set(101, 'icon-kbd__mod--menu');
this.mediaScancodeIcons = new Map<number, string>();
this.mediaScancodeIcons.set(138, 'icon-kbd__fn--browser');

File diff suppressed because it is too large Load Diff

View File

@@ -8,6 +8,7 @@ import { ConfirmationPopoverModule } from 'angular-confirmation-popover';
import { DragulaModule } from 'ng2-dragula/ng2-dragula';
import { Select2Module } from 'ng2-select2/ng2-select2';
import { NouisliderModule } from 'ng2-nouislider';
import { ClipboardModule } from 'ngx-clipboard';
import { AddOnComponent } from './components/add-on';
import { KeyboardSliderComponent } from './components/keyboard/slider';
@@ -15,7 +16,8 @@ import {
DeviceConfigurationComponent,
DeviceFirmwareComponent,
MouseSpeedComponent,
LEDBrightnessComponent
LEDBrightnessComponent,
RestoreConfigurationComponent
} from './components/device';
import { KeymapAddComponent, KeymapEditComponent, KeymapHeaderComponent } from './components/keymap';
import { LayersComponent } from './components/layers';
@@ -173,7 +175,8 @@ import { Autofocus } from './directives/autofocus/autofocus.directive';
XtermComponent,
SliderWrapperComponent,
EditableTextComponent,
Autofocus
Autofocus,
RestoreConfigurationComponent
],
imports: [
CommonModule,
@@ -186,7 +189,8 @@ import { Autofocus } from './directives/autofocus/autofocus.directive';
NotifierModule.withConfig(angularNotifierConfig),
ConfirmationPopoverModule.forRoot({
confirmButtonType: 'danger' // set defaults here
})
}),
ClipboardModule
],
providers: [
SvgModuleProviderService,

View File

@@ -17,7 +17,10 @@ export const ActionTypes = {
DISMISS_UNDO_NOTIFICATION: type(PREFIX + 'dismiss notification action'),
LOAD_HARDWARE_CONFIGURATION_SUCCESS: type(PREFIX + 'load hardware configuration success'),
ELECTRON_MAIN_LOG_RECEIVED: type(PREFIX + 'Electron main log received'),
OPEN_URL_IN_NEW_WINDOW: type(PREFIX + 'Open URL in new Window')
OPEN_URL_IN_NEW_WINDOW: type(PREFIX + 'Open URL in new Window'),
PRIVILEGE_WHAT_WILL_THIS_DO: type(PREFIX + 'What will this do clicked'),
SETUP_PERMISSION_ERROR: type(PREFIX + 'Setup permission error'),
LOAD_APP_START_INFO: type(PREFIX + 'Load app start info')
};
export class AppBootsrappedAction implements Action {
@@ -31,25 +34,29 @@ export class AppStartedAction implements Action {
export class ShowNotificationAction implements Action {
type = ActionTypes.APP_SHOW_NOTIFICATION;
constructor(public payload: Notification) { }
constructor(public payload: Notification) {
}
}
export class ApplyCommandLineArgsAction implements Action {
type = ActionTypes.APPLY_COMMAND_LINE_ARGS;
constructor(public payload: CommandLineArgs) { }
constructor(public payload: CommandLineArgs) {
}
}
export class ProcessAppStartInfoAction implements Action {
type = ActionTypes.APP_PROCESS_START_INFO;
constructor(public payload: AppStartInfo) { }
constructor(public payload: AppStartInfo) {
}
}
export class UndoLastAction implements Action {
type = ActionTypes.UNDO_LAST;
constructor(public payload: any) {}
constructor(public payload: any) {
}
}
export class UndoLastSuccessAction implements Action {
@@ -63,19 +70,37 @@ export class DismissUndoNotificationAction implements Action {
export class LoadHardwareConfigurationSuccessAction implements Action {
type = ActionTypes.LOAD_HARDWARE_CONFIGURATION_SUCCESS;
constructor(public payload: HardwareConfiguration) {}
constructor(public payload: HardwareConfiguration) {
}
}
export class ElectronMainLogReceivedAction implements Action {
type = ActionTypes.ELECTRON_MAIN_LOG_RECEIVED;
constructor(public payload: ElectronLogEntry) {}
constructor(public payload: ElectronLogEntry) {
}
}
export class OpenUrlInNewWindowAction implements Action {
type = ActionTypes.OPEN_URL_IN_NEW_WINDOW;
constructor(public payload: string) {}
constructor(public payload: string) {
}
}
export class PrivilegeWhatWillThisDoAction implements Action {
type = ActionTypes.PRIVILEGE_WHAT_WILL_THIS_DO;
}
export class SetupPermissionErrorAction implements Action {
type = ActionTypes.SETUP_PERMISSION_ERROR;
constructor(public payload: string) {
}
}
export class LoadAppStartInfoAction implements Action {
type = ActionTypes.LOAD_APP_START_INFO;
}
export type Actions
@@ -90,4 +115,7 @@ export type Actions
| LoadHardwareConfigurationSuccessAction
| ElectronMainLogReceivedAction
| OpenUrlInNewWindowAction
| PrivilegeWhatWillThisDoAction
| SetupPermissionErrorAction
| LoadAppStartInfoAction
;

View File

@@ -1,5 +1,5 @@
import { Action } from '@ngrx/store';
import { DeviceConnectionState, IpcResponse, type } from 'uhk-common';
import { DeviceConnectionState, HardwareModules, IpcResponse, type } from 'uhk-common';
const PREFIX = '[device] ';
@@ -22,7 +22,11 @@ export const ActionTypes = {
UPDATE_FIRMWARE_REPLY: type(PREFIX + 'update firmware reply'),
UPDATE_FIRMWARE_SUCCESS: type(PREFIX + 'update firmware success'),
UPDATE_FIRMWARE_FAILED: type(PREFIX + 'update firmware failed'),
UPDATE_FIRMWARE_OK_BUTTON: type(PREFIX + 'update firmware ok button click')
UPDATE_FIRMWARE_OK_BUTTON: type(PREFIX + 'update firmware ok button click'),
MODULES_INFO_LOADED: type(PREFIX + 'module info loaded'),
HAS_BACKUP_USER_CONFIGURATION: type(PREFIX + 'Store backup user configuration'),
RESTORE_CONFIGURATION_FROM_BACKUP: type(PREFIX + 'Restore configuration from backup'),
RESTORE_CONFIGURATION_FROM_BACKUP_SUCCESS: type(PREFIX + 'Restore configuration from backup success')
};
export class SetPrivilegeOnLinuxAction implements Action {
@@ -114,6 +118,28 @@ export class ResetMouseSpeedSettingsAction implements Action {
type = ActionTypes.RESET_MOUSE_SPEED_SETTINGS;
}
export class HardwareModulesLoadedAction implements Action {
type = ActionTypes.MODULES_INFO_LOADED;
constructor(public payload: HardwareModules) {
}
}
export class RestoreUserConfigurationFromBackupAction implements Action {
type = ActionTypes.RESTORE_CONFIGURATION_FROM_BACKUP;
}
export class HasBackupUserConfigurationAction implements Action {
type = ActionTypes.HAS_BACKUP_USER_CONFIGURATION;
constructor(public payload: boolean) {
}
}
export class RestoreUserConfigurationFromBackupSuccessAction implements Action {
type = ActionTypes.RESTORE_CONFIGURATION_FROM_BACKUP_SUCCESS;
}
export type Actions
= SetPrivilegeOnLinuxAction
| SetPrivilegeOnLinuxReplyAction
@@ -132,4 +158,8 @@ export type Actions
| UpdateFirmwareSuccessAction
| UpdateFirmwareFailedAction
| UpdateFirmwareOkButtonAction
| HardwareModulesLoadedAction
| RestoreUserConfigurationFromBackupAction
| HasBackupUserConfigurationAction
| RestoreUserConfigurationFromBackupSuccessAction
;

View File

@@ -40,6 +40,13 @@ export class ApplicationEffects {
this.logService.info('Renderer appStart effect end');
});
@Effect({dispatch: false})
appStartInfo$: Observable<Action> = this.actions$
.ofType(ActionTypes.LOAD_APP_START_INFO)
.do(() => {
this.appRendererService.getAppStartInfo();
});
@Effect({dispatch: false})
showNotification$: Observable<Action> = this.actions$
.ofType<ShowNotificationAction>(ActionTypes.APP_SHOW_NOTIFICATION)

View File

@@ -13,11 +13,19 @@ import 'rxjs/add/operator/mergeMap';
import 'rxjs/add/operator/withLatestFrom';
import 'rxjs/add/operator/switchMap';
import { DeviceConnectionState, IpcResponse, NotificationType, UhkBuffer, UserConfiguration } from 'uhk-common';
import {
DeviceConnectionState,
HardwareConfiguration,
IpcResponse,
NotificationType,
UserConfiguration
} from 'uhk-common';
import {
ActionTypes,
ConnectionStateChangedAction,
HideSaveToKeyboardButton,
ResetUserConfigurationAction,
RestoreUserConfigurationFromBackupSuccessAction,
SaveConfigurationAction,
SaveConfigurationReplyAction,
SaveToKeyboardSuccessAction,
@@ -31,7 +39,7 @@ import {
UpdateFirmwareWithAction
} from '../actions/device';
import { DeviceRendererService } from '../../services/device-renderer.service';
import { ShowNotificationAction } from '../actions/app';
import { SetupPermissionErrorAction, ShowNotificationAction } from '../actions/app';
import { AppState } from '../index';
import {
ActionTypes as UserConfigActions,
@@ -78,30 +86,23 @@ export class DeviceEffects {
setPrivilegeOnLinuxReply$: Observable<Action> = this.actions$
.ofType<SetPrivilegeOnLinuxReplyAction>(ActionTypes.SET_PRIVILEGE_ON_LINUX_REPLY)
.map(action => action.payload)
.mergeMap((response: any) => {
.map((response: any): any => {
if (response.success) {
return [
new ConnectionStateChangedAction({
return new ConnectionStateChangedAction({
connected: true,
hasPermission: true
})
];
});
}
return [
<any>new ShowNotificationAction({
type: NotificationType.Error,
message: response.error.message || response.error
})
];
return new SetupPermissionErrorAction(response.error);
});
@Effect({dispatch: false})
saveConfiguration$: Observable<Action> = this.actions$
.ofType(ActionTypes.SAVE_CONFIGURATION)
.withLatestFrom(this.store)
.map(([action, state]) => state.userConfiguration)
.do((userConfiguration: UserConfiguration) => {
setTimeout(() => this.sendUserConfigToKeyboard(userConfiguration), 100);
.do(([action, state]) => {
setTimeout(() => this.sendUserConfigToKeyboard(state.userConfiguration, state.app.hardwareConfig), 100);
})
.switchMap(() => Observable.empty());
@@ -128,8 +129,18 @@ export class DeviceEffects {
@Effect()
autoHideSaveToKeyboardButton$: Observable<Action> = this.actions$
.ofType(ActionTypes.SAVE_TO_KEYBOARD_SUCCESS)
.switchMap(() => Observable.timer(1000)
.switchMap(() => Observable.of(new HideSaveToKeyboardButton()))
.withLatestFrom(this.store)
.switchMap(([action, state]) => Observable.timer(1000)
.mergeMap(() => {
const actions = [new HideSaveToKeyboardButton()];
if (state.device.hasBackupUserConfiguration) {
actions.push(new RestoreUserConfigurationFromBackupSuccessAction());
this.router.navigate(['/']);
}
return actions;
})
);
@Effect()
@@ -199,6 +210,10 @@ export class DeviceEffects {
.ofType<UpdateFirmwareOkButtonAction>(ActionTypes.UPDATE_FIRMWARE_OK_BUTTON)
.do(() => this.deviceRendererService.startConnectionPoller());
@Effect() restoreUserConfiguration$ = this.actions$
.ofType<ResetUserConfigurationAction>(ActionTypes.RESTORE_CONFIGURATION_FROM_BACKUP)
.map(() => new SaveConfigurationAction());
constructor(private actions$: Actions,
private router: Router,
private deviceRendererService: DeviceRendererService,
@@ -207,9 +222,10 @@ export class DeviceEffects {
private defaultUserConfigurationService: DefaultUserConfigurationService) {
}
private sendUserConfigToKeyboard(userConfiguration: UserConfiguration): void {
const uhkBuffer = new UhkBuffer();
userConfiguration.toBinary(uhkBuffer);
this.deviceRendererService.saveUserConfiguration(uhkBuffer.getBufferContent());
private sendUserConfigToKeyboard(userConfiguration: UserConfiguration, hardwareConfig: HardwareConfiguration): void {
this.deviceRendererService.saveUserConfiguration({
uniqueId: hardwareConfig && hardwareConfig.uniqueId,
configuration: userConfiguration.toJsonObject()
});
}
}

View File

@@ -15,8 +15,9 @@ import 'rxjs/add/observable/of';
import 'rxjs/add/observable/empty';
import {
getHardwareConfigFromDeviceResponse,
getUserConfigFromDeviceResponse,
ConfigurationReply,
HardwareConfiguration,
LogService,
NotificationType,
UhkBuffer,
@@ -43,7 +44,11 @@ import {
ShowNotificationAction,
UndoLastAction
} from '../actions/app';
import { ShowSaveToKeyboardButtonAction } from '../actions/device';
import {
HardwareModulesLoadedAction,
ShowSaveToKeyboardButtonAction,
HasBackupUserConfigurationAction
} from '../actions/device';
import { DeviceRendererService } from '../../services/device-renderer.service';
import { UndoUserConfigData } from '../../models/undo-user-config-data';
import { UploadFileData } from '../../models/upload-file-data';
@@ -51,29 +56,6 @@ import { UploadFileData } from '../../models/upload-file-data';
@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.userConfigMajorVersion > 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.uniqueId > 0) {
return hardwareConfig;
}
return null;
}
@Effect() loadUserConfig$: Observable<Action> = defer(() => {
return Observable.of(new LoadUserConfigSuccessAction(this.getUserConfiguration()));
});
@@ -146,23 +128,24 @@ export class UserConfigEffects {
}
const result = [];
let newPageDestination = ['/'];
try {
const userConfig = UserConfigEffects.getUserConfigFromDeviceResponse(data.userConfiguration);
const userConfig = getUserConfigFromDeviceResponse(data.userConfiguration);
result.push(new LoadUserConfigSuccessAction(userConfig));
} catch (err) {
this.logService.error('Eeprom user-config parse error:', err);
result.push(
new ShowNotificationAction({
type: NotificationType.Error,
message: err
}));
const userConfig = new UserConfiguration().fromJsonObject(data.backupConfiguration);
result.push(new LoadUserConfigSuccessAction(this.getUserConfiguration()));
result.push(new HasBackupUserConfigurationAction(!!data.backupConfiguration));
result.push(new LoadUserConfigSuccessAction(userConfig));
newPageDestination = ['/device/restore-user-configuration'];
}
try {
const hardwareConfig = UserConfigEffects.getHardwareConfigFromDeviceResponse(data.hardwareConfiguration);
const hardwareConfig = getHardwareConfigFromDeviceResponse(data.hardwareConfiguration);
result.push(new LoadHardwareConfigurationSuccessAction(hardwareConfig));
} catch (err) {
this.logService.error('Eeprom hardware-config parse error:', err);
@@ -173,7 +156,9 @@ export class UserConfigEffects {
}));
}
this.router.navigate(['/']);
result.push(new HardwareModulesLoadedAction(data.modules));
this.router.navigate(newPageDestination);
return result;
});

View File

@@ -39,7 +39,6 @@ export const metaReducers: MetaReducer<AppState>[] = environment.production
: [storeFreeze];
export const getUserConfiguration = (state: AppState) => state.userConfiguration;
export const getDeviceName = createSelector(getUserConfiguration, fromUserConfig.getDeviceName);
export const appState = (state: AppState) => state.app;
@@ -47,10 +46,10 @@ 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 deviceConfigurationLoaded = createSelector(appState, fromApp.deviceConfigurationLoaded);
export const getAgentVersionInfo = createSelector(appState, fromApp.getAgentVersionInfo);
export const getPrivilegePageState = createSelector(appState, fromApp.getPrivilagePageState);
export const appUpdateState = (state: AppState) => state.appUpdate;
export const getShowAppUpdateAvailable = createSelector(appUpdateState, fromAppUpdate.getShowAppUpdateAvailable);
@@ -77,3 +76,29 @@ export const xtermLog = createSelector(deviceState, fromDevice.xtermLog);
export const firmwareOkButtonDisabled = createSelector(deviceState, fromDevice.firmwareOkButtonDisabled);
// tslint:disable-next-line: max-line-length
export const flashFirmwareButtonDisbabled = createSelector(runningInElectron, deviceState, (electron, state: fromDevice.State) => !electron || state.updatingFirmware);
export const getHardwareModules = createSelector(deviceState, fromDevice.getHardwareModules);
export const getBackupUserConfigurationState = createSelector(deviceState, fromDevice.getBackupUserConfigurationState);
export const getRestoreUserConfiguration = createSelector(deviceState, fromDevice.getHasBackupUserConfiguration);
export const getSideMenuPageState = createSelector(
showAddonMenu,
runningInElectron,
updatingFirmware,
getUserConfiguration,
getRestoreUserConfiguration,
(showAddonMenuValue: boolean,
runningInElectronValue: boolean,
updatingFirmwareValue: boolean,
userConfiguration: UserConfiguration,
restoreUserConfiguration: boolean) => {
return {
showAddonMenu: showAddonMenuValue,
runInElectron: runningInElectronValue,
updatingFirmware: updatingFirmwareValue,
deviceName: userConfiguration.deviceName,
keymaps: userConfiguration.keymaps,
macros: userConfiguration.macros,
restoreUserConfiguration
};
}
);

View File

@@ -1,13 +1,20 @@
import { ROUTER_NAVIGATION } from '@ngrx/router-store';
import { Action } from '@ngrx/store';
import { VersionInformation } from 'uhk-common';
import {
HardwareConfiguration,
Notification,
NotificationType,
runInElectron,
UserConfiguration,
VersionInformation
} from 'uhk-common';
import { HardwareConfiguration, Notification, NotificationType, runInElectron, UserConfiguration } from 'uhk-common';
import { ActionTypes, ShowNotificationAction } from '../actions/app';
import { ActionTypes as UserConfigActionTypes } from '../actions/user-config';
import { ActionTypes as DeviceActionTypes } from '../actions/device';
import { KeyboardLayout } from '../../keyboard/keyboard-layout.enum';
import { getVersions } from '../../util';
import { PrivilagePageSate } from '../../models/privilage-page-sate';
export interface State {
started: boolean;
@@ -19,6 +26,8 @@ export interface State {
configLoading: boolean;
hardwareConfig?: HardwareConfiguration;
agentVersionInfo?: VersionInformation;
privilegeWhatWillThisDoClicked: boolean;
permissionError?: any;
}
export const initialState: State = {
@@ -27,7 +36,8 @@ export const initialState: State = {
navigationCountAfterNotification: 0,
runningInElectron: runInElectron(),
configLoading: true,
agentVersionInfo: getVersions()
agentVersionInfo: getVersions(),
privilegeWhatWillThisDoClicked: false
};
export function reducer(state = initialState, action: Action & { payload: any }) {
@@ -115,6 +125,24 @@ export function reducer(state = initialState, action: Action & { payload: any })
};
}
case ActionTypes.PRIVILEGE_WHAT_WILL_THIS_DO:
return {
...state,
privilegeWhatWillThisDoClicked: true
};
case ActionTypes.SETUP_PERMISSION_ERROR:
return {
...state,
permissionError: action.payload
};
case DeviceActionTypes.SET_PRIVILEGE_ON_LINUX:
return {
...state,
permissionError: null
};
default:
return state;
}
@@ -124,7 +152,6 @@ 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;
@@ -134,3 +161,12 @@ export const getKeyboardLayout = (state: State): KeyboardLayout => {
};
export const deviceConfigurationLoaded = (state: State) => !state.runningInElectron ? true : !!state.hardwareConfig;
export const getAgentVersionInfo = (state: State) => state.agentVersionInfo || {} as VersionInformation;
export const getPrivilagePageState = (state: State): PrivilagePageSate => {
const permissionSetupFailed = !!state.permissionError;
return {
permissionSetupFailed,
showWhatWillThisDo: !state.privilegeWhatWillThisDoClicked && !permissionSetupFailed,
showWhatWillThisDoContent: state.privilegeWhatWillThisDoClicked || permissionSetupFailed
};
};

View File

@@ -1,14 +1,18 @@
import { Action } from '@ngrx/store';
import { HardwareModules } from 'uhk-common';
import {
ActionTypes,
ConnectionStateChangedAction,
HardwareModulesLoadedAction,
SaveConfigurationAction,
HasBackupUserConfigurationAction,
UpdateFirmwareFailedAction
} from '../actions/device';
import { ActionTypes as AppActions, ElectronMainLogReceivedAction } from '../actions/app';
import { initProgressButtonState, ProgressButtonState } from './progress-button-state';
import { XtermCssClass, XtermLog } from '../../models/xterm-log';
import { RestoreConfigurationState } from '../../models/restore-configuration-state';
export interface State {
connected: boolean;
@@ -16,7 +20,10 @@ export interface State {
saveToKeyboard: ProgressButtonState;
updatingFirmware: boolean;
firmwareUpdateFinished: boolean;
modules: HardwareModules;
log: Array<XtermLog>;
restoringUserConfiguration: boolean;
hasBackupUserConfiguration: boolean;
}
export const initialState: State = {
@@ -25,7 +32,18 @@ export const initialState: State = {
saveToKeyboard: initProgressButtonState,
updatingFirmware: false,
firmwareUpdateFinished: false,
log: [{message: '', cssClass: XtermCssClass.standard}]
modules: {
leftModuleInfo: {
firmwareVersion: '',
moduleProtocolVersion: ''
},
rightModuleInfo: {
firmwareVersion: ''
}
},
log: [{message: '', cssClass: XtermCssClass.standard}],
restoringUserConfiguration: false,
hasBackupUserConfiguration: false
};
export function reducer(state = initialState, action: Action) {
@@ -75,7 +93,8 @@ export function reducer(state = initialState, action: Action) {
showButton: true,
text: 'Saved!',
action: null
}
},
restoringUserConfiguration: false
};
}
@@ -148,6 +167,32 @@ export function reducer(state = initialState, action: Action) {
log: [...state.log, logEntry]
};
}
case ActionTypes.MODULES_INFO_LOADED:
return {
...state,
modules: (action as HardwareModulesLoadedAction).payload
};
case ActionTypes.RESET_USER_CONFIGURATION:
case ActionTypes.RESTORE_CONFIGURATION_FROM_BACKUP:
return {
...state,
restoringUserConfiguration: true
};
case ActionTypes.HAS_BACKUP_USER_CONFIGURATION:
return {
...state,
hasBackupUserConfiguration: (action as HasBackupUserConfigurationAction).payload
};
case ActionTypes.RESTORE_CONFIGURATION_FROM_BACKUP_SUCCESS:
return {
...state,
hasBackupUserConfiguration: false
};
default:
return state;
}
@@ -159,3 +204,11 @@ export const hasDevicePermission = (state: State) => state.hasPermission;
export const getSaveToKeyboardState = (state: State) => state.saveToKeyboard;
export const xtermLog = (state: State) => state.log;
export const firmwareOkButtonDisabled = (state: State) => !state.firmwareUpdateFinished;
export const getHardwareModules = (state: State) => state.modules;
export const getHasBackupUserConfiguration = (state: State) => state.hasBackupUserConfiguration;
export const getBackupUserConfigurationState = (state: State): RestoreConfigurationState => {
return {
restoringUserConfiguration: state.restoringUserConfiguration,
hasBackupUserConfiguration: state.hasBackupUserConfiguration
};
};

View File

@@ -494,5 +494,3 @@ function setKeyActionToLayer(newLayer: Layer, moduleIndex: number, keyIndex: num
newModule.keyActions = newModule.keyActions.slice();
newModule.keyActions[keyIndex] = newKeyAction;
}
export const getDeviceName = (state: UserConfiguration) => state.deviceName;

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

View File

@@ -115,3 +115,43 @@ a.disabled {
display: block;
}
}
a.link-inline {
cursor: pointer;
}
@mixin code-style() {
color: #6a737d;
background-color: #f6f8fa;
text-align: left;
}
code {
@include code-style();
}
pre {
code {
@include code-style();
}
}
.mt-10 {
margin-top: 10px;
}
.copy-container {
position: relative;
.fa-copy {
cursor: pointer;
color: #6a737d;
position: absolute;
right: 4px;
top: 4px;
&:hover {
color: darken(#6a737d, 15);
}
}
}

View File

@@ -1,10 +1,149 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="15px" height="15px" viewBox="0 0 15 15" enable-background="new 0 0 15 15" xml:space="preserve">
<path fill="#333333" d="M3,0C1.338,0,0,1.338,0,3v9c0,1.662,1.338,3,3,3h9c1.662,0,3-1.338,3-3V3c0-1.662-1.338-3-3-3H3z
M1.375,3.75c0.07-0.012,0.125,0,0.125,0H6c0.375,0,0.75,0.375,0.75,0.375S7.125,4.5,7.5,4.5s0.75-0.375,0.75-0.375
S8.625,3.75,9,3.75h4.5c0.75,0,0.75,0.75,0.75,0.75V6c0,2.25-1.5,2.25-1.5,2.25H9c-0.375,0-0.75-1.125-0.75-1.125S7.875,6,7.5,6
S6.75,7.125,6.75,7.125S6.375,8.25,6,8.25H2.25c0,0-1.5,0-1.5-2.25V4.5C0.75,3.938,1.164,3.785,1.375,3.75z"/>
</svg>
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:osb="http://www.openswatchbook.org/uri/2009/osb"
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
id="svg2"
version="1.1"
xml:space="preserve"
width="15"
height="15"
viewBox="0 0 15 15"><metadata
id="metadata20"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
id="defs6"><linearGradient
x1="0"
y1="0"
x2="1"
y2="0"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(12,0,0,-12,0,6)"
spreadMethod="pad"
id="linearGradient5644"><stop
style="stop-opacity:1;stop-color:#85878d"
offset="0"
id="stop5646" /><stop
style="stop-opacity:1;stop-color:#85878d"
offset="0.00137794"
id="stop5648" /><stop
style="stop-opacity:1;stop-color:#5d5f63"
offset="0.00137794"
id="stop5650" /><stop
style="stop-opacity:1;stop-color:#cccccc"
offset="0.52557927"
id="stop5652" /><stop
style="stop-opacity:1;stop-color:#5d5c62"
offset="1"
id="stop5654" /></linearGradient><linearGradient
x1="0"
y1="0"
x2="1"
y2="0"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(12,0,0,-12,0,6)"
spreadMethod="pad"
id="linearGradient4158-3"><stop
style="stop-opacity:1;stop-color:#85878d"
offset="0"
id="stop5634" /><stop
style="stop-opacity:1;stop-color:#85878d"
offset="0.00137794"
id="stop5636" /><stop
style="stop-opacity:1;stop-color:#85878d"
offset="0.00137794"
id="stop5638" /><stop
style="stop-opacity:1;stop-color:#cccccc"
offset="0.49464694"
id="stop5640" /><stop
style="stop-opacity:1;stop-color:#5d5c62"
offset="1"
id="stop5642" /></linearGradient><linearGradient
osb:paint="solid"
id="linearGradient5618"><stop
id="stop5620"
offset="0"
style="stop-color:#cccccc;stop-opacity:1;" /></linearGradient><linearGradient
id="linearGradient4158"
spreadMethod="pad"
gradientTransform="matrix(12,0,0,-12,0,6)"
gradientUnits="userSpaceOnUse"
y2="0"
x2="1"
y1="0"
x1="0"><stop
id="stop4160"
offset="0"
style="stop-opacity:1;stop-color:#85878d" /><stop
id="stop4162"
offset="0.00137794"
style="stop-opacity:1;stop-color:#85878d" /><stop
id="stop4164"
offset="0.00137794"
style="stop-opacity:1;stop-color:#85878d" /><stop
id="stop4166"
offset="0.49464694"
style="stop-opacity:1;stop-color:#cccccc" /><stop
id="stop4168"
offset="1"
style="stop-opacity:1;stop-color:#5d5c62" /></linearGradient><clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath18"><path
d="M 2.398,12 C 1.07,12 0,10.93 0,9.602 L 0,9.602 0,2.398 C 0,1.07 1.07,0 2.398,0 L 2.398,0 9.601,0 C 10.93,0 12,1.07 12,2.398 L 12,2.398 12,9.602 C 12,10.93 10.93,12 9.601,12 L 9.601,12 2.398,12 Z M 0.602,7.199 0.602,8.398 C 0.602,8.852 0.931,8.973 1.102,9 L 1.102,9 C 1.156,9.008 1.199,9 1.199,9 L 1.199,9 4.801,9 C 5.102,9 5.398,8.699 5.398,8.699 L 5.398,8.699 C 5.398,8.699 5.699,8.398 6,8.398 L 6,8.398 C 6.301,8.398 6.602,8.699 6.602,8.699 L 6.602,8.699 C 6.602,8.699 6.898,9 7.199,9 L 7.199,9 10.801,9 C 11.398,9 11.398,8.398 11.398,8.398 L 11.398,8.398 11.398,7.199 C 11.398,5.398 10.199,5.398 10.199,5.398 L 10.199,5.398 7.199,5.398 C 6.898,5.398 6.602,6.301 6.602,6.301 L 6.602,6.301 C 6.602,6.301 6.301,7.199 6,7.199 L 6,7.199 C 5.699,7.199 5.398,6.301 5.398,6.301 L 5.398,6.301 C 5.398,6.301 5.102,5.398 4.801,5.398 L 4.801,5.398 1.801,5.398 C 1.801,5.398 0.602,5.398 0.602,7.199"
id="path20" /></clipPath><linearGradient
x1="0"
y1="0"
x2="1"
y2="0"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(12,0,0,-12,0,6)"
spreadMethod="pad"
id="linearGradient26"><stop
style="stop-opacity:1;stop-color:#85878d"
offset="0"
id="stop28" /><stop
style="stop-opacity:1;stop-color:#85878d"
offset="0.00137794"
id="stop30" /><stop
style="stop-opacity:1;stop-color:#85878d"
offset="0.00137794"
id="stop32" /><stop
style="stop-opacity:1;stop-color:#b6b6b6"
offset="0.48571813"
id="stop34" /><stop
style="stop-opacity:1;stop-color:#5d5c62"
offset="1"
id="stop36" /></linearGradient><linearGradient
gradientUnits="userSpaceOnUse"
y2="0"
x2="11.745763"
y1="11.542373"
x1="0.91525424"
id="linearGradient4154"
xlink:href="#linearGradient4158" /><linearGradient
y2="-0.050847456"
x2="12"
y1="11.898305"
x1="0.10169491"
gradientUnits="userSpaceOnUse"
id="linearGradient4156"
xlink:href="#linearGradient5644" /></defs><g
id="g10"
transform="matrix(1.25,0,0,-1.25,0,15)"><path
d="M 11.473,5.117 0.416,5.117 0.416,9.383 11.473,9.383 11.473,5.117 Z"
style="fill:#343434;fill-opacity:1;fill-rule:nonzero;stroke:none"
id="path12" /><g
style="fill:url(#linearGradient4154);fill-opacity:1.0"
clip-path="url(#clipPath18)"
id="g16"><g
style="fill:url(#linearGradient4154);fill-opacity:1.0"
id="g22"><g
style="fill:url(#linearGradient4154);fill-opacity:1.0"
id="g24"><path
id="path38"
style="fill:url(#linearGradient4156);stroke:none;fill-opacity:1;stroke-opacity:1"
d="M 2.398,12 C 1.07,12 0,10.93 0,9.602 L 0,9.602 0,2.398 C 0,1.07 1.07,0 2.398,0 L 2.398,0 9.601,0 C 10.93,0 12,1.07 12,2.398 L 12,2.398 12,9.602 C 12,10.93 10.93,12 9.601,12 L 9.601,12 2.398,12 Z M 0.602,7.199 0.602,8.398 C 0.602,8.852 0.931,8.973 1.102,9 L 1.102,9 C 1.156,9.008 1.199,9 1.199,9 L 1.199,9 4.801,9 C 5.102,9 5.398,8.699 5.398,8.699 L 5.398,8.699 C 5.398,8.699 5.699,8.398 6,8.398 L 6,8.398 C 6.301,8.398 6.602,8.699 6.602,8.699 L 6.602,8.699 C 6.602,8.699 6.898,9 7.199,9 L 7.199,9 10.801,9 C 11.398,9 11.398,8.398 11.398,8.398 L 11.398,8.398 11.398,7.199 C 11.398,5.398 10.199,5.398 10.199,5.398 L 10.199,5.398 7.199,5.398 C 6.898,5.398 6.602,6.301 6.602,6.301 L 6.602,6.301 C 6.602,6.301 6.301,7.199 6,7.199 L 6,7.199 C 5.699,7.199 5.398,6.301 5.398,6.301 L 5.398,6.301 C 5.398,6.301 5.102,5.398 4.801,5.398 L 4.801,5.398 1.801,5.398 C 1.801,5.398 0.602,5.398 0.602,7.199" /></g></g></g></g></svg>

Before

Width:  |  Height:  |  Size: 935 B

After

Width:  |  Height:  |  Size: 6.9 KiB

View File

@@ -1,4 +1,149 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="-5 -5 9.9999999 10" height="15" width="15">
<path style="fill:#333" d="M -3,-5 C -4.108,-5 -5,-4.108 -5,-3 L -5,3 C -5,4.108 -4.108,5 -3,5 L 3,5 C 4.108,5 5,4.108 5,3 L 5,-3 C 5,-4.108 4.108,-5 3,-5 L -3,-5 Z M -4.0833333,-2.5 C -4.0364583,-2.5078125 -4,-2.5 -4,-2.5 L -0.99999998,-2.5 C -0.74999998,-2.5 -0.49999998,-2.25 -0.49999998,-2.25 -0.49999998,-2.25 -0.24999998,-2 2.4999999e-8,-2 0.25000003,-2 0.50000003,-2.25 0.50000003,-2.25 0.50000003,-2.25 0.75000003,-2.5 1,-2.5 L 4,-2.5 C 4.5,-2.5 4.5,-2 4.5,-2 L 4.5,-0.99999998 C 4.5,0.50000003 3.5,0.50000003 3.5,0.50000003 L 1,0.50000003 C 0.75000003,0.50000003 0.50000003,-0.24999998 0.50000003,-0.24999998 0.50000003,-0.24999998 0.25000003,-0.99999998 2.4999999e-8,-0.99999998 -0.24999998,-0.99999998 -0.49999998,-0.24999998 -0.49999998,-0.24999998 -0.49999998,-0.24999998 -0.74999998,0.50000003 -0.99999998,0.50000003 L -3.5,0.50000003 C -3.5,0.50000003 -4.5,0.50000003 -4.5,-0.99999998 L -4.5,-2 C -4.5,-2.375 -4.2239583,-2.4765625 -4.0833333,-2.5 Z" />
</svg>
<svg
xmlns:osb="http://www.openswatchbook.org/uri/2009/osb"
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
id="svg2"
version="1.1"
xml:space="preserve"
width="15"
height="15"
viewBox="0 0 15 15"><metadata
id="metadata20"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
id="defs6"><linearGradient
x1="0"
y1="0"
x2="1"
y2="0"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(12,0,0,-12,0,6)"
spreadMethod="pad"
id="linearGradient5644"><stop
style="stop-opacity:1;stop-color:#85878d"
offset="0"
id="stop5646" /><stop
style="stop-opacity:1;stop-color:#85878d"
offset="0.00137794"
id="stop5648" /><stop
style="stop-opacity:1;stop-color:#5d5f63"
offset="0.00137794"
id="stop5650" /><stop
style="stop-opacity:1;stop-color:#cccccc"
offset="0.52557927"
id="stop5652" /><stop
style="stop-opacity:1;stop-color:#5d5c62"
offset="1"
id="stop5654" /></linearGradient><linearGradient
x1="0"
y1="0"
x2="1"
y2="0"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(12,0,0,-12,0,6)"
spreadMethod="pad"
id="linearGradient4158-3"><stop
style="stop-opacity:1;stop-color:#85878d"
offset="0"
id="stop5634" /><stop
style="stop-opacity:1;stop-color:#85878d"
offset="0.00137794"
id="stop5636" /><stop
style="stop-opacity:1;stop-color:#85878d"
offset="0.00137794"
id="stop5638" /><stop
style="stop-opacity:1;stop-color:#cccccc"
offset="0.49464694"
id="stop5640" /><stop
style="stop-opacity:1;stop-color:#5d5c62"
offset="1"
id="stop5642" /></linearGradient><linearGradient
osb:paint="solid"
id="linearGradient5618"><stop
id="stop5620"
offset="0"
style="stop-color:#cccccc;stop-opacity:1;" /></linearGradient><linearGradient
id="linearGradient4158"
spreadMethod="pad"
gradientTransform="matrix(12,0,0,-12,0,6)"
gradientUnits="userSpaceOnUse"
y2="0"
x2="1"
y1="0"
x1="0"><stop
id="stop4160"
offset="0"
style="stop-opacity:1;stop-color:#85878d" /><stop
id="stop4162"
offset="0.00137794"
style="stop-opacity:1;stop-color:#85878d" /><stop
id="stop4164"
offset="0.00137794"
style="stop-opacity:1;stop-color:#85878d" /><stop
id="stop4166"
offset="0.49464694"
style="stop-opacity:1;stop-color:#cccccc" /><stop
id="stop4168"
offset="1"
style="stop-opacity:1;stop-color:#5d5c62" /></linearGradient><clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath18"><path
d="M 2.398,12 C 1.07,12 0,10.93 0,9.602 L 0,9.602 0,2.398 C 0,1.07 1.07,0 2.398,0 L 2.398,0 9.601,0 C 10.93,0 12,1.07 12,2.398 L 12,2.398 12,9.602 C 12,10.93 10.93,12 9.601,12 L 9.601,12 2.398,12 Z M 0.602,7.199 0.602,8.398 C 0.602,8.852 0.931,8.973 1.102,9 L 1.102,9 C 1.156,9.008 1.199,9 1.199,9 L 1.199,9 4.801,9 C 5.102,9 5.398,8.699 5.398,8.699 L 5.398,8.699 C 5.398,8.699 5.699,8.398 6,8.398 L 6,8.398 C 6.301,8.398 6.602,8.699 6.602,8.699 L 6.602,8.699 C 6.602,8.699 6.898,9 7.199,9 L 7.199,9 10.801,9 C 11.398,9 11.398,8.398 11.398,8.398 L 11.398,8.398 11.398,7.199 C 11.398,5.398 10.199,5.398 10.199,5.398 L 10.199,5.398 7.199,5.398 C 6.898,5.398 6.602,6.301 6.602,6.301 L 6.602,6.301 C 6.602,6.301 6.301,7.199 6,7.199 L 6,7.199 C 5.699,7.199 5.398,6.301 5.398,6.301 L 5.398,6.301 C 5.398,6.301 5.102,5.398 4.801,5.398 L 4.801,5.398 1.801,5.398 C 1.801,5.398 0.602,5.398 0.602,7.199"
id="path20" /></clipPath><linearGradient
x1="0"
y1="0"
x2="1"
y2="0"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(12,0,0,-12,0,6)"
spreadMethod="pad"
id="linearGradient26"><stop
style="stop-opacity:1;stop-color:#85878d"
offset="0"
id="stop28" /><stop
style="stop-opacity:1;stop-color:#85878d"
offset="0.00137794"
id="stop30" /><stop
style="stop-opacity:1;stop-color:#85878d"
offset="0.00137794"
id="stop32" /><stop
style="stop-opacity:1;stop-color:#b6b6b6"
offset="0.48571813"
id="stop34" /><stop
style="stop-opacity:1;stop-color:#5d5c62"
offset="1"
id="stop36" /></linearGradient><linearGradient
gradientUnits="userSpaceOnUse"
y2="0"
x2="11.745763"
y1="11.542373"
x1="0.91525424"
id="linearGradient4154"
xlink:href="#linearGradient4158" /><linearGradient
y2="-0.050847456"
x2="12"
y1="11.898305"
x1="0.10169491"
gradientUnits="userSpaceOnUse"
id="linearGradient4156"
xlink:href="#linearGradient5644" /></defs><g
id="g10"
transform="matrix(1.25,0,0,-1.25,0,15)"><path
d="M 11.473,5.117 0.416,5.117 0.416,9.383 11.473,9.383 11.473,5.117 Z"
style="fill:#343434;fill-opacity:1;fill-rule:nonzero;stroke:none"
id="path12" /><g
style="fill:url(#linearGradient4154);fill-opacity:1.0"
clip-path="url(#clipPath18)"
id="g16"><g
style="fill:url(#linearGradient4154);fill-opacity:1.0"
id="g22"><g
style="fill:url(#linearGradient4154);fill-opacity:1.0"
id="g24"><path
id="path38"
style="fill:url(#linearGradient4156);stroke:none;fill-opacity:1;stroke-opacity:1"
d="M 2.398,12 C 1.07,12 0,10.93 0,9.602 L 0,9.602 0,2.398 C 0,1.07 1.07,0 2.398,0 L 2.398,0 9.601,0 C 10.93,0 12,1.07 12,2.398 L 12,2.398 12,9.602 C 12,10.93 10.93,12 9.601,12 L 9.601,12 2.398,12 Z M 0.602,7.199 0.602,8.398 C 0.602,8.852 0.931,8.973 1.102,9 L 1.102,9 C 1.156,9.008 1.199,9 1.199,9 L 1.199,9 4.801,9 C 5.102,9 5.398,8.699 5.398,8.699 L 5.398,8.699 C 5.398,8.699 5.699,8.398 6,8.398 L 6,8.398 C 6.301,8.398 6.602,8.699 6.602,8.699 L 6.602,8.699 C 6.602,8.699 6.898,9 7.199,9 L 7.199,9 10.801,9 C 11.398,9 11.398,8.398 11.398,8.398 L 11.398,8.398 11.398,7.199 C 11.398,5.398 10.199,5.398 10.199,5.398 L 10.199,5.398 7.199,5.398 C 6.898,5.398 6.602,6.301 6.602,6.301 L 6.602,6.301 C 6.602,6.301 6.301,7.199 6,7.199 L 6,7.199 C 5.699,7.199 5.398,6.301 5.398,6.301 L 5.398,6.301 C 5.398,6.301 5.102,5.398 4.801,5.398 L 4.801,5.398 1.801,5.398 C 1.801,5.398 0.602,5.398 0.602,7.199" /></g></g></g></g></svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 6.9 KiB

View File

@@ -1,8 +1,7 @@
#!/usr/bin/env node
const uhk = require('./uhk');
(async function() {
const device = uhk.getUhkDevice();
const sendData = new Buffer([uhk.usbCommands.applyConfig]);
device.write(uhk.getTransferData(sendData));
const response = Buffer.from(device.readSync());
console.log(response);
uhk.applyConfig(device);
})();

View File

@@ -9,23 +9,5 @@ if (eepromTransfer === undefined) {
process.exit(1);
}
const device = uhk.getUhkDevice();
device.write(uhk.getTransferData(new Buffer([uhk.usbCommands.launchEepromTransfer, eepromTransfer.operation, eepromTransfer.configBuffer])));
const buffer = Buffer.from(device.readSync());
const responseCode = buffer[0];
if (responseCode !== 0) {
console.error(`Write user config to eeprom failed. Response code: ${responseCode}`);
process.exit(1);
}
// const buffer = await uhk.writeDevice(device, [uhk.usbCommands.launchEepromTransfer, eepromTransfer.operation, eepromTransfer.configBuffer]);
function waitUntilKeyboardBusy() {
device.write(uhk.getTransferData(new Buffer([uhk.usbCommands.getDeviceState])));
const keyboardStateBuffer = Buffer.from(device.readSync());
if (keyboardStateBuffer[1] === 1) {
setTimeout(waitUntilKeyboardBusy, 200);
}
}
waitUntilKeyboardBusy();

40
packages/usb/factory-update.js Executable file
View File

@@ -0,0 +1,40 @@
#!/usr/bin/env node
const fs = require('fs');
const program = require('commander');
const uhk = require('./uhk')
require('shelljs/global');
(async function() {
config.fatal = true;
program
.usage(`firmwarePath`)
.parse(process.argv);
if (program.args.length < 1) {
console.error('No firmware path specified.');
exit(1);
}
const firmwarePath = program.args[0];
if (program.args.length < 2) {
console.error('No layout specified.');
exit(1);
}
const layout = program.args[1];
if (!['ansi', 'iso'].includes(layout)) {
console.error('The specified layout is neither ansi nor iso.');
exit(1);
}
const isIso = layout === 'iso';
config.verbose = true;
await uhk.updateFirmwares(firmwarePath);
const device = uhk.getUhkDevice();
const configBuffer = fs.readFileSync(`${firmwarePath}/devices/uhk60-right/config.bin`);
await uhk.writeUca(device, configBuffer);
await uhk.writeHca(device, isIso);
await uhk.switchKeymap(device, 'TES');
console.log('All done!')
config.verbose = false;
})();

View File

@@ -1,14 +1,7 @@
#!/usr/bin/env node
const uhk = require('./uhk');
(async function() {
const device = uhk.getUhkDevice();
function getModuleState() {
const payload = new Buffer([uhk.usbCommands.getModuleProperty, 1]);
console.log('Sending ', uhk.bufferToString(payload));
device.write(uhk.getTransferData(payload));
const receivedBuffer = device.readSync();
console.log('Received', uhk.bufferToString(receivedBuffer));
setTimeout(getModuleState, 500)
}
getModuleState();
await uhk.getModuleProperty(device, 1, uhk.modulePropertyIds.protocolVersions);
})();

View File

@@ -0,0 +1 @@
@#%#@^^@#^@#$invalid config@#$@^%@^@@%

View File

@@ -149,6 +149,14 @@
"strip-dirs": "2.1.0"
}
},
"decompress-response": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz",
"integrity": "sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M=",
"requires": {
"mimic-response": "1.0.0"
}
},
"decompress-tar": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/decompress-tar/-/decompress-tar-4.1.1.tgz",
@@ -216,6 +224,11 @@
"resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
"integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o="
},
"detect-libc": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz",
"integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups="
},
"end-of-stream": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.0.tgz",
@@ -339,9 +352,9 @@
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
},
"ini": {
"version": "1.3.4",
"resolved": "https://registry.npmjs.org/ini/-/ini-1.3.4.tgz",
"integrity": "sha1-BTfLedr1m1mhpRff9wbIbsA5Fi4="
"version": "1.3.5",
"resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz",
"integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw=="
},
"interpret": {
"version": "1.0.4",
@@ -386,6 +399,11 @@
}
}
},
"mimic-response": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.0.tgz",
"integrity": "sha1-3z02Uqc/3ta5sLJBRub9BSNTRY4="
},
"minimatch": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
@@ -415,14 +433,17 @@
}
},
"nan": {
"version": "2.6.2",
"resolved": "https://registry.npmjs.org/nan/-/nan-2.6.2.tgz",
"integrity": "sha1-5P805slf37WuzAjeZZb0NgWn20U="
"version": "2.10.0",
"resolved": "https://registry.npmjs.org/nan/-/nan-2.10.0.tgz",
"integrity": "sha512-bAdJv7fBLhWC+/Bls0Oza+mvTaNQtP+1RyhhhvD95pgUJz6XM5IzgmxOkItJ9tkoCiplvAnXI1tNmmUD/eScyA=="
},
"node-abi": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/node-abi/-/node-abi-2.1.1.tgz",
"integrity": "sha512-6oxV13poCOv7TfGvhsSz6XZWpXeKkdGVh72++cs33OfMh3KAX8lN84dCvmqSETyDXAFcUHtV7eJrgFBoOqZbNQ=="
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/node-abi/-/node-abi-2.3.0.tgz",
"integrity": "sha512-zwm6vU3SsVgw3e9fu48JBaRBCJGIvAgysDsqtf5+vEexFE71bEOtaMWb5zr/zODZNzTPtQlqUUpC79k68Hspow==",
"requires": {
"semver": "5.5.0"
}
},
"node-hid": {
"version": "0.5.7",
@@ -430,8 +451,8 @@
"integrity": "sha512-dwwpOetL2+MGYgivbO22ML+45ieCGbueWv1rYxRgBoEc2QMp6UF6ZucEkYts1IA3YPWJNkmpGh6dqQ85n19szw==",
"requires": {
"bindings": "1.3.0",
"nan": "2.6.2",
"prebuild-install": "2.3.0"
"nan": "2.10.0",
"prebuild-install": "2.5.1"
}
},
"noop-logger": {
@@ -512,24 +533,25 @@
}
},
"prebuild-install": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-2.3.0.tgz",
"integrity": "sha512-gzjq2oHB8oMbzJSsSh9MQ64zrXZGt092/uT4TLZlz2qnrPxpWqp4vYB7LZrDxnlxf5RfbCjkgDI/z0EIVuYzAw==",
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-2.5.1.tgz",
"integrity": "sha512-3DX9L6pzwc1m1ksMkW3Ky2WLgPQUBiySOfXVl3WZyAeJSyJb4wtoH9OmeRGcubAWsMlLiL8BTHbwfm/jPQE9Ag==",
"requires": {
"detect-libc": "1.0.3",
"expand-template": "1.1.0",
"github-from-package": "0.0.0",
"minimist": "1.2.0",
"mkdirp": "0.5.1",
"node-abi": "2.1.1",
"node-abi": "2.3.0",
"noop-logger": "0.1.1",
"npmlog": "4.1.2",
"os-homedir": "1.0.2",
"pump": "1.0.2",
"rc": "1.2.2",
"simple-get": "1.4.3",
"pump": "2.0.1",
"rc": "1.2.6",
"simple-get": "2.7.0",
"tar-fs": "1.16.0",
"tunnel-agent": "0.6.0",
"xtend": "4.0.1"
"which-pm-runs": "1.0.0"
}
},
"process-nextick-args": {
@@ -538,21 +560,21 @@
"integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M="
},
"pump": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/pump/-/pump-1.0.2.tgz",
"integrity": "sha1-Oz7mUS+U8OV1U4wXmV+fFpkKXVE=",
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz",
"integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==",
"requires": {
"end-of-stream": "1.4.0",
"once": "1.4.0"
}
},
"rc": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/rc/-/rc-1.2.2.tgz",
"integrity": "sha1-2M6ctX6NZNnHut2YdsfDTL48cHc=",
"version": "1.2.6",
"resolved": "https://registry.npmjs.org/rc/-/rc-1.2.6.tgz",
"integrity": "sha1-6xiYnG1PTxYsOZ953dKfODVWgJI=",
"requires": {
"deep-extend": "0.4.2",
"ini": "1.3.4",
"ini": "1.3.5",
"minimist": "1.2.0",
"strip-json-comments": "2.0.1"
}
@@ -610,6 +632,11 @@
}
}
},
"semver": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz",
"integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA=="
},
"set-blocking": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
@@ -640,14 +667,19 @@
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz",
"integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0="
},
"simple-concat": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.0.tgz",
"integrity": "sha1-c0TLuLbib7J9ZrL8hvn21Zl1IcY="
},
"simple-get": {
"version": "1.4.3",
"resolved": "https://registry.npmjs.org/simple-get/-/simple-get-1.4.3.tgz",
"integrity": "sha1-6XVe2kB+ltpAxeUVjJ6jezO+y+s=",
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/simple-get/-/simple-get-2.7.0.tgz",
"integrity": "sha512-RkE9rGPHcxYZ/baYmgJtOSM63vH0Vyq+ma5TijBcLla41SWlh8t6XYIGMR/oeZcmr+/G8k+zrClkkVrtnQ0esg==",
"requires": {
"decompress-response": "3.3.0",
"once": "1.4.0",
"unzip-response": "1.0.2",
"xtend": "4.0.1"
"simple-concat": "1.0.0"
}
},
"string-width": {
@@ -704,8 +736,19 @@
"requires": {
"chownr": "1.0.1",
"mkdirp": "0.5.1",
"pump": "1.0.2",
"pump": "1.0.3",
"tar-stream": "1.5.4"
},
"dependencies": {
"pump": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/pump/-/pump-1.0.3.tgz",
"integrity": "sha512-8k0JupWme55+9tCVE+FS5ULT3K6AbgqrGa58lTT49RpyfwwcGedHqaC5LlQNdEAumn/wFsu6aPwkuPMioy8kqw==",
"requires": {
"end-of-stream": "1.4.0",
"once": "1.4.0"
}
}
}
},
"tar-stream": {
@@ -754,16 +797,16 @@
"through": "2.3.8"
}
},
"unzip-response": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/unzip-response/-/unzip-response-1.0.2.tgz",
"integrity": "sha1-uYTwh3/AqJwsdzzB73tbIytbBv4="
},
"util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
"integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
},
"which-pm-runs": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/which-pm-runs/-/which-pm-runs-1.0.0.tgz",
"integrity": "sha1-Zws6+8VS4LVd9rd4DKdGFfI60cs="
},
"wide-align": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.2.tgz",

15
packages/usb/switch-keymap.js Executable file
View File

@@ -0,0 +1,15 @@
#!/usr/bin/env node
const uhk = require('./uhk');
(async function() {
const keymapAbbreviation = process.argv[2];
if (keymapAbbreviation === undefined) {
console.log('Usage: switch-keymap.js keymapName');
return;
}
const device = uhk.getUhkDevice();
const sendData = await uhk.switchKeymap(device, keymapAbbreviation);
console.log(sendData)
})();

View File

@@ -1,7 +1,24 @@
const util = require('util');
const HID = require('node-hid');
// const debug = process.env.DEBUG;
const debug = true;
const {HardwareConfiguration, UhkBuffer} = require('uhk-common');
const {getTransferBuffers, ConfigBufferId, UhkHidDevice, UsbCommand} = require('uhk-usb');
const Logger = require('./logger');
const debug = process.env.DEBUG;
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
const kbootCommandIdToName = {
0: 'idle',
1: 'ping',
2: 'reset',
};
const eepromOperationIdToName = {
0: 'read',
1: 'write',
}
function bufferToString(buffer) {
let str = '';
@@ -28,11 +45,27 @@ function uint32ToArray(value) {
}
function writeDevice(device, data, options={}) {
device.write(getTransferData(new Buffer(data)));
const dataBuffer = new Buffer(data);
if (!options.noDebug) {
writeLog('W: ', dataBuffer);
}
device.write(getTransferData(dataBuffer));
if (options.noRead) {
return Promise.resolve();
}
return util.promisify(device.read.bind(device))();
return new Promise((resolve, reject) => {
device.read((err, data) => {
if (err) {
reject(err);
} else {
if (!options.noDebug) {
writeLog('R: ', data);
}
resolve(data);
}
});
});
}
function getUhkDevice() {
@@ -119,12 +152,18 @@ function execRetry(command) {
} while(code && --remainingRetries);
}
let configBufferIds = {
const configBufferIds = {
hardwareConfig: 0,
stagingUserConfig: 1,
validatedUserConfig: 2,
};
const configBufferIdToName = {
0: 'hardwareConfig',
1: 'stagingUserConfig',
2: 'validatedUserConfig',
}
let eepromOperations = {
read: 0,
write: 1,
@@ -199,14 +238,24 @@ function reenumerate(enumerationMode) {
};
async function sendKbootCommandToModule(device, kbootCommandId, i2cAddress) {
writeLog(`T: sendKbootCommandToModule kbootCommandId:${kbootCommandIdToName[kbootCommandId]} i2cAddress:${i2cAddress}`);
return await uhk.writeDevice(device, [uhk.usbCommands.sendKbootCommandToModule, kbootCommandId, parseInt(i2cAddress)])
};
async function jumpToModuleBootloader(device, moduleSlotId) {
writeLog(`T: jumpToModuleBootloader moduleSlotId:${moduleSlotId}`);
await uhk.writeDevice(device, [uhk.usbCommands.jumpToModuleBootloader, moduleSlotId]);
};
async function switchKeymap(device, keymapAbbreviation) {
writeLog(`T: switchKeymap keymapAbbreviation:${keymapAbbreviation}`);
const keymapAbbreviationAscii = keymapAbbreviation.split('').map(char => char.charCodeAt(0));
const payload = [uhk.usbCommands.switchKeymap, keymapAbbreviation.length, ...keymapAbbreviationAscii];
return await uhk.writeDevice(device, payload);
}
async function waitForKbootIdle(device) {
writeLog(`T: waitForKbootIdle`);
const intervalMs = 100;
const pingMessageInterval = 500;
let timeoutMs = 10000;
@@ -256,12 +305,98 @@ async function updateModuleFirmware(i2cAddress, moduleSlotId, firmwareImage) {
await uhk.reenumerate('normalKeyboard');
device = uhk.getUhkDevice();
await uhk.sendKbootCommandToModule(device, uhk.kbootCommands.reset, i2cAddress);
await sleep(1000);
await uhk.sendKbootCommandToModule(device, uhk.kbootCommands.idle, i2cAddress);
device.close();
config.verbose = false;
echo('Firmware updated successfully.');
};
async function updateFirmwares(firmwarePath) {
console.log('Updating right firmware');
await uhk.updateDeviceFirmware(`${firmwarePath}/devices/uhk60-right/firmware.hex`, 'hex');
await uhk.reenumerate('normalKeyboard');
console.log('Updating left firmware');
await uhk.updateModuleFirmware(uhk.moduleSlotToI2cAddress.leftHalf, uhk.moduleSlotToId.leftHalf, `${firmwarePath}/modules/uhk60-left.bin`);
}
async function writeConfig(device, configBuffer, isHardwareConfig) {
writeLog(`T: writeConfig isHardwareConfig:${isHardwareConfig}`);
const chunkSize = 60;
let offset = 0;
let chunkSizeToRead;
let buffer = await uhk.writeDevice(device, [uhk.usbCommands.getDeviceProperty, uhk.devicePropertyIds.configSizes]);
const hardwareConfigMaxSize = getUint16(buffer, 1);
const userConfigMaxSize = getUint16(buffer, 3);
const configMaxSize = isHardwareConfig ? hardwareConfigMaxSize : userConfigMaxSize;
const configSize = Math.min(configMaxSize, configBuffer.length);
writeLog('WR: ...');
while (offset < configSize) {
const usbCommand = isHardwareConfig ? uhk.usbCommands.writeHardwareConfig : uhk.usbCommands.writeStagingUserConfig;
chunkSizeToRead = Math.min(chunkSize, configSize - offset);
if (chunkSizeToRead === 0) {
break;
}
buffer = [
usbCommand, chunkSizeToRead, offset & 0xff, offset >> 8,
...configBuffer.slice(offset, offset+chunkSizeToRead)
];
await uhk.writeDevice(device, buffer, {noDebug:true})
offset += chunkSizeToRead;
}
}
async function applyConfig(device) {
writeLog(`T: applyConfig`);
await uhk.writeDevice(device, [uhk.usbCommands.applyConfig]);
}
async function launchEepromTransfer(device, operation, configBufferId) {
writeLog(`T: launchEepromTransfer operation:${eepromOperationIdToName[operation]}`);
const buffer = await uhk.writeDevice(device, [uhk.usbCommands.launchEepromTransfer, operation, configBufferId]);
isBusy = true;
writeLog(`T: getDeviceState`);
do {
const buffer = await uhk.writeDevice(device, [uhk.usbCommands.getDeviceState]);
isBusy = buffer[1] === 1;
} while (isBusy);
};
async function writeUca(device, configBuffer) {
await uhk.writeConfig(device, configBuffer, false);
await uhk.applyConfig(device);
await uhk.launchEepromTransfer(device, uhk.eepromOperations.write, configBufferIds.validatedUserConfig);
}
async function writeHca(device, isIso) {
const hardwareConfig = new HardwareConfiguration();
hardwareConfig.signature = 'UHK';
hardwareConfig.majorVersion = 1;
hardwareConfig.minorVersion = 0;
hardwareConfig.patchVersion = 0;
hardwareConfig.brandId = 0;
hardwareConfig.deviceId = 1;
hardwareConfig.uniqueId = Math.floor(2**32 * Math.random());
hardwareConfig.isVendorModeOn = false;
hardwareConfig.isIso = isIso;
const logger = new Logger();
const hardwareBuffer = new UhkBuffer();
hardwareConfig.toBinary(hardwareBuffer);
const buffer = hardwareBuffer.getBufferContent();
await uhk.writeConfig(device, buffer, true);
await uhk.launchEepromTransfer(device, uhk.eepromOperations.write, configBufferIds.hardwareConfig);
}
async function getModuleProperty(device, slotId, moduleProperty) {
await writeDevice(device, [uhk.usbCommands.getModuleProperty, slotId, moduleProperty]);
}
uhk = exports = module.exports = moduleExports = {
bufferToString,
getUint16,
@@ -279,8 +414,16 @@ uhk = exports = module.exports = moduleExports = {
reenumerate,
sendKbootCommandToModule,
jumpToModuleBootloader,
switchKeymap,
waitForKbootIdle,
updateModuleFirmware,
updateFirmwares,
writeConfig,
applyConfig,
launchEepromTransfer,
writeUca,
writeHca,
getModuleProperty,
usbCommands: {
getDeviceProperty : 0x00,
reenumerate : 0x01,
@@ -299,6 +442,7 @@ uhk = exports = module.exports = moduleExports = {
getModuleProperty : 0x0e,
getSlaveI2cErrors : 0x0f,
setI2cBaudRate : 0x10,
switchKeymap : 0x11,
},
enumerationModes: {
bootloader: 0,
@@ -332,24 +476,6 @@ uhk = exports = module.exports = moduleExports = {
},
configBufferIds,
eepromOperations,
eepromTransfer: {
readHardwareConfig: {
operation: eepromOperations.read,
configBuffer: configBufferIds.hardwareConfig,
},
writeHardwareConfig: {
operation: eepromOperations.write,
configBuffer:configBufferIds.hardwareConfig,
},
readUserConfig: {
operation: eepromOperations.read,
configBuffer: configBufferIds.validatedUserConfig,
},
writeUserConfig: {
operation: eepromOperations.write,
configBuffer: configBufferIds.validatedUserConfig,
},
},
kbootCommands: {
idle: 0,
ping: 1,
@@ -395,7 +521,11 @@ function writeLog(prefix, buffer) {
if (!debug) {
return;
}
if (buffer) {
console.log(prefix + bufferToString(buffer))
} else {
console.log(prefix);
}
}
function checkModuleSlot(moduleSlot, mapping) {

View File

@@ -18,7 +18,7 @@ const firmwarePath = program.args[0];
const layout = program.args[1];
config.verbose = true;
exec(`${__dirname}/update-all-firmwares.js --overwrite-user-config ${firmwarePath}`);
exec(`${__dirname}/update-firmwares.js --overwrite-user-config ${firmwarePath}`);
exec(`${__dirname}/write-hca.js ${layout}`);
config.verbose = false;

View File

@@ -1,4 +1,5 @@
#!/usr/bin/env node
const fs = require('fs');
const program = require('commander');
const tmp = require('tmp');
const decompress = require('decompress');
@@ -29,16 +30,14 @@ require('shelljs/global');
firmwarePath = tmpObj.name;
}
config.verbose = true;
console.log('Updating right firmware');
await uhk.updateDeviceFirmware(`${firmwarePath}/devices/uhk60-right/firmware.hex`, 'hex');
await uhk.reenumerate('normalKeyboard');
console.log('Updating left firmware');
await uhk.updateModuleFirmware(uhk.moduleSlotToI2cAddress.leftHalf, uhk.moduleSlotToId.leftHalf, `${firmwarePath}/modules/uhk60-left.bin`);
await uhk.updateFirmwares(firmwarePath);
if (program.overwriteUserConfig) {
exec(`${__dirname}/write-config.js ${firmwarePath}/devices/uhk60-right/config.bin`);
exec(`${__dirname}/apply-config.js`);
exec(`${__dirname}/eeprom.js writeUserConfig`);
const device = uhk.getUhkDevice();
const configBuffer = fs.readFileSync(`${firmwarePath}/devices/uhk60-right/config.bin`);
await uhk.writeConfig(device, configBuffer, false);
await uhk.applyConfig(device);
await uhk.launchEepromTransfer(device, uhk.eepromOperations.write, uhk.eepromTransfer.writeUserConfig);
}
config.verbose = false;

View File

@@ -2,6 +2,8 @@
const program = require('commander');
const fs = require('fs');
const uhk = require('./uhk');
(async function() {
const device = uhk.getUhkDevice();
require('shelljs/global');
@@ -16,36 +18,9 @@ if (program.args.length == 0) {
}
const configBin = program.args[0];
const chunkSize = 60;
const isHardwareConfig = program.hardwareConfig;
const configTypeString = isHardwareConfig ? 'hardware' : 'user';
let offset = 0;
let configBuffer = fs.readFileSync(configBin);
let chunkSizeToRead;
const configBuffer = fs.readFileSync(configBin);
const payload = new Buffer([uhk.usbCommands.getDeviceProperty, uhk.devicePropertyIds.configSizes]);
device.write(uhk.getTransferData(payload));
let buffer = Buffer.from(device.readSync());
const hardwareConfigMaxSize = buffer[1] + (buffer[2]<<8);
const userConfigMaxSize = buffer[3] + (buffer[4]<<8);
const configMaxSize = isHardwareConfig ? hardwareConfigMaxSize : userConfigMaxSize;
const configSize = Math.min(configMaxSize, configBuffer.length);
while (offset < configSize) {
const usbCommand = isHardwareConfig ? uhk.usbCommands.writeHardwareConfig : uhk.usbCommands.writeStagingUserConfig;
chunkSizeToRead = Math.min(chunkSize, configSize - offset);
if (chunkSizeToRead === 0) {
break;
}
buffer = Buffer.concat([
new Buffer([usbCommand, chunkSizeToRead, offset & 0xff, offset >> 8]),
configBuffer.slice(offset, offset+chunkSizeToRead)
]);
device.write(uhk.getTransferData(buffer));
device.readSync();
offset += chunkSizeToRead;
}
await uhk.writeUserConfig(device, configBuffer, isHardwareConfig);
})();

View File

@@ -1,7 +1,5 @@
#!/usr/bin/env node
const {HardwareConfiguration, UhkBuffer} = require('uhk-common');
const {EepromTransfer, getTransferBuffers, ConfigBufferId, UhkHidDevice, UsbCommand} = require('uhk-usb');
const Logger = require('./logger');
const uhk = require('./uhk');
if (process.argv.length < 2) {
console.log(`use: write-hca {iso|ansi}`);
@@ -14,36 +12,7 @@ if (layout !== 'iso' && layout !== 'ansi') {
process.exit(1);
}
const hardwareConfig = new HardwareConfiguration();
hardwareConfig.signature = 'UHK';
hardwareConfig.majorVersion = 1;
hardwareConfig.minorVersion = 0;
hardwareConfig.patchVersion = 0;
hardwareConfig.brandId = 0;
hardwareConfig.deviceId = 1;
hardwareConfig.uniqueId = Math.floor(2**32 * Math.random());
hardwareConfig.isVendorModeOn = false;
hardwareConfig.isIso = layout === 'iso';
const logger = new Logger();
async function writeHca() {
const device = new UhkHidDevice(logger);
const hardwareBuffer = new UhkBuffer();
hardwareConfig.toBinary(hardwareBuffer);
const buffer = hardwareBuffer.getBufferContent();
const fragments = getTransferBuffers(UsbCommand.WriteHardwareConfig, buffer);
logger.debug('USB[T]: Write hardware configuration to keyboard');
for (const fragment of fragments) {
await device.write(fragment);
}
logger.debug('USB[T]: Write hardware configuration to EEPROM');
await device.writeConfigToEeprom(ConfigBufferId.hardwareConfig);
}
writeHca()
uhk.writeHca(layout === 'iso')
.catch((err)=>{
console.error(err);
});

View File

@@ -0,0 +1,21 @@
#!/usr/bin/env node
const fs = require('fs');
const program = require('commander');
const uhk = require('./uhk');
(async function() {
program
.usage(`configPath`)
.parse(process.argv);
if (program.args.length < 1) {
console.error('No configPath path specified.');
exit(1);
}
const configPath = program.args[0];
const device = uhk.getUhkDevice();
const configBuffer = fs.readFileSync(configPath);
await uhk.writeConfig(device, configBuffer, false);
await uhk.launchEepromTransfer(device, uhk.eepromOperations.write, uhk.configBufferIds.stagingUserConfig);
})();

View File

@@ -1,31 +0,0 @@
#!/usr/bin/env ts-node
///<reference path="./node_modules/@types/node/index.d.ts"/>
import { UhkBlhost, UhkHidDevice, UhkOperations } from 'uhk-usb';
import { LogService } from 'uhk-common';
import * as fs from 'fs';
if (process.argv.length < 3) {
console.log(`use: write-userconfig <path to config file.bin>`);
process.exit(1);
}
const fileContent = fs.readFileSync(process.argv[2]);
const json = JSON.stringify(fileContent);
const logger = new LogService();
const uhkDevice = new UhkHidDevice(logger);
const uhkBlHost = new UhkBlhost(logger, '.');
const uhkOperations = new UhkOperations(logger, uhkBlHost, uhkDevice, '.');
const init = async (): Promise<void> => {
await uhkOperations.saveUserConfiguration(json);
};
init()
.then(() => {
console.log('Success');
})
.catch(error => {
console.log(error);
process.exit(-1);
});

View File

@@ -2,7 +2,7 @@
# apt-get install inkscape imagemagick-6.q16 icnsutils
agentIconSvg=../packages/uhk-web/src/svgs/keyboard/icons/agent-icon.svg
agentIconSvg=../packages/uhk-web/src/svgs/icons/agent-icon.svg
for size in 16 24 32 48 64 96 128 256 512 1024; do
inkscape -z --export-png=../build/icons/${size}x${size}.png -w $size $agentIconSvg