Skip to content
Closed
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
4 changes: 4 additions & 0 deletions apps/desktop/src/ipc/DesktopIpcHandlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ import {
import * as PreviewIpc from "./methods/preview.ts";
import * as AppActivationIpc from "./methods/appActivation.ts";
import { getWslState, setWslBackendEnabled, setWslDistro, setWslOnly } from "./methods/wsl.ts";
import { getKeepAwakeState, setKeepAwakeEnabled } from "./methods/keepAwake.ts";

export const installDesktopIpcHandlers = Effect.fn("desktop.ipc.installHandlers")(function* () {
const ipc = yield* DesktopIpc.DesktopIpc;
Expand Down Expand Up @@ -125,6 +126,9 @@ export const installDesktopIpcHandlers = Effect.fn("desktop.ipc.installHandlers"
yield* ipc.handle(setWslDistro);
yield* ipc.handle(setWslOnly);

yield* ipc.handle(getKeepAwakeState);
yield* ipc.handle(setKeepAwakeEnabled);

yield* ipc.handle(pickFolder);
yield* ipc.handle(pickProjectFavicon);
yield* ipc.handle(pickThemeFiles);
Expand Down
2 changes: 2 additions & 0 deletions apps/desktop/src/ipc/channels.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ export const GET_WSL_STATE_CHANNEL = "desktop:get-wsl-state";
export const SET_WSL_BACKEND_ENABLED_CHANNEL = "desktop:set-wsl-backend-enabled";
export const SET_WSL_DISTRO_CHANNEL = "desktop:set-wsl-distro";
export const SET_WSL_ONLY_CHANNEL = "desktop:set-wsl-only";
export const GET_KEEP_AWAKE_STATE_CHANNEL = "desktop:get-keep-awake-state";
export const SET_KEEP_AWAKE_ENABLED_CHANNEL = "desktop:set-keep-awake-enabled";
export const SSH_PASSWORD_PROMPT_CANCELLED_RESULT = "ssh-password-prompt-cancelled";
export const PREVIEW_CREATE_TAB_CHANNEL = "desktop:preview-create-tab";
export const PREVIEW_CLOSE_TAB_CHANNEL = "desktop:preview-close-tab";
Expand Down
30 changes: 30 additions & 0 deletions apps/desktop/src/ipc/methods/keepAwake.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import * as Effect from "effect/Effect";
import * as Schema from "effect/Schema";

import * as DesktopKeepAwake from "../../power/DesktopKeepAwake.ts";
import * as IpcChannels from "../channels.ts";
import * as DesktopIpc from "../DesktopIpc.ts";

const SetKeepAwakeEnabledInput = Schema.Struct({
enabled: Schema.Boolean,
});

export const getKeepAwakeState = DesktopIpc.makeIpcMethod({
channel: IpcChannels.GET_KEEP_AWAKE_STATE_CHANNEL,
payload: Schema.Void,
result: DesktopKeepAwake.DesktopKeepAwakeStateSchema,
handler: Effect.fn("desktop.ipc.keepAwake.getState")(function* () {
const keepAwake = yield* DesktopKeepAwake.DesktopKeepAwake;
return yield* keepAwake.getState;
}),
});

export const setKeepAwakeEnabled = DesktopIpc.makeIpcMethod({
channel: IpcChannels.SET_KEEP_AWAKE_ENABLED_CHANNEL,
payload: SetKeepAwakeEnabledInput,
result: DesktopKeepAwake.DesktopKeepAwakeStateSchema,
handler: Effect.fn("desktop.ipc.keepAwake.setEnabled")(function* (input) {
const keepAwake = yield* DesktopKeepAwake.DesktopKeepAwake;
return yield* keepAwake.setEnabled(input.enabled);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 High methods/keepAwake.ts:28

On Windows, enabling Keep Awake and then switching power plans leaves the original plan permanently forced to lid=Do Nothing and sleep/hibernate never, while disable or crash recovery writes those saved values into the newly active plan. DesktopKeepAwake saves only numeric values from SCHEME_CURRENT and later restores SCHEME_CURRENT again, so this handler can cause persistent, incorrect system power settings. Persist the saved scheme GUID and restore each affected scheme, or prevent and reconcile plan changes while Keep Awake is enabled.

Also found in 1 other location(s)

apps/web/src/components/settings/ConnectionsSettings.tsx:3366

Enabling this row makes the Keep Awake service modify SCHEME_CURRENT, but it saves only numeric values, not the active scheme identity. If a Windows user switches power plans while Keep Awake is on, the reassert loop will force the new current plan too; disable/quit then restores the old plan's saved values into whichever plan is current, while the original plan remains forced to never sleep/lid-do-nothing. This leaves persistent, incorrect system power settings (and potentially a laptop that will not sleep) until manually repaired. Save the scheme GUID and restore each affected scheme, or prevent/reconcile plan changes before restoring.

🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/desktop/src/ipc/methods/keepAwake.ts around line 28:

On Windows, enabling Keep Awake and then switching power plans leaves the original plan permanently forced to `lid=Do Nothing` and sleep/hibernate `never`, while disable or crash recovery writes those saved values into the newly active plan. `DesktopKeepAwake` saves only numeric values from `SCHEME_CURRENT` and later restores `SCHEME_CURRENT` again, so this handler can cause persistent, incorrect system power settings. Persist the saved scheme GUID and restore each affected scheme, or prevent and reconcile plan changes while Keep Awake is enabled.

Also found in 1 other location(s):
- apps/web/src/components/settings/ConnectionsSettings.tsx:3366 -- Enabling this row makes the Keep Awake service modify `SCHEME_CURRENT`, but it saves only numeric values, not the active scheme identity. If a Windows user switches power plans while Keep Awake is on, the reassert loop will force the new current plan too; disable/quit then restores the old plan's saved values into whichever plan is current, while the original plan remains forced to never sleep/lid-do-nothing. This leaves persistent, incorrect system power settings (and potentially a laptop that will not sleep) until manually repaired. Save the scheme GUID and restore each affected scheme, or prevent/reconcile plan changes before restoring.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 0ad37c0 — originals are saved per scheme GUID (resolved via /getactivescheme, SCHEME_CURRENT alias only as fallback), the reassert loop tracks newly switched-to plans as additional touched schemes, and disable restores every touched scheme to its own originals with a single /setactive.

}),
});
10 changes: 10 additions & 0 deletions apps/desktop/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ import * as DesktopWindow from "./window/DesktopWindow.ts";
import * as DesktopWslBackend from "./wsl/DesktopWslBackend.ts";
import * as DesktopWslEnvironment from "./wsl/DesktopWslEnvironment.ts";
import * as DesktopWslServerTree from "./wsl/DesktopWslServerTree.ts";
import * as DesktopKeepAwake from "./power/DesktopKeepAwake.ts";

const desktopEnvironmentLayer = Layer.unwrap(
Effect.gen(function* () {
Expand Down Expand Up @@ -186,6 +187,14 @@ const desktopWslBackendLayer = DesktopWslBackend.layer.pipe(
Layer.provideMerge(desktopBackendLayer),
);

// KeepAwake implements Brutal Awake behavior on Windows: a
// powerSaveBlocker ("prevent-display-sleep") plus SetThreadExecutionState
// with display/away-mode flags plus powercfg lid=Do Nothing and
// sleep/hibernate=never with save/restore, a userData crash-recovery file,
// and a scoped 25 s re-assert loop. Other platforms hold the display-sleep
// blocker only. The Layer.scoped release restores power settings on app quit.
const desktopKeepAwakeLayer = DesktopKeepAwake.layer;

const desktopLocalEnvironmentAuthLayer = DesktopLocalEnvironmentAuth.layer.pipe(
Layer.provideMerge(desktopBackendLayer),
);
Expand All @@ -201,6 +210,7 @@ const desktopApplicationLayer = Layer.mergeAll(
Layer.provideMerge(desktopSnapShotLayer),
Layer.provideMerge(DesktopUpdates.layer),
Layer.provideMerge(desktopWslBackendLayer),
Layer.provideMerge(desktopKeepAwakeLayer),
Layer.provideMerge(desktopLocalEnvironmentAuthLayer),
);

Expand Down
Loading
Loading