fix(tauri): align wake lock bridge with v2 API

This commit is contained in:
Shantur Rathore
2026-03-19 19:20:18 +00:00
parent b58728dc0e
commit 3d575f4f68
13 changed files with 1102 additions and 1515 deletions

16
package-lock.json generated
View File

@@ -3253,9 +3253,9 @@
}
},
"node_modules/@tauri-apps/api": {
"version": "2.9.1",
"resolved": "https://registry.npmjs.org/@tauri-apps/api/-/api-2.9.1.tgz",
"integrity": "sha512-IGlhP6EivjXHepbBic618GOmiWe4URJiIeZFlB7x3czM0yDHHYviH1Xvoiv4FefdkQtn6v7TuwWCRfOGdnVUGw==",
"version": "2.10.1",
"resolved": "https://registry.npmjs.org/@tauri-apps/api/-/api-2.10.1.tgz",
"integrity": "sha512-hKL/jWf293UDSUN09rR69hrToyIXBb8CjGaWC7gfinvnQrBVvnLr08FeFi38gxtugAVyVcTa5/FD/Xnkb1siBw==",
"license": "Apache-2.0 OR MIT",
"funding": {
"type": "opencollective",
@@ -10235,14 +10235,6 @@
"dev": true,
"license": "ISC"
},
"node_modules/tauri-plugin-keepawake-api": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/tauri-plugin-keepawake-api/-/tauri-plugin-keepawake-api-0.1.0.tgz",
"integrity": "sha512-XPUl66zUYiB7kCRxsTdmCoNjFM/++NWCJ4kdTo2NUOgBUa8UVYfayDWnnTzGIQbhT7qNAHs+jgKSjhqSKs/QHA==",
"dependencies": {
"@tauri-apps/api": ">=2.0.0-beta.6"
}
},
"node_modules/temp-dir": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-2.0.0.tgz",
@@ -12098,6 +12090,7 @@
"@suid/icons-material": "^0.9.0",
"@suid/material": "^0.19.0",
"@suid/system": "^0.14.0",
"@tauri-apps/api": "^2.10.1",
"@tauri-apps/plugin-notification": "^2.3.3",
"@tauri-apps/plugin-opener": "^2.5.3",
"ansi-sequence-parser": "^1.1.3",
@@ -12110,7 +12103,6 @@
"shiki": "^3.13.0",
"solid-js": "^1.8.0",
"solid-toast": "^0.5.0",
"tauri-plugin-keepawake-api": "^0.1.0",
"yaml": "^2.4.2"
},
"devDependencies": {

File diff suppressed because it is too large Load Diff

View File

@@ -19,11 +19,11 @@ thiserror = "1"
anyhow = "1"
which = "4"
libc = "0.2"
keepawake = "0.6"
tauri-plugin-dialog = "2"
dirs = "5"
tauri-plugin-opener = "2"
url = "2"
tauri-plugin-keepawake = "0.1.1"
tauri-plugin-notification = "2"
[target.'cfg(windows)'.dependencies]

File diff suppressed because one or more lines are too long

View File

@@ -2378,36 +2378,6 @@
"const": "dialog:deny-save",
"markdownDescription": "Denies the save command without any pre-configured scope."
},
{
"description": "Default permissions for the plugin\n#### This default permission set includes:\n\n- `allow-start`\n- `allow-stop`",
"type": "string",
"const": "keepawake:default",
"markdownDescription": "Default permissions for the plugin\n#### This default permission set includes:\n\n- `allow-start`\n- `allow-stop`"
},
{
"description": "Enables the start command without any pre-configured scope.",
"type": "string",
"const": "keepawake:allow-start",
"markdownDescription": "Enables the start command without any pre-configured scope."
},
{
"description": "Enables the stop command without any pre-configured scope.",
"type": "string",
"const": "keepawake:allow-stop",
"markdownDescription": "Enables the stop command without any pre-configured scope."
},
{
"description": "Denies the start command without any pre-configured scope.",
"type": "string",
"const": "keepawake:deny-start",
"markdownDescription": "Denies the start command without any pre-configured scope."
},
{
"description": "Denies the stop command without any pre-configured scope.",
"type": "string",
"const": "keepawake:deny-stop",
"markdownDescription": "Denies the stop command without any pre-configured scope."
},
{
"description": "This permission set configures which\nnotification features are by default exposed.\n\n#### Granted Permissions\n\nIt allows all notification related features.\n\n\n#### This default permission set includes:\n\n- `allow-is-permission-granted`\n- `allow-request-permission`\n- `allow-notify`\n- `allow-register-action-types`\n- `allow-register-listener`\n- `allow-cancel`\n- `allow-get-pending`\n- `allow-remove-active`\n- `allow-get-active`\n- `allow-check-permissions`\n- `allow-show`\n- `allow-batch`\n- `allow-list-channels`\n- `allow-delete-channel`\n- `allow-create-channel`\n- `allow-permission-state`",
"type": "string",

View File

@@ -2378,36 +2378,6 @@
"const": "dialog:deny-save",
"markdownDescription": "Denies the save command without any pre-configured scope."
},
{
"description": "Default permissions for the plugin\n#### This default permission set includes:\n\n- `allow-start`\n- `allow-stop`",
"type": "string",
"const": "keepawake:default",
"markdownDescription": "Default permissions for the plugin\n#### This default permission set includes:\n\n- `allow-start`\n- `allow-stop`"
},
{
"description": "Enables the start command without any pre-configured scope.",
"type": "string",
"const": "keepawake:allow-start",
"markdownDescription": "Enables the start command without any pre-configured scope."
},
{
"description": "Enables the stop command without any pre-configured scope.",
"type": "string",
"const": "keepawake:allow-stop",
"markdownDescription": "Enables the stop command without any pre-configured scope."
},
{
"description": "Denies the start command without any pre-configured scope.",
"type": "string",
"const": "keepawake:deny-start",
"markdownDescription": "Denies the start command without any pre-configured scope."
},
{
"description": "Denies the stop command without any pre-configured scope.",
"type": "string",
"const": "keepawake:deny-stop",
"markdownDescription": "Denies the stop command without any pre-configured scope."
},
{
"description": "This permission set configures which\nnotification features are by default exposed.\n\n#### Granted Permissions\n\nIt allows all notification related features.\n\n\n#### This default permission set includes:\n\n- `allow-is-permission-granted`\n- `allow-request-permission`\n- `allow-notify`\n- `allow-register-action-types`\n- `allow-register-listener`\n- `allow-cancel`\n- `allow-get-pending`\n- `allow-remove-active`\n- `allow-get-active`\n- `allow-check-permissions`\n- `allow-show`\n- `allow-batch`\n- `allow-list-channels`\n- `allow-delete-channel`\n- `allow-create-channel`\n- `allow-permission-state`",
"type": "string",

View File

@@ -3,8 +3,11 @@
mod cli_manager;
use cli_manager::{CliProcessManager, CliStatus};
use keepawake::KeepAwake;
use serde::Deserialize;
use serde_json::json;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Mutex;
use tauri::menu::{MenuBuilder, MenuItem, SubmenuBuilder};
use tauri::plugin::{Builder as PluginBuilder, TauriPlugin};
use tauri::webview::Webview;
@@ -26,9 +29,17 @@ static QUIT_REQUESTED: AtomicBool = AtomicBool::new(false);
#[cfg(windows)]
const WINDOWS_APP_USER_MODEL_ID: &str = "ai.neuralnomads.codenomad.client";
#[derive(Clone)]
pub struct AppState {
pub manager: CliProcessManager,
pub wake_lock: Mutex<Option<KeepAwake>>,
}
#[derive(Debug, Default, Deserialize)]
#[serde(default, rename_all = "camelCase")]
struct WakeLockConfig {
display: bool,
idle: bool,
sleep: bool,
}
#[tauri::command]
@@ -47,6 +58,39 @@ fn cli_restart(app: AppHandle, state: tauri::State<AppState>) -> Result<CliStatu
Ok(state.manager.status())
}
#[tauri::command]
fn wake_lock_start(
state: tauri::State<AppState>,
config: Option<WakeLockConfig>,
) -> Result<(), String> {
let config = config.unwrap_or(WakeLockConfig {
display: true,
idle: false,
sleep: false,
});
let mut builder = keepawake::Builder::default();
builder
.display(config.display)
.idle(config.idle)
.sleep(config.sleep)
.reason("CodeNomad active session")
.app_name("CodeNomad")
.app_reverse_domain("ai.neuralnomads.codenomad.client");
let wake_lock = builder.create().map_err(|err| err.to_string())?;
let mut state_lock = state.wake_lock.lock().map_err(|err| err.to_string())?;
*state_lock = Some(wake_lock);
Ok(())
}
#[tauri::command]
fn wake_lock_stop(state: tauri::State<AppState>) -> Result<(), String> {
let mut state_lock = state.wake_lock.lock().map_err(|err| err.to_string())?;
state_lock.take();
Ok(())
}
fn is_dev_mode() -> bool {
cfg!(debug_assertions) || std::env::var("TAURI_DEV").is_ok()
}
@@ -137,11 +181,11 @@ fn main() {
tauri::Builder::default()
.plugin(tauri_plugin_dialog::init())
.plugin(tauri_plugin_opener::init())
.plugin(tauri_plugin_keepawake::init())
.plugin(tauri_plugin_notification::init())
.plugin(navigation_guard)
.manage(AppState {
manager: CliProcessManager::new(),
wake_lock: Mutex::new(None),
})
.setup(|app| {
set_windows_app_user_model_id();
@@ -156,7 +200,12 @@ fn main() {
});
Ok(())
})
.invoke_handler(tauri::generate_handler![cli_get_status, cli_restart])
.invoke_handler(tauri::generate_handler![
cli_get_status,
cli_restart,
wake_lock_start,
wake_lock_stop
])
.on_menu_event(|app_handle, event| {
match event.id().0.as_str() {
// File menu

View File

@@ -18,8 +18,9 @@
"@suid/icons-material": "^0.9.0",
"@suid/material": "^0.19.0",
"@suid/system": "^0.14.0",
"@tauri-apps/plugin-opener": "^2.5.3",
"@tauri-apps/api": "^2.10.1",
"@tauri-apps/plugin-notification": "^2.3.3",
"@tauri-apps/plugin-opener": "^2.5.3",
"ansi-sequence-parser": "^1.1.3",
"debug": "^4.4.3",
"github-markdown-css": "^5.8.1",
@@ -30,7 +31,6 @@
"shiki": "^3.13.0",
"solid-js": "^1.8.0",
"solid-toast": "^0.5.0",
"tauri-plugin-keepawake-api": "^0.1.0",
"yaml": "^2.4.2"
},
"devDependencies": {

View File

@@ -1,3 +1,4 @@
import { invoke } from "@tauri-apps/api/core"
import { runtimeEnv } from "../runtime-env"
import { getLogger } from "../logger"
const log = getLogger("actions")
@@ -15,9 +16,8 @@ export async function restartCli(): Promise<boolean> {
}
if (runtimeEnv.host === "tauri") {
const tauri = (window as typeof window & { __TAURI__?: { invoke?: <T = unknown>(cmd: string, args?: Record<string, unknown>) => Promise<T> } }).__TAURI__
if (tauri?.invoke) {
await tauri.invoke("cli_restart")
if (typeof window.__TAURI__?.core?.invoke === "function") {
await invoke("cli_restart")
return true
}
return false

View File

@@ -1,3 +1,4 @@
import { invoke } from "@tauri-apps/api/core"
import { runtimeEnv } from "../runtime-env"
import { getLogger } from "../logger"
@@ -60,8 +61,7 @@ function hasAnyWakeLockSupport(): boolean {
if (api?.setWakeLock) return true
}
if (runtimeEnv.host === "tauri") {
// We'll attempt dynamic import; treat as potentially supported.
return true
return typeof window.__TAURI__?.core?.invoke === "function"
}
return Boolean((navigator as any)?.wakeLock?.request)
}
@@ -84,21 +84,18 @@ async function setElectronWakeLock(enabled: boolean): Promise<boolean> {
async function setTauriWakeLock(enabled: boolean): Promise<boolean> {
try {
const mod = await import("tauri-plugin-keepawake-api")
const start = (mod as any).start as ((config?: any) => Promise<void>) | undefined
const stop = (mod as any).stop as (() => Promise<void>) | undefined
if (!start || !stop) {
if (!hasAnyWakeLockSupport()) {
return false
}
if (enabled) {
// Plugin config supports toggling display/idle/sleep. Use a conservative
// default to keep both system + display awake.
await start({ display: true, idle: true, sleep: true })
// Match Electron's prevent-display-sleep behavior by keeping the display
// awake without blocking explicit system sleep requests.
await invoke("wake_lock_start", { config: { display: true, idle: false, sleep: false } })
return true
}
await stop()
await invoke("wake_lock_stop")
return false
} catch (error) {
log.log("[wake-lock] tauri wake lock failed", error)
@@ -137,13 +134,12 @@ export function setWakeLockDesired(nextDesired: boolean): Promise<boolean> {
inFlight = (async () => {
try {
const ok = await applyWakeLock(target)
// Treat disable attempts as applied even if the underlying API doesn't exist.
applied = target
applied = target ? ok : false
return ok
} finally {
inFlight = null
// If desired changed while in-flight, re-apply once.
if (desired !== applied) {
if (desired !== target) {
void setWakeLockDesired(desired)
}

View File

@@ -9,10 +9,14 @@ export interface RuntimeEnvironment {
}
declare global {
interface TauriCoreModule {
invoke: <T = unknown>(cmd: string, args?: Record<string, unknown>) => Promise<T>
}
interface Window {
electronAPI?: unknown
__TAURI__?: {
invoke?: <T = unknown>(cmd: string, args?: Record<string, unknown>) => Promise<T>
core?: TauriCoreModule
event?: {
listen: (event: string, handler: (payload: { payload: unknown }) => void) => Promise<() => void>
}

View File

@@ -53,7 +53,9 @@ declare global {
}
interface TauriBridge {
invoke?: <T = unknown>(cmd: string, args?: Record<string, unknown>) => Promise<T>
core?: {
invoke: <T = unknown>(cmd: string, args?: Record<string, unknown>) => Promise<T>
}
dialog?: TauriDialogModule
event?: {
listen: (event: string, handler: (payload: { payload: unknown }) => void) => Promise<() => void>

View File

@@ -1,10 +0,0 @@
declare module "tauri-plugin-keepawake-api" {
export interface KeepAwakeConfig {
display?: boolean
idle?: boolean
sleep?: boolean
}
export function start(config?: KeepAwakeConfig): Promise<void>
export function stop(): Promise<void>
}