Skip to content
Merged
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
33 changes: 26 additions & 7 deletions src/device/controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -253,9 +253,10 @@ export const PULSAR_SLEEP_OPTIONS: ReadonlyArray<readonly [number, string]> = [
const BACKGROUND = "Background refresh";
const FLASH_STEP_DELAY_MS = 420;
const FLASH_SETTLE_MS = 320;
// Longest a flash waits for an in-flight device refresh before giving up
// instead of busy-waiting forever.
const REFRESH_WAIT_TIMEOUT_MS = 2000;
// Longest a flash waits for an in-flight device read before giving up
// instead of busy-waiting forever. A full onboard-profile reread over a
// wireless link can take a few seconds.
const REFRESH_WAIT_TIMEOUT_MS = 5000;

const previewModeEnabled = previewsEnabled(__BUILD_CHANNEL__, import.meta.env.DEV);
const previewMode = previewModeEnabled
Expand Down Expand Up @@ -357,6 +358,8 @@ const napeKeymaps = new Map<number, KeychronNapeLayerKeymap>();
const stagedNapeAssignments = new Map<string, StagedNapeAssignment>();
let keymapReadToken = 0;
let onboardProfilesLoading = false;
// A status applied mid-write skips the profile read; the write's end retries it.
let onboardReloadDeferred = false;
let lastDeviceMode: MouseStatus["deviceMode"] = "Unknown";
let lastProfileFormat: MouseStatus["onboardProfileFormat"] = null;

Expand Down Expand Up @@ -2192,13 +2195,17 @@ export async function flashPendingChanges(): Promise<void> {
emit();
let written = 0;
let failure: string | null = null;
if (refreshInProgress) {
// HID++ replies are matched by feature and function only, so a profile read
// still running in the background would take the write's sector reads and
// fail its checksum check. Let every read finish first.
const reading = (): boolean => refreshInProgress || onboardProfilesLoading;
if (reading()) {
pendingStatusText = st("ctl.waitRefresh");
readStatus = pendingStatusText;
emit();
const refreshDeadline = Date.now() + REFRESH_WAIT_TIMEOUT_MS;
while (refreshInProgress && Date.now() <= refreshDeadline) await wait(25);
if (refreshInProgress) {
while (reading() && Date.now() <= refreshDeadline) await wait(25);
if (reading()) {
const timeout = new Error("Timed out waiting for the device refresh.");
recordDiagnosticError(timeout, st("ctl.unableFlash"));
failure = timeout.message;
Expand Down Expand Up @@ -2248,8 +2255,11 @@ export async function flashPendingChanges(): Promise<void> {

function endDeviceWrite(): void {
settingInProgress = false;
const retryProfiles = onboardReloadDeferred && onboardProfiles === null && !onboardProfilesLoading;
onboardReloadDeferred = false;
syncProfileDerivedState();
emit();
if (retryProfiles) void reloadOnboardProfiles();
}

function saveInterfacePreferences(): void {
Expand Down Expand Up @@ -2697,7 +2707,10 @@ function applyStatusInner(deviceStatus: MouseStatus, statusKey?: string): void {
if (supportsOnboard) {
lastDeviceMode = status.deviceMode;
lastProfileFormat = status.onboardProfileFormat ?? null;
if (onboardProfiles === null && !onboardProfilesLoading) void reloadOnboardProfiles();
// A read started mid-write would interleave with the write's sector reads,
// so it waits for endDeviceWrite().
if (onboardProfiles === null && !onboardProfilesLoading && settingInProgress) onboardReloadDeferred = true;
if (onboardProfiles === null && !onboardProfilesLoading && !settingInProgress) void reloadOnboardProfiles();
Comment thread
coderabbitai[bot] marked this conversation as resolved.
else syncProfileDerivedState();
}
syncEditedNapeLayer(deviceStatus);
Expand Down Expand Up @@ -4696,6 +4709,12 @@ export function describeProfileEntry(entry: OnboardProfile | null): { name: stri
};
}

/** The reload button. Skipped mid-write, which rereads the profiles itself. */
export function rereadOnboardProfiles(): void {
if (settingInProgress) return;
void reloadOnboardProfiles();
}

export async function reloadOnboardProfiles(): Promise<boolean> {
const client = logitechClient();
if (!client || onboardProfilesLoading) return false;
Expand Down
2 changes: 1 addition & 1 deletion src/features/profiles/Profiles.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ export function Profiles({
type="button"
aria-label={t(locale, "prof.reload")}
title={t(locale, "prof.reload")}
onClick={() => void control.reloadOnboardProfiles()}
onClick={() => control.rereadOnboardProfiles()}
>
<IconRefresh />
</button>
Expand Down
Loading