Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions packages/uhk-agent/src/services/device.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ export class DeviceService {
currentDeviceFn: getCurrentUhkDongleHID,
logService: this.logService,
ipcEvents: {
execShellCommand: IpcEvents.device.execShellCommandOnDongle,
isZephyrLoggingEnabled: IpcEvents.device.isDongleZephyrLoggingEnabled,
isZephyrLoggingEnabledReply: IpcEvents.device.isDongleZephyrLoggingEnabledReply,
toggleZephyrLogging: IpcEvents.device.toggleDongleZephyrLogging,
Expand All @@ -138,6 +139,7 @@ export class DeviceService {
currentDeviceFn: getCurrenUhk80LeftHID,
logService: this.logService,
ipcEvents: {
execShellCommand: IpcEvents.device.execShellCommandOnLeftHalf,
isZephyrLoggingEnabled: IpcEvents.device.isLeftHalfZephyrLoggingEnabled,
isZephyrLoggingEnabledReply: IpcEvents.device.isLeftHalfZephyrLoggingEnabledReply,
toggleZephyrLogging: IpcEvents.device.toggleLeftHalfZephyrLogging,
Expand Down Expand Up @@ -195,6 +197,15 @@ export class DeviceService {
});
});

ipcMain.on(IpcEvents.device.execShellCommandOnRightHalf, (...args: any[]) => {
this.queueManager.add({
method: this.execShellCommand,
bind: this,
params: args,
asynchronous: true
});
});

ipcMain.on(IpcEvents.device.toggleI2cDebugging, this.toggleI2cDebugging.bind(this));

ipcMain.on(IpcEvents.device.isRightHalfZephyrLoggingEnabled, (...args: any[]) => {
Expand Down Expand Up @@ -973,6 +984,22 @@ export class DeviceService {
event.sender.send(IpcEvents.device.eraseBleSettingsReply, response);
}

public async execShellCommand(_: Electron.IpcMainEvent, [command]): Promise<void> {
this.logService.misc(`[DeviceService] execute shell command: ${command}`);

try {
await this.stopPollUhkDevice();
await this.operations.execShellCommand(command);
this.logService.misc('[DeviceService] execute shell command success');
}
catch(error) {
this.logService.error('[DeviceService] execute shell command failed', error);
}
finally {
this.startPollUhkDevice();
}
}

public async startDonglePairing(event: Electron.IpcMainEvent): Promise<void> {
this.logService.misc('[DeviceService] start Dongle pairing');
try {
Expand Down
31 changes: 31 additions & 0 deletions packages/uhk-agent/src/services/zephyr-log.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export interface ZephyrLogServiceOptions {
currentDeviceFn: typeof getCurrenUhk80LeftHID | typeof getCurrentUhkDongleHID;
logService: LogService;
ipcEvents: {
execShellCommand: string;
isZephyrLoggingEnabled: string;
isZephyrLoggingEnabledReply: string;
toggleZephyrLogging: string;
Expand All @@ -31,6 +32,15 @@ export class ZephyrLogService {
private operationLimiter = pLimit(1);

constructor(private options: ZephyrLogServiceOptions) {
ipcMain.on(options.ipcEvents.execShellCommand, (...args: any[]) => {
this.queueManager.add({
method: this.execShellCommand,
bind: this,
params: args,
asynchronous: true
});
});

ipcMain.on(options.ipcEvents.isZephyrLoggingEnabled, (...args: any[]) => {
this.queueManager.add({
method: this.isZephyrLoggingEnabled,
Expand Down Expand Up @@ -81,6 +91,27 @@ export class ZephyrLogService {
this.options.logService.misc(`[ZephyrLogService | ${this.options.uhkDeviceProduct.logName}] Disabled`);
}

private async execShellCommand(_: Electron.IpcMainEvent, [command]): Promise<void> {
try {
await this.pauseLogging();

const operations = await this.getOperations();
if (!operations) {
const logEntry: ZephyrLogEntry = {
log: "Device is not connected. Can't execute shell command",
level: 'error',
device: this.options.uhkDeviceProduct.logName,
}
this.options.win.webContents.send(IpcEvents.device.zephyrLog, logEntry)
return;
}
await operations.execShellCommand(command);
}
finally {
await this.resumeLogging();
}
}

private async getOperations(logEarlierInited = true): Promise<UhkOperations> {
if (logEarlierInited) {
this.options.logService.misc(`[ZephyrLogService | ${this.options.uhkDeviceProduct.logName}] getOperations`);
Expand Down
3 changes: 3 additions & 0 deletions packages/uhk-common/src/util/ipcEvents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ export class Device {
public static readonly dongleVersionInfoLoaded = 'device-dongle-version-info-loaded';
public static readonly eraseBleSettings = 'device-erase-ble-settings';
public static readonly eraseBleSettingsReply = 'device-erase-ble-settings-reply';
public static readonly execShellCommandOnDongle = 'device-exec-shell-command-on-dongle';
public static readonly execShellCommandOnLeftHalf = 'device-exec-shell-command-on-left-half';
public static readonly execShellCommandOnRightHalf = 'device-exec-shell-command-on-right-half';
public static readonly hardwareModulesLoaded = 'device-hardware-modules-loaded';
public static readonly isDongleZephyrLoggingEnabled = 'device-is-dongle-zephyr-logging-enabled';
public static readonly isDongleZephyrLoggingEnabledReply = 'device-is-dongle-zephyr-logging-enabled-reply';
Expand Down
14 changes: 14 additions & 0 deletions packages/uhk-usb/src/uhk-operations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -937,4 +937,18 @@ export class UhkOperations {

await this.device.write(buffer);
}

public async execShellCommand(cmd: string): Promise<void> {
this.logService.usbOps('[DeviceOperation] USB[T]: Execute Shell Command');
const b1 = Buffer.from([UsbCommand.ExecShellCommand]);
const b2 = Buffer.from(cmd);
const b0 = Buffer.from([0x00]);
const buffer = Buffer.concat([b1, b2, b0]);

if (buffer.length > MAX_USB_PAYLOAD_SIZE) {
throw new Error('Shel command is too long. At most 61 characters are supported.')
}

await this.device.write(buffer);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ <h1 class="pane-title">
</div>

<div *ngIf="state.activeButton === ActiveButton.ShowZephyrLogs" class="checkbox d-flex mb-0 mt-3 gap-3">
<label *ngIf="showLeftHalfZephyrLogCheckbox" class="d-inline-flex align-items-center gap-1">
<label *ngIf="isLeftHalfConnected" class="d-inline-flex align-items-center gap-1">
<input type="checkbox"
[checked]="state.isLeftHalfZephyrLoggingEnabled"
(click)="onToggleLeftHalfZephyrLogging()"
Expand All @@ -93,7 +93,7 @@ <h1 class="pane-title">
> Right half
</label>

<label *ngIf="showDongleZephyrLogCheckbox" class="d-inline-flex align-items-center gap-1">
<label *ngIf="isDongleConnected" class="d-inline-flex align-items-center gap-1">
<input type="checkbox"
[checked]="state.isDongleZephyrLoggingEnabled"
(click)="onToggleDongleZephyrLogging()"
Expand All @@ -104,6 +104,38 @@ <h1 class="pane-title">
<div class="flex-grow mt-3">
<xterm [logs]="state.i2cLogs"></xterm>
</div>

<div *ngIf="state.activeButton === ActiveButton.ShowZephyrLogs" class="d-flex flex-column mb-0 mt-3 gap-3">
<form (ngSubmit)="onExecShellCommand()">
<div class="checkbox d-flex mb-3 gap-3">
<label *ngIf="isLeftHalfConnected" class="d-inline-flex align-items-center gap-1">
<input type="checkbox"
name="execShellCommandOnLeftHalf"
[(ngModel)]="execShellCommandOnLeftHalf"
> Left half
</label>

<label class="d-inline-flex align-items-center gap-1">
<input type="checkbox"
name="execShellCommandOnRightHalf"
[(ngModel)]="execShellCommandOnRightHalf"
> Right half
</label>

<label *ngIf="isDongleConnected" class="d-inline-flex align-items-center gap-1">
<input type="checkbox"
name="execShellCommandOnDongle"
[(ngModel)]="execShellCommandOnDongle"
> Dongle
</label>
</div>

<div class="d-flex gap-3">
<input type="text" class="form-control flex-grow-1" [(ngModel)]="shellCommand" name="shellCommand" maxlength="59">
<button type="submit" class="btn btn-primary">Execute</button>
</div>
</form>
</div>
<audio #audioPlayer src="assets/beep.mp3" preload="auto"></audio>
<div class="flex-footer">
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,12 @@ import {
ToggleRightHalfZephyrLoggingAction,
ToggleZephyrLoggingAction,
} from '../../../store/actions/advance-settings.action';
import { ChangeKeyboardLayoutAction } from '../../../store/actions/device';
import {
ChangeKeyboardLayoutAction,
ExecShellCommandOnDongleAction,
ExecShellCommandOnLeftHalfAction,
ExecShellCommandOnRightHalfAction,
} from '../../../store/actions/device';
import { ActiveButton, initialState, State } from '../../../store/reducers/advanced-settings.reducer';

@Component({
Expand All @@ -53,9 +58,13 @@ export class AdvancedSettingsPageComponent implements OnInit, OnDestroy {
isZephyrLoggingAllowed: boolean;
keyboardLayout: KeyboardLayout;
keyboardLayoutEnum = KeyboardLayout;
showDongleZephyrLogCheckbox: boolean;
shellCommand = ''
execShellCommandOnDongle = false;
execShellCommandOnLeftHalf = false;
execShellCommandOnRightHalf = true;
isDongleConnected: boolean;
showI2CRecoverButton: boolean;
showLeftHalfZephyrLogCheckbox: boolean;
isLeftHalfConnected: boolean;
state: State;

private i2cErrorsLength = 0;
Expand Down Expand Up @@ -93,7 +102,7 @@ export class AdvancedSettingsPageComponent implements OnInit, OnDestroy {
});
this.dongleSubscription = this.store.select(getDongle)
.subscribe(dongle => {
this.showDongleZephyrLogCheckbox = !!dongle.serialNumber;
this.isDongleConnected = !!dongle?.serialNumber;
this.cdRef.detectChanges();
})
this.keyboardLayoutSubscription = this.store.select(getKeyboardLayout)
Expand All @@ -103,7 +112,7 @@ export class AdvancedSettingsPageComponent implements OnInit, OnDestroy {
});
this.leftHalfDetectedSubscription = this.store.select(getLeftHalfDetected)
.subscribe(leftHalfDetected => {
this.showLeftHalfZephyrLogCheckbox = leftHalfDetected;
this.isLeftHalfConnected = leftHalfDetected;
this.cdRef.detectChanges();
});
this.stateSubscription = this.store.select(advanceSettingsState)
Expand Down Expand Up @@ -131,6 +140,20 @@ export class AdvancedSettingsPageComponent implements OnInit, OnDestroy {
this.store.dispatch(new ChangeKeyboardLayoutAction(layout));
}

onExecShellCommand(): void {
if (this.isDongleConnected && this.execShellCommandOnDongle) {
this.store.dispatch(new ExecShellCommandOnDongleAction(this.shellCommand));
}

if (this.isLeftHalfConnected && this.execShellCommandOnLeftHalf) {
this.store.dispatch(new ExecShellCommandOnLeftHalfAction(this.shellCommand));
}

if (this.execShellCommandOnRightHalf) {
this.store.dispatch(new ExecShellCommandOnRightHalfAction(this.shellCommand));
}
}

onToggleI2cDebug(): void {
this.store.dispatch(new ToggleI2cDebuggingAction());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<div class="x-term-wrapper" #scrollMe>
<ul class="list-unstyled">
<li *ngFor="let log of logs" [ngClass]="log.cssClass">
<span [innerHTML]="log.message | escapeHtml | newLineToBr | safeHtml"></span>
<pre [innerHTML]="log.message | escapeHtml | newLineToBr | safeHtml"></pre>
</li>
</ul>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,12 @@
ul {
li {
padding-left: 5px;
span:before {
content: '$ ';
pre {
white-space: wrap;
margin: 0;
&:before {
content: '$ ';
}
}
}
}
12 changes: 12 additions & 0 deletions packages/uhk-web/src/app/services/device-renderer.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,18 @@ export class DeviceRendererService {
this.ipcRenderer.send(IpcEvents.device.eraseBleSettings);
}

execShellCommandOnDongle(command: string): void {
this.ipcRenderer.send(IpcEvents.device.execShellCommandOnDongle, command);
}

execShellCommandOnLeftHalf(command: string): void {
this.ipcRenderer.send(IpcEvents.device.execShellCommandOnLeftHalf, command);
}

execShellCommandOnRightHalf(command: string): void {
this.ipcRenderer.send(IpcEvents.device.execShellCommandOnRightHalf, command);
}

isDongleZephyrLoggingEnabled(): void {
this.ipcRenderer.send(IpcEvents.device.isDongleZephyrLoggingEnabled);
}
Expand Down
24 changes: 24 additions & 0 deletions packages/uhk-web/src/app/store/actions/device.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ export enum ActionTypes {
DongleVersionInfoLoaded = '[device] dongle version info loaded',
EraseBleSettings = '[device] erase ble settings',
EraseBleSettingsReply = '[device] erase ble settings reply',
ExecShellCommandOnDongle = '[device] exec shell command on dongle',
ExecShellCommandOnLeftHalf = '[device] exec shell command on left half',
ExecShellCommandOnRightHalf = '[device] exec shell command on right half',
SetPrivilegeOnLinux = '[device] set privilege on linux',
SetPrivilegeOnLinuxReply = '[device] set privilege on linux reply',
ConnectionStateChanged = '[device] connection state changed',
Expand Down Expand Up @@ -112,6 +115,24 @@ export class EraseBleSettingReplyAction implements Action {
constructor(public payload: IpcResponse) {}
}

export class ExecShellCommandOnDongleAction implements Action {
type = ActionTypes.ExecShellCommandOnDongle;

constructor(public payload: string) {}
}

export class ExecShellCommandOnLeftHalfAction implements Action {
type = ActionTypes.ExecShellCommandOnLeftHalf;

constructor(public payload: string) {}
}

export class ExecShellCommandOnRightHalfAction implements Action {
type = ActionTypes.ExecShellCommandOnRightHalf;

constructor(public payload: string) {}
}

export class SetPrivilegeOnLinuxAction implements Action {
type = ActionTypes.SetPrivilegeOnLinux;
}
Expand Down Expand Up @@ -328,6 +349,9 @@ export type Actions
| DongleVersionInfoLoadedAction
| EraseBleSettingAction
| EraseBleSettingReplyAction
| ExecShellCommandOnDongleAction
| ExecShellCommandOnLeftHalfAction
| ExecShellCommandOnRightHalfAction
| SetPrivilegeOnLinuxAction
| SetPrivilegeOnLinuxReplyAction
| ConnectionStateChangedAction
Expand Down
33 changes: 33 additions & 0 deletions packages/uhk-web/src/app/store/effects/device.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ import {
ConnectionStateChangedAction,
EnableUsbStackTestAction,
EraseBleSettingReplyAction,
ExecShellCommandOnDongleAction,
ExecShellCommandOnLeftHalfAction,
ExecShellCommandOnRightHalfAction,
HideSaveToKeyboardButton,
ReadConfigSizesAction,
RecoveryDeviceAction,
Expand Down Expand Up @@ -272,6 +275,36 @@ export class DeviceEffects {
),
);

execShellCommandOnDongle$ = createEffect(() => this.actions$
.pipe(
ofType<ExecShellCommandOnDongleAction>(ActionTypes.ExecShellCommandOnDongle),
tap((action) => {
this.deviceRendererService.execShellCommandOnDongle(action.payload);
})
),
{ dispatch: false }
);

execShellCommandOnLeftHalf$ = createEffect(() => this.actions$
.pipe(
ofType<ExecShellCommandOnLeftHalfAction>(ActionTypes.ExecShellCommandOnLeftHalf),
tap((action) => {
this.deviceRendererService.execShellCommandOnLeftHalf(action.payload);
})
),
{ dispatch: false }
);

execShellCommandOnRightHalf$ = createEffect(() => this.actions$
.pipe(
ofType<ExecShellCommandOnRightHalfAction>(ActionTypes.ExecShellCommandOnRightHalf),
tap((action) => {
this.deviceRendererService.execShellCommandOnRightHalf(action.payload);
})
),
{ dispatch: false }
);

setPrivilegeOnLinux$ = createEffect(() => this.actions$
.pipe(
ofType(ActionTypes.SetPrivilegeOnLinux),
Expand Down
Loading