mirror of
https://github.com/SerTimBerrners-Lee/talkis.git
synced 2026-10-02 20:29:19 +00:00
fix: keep hidden windows active during idle
This commit is contained in:
parent
048f9d2096
commit
03fb898932
6 changed files with 135 additions and 0 deletions
|
|
@ -37,6 +37,7 @@ fn create_settings_window(
|
|||
.title("Talkis")
|
||||
.visible(visible)
|
||||
.focused(visible)
|
||||
.background_throttling(tauri::utils::config::BackgroundThrottlingPolicy::Disabled)
|
||||
.inner_size(920.0, 680.0)
|
||||
.min_inner_size(820.0, 560.0)
|
||||
.center();
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@
|
|||
{
|
||||
"label": "widget",
|
||||
"visible": false,
|
||||
"backgroundThrottling": "disabled",
|
||||
"title": "Talkis",
|
||||
"url": "index.html?window=widget",
|
||||
"width": 129,
|
||||
|
|
|
|||
69
src/lib/backgroundActivity.test.ts
Normal file
69
src/lib/backgroundActivity.test.ts
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
import { describe, expect, test } from "bun:test";
|
||||
|
||||
import { holdBackgroundActivity } from "./backgroundActivity";
|
||||
|
||||
describe("background activity lifetime", () => {
|
||||
test("keeps a granted shared lock pending until the window unmounts", async () => {
|
||||
let settled = false;
|
||||
let signal: AbortSignal | undefined;
|
||||
let request: Promise<void> = Promise.resolve();
|
||||
const release = holdBackgroundActivity("widget", {
|
||||
request: (name, options, callback): Promise<void> => {
|
||||
expect(name).toBe("talkis:widget:background");
|
||||
expect(options.mode).toBe("shared");
|
||||
signal = options.signal;
|
||||
request = callback({ name, mode: "shared" }).then(() => {
|
||||
settled = true;
|
||||
});
|
||||
|
||||
return request;
|
||||
},
|
||||
});
|
||||
|
||||
await Promise.resolve();
|
||||
expect(settled).toBe(false);
|
||||
expect(signal?.aborted).toBe(false);
|
||||
|
||||
release();
|
||||
release();
|
||||
await request;
|
||||
|
||||
expect(settled).toBe(true);
|
||||
expect(signal?.aborted).toBe(true);
|
||||
});
|
||||
|
||||
test("releases a late grant when cleanup precedes acquisition", async () => {
|
||||
let grant: (() => Promise<void>) | undefined;
|
||||
let signal: AbortSignal | undefined;
|
||||
const release = holdBackgroundActivity("settings", {
|
||||
request: (name, options, callback): Promise<void> => {
|
||||
signal = options.signal;
|
||||
grant = (): Promise<void> => callback({ name, mode: "shared" });
|
||||
|
||||
return Promise.resolve();
|
||||
},
|
||||
});
|
||||
|
||||
release();
|
||||
expect(signal?.aborted).toBe(true);
|
||||
expect(grant).toBeDefined();
|
||||
await grant?.();
|
||||
});
|
||||
|
||||
test("does not block window startup if the browser rejects the request", async () => {
|
||||
const release = holdBackgroundActivity("widget", {
|
||||
request: (): Promise<void> => Promise.reject(new Error("locks disabled")),
|
||||
});
|
||||
|
||||
await Promise.resolve();
|
||||
expect(release).toBeFunction();
|
||||
release();
|
||||
});
|
||||
|
||||
test("supports browsers without the Locks API", () => {
|
||||
const release = holdBackgroundActivity("settings");
|
||||
|
||||
expect(release).toBeFunction();
|
||||
release();
|
||||
});
|
||||
});
|
||||
58
src/lib/backgroundActivity.ts
Normal file
58
src/lib/backgroundActivity.ts
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
import { logError, logInfo } from "./logger";
|
||||
|
||||
interface BackgroundLockManager {
|
||||
request(
|
||||
name: string,
|
||||
options: LockOptions,
|
||||
callback: (lock: Lock | null) => Promise<void>,
|
||||
): Promise<void>;
|
||||
}
|
||||
|
||||
/** Keep event-driven background windows eligible to receive hotkeys and IPC. */
|
||||
export function holdBackgroundActivity(
|
||||
windowLabel: "widget" | "settings",
|
||||
locks: BackgroundLockManager | undefined =
|
||||
typeof navigator === "undefined" ? undefined : navigator.locks,
|
||||
): () => void {
|
||||
if (!locks) {
|
||||
return (): void => {};
|
||||
}
|
||||
|
||||
const controller = new AbortController();
|
||||
let released = false;
|
||||
let finish: () => void = (): void => {};
|
||||
const lifetime = new Promise<void>((resolve) => {
|
||||
finish = resolve;
|
||||
});
|
||||
|
||||
try {
|
||||
void locks.request(
|
||||
`talkis:${windowLabel}:background`,
|
||||
{ mode: "shared", signal: controller.signal },
|
||||
async (): Promise<void> => {
|
||||
if (released) return;
|
||||
|
||||
void logInfo("BACKGROUND", `Activity lock acquired: ${windowLabel}`);
|
||||
await lifetime;
|
||||
},
|
||||
).catch((error: unknown) => {
|
||||
if (!released) {
|
||||
void logError(
|
||||
"BACKGROUND",
|
||||
`Activity lock unavailable (${windowLabel}): ${String(error)}`,
|
||||
);
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
void logError(
|
||||
"BACKGROUND",
|
||||
`Failed to request activity lock (${windowLabel}): ${String(error)}`,
|
||||
);
|
||||
}
|
||||
|
||||
return (): void => {
|
||||
released = true;
|
||||
finish();
|
||||
controller.abort();
|
||||
};
|
||||
}
|
||||
|
|
@ -45,6 +45,7 @@ import {
|
|||
} from "../../lib/store";
|
||||
import { checkAllPermissions } from "../../lib/permissions";
|
||||
import { logError, logInfo } from "../../lib/logger";
|
||||
import { holdBackgroundActivity } from "../../lib/backgroundActivity";
|
||||
import { UserPanel } from "../../components/UserPanel";
|
||||
import { watchThemePreference } from "../../lib/theme";
|
||||
import { syncWindowsTitlebarTheme } from "../../lib/windowsTitlebar";
|
||||
|
|
@ -381,6 +382,8 @@ function getCurrentDomSelectionText(): string {
|
|||
}
|
||||
|
||||
export function SettingsApp(): ReactElement {
|
||||
useEffect(() => holdBackgroundActivity("settings"), []);
|
||||
|
||||
const { t } = useI18n();
|
||||
const initialQuery = new URLSearchParams(window.location.search);
|
||||
const checkUpdateAtStartup = initialQuery.get("checkUpdate") === "1";
|
||||
|
|
|
|||
|
|
@ -54,6 +54,7 @@ import {
|
|||
transcribeFilePathOnly,
|
||||
} from "../../lib/fileTranscription";
|
||||
import { logError, logInfo } from "../../lib/logger";
|
||||
import { holdBackgroundActivity } from "../../lib/backgroundActivity";
|
||||
import { tn, useI18n } from "../../lib/i18n";
|
||||
import {
|
||||
requestSystemAudioPermission,
|
||||
|
|
@ -339,6 +340,8 @@ function isCallCapturePermissionError(error: unknown): boolean {
|
|||
}
|
||||
|
||||
export function Widget() {
|
||||
useEffect(() => holdBackgroundActivity("widget"), []);
|
||||
|
||||
const { t } = useI18n();
|
||||
const widgetWindow = getCurrentWindow();
|
||||
const dragStartRef = useRef<{ x: number; y: number } | null>(null);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue