diff --git a/packages/uhk-agent/package.json b/packages/uhk-agent/package.json index 5552e7dd..e693c4a3 100644 --- a/packages/uhk-agent/package.json +++ b/packages/uhk-agent/package.json @@ -31,10 +31,10 @@ "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": "webpack && npm run install:build-deps && npm run build:usb && npm run download-firmware && npm run copy-to-tmp-folder", "build:usb": "electron-rebuild -w node-hid -p -m ./dist", "install:build-deps": "cd ./dist && npm i", "download-firmware": "node ../../scripts/download-firmware.js", - "copy-blhost": "node ../../scripts/copy-blhost.js" + "copy-to-tmp-folder": "node ../../scripts/copy-to-tmp-folder.js" } } diff --git a/packages/uhk-agent/src/electron-main.ts b/packages/uhk-agent/src/electron-main.ts index 58c63f51..549ebc27 100644 --- a/packages/uhk-agent/src/electron-main.ts +++ b/packages/uhk-agent/src/electron-main.ts @@ -102,7 +102,7 @@ function createWindow() { }); setMenu(win); win.maximize(); - uhkHidDeviceService = new UhkHidDevice(logger, options); + uhkHidDeviceService = new UhkHidDevice(logger, options, packagesDir); uhkBlhost = new UhkBlhost(logger, packagesDir); uhkOperations = new UhkOperations(logger, uhkBlhost, uhkHidDeviceService, packagesDir); deviceService = new DeviceService(logger, win, uhkHidDeviceService, uhkOperations, packagesDir); diff --git a/packages/uhk-agent/src/services/app.service.ts b/packages/uhk-agent/src/services/app.service.ts index 16117296..874bc93b 100644 --- a/packages/uhk-agent/src/services/app.service.ts +++ b/packages/uhk-agent/src/services/app.service.ts @@ -23,14 +23,12 @@ export class AppService extends MainServiceBase { private async handleAppStartInfo(event: Electron.Event) { this.logService.info('[AppService] getAppStartInfo'); - const deviceConnectionState = this.uhkHidDeviceService.getDeviceConnectionState(); + const deviceConnectionState = await this.uhkHidDeviceService.getDeviceConnectionStateAsync(); const response: AppStartInfo = { + deviceConnectionState, commandLineArgs: { addons: this.options.addons || false }, - deviceConnected: deviceConnectionState.connected, - hasPermission: deviceConnectionState.hasPermission, - bootloaderActive: deviceConnectionState.bootloaderActive, platform: process.platform as string, osVersion: os.release() }; diff --git a/packages/uhk-agent/src/services/device.service.ts b/packages/uhk-agent/src/services/device.service.ts index 869b5349..7cf61584 100644 --- a/packages/uhk-agent/src/services/device.service.ts +++ b/packages/uhk-agent/src/services/device.service.ts @@ -1,4 +1,5 @@ import { ipcMain } from 'electron'; +import { isEqual } from 'lodash'; import { ConfigurationReply, DeviceConnectionState, @@ -12,15 +13,16 @@ import { SaveUserConfigurationData, UpdateFirmwareData } from 'uhk-common'; -import { deviceConnectionStateComparer, snooze, UhkHidDevice, UhkOperations } from 'uhk-usb'; +import { snooze, UhkHidDevice, UhkOperations } from 'uhk-usb'; import { Observable } from 'rxjs/Observable'; import { Subscription } from 'rxjs/Subscription'; import { emptyDir } from 'fs-extra'; import * as path from 'path'; import 'rxjs/add/observable/interval'; +import 'rxjs/add/observable/fromPromise'; +import 'rxjs/add/operator/switchMap'; import 'rxjs/add/operator/startWith'; -import 'rxjs/add/operator/map'; import 'rxjs/add/operator/do'; import 'rxjs/add/operator/distinctUntilChanged'; @@ -253,8 +255,8 @@ export class DeviceService { this.pollTimer$ = Observable.interval(1000) .startWith(0) - .map(() => this.device.getDeviceConnectionState()) - .distinctUntilChanged(deviceConnectionStateComparer) + .switchMap(() => Observable.fromPromise(this.device.getDeviceConnectionStateAsync())) + .distinctUntilChanged(isEqual) .do((state: DeviceConnectionState) => { this.win.webContents.send(IpcEvents.device.deviceConnectionStateChanged, state); this.logService.info('[DeviceService] Device connection state changed to:', state); diff --git a/packages/uhk-common/src/models/app-start-info.ts b/packages/uhk-common/src/models/app-start-info.ts index 52390019..d0934230 100644 --- a/packages/uhk-common/src/models/app-start-info.ts +++ b/packages/uhk-common/src/models/app-start-info.ts @@ -1,10 +1,9 @@ import { CommandLineArgs } from './command-line-args'; +import { DeviceConnectionState } from './device-connection-state'; export interface AppStartInfo { commandLineArgs: CommandLineArgs; - deviceConnected: boolean; - hasPermission: boolean; - bootloaderActive: boolean; + deviceConnectionState: DeviceConnectionState; platform: string; osVersion: string; } diff --git a/packages/uhk-common/src/models/device-connection-state.ts b/packages/uhk-common/src/models/device-connection-state.ts index 0d688bc0..1cdb6063 100644 --- a/packages/uhk-common/src/models/device-connection-state.ts +++ b/packages/uhk-common/src/models/device-connection-state.ts @@ -1,5 +1,9 @@ +import { UdevRulesInfo } from './udev-rules-info'; + export interface DeviceConnectionState { connected: boolean; hasPermission: boolean; bootloaderActive: boolean; + zeroInterfaceAvailable: boolean; + udevRulesInfo: UdevRulesInfo; } diff --git a/packages/uhk-common/src/models/index.ts b/packages/uhk-common/src/models/index.ts index 27609b0f..e133829c 100644 --- a/packages/uhk-common/src/models/index.ts +++ b/packages/uhk-common/src/models/index.ts @@ -8,4 +8,5 @@ export * from './device-connection-state'; export * from './hardware-modules'; export * from './hardware-module-info'; export * from './save-user-configuration-data'; +export * from './udev-rules-info'; export * from './update-firmware-data'; diff --git a/packages/uhk-common/src/models/udev-rules-info.ts b/packages/uhk-common/src/models/udev-rules-info.ts new file mode 100644 index 00000000..3b3e5cae --- /dev/null +++ b/packages/uhk-common/src/models/udev-rules-info.ts @@ -0,0 +1,16 @@ +/** + * What is the state of the udev rules. + * Only on Linux need extra udev rules. + */ +export enum UdevRulesInfo { + Unkonwn, + Ok, + /** + * Udev rules not exists need to setup on Linux + */ + NeedToSetup, + /** + * Udev rules exist but different than expected on Linux + */ + Different +} diff --git a/packages/uhk-usb/src/uhk-hid-device.ts b/packages/uhk-usb/src/uhk-hid-device.ts index 6e43da11..7e5f8f4c 100644 --- a/packages/uhk-usb/src/uhk-hid-device.ts +++ b/packages/uhk-usb/src/uhk-hid-device.ts @@ -1,5 +1,7 @@ import { Device, devices, HID } from 'node-hid'; -import { CommandLineArgs, DeviceConnectionState, isEqualArray, LogService } from 'uhk-common'; +import { pathExists } from 'fs-extra'; +import * as path from 'path'; +import { CommandLineArgs, DeviceConnectionState, isEqualArray, LogService, UdevRulesInfo } from 'uhk-common'; import { ConfigBufferId, @@ -13,7 +15,7 @@ import { UsbCommand, UsbVariables } from './constants'; -import { bufferToString, getTransferData, isUhkDevice, retry, snooze } from './util'; +import { bufferToString, getFileContentAsync, getTransferData, isUhkDevice, isUhkZeroInterface, retry, snooze } from './util'; export const BOOTLOADER_TIMEOUT_MS = 5000; @@ -28,9 +30,11 @@ export class UhkHidDevice { private _prevDevices = []; private _device: HID; private _hasPermission = false; + private _udevRulesInfo = UdevRulesInfo.Unkonwn; constructor(private logService: LogService, - private options: CommandLineArgs) { + private options: CommandLineArgs, + private rootDir: string) { } /** @@ -54,7 +58,7 @@ export class UhkHidDevice { const devs = devices(); this.logDevices(devs); - const dev = devs.find((x: Device) => isUhkDevice(x) || x.productId === Constants.BOOTLOADER_ID); + const dev = devs.find((x: Device) => isUhkZeroInterface(x) || x.productId === Constants.BOOTLOADER_ID); if (!dev) { return true; @@ -74,20 +78,26 @@ export class UhkHidDevice { } /** - * Return with true is an UHK Device is connected to the computer. - * @returns {boolean} + * Return with the USB device communication sate. + * @returns {DeviceConnectionState} */ - public getDeviceConnectionState(): DeviceConnectionState { + public async getDeviceConnectionStateAsync(): Promise { const devs = devices(); const result: DeviceConnectionState = { bootloaderActive: false, connected: false, - hasPermission: this.hasPermission() + zeroInterfaceAvailable: false, + hasPermission: this.hasPermission(), + udevRulesInfo: await this.getUdevInfoAsync() }; for (const dev of devs) { if (isUhkDevice(dev)) { result.connected = true; + } + + if (isUhkZeroInterface(dev)) { + result.zeroInterfaceAvailable = true; } else if (dev.vendorId === Constants.VENDOR_ID && dev.productId === Constants.BOOTLOADER_ID) { result.bootloaderActive = true; @@ -270,7 +280,7 @@ export class UhkHidDevice { this.logService.debug('[UhkHidDevice] Available devices unchanged'); } - const dev = devs.find(isUhkDevice); + const dev = devs.find(isUhkZeroInterface); if (!dev) { this.logService.debug('[UhkHidDevice] UHK Device not found:'); @@ -279,8 +289,7 @@ export class UhkHidDevice { const device = new HID(dev.path); this.logService.debug('[UhkHidDevice] Used device:', JSON.stringify(dev)); return device; - } - catch (err) { + } catch (err) { this.logService.error('[UhkHidDevice] Can not create device:', err); } @@ -292,6 +301,31 @@ export class UhkHidDevice { this.logService.debug(JSON.stringify(logDevice)); } } + + private async getUdevInfoAsync(): Promise { + if (this._udevRulesInfo === UdevRulesInfo.Ok) { + return UdevRulesInfo.Ok; + } + + if (process.platform === 'win32' || process.platform === 'darwin') { + this._udevRulesInfo = UdevRulesInfo.Ok; + return UdevRulesInfo.Ok; + } + + if (!(await pathExists('/etc/udev/rules.d/50-uhk60.rules'))) { + return UdevRulesInfo.NeedToSetup; + } + + const expectedUdevSettings = await getFileContentAsync(path.join(this.rootDir, 'rules/50-uhk60.rules')); + const currentUdevSettings = await getFileContentAsync('/etc/udev/rules.d/50-uhk60.rules'); + + if (isEqualArray(expectedUdevSettings, currentUdevSettings)) { + this._udevRulesInfo = UdevRulesInfo.Ok; + return UdevRulesInfo.Ok; + } + + return UdevRulesInfo.Different; + } } function kbootCommandName(module: ModuleSlotToI2cAddress): string { diff --git a/packages/uhk-usb/src/util.ts b/packages/uhk-usb/src/util.ts index 62978309..de41dc31 100644 --- a/packages/uhk-usb/src/util.ts +++ b/packages/uhk-usb/src/util.ts @@ -1,5 +1,7 @@ import { Device } from 'node-hid'; -import { DeviceConnectionState, LogService } from 'uhk-common'; +import { readFile } from 'fs-extra'; +import { EOL } from 'os'; +import { LogService } from 'uhk-common'; import { Constants, UsbCommand } from './constants'; @@ -98,13 +100,7 @@ export async function retry(command: Function, maxTry = 3, logService?: LogServi } } -export const deviceConnectionStateComparer = (a: DeviceConnectionState, b: DeviceConnectionState): boolean => { - return a.hasPermission === b.hasPermission - && a.connected === b.connected - && a.bootloaderActive === b.bootloaderActive; -}; - -export const isUhkDevice = (dev: Device): boolean => { +export const isUhkZeroInterface = (dev: Device): boolean => { return dev.vendorId === Constants.VENDOR_ID && dev.productId === Constants.PRODUCT_ID && // hidapi can not read the interface number on Mac, so check the usage page and usage @@ -112,3 +108,17 @@ export const isUhkDevice = (dev: Device): boolean => { (dev.usagePage === (0xFF00 | 0x00) && dev.usage === 0x01) || // New firmware dev.interface === 0); }; + +export const isUhkDevice = (dev: Device): boolean => { + return dev.vendorId === Constants.VENDOR_ID && + (dev.productId === Constants.PRODUCT_ID || dev.productId === Constants.BOOTLOADER_ID); +}; + +export const getFileContentAsync = async (filePath: string): Promise> => { + const fileContent = await readFile(filePath, {encoding: 'utf-8'}); + + return fileContent + .split(EOL) + .map(x => x.trim()) + .filter(x => !x.startsWith('#') && x.length > 0); +}; diff --git a/packages/uhk-web/src/app/components/missing-device/missing-device.component.html b/packages/uhk-web/src/app/components/missing-device/missing-device.component.html index acfc6740..f54de816 100644 --- a/packages/uhk-web/src/app/components/missing-device/missing-device.component.html +++ b/packages/uhk-web/src/app/components/missing-device/missing-device.component.html @@ -1 +1 @@ - + diff --git a/packages/uhk-web/src/app/components/missing-device/missing-device.component.ts b/packages/uhk-web/src/app/components/missing-device/missing-device.component.ts index 478e8224..e240d6d3 100644 --- a/packages/uhk-web/src/app/components/missing-device/missing-device.component.ts +++ b/packages/uhk-web/src/app/components/missing-device/missing-device.component.ts @@ -1,14 +1,27 @@ -import { Component } from '@angular/core'; +import { Component, OnDestroy } from '@angular/core'; +import { Store } from '@ngrx/store'; +import { Subscription } from 'rxjs/Subscription'; -import 'rxjs/add/operator/distinctUntilChanged'; -import 'rxjs/add/operator/ignoreElements'; -import 'rxjs/add/operator/takeWhile'; +import { AppState, getMissingDeviceState } from '../../store'; +import { MissingDeviceState } from '../../models/missing-device-state'; @Component({ selector: 'missing-device', templateUrl: './missing-device.component.html' }) -export class MissingDeviceComponent { +export class MissingDeviceComponent implements OnDestroy { - constructor() {} + state: MissingDeviceState; + + private stateSubscription: Subscription; + + constructor(private store: Store) { + this.stateSubscription = this.store + .select(getMissingDeviceState) + .subscribe(state => this.state = state); + } + + ngOnDestroy(): void { + this.stateSubscription.unsubscribe(); + } } diff --git a/packages/uhk-web/src/app/components/privilege-checker/privilege-checker.component.html b/packages/uhk-web/src/app/components/privilege-checker/privilege-checker.component.html index d440a03e..846d19e7 100644 --- a/packages/uhk-web/src/app/components/privilege-checker/privilege-checker.component.html +++ b/packages/uhk-web/src/app/components/privilege-checker/privilege-checker.component.html @@ -2,8 +2,14 @@ -
@@ -22,21 +28,7 @@
If you want to set up permissions manually: -
    -
  1. Open a terminal.
  2. -
  3. Run su to become root.
  4. -
  5. Paste the following script, and retry.
  6. -
- -
- -
{{ command }}
-
+
diff --git a/packages/uhk-web/src/app/components/privilege-checker/privilege-checker.component.ts b/packages/uhk-web/src/app/components/privilege-checker/privilege-checker.component.ts index c674dd2a..dd026c94 100644 --- a/packages/uhk-web/src/app/components/privilege-checker/privilege-checker.component.ts +++ b/packages/uhk-web/src/app/components/privilege-checker/privilege-checker.component.ts @@ -18,17 +18,6 @@ export class PrivilegeCheckerComponent implements OnInit, OnDestroy { state: PrivilagePageSate; - command = `cat </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. -SUBSYSTEM=="input", GROUP="input", MODE="0666" -SUBSYSTEMS=="usb", ATTRS{idVendor}=="1d50", ATTRS{idProduct}=="612[0-7]", MODE:="0666", GROUP="plugdev" -KERNEL=="hidraw*", ATTRS{idVendor}=="1d50", ATTRS{idProduct}=="612[0-7]", MODE="0666", GROUP="plugdev" -EOF -udevadm trigger -udevadm settle`; - private stateSubscription: Subscription; constructor(private store: Store, diff --git a/packages/uhk-web/src/app/components/udev-rules/udev-rules.component.html b/packages/uhk-web/src/app/components/udev-rules/udev-rules.component.html new file mode 100644 index 00000000..d0a9e0e2 --- /dev/null +++ b/packages/uhk-web/src/app/components/udev-rules/udev-rules.component.html @@ -0,0 +1,15 @@ +
    +
  1. Open a terminal.
  2. +
  3. Run su to become root.
  4. +
  5. Paste the following script:
  6. +
+ +
+ +
{{ command }}
+
diff --git a/packages/uhk-web/src/app/components/udev-rules/udev-rules.component.ts b/packages/uhk-web/src/app/components/udev-rules/udev-rules.component.ts new file mode 100644 index 00000000..7da33ed8 --- /dev/null +++ b/packages/uhk-web/src/app/components/udev-rules/udev-rules.component.ts @@ -0,0 +1,19 @@ +import { ChangeDetectionStrategy, Component } from '@angular/core'; + +@Component({ + selector: 'udev-rules', + changeDetection: ChangeDetectionStrategy.OnPush, + templateUrl: './udev-rules.component.html' +}) +export class UdevRulesComponent { + command = `cat </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. +SUBSYSTEM=="input", GROUP="input", MODE="0666" +SUBSYSTEMS=="usb", ATTRS{idVendor}=="1d50", ATTRS{idProduct}=="612[0-7]", MODE:="0666", GROUP="plugdev" +KERNEL=="hidraw*", ATTRS{idVendor}=="1d50", ATTRS{idProduct}=="612[0-7]", MODE="0666", GROUP="plugdev" +EOF +udevadm trigger +udevadm settle`; +} diff --git a/packages/uhk-web/src/app/models/missing-device-state.ts b/packages/uhk-web/src/app/models/missing-device-state.ts new file mode 100644 index 00000000..fd6e436a --- /dev/null +++ b/packages/uhk-web/src/app/models/missing-device-state.ts @@ -0,0 +1,4 @@ +export interface MissingDeviceState { + header: string; + subtitle: string; +} diff --git a/packages/uhk-web/src/app/models/privilage-page-sate.ts b/packages/uhk-web/src/app/models/privilage-page-sate.ts index 969527ff..626a5728 100644 --- a/packages/uhk-web/src/app/models/privilage-page-sate.ts +++ b/packages/uhk-web/src/app/models/privilage-page-sate.ts @@ -2,4 +2,5 @@ export interface PrivilagePageSate { showWhatWillThisDo: boolean; showWhatWillThisDoContent: boolean; permissionSetupFailed: boolean; + updateUdevRules: boolean; } diff --git a/packages/uhk-web/src/app/shared.module.ts b/packages/uhk-web/src/app/shared.module.ts index 93b45ee7..ea863038 100644 --- a/packages/uhk-web/src/app/shared.module.ts +++ b/packages/uhk-web/src/app/shared.module.ts @@ -111,6 +111,7 @@ import { UhkDeviceBootloaderNotActiveGuard } from './services/uhk-device-bootloa import { FileUploadComponent } from './components/file-upload'; import { AutoGrowInputComponent } from './components/auto-grow-input'; import { HelpPageComponent } from './components/agent/help-page/help-page.component'; +import { UdevRulesComponent } from './components/udev-rules/udev-rules.component'; @NgModule({ declarations: [ @@ -188,7 +189,8 @@ import { HelpPageComponent } from './components/agent/help-page/help-page.compon AutoGrowInputComponent, HelpPageComponent, ExternalUrlDirective, - SvgSecondaryRoleComponent + SvgSecondaryRoleComponent, + UdevRulesComponent ], imports: [ CommonModule, diff --git a/packages/uhk-web/src/app/store/effects/app.ts b/packages/uhk-web/src/app/store/effects/app.ts index 32979ac3..b7748c0b 100644 --- a/packages/uhk-web/src/app/store/effects/app.ts +++ b/packages/uhk-web/src/app/store/effects/app.ts @@ -66,11 +66,7 @@ export class ApplicationEffects { this.logService.debug('[AppEffect][processStartInfo] payload:', appInfo); return [ new ApplyAppStartInfoAction(appInfo), - new ConnectionStateChangedAction({ - connected: appInfo.deviceConnected, - hasPermission: appInfo.hasPermission, - bootloaderActive: appInfo.bootloaderActive - }) + new ConnectionStateChangedAction(appInfo.deviceConnectionState) ]; }); diff --git a/packages/uhk-web/src/app/store/effects/device.ts b/packages/uhk-web/src/app/store/effects/device.ts index ddbff682..1f720f6a 100644 --- a/packages/uhk-web/src/app/store/effects/device.ts +++ b/packages/uhk-web/src/app/store/effects/device.ts @@ -18,6 +18,7 @@ import { HardwareConfiguration, IpcResponse, NotificationType, + UdevRulesInfo, UserConfiguration } from 'uhk-common'; import { @@ -39,9 +40,10 @@ import { UpdateFirmwareSuccessAction, UpdateFirmwareWithAction } from '../actions/device'; +import { AppRendererService } from '../../services/app-renderer.service'; import { DeviceRendererService } from '../../services/device-renderer.service'; import { SetupPermissionErrorAction, ShowNotificationAction } from '../actions/app'; -import { AppState, getRouterState } from '../index'; +import { AppState, deviceConnected, getRouterState } from '../index'; import { ActionTypes as UserConfigActions, ApplyUserConfigurationFromFileAction, @@ -57,7 +59,7 @@ export class DeviceEffects { @Effect() deviceConnectionStateChange$: Observable = this.actions$ .ofType(ActionTypes.CONNECTION_STATE_CHANGED) - .withLatestFrom(this.store.select(getRouterState)) + .withLatestFrom(this.store.select(getRouterState), this.store.select(deviceConnected)) .do(([action, route]) => { const state = action.payload; @@ -65,23 +67,24 @@ export class DeviceEffects { return; } - if (!state.hasPermission) { - this.router.navigate(['/privilege']); + if (!state.hasPermission || !state.zeroInterfaceAvailable) { + return this.router.navigate(['/privilege']); } - else if (state.bootloaderActive) { - this.router.navigate(['/recovery-device']); - } - else if (state.connected) { - this.router.navigate(['/']); - } - else { - this.router.navigate(['/detection']); - } - }) - .switchMap(([action, route]) => { - const state = action.payload; - if (state.connected && state.hasPermission) { + if (state.bootloaderActive) { + return this.router.navigate(['/recovery-device']); + } + + if (state.connected && state.zeroInterfaceAvailable) { + return this.router.navigate(['/']); + } + + return this.router.navigate(['/detection']); + }) + .switchMap(([action, route, connected]) => { + const payload = action.payload; + + if (connected && payload.hasPermission && payload.zeroInterfaceAvailable) { return Observable.of(new LoadConfigFromDeviceAction()); } @@ -99,16 +102,13 @@ export class DeviceEffects { setPrivilegeOnLinuxReply$: Observable = this.actions$ .ofType(ActionTypes.SET_PRIVILEGE_ON_LINUX_REPLY) .map(action => action.payload) - .map((response: any): any => { + .switchMap((response: any): any => { if (response.success) { - return new ConnectionStateChangedAction({ - connected: true, - hasPermission: true, - bootloaderActive: false - }); + this.appRendererService.getAppStartInfo(); + return Observable.empty(); } - return new SetupPermissionErrorAction(response.error); + return Observable.of(new SetupPermissionErrorAction(response.error)); }); @Effect({dispatch: false}) @@ -243,6 +243,7 @@ export class DeviceEffects { constructor(private actions$: Actions, private router: Router, + private appRendererService: AppRendererService, private deviceRendererService: DeviceRendererService, private store: Store, private dataStorageRepository: DataStorageRepositoryService, diff --git a/packages/uhk-web/src/app/store/index.ts b/packages/uhk-web/src/app/store/index.ts index f2aae19f..d5ecf224 100644 --- a/packages/uhk-web/src/app/store/index.ts +++ b/packages/uhk-web/src/app/store/index.ts @@ -1,6 +1,6 @@ import { createSelector } from 'reselect'; import { ActionReducerMap, MetaReducer } from '@ngrx/store'; -import { RouterReducerState, routerReducer } from '@ngrx/router-store'; +import { routerReducer, RouterReducerState } from '@ngrx/router-store'; import { storeFreeze } from 'ngrx-store-freeze'; import { HardwareModules, Keymap, UserConfiguration } from 'uhk-common'; @@ -14,6 +14,7 @@ import * as fromSelectors from './reducers/selectors'; import { initProgressButtonState } from './reducers/progress-button-state'; import { environment } from '../../environments/environment'; import { RouterStateUrl } from './router-util'; +import { PrivilagePageSate } from '../models/privilage-page-sate'; import { isVersionGte } from '../util'; // State interface for the application @@ -52,7 +53,6 @@ export const runningInElectron = createSelector(appState, fromApp.runningInElect 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 getOperatingSystem = createSelector(appState, fromSelectors.getOperatingSystem); export const keypressCapturing = createSelector(appState, fromApp.keypressCapturing); export const runningOnNotSupportedWindows = createSelector(appState, fromApp.runningOnNotSupportedWindows); @@ -66,14 +66,21 @@ export const getAutoUpdateSettings = createSelector(appUpdateSettingsState, auto export const getCheckingForUpdate = createSelector(appUpdateSettingsState, autoUpdateSettings.checkingForUpdate); export const deviceState = (state: AppState) => state.device; -export const isDeviceConnected = createSelector(deviceState, fromDevice.isDeviceConnected); -export const deviceConnected = createSelector(runningInElectron, isDeviceConnected, (electron, connected) => { - return !electron ? true : connected; -}); -export const devicePermission = createSelector(deviceState, fromDevice.hasDevicePermission); -export const hasDevicePermission = createSelector(runningInElectron, devicePermission, (electron, permission) => { - return !electron ? true : permission; -}); +export const deviceConnected = createSelector( + runningInElectron, deviceState, appState, + (electron, device, app) => { + if (!electron) { + return true; + } + + if (app.platform === 'linux') { + return device.connected && (device.zeroInterfaceAvailable || device.updatingFirmware); + } + + return device.connected; + }); +export const hasDevicePermission = createSelector(deviceState, fromDevice.hasDevicePermission); +export const getMissingDeviceState = createSelector(deviceState, fromDevice.getMissingDeviceState); export const saveToKeyboardStateSelector = createSelector(deviceState, fromDevice.getSaveToKeyboardState); export const saveToKeyboardState = createSelector(runningInElectron, saveToKeyboardStateSelector, (electron, state) => { return electron ? state : initProgressButtonState; @@ -88,6 +95,18 @@ export const getRestoreUserConfiguration = createSelector(deviceState, fromDevic export const bootloaderActive = createSelector(deviceState, fromDevice.bootloaderActive); export const firmwareUpgradeFailed = createSelector(deviceState, fromDevice.firmwareUpgradeFailed); export const firmwareUpgradeSuccess = createSelector(deviceState, fromDevice.firmwareUpgradeSuccess); +export const getUpdateUdevRules = createSelector(deviceState, fromDevice.updateUdevRules); + +export const getPrivilegePageState = createSelector(appState, getUpdateUdevRules, (app, updateUdevRules): PrivilagePageSate => { + const permissionSetupFailed = !!app.permissionError; + + return { + permissionSetupFailed, + updateUdevRules, + showWhatWillThisDo: !app.privilegeWhatWillThisDoClicked && !permissionSetupFailed, + showWhatWillThisDoContent: app.privilegeWhatWillThisDoClicked || permissionSetupFailed + }; +}); export const getSideMenuPageState = createSelector( showAddonMenu, diff --git a/packages/uhk-web/src/app/store/reducers/app.reducer.ts b/packages/uhk-web/src/app/store/reducers/app.reducer.ts index a6061abe..6f1fc950 100644 --- a/packages/uhk-web/src/app/store/reducers/app.reducer.ts +++ b/packages/uhk-web/src/app/store/reducers/app.reducer.ts @@ -183,15 +183,6 @@ 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 - }; -}; export const runningOnNotSupportedWindows = (state: State): boolean => { if (!state.osVersion || state.platform !== 'win32') { diff --git a/packages/uhk-web/src/app/store/reducers/device.ts b/packages/uhk-web/src/app/store/reducers/device.ts index d54f90b8..03be3629 100644 --- a/packages/uhk-web/src/app/store/reducers/device.ts +++ b/packages/uhk-web/src/app/store/reducers/device.ts @@ -1,12 +1,12 @@ import { Action } from '@ngrx/store'; -import { HardwareModules } from 'uhk-common'; +import { HardwareModules, UdevRulesInfo } from 'uhk-common'; import { ActionTypes, ConnectionStateChangedAction, HardwareModulesLoadedAction, - SaveConfigurationAction, HasBackupUserConfigurationAction, + SaveConfigurationAction, UpdateFirmwareFailedAction, UpdateFirmwareSuccessAction } from '../actions/device'; @@ -14,11 +14,14 @@ import { ActionTypes as AppActions, ElectronMainLogReceivedAction } from '../act import { initProgressButtonState, ProgressButtonState } from './progress-button-state'; import { XtermCssClass, XtermLog } from '../../models/xterm-log'; import { RestoreConfigurationState } from '../../models/restore-configuration-state'; +import { MissingDeviceState } from '../../models/missing-device-state'; export interface State { connected: boolean; hasPermission: boolean; bootloaderActive: boolean; + zeroInterfaceAvailable: boolean; + udevRuleInfo: UdevRulesInfo; saveToKeyboard: ProgressButtonState; savingToKeyboard: boolean; updatingFirmware: boolean; @@ -35,6 +38,8 @@ export const initialState: State = { connected: true, hasPermission: true, bootloaderActive: false, + zeroInterfaceAvailable: true, + udevRuleInfo: UdevRulesInfo.Unkonwn, saveToKeyboard: initProgressButtonState, savingToKeyboard: false, updatingFirmware: false, @@ -61,7 +66,9 @@ export function reducer(state = initialState, action: Action): State { ...state, connected: data.connected, hasPermission: data.hasPermission, - bootloaderActive: data.bootloaderActive + zeroInterfaceAvailable: data.zeroInterfaceAvailable, + bootloaderActive: data.bootloaderActive, + udevRuleInfo: data.udevRulesInfo }; } @@ -222,7 +229,20 @@ export function reducer(state = initialState, action: Action): State { export const updatingFirmware = (state: State) => state.updatingFirmware; export const isDeviceConnected = (state: State) => state.connected || state.updatingFirmware; -export const hasDevicePermission = (state: State) => state.hasPermission; +export const hasDevicePermission = (state: State) => state.udevRuleInfo === UdevRulesInfo.Ok; +export const getMissingDeviceState = (state: State): MissingDeviceState => { + if (state.connected && !state.zeroInterfaceAvailable) { + return { + header: 'Cannot find your UHK', + subtitle: 'Please reconnect it!' + }; + } + + return { + header: 'Cannot find your UHK', + subtitle: 'Please plug it in!' + }; +}; export const getSaveToKeyboardState = (state: State) => state.saveToKeyboard; export const xtermLog = (state: State) => state.log; export const getHardwareModules = (state: State) => state.modules; @@ -236,3 +256,4 @@ export const getBackupUserConfigurationState = (state: State): RestoreConfigurat export const bootloaderActive = (state: State) => state.bootloaderActive; export const firmwareUpgradeFailed = (state: State) => state.firmwareUpdateFailed; export const firmwareUpgradeSuccess = (state: State) => state.firmwareUpdateSuccess; +export const updateUdevRules = (state: State) => state.udevRuleInfo === UdevRulesInfo.Different; diff --git a/scripts/copy-blhost.js b/scripts/copy-blhost.js deleted file mode 100644 index 086fb49c..00000000 --- a/scripts/copy-blhost.js +++ /dev/null @@ -1,10 +0,0 @@ -const fse = require('fs-extra'); -const path = require('path'); - -fse.copy( - path.join(__dirname, '../packages/usb/blhost'), - path.join(__dirname, '../tmp/packages/blhost'), - { - overwrite:true, - recursive:true - }); diff --git a/scripts/copy-to-tmp-folder.js b/scripts/copy-to-tmp-folder.js new file mode 100644 index 00000000..5edeba34 --- /dev/null +++ b/scripts/copy-to-tmp-folder.js @@ -0,0 +1,27 @@ +const fse = require('fs-extra'); +const path = require('path'); + +const copyOptions = { + overwrite: true, + recursive: true +}; + +const promises = []; + +promises.push( + fse.copy( + path.join(__dirname, '../packages/usb/blhost'), + path.join(__dirname, '../tmp/packages/blhost'), + copyOptions) +); + +promises.push( + fse.copy( + path.join(__dirname, '../rules'), + path.join(__dirname, '../tmp/rules'), + copyOptions) +); + +Promise + .all(promises) + .catch(console.error);