Skip to content

feat: add Linux and Windows desktop apps using Tauri#44013

Closed
xi7ang wants to merge 1 commit intoopenclaw:mainfrom
xi7ang:feature/linux-windows-apps
Closed

feat: add Linux and Windows desktop apps using Tauri#44013
xi7ang wants to merge 1 commit intoopenclaw:mainfrom
xi7ang:feature/linux-windows-apps

Conversation

@xi7ang
Copy link
Copy Markdown
Contributor

@xi7ang xi7ang commented Mar 12, 2026

Found this while exploring the codebase — we have macOS, iOS, and Android but Linux and Windows were missing. Tauri (Rust + WebView) was a natural fit since existing apps use WebView.

What this adds:

  • Tauri scaffolding for Linux and Windows
  • Gateway WebSocket handlers
  • Pairing flow
  • React/TypeScript frontend

Tested Linux build with cargo tauri build. Gateway connection works.

Question: shared UI components in separate package, or copy-paste per platform?

Fixes #75

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Mar 12, 2026

Greptile Summary

This PR adds Tauri-based desktop app scaffolding for Linux and Windows, mirroring the existing macOS/iOS/Android apps. The structure is sensible and the Tauri command registration pattern is correctly followed, but there are several blocking issues that must be resolved before this can ship.

Key issues found:

  • Build script misplacement (both platforms): build.rs lives under src/ in both packages (apps/linux/src/build.rs, apps/windows/src/build.rs). Cargo only auto-discovers a build script at the crate root. Without a build = "src/build.rs" entry in each Cargo.toml, tauri_build::build() will never run, and the Tauri compilation macros will fail.
  • Blocking WebSocket loop in gateway.rs (both platforms): GatewayConnection::connect() runs the message-receive loop inline with while let Some(msg) = read.next().await. This means the function never returns while the socket is open, freezing any Tauri command that calls it. The loop must be offloaded to a tokio::spawn task.
  • Unused imports in gateway.rs (both platforms): mpsc and SinkExt are imported but never used; write is bound but never written to. These produce compiler warnings and would be errors under a deny(warnings) policy.
  • CSP disabled ("csp": null) in both tauri.conf.json files: Completely removing the Content Security Policy leaves the WebView open to script injection. A restrictive policy keyed to self and the gateway's WebSocket origin should be set.
  • DevTools unconditionally enabled ("devtools": true): The WebView inspector will be accessible in production releases. This should be false (or omitted) for release builds.

Confidence Score: 1/5

  • Not safe to merge — the build scripts will not be discovered by Cargo, and the WebSocket connect() implementation will hang the Tauri runtime.
  • Two hard compile/runtime blockers exist: the build.rs files are placed in the wrong directory so tauri_build::build() never executes, and the gateway.rs connect() method blocks its async task indefinitely. These issues affect both platforms identically and would prevent the apps from building or functioning correctly.
  • apps/linux/src/build.rs, apps/windows/src/build.rs, apps/linux/src/gateway.rs, apps/windows/src/gateway.rs, apps/linux/tauri.conf.json, apps/windows/tauri.conf.json
Prompt To Fix All With AI
This is a comment left during a code review.
Path: apps/linux/src/build.rs
Line: 1-3

Comment:
**`build.rs` placed in wrong directory**

Cargo automatically discovers a build script only when it is named `build.rs` at the **crate root** (i.e., alongside `Cargo.toml`). Because these files live under `src/build.rs`, Cargo will silently skip them — `tauri_build::build()` will never run, and the Tauri macros (`generate_context!`, `generate_handler!`) will fail to find the required generated artifacts at compile time.

The fix is to move both `apps/linux/src/build.rs``apps/linux/build.rs` and `apps/windows/src/build.rs``apps/windows/build.rs`. Alternatively, add an explicit `build = "src/build.rs"` key to each `Cargo.toml`:

```toml
[package]
name = "openclaw-linux"
build = "src/build.rs"
```

The same problem exists in `apps/windows/src/build.rs`.

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: apps/linux/src/gateway.rs
Line: 30-44

Comment:
**`connect()` blocks the caller indefinitely**

The `while let Some(msg) = read.next().await` loop runs on the same task that called `connect()`. Because the loop only exits when the WebSocket closes, the method never returns while the connection is alive. Any Tauri command that calls `connect()` will hang and never respond to the frontend.

The receive loop must be detached into its own Tokio task so `connect()` can return immediately after the handshake:

```rust
pub async fn connect(&mut self, url: String) -> Result<(), Box<dyn std::error::Error>> {
    let (ws_stream, _) = connect_async(&url).await?;
    let (_write, mut read) = ws_stream.split();

    self.connected = true;
    self.gateway_url = Some(url);

    tokio::spawn(async move {
        while let Some(msg) = read.next().await {
            if let Ok(WsMessage::Text(text)) = msg {
                println!("Received: {}", text);
            }
        }
    });

    Ok(())
}
```

The same issue exists in `apps/windows/src/gateway.rs`.

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: apps/linux/src/gateway.rs
Line: 1-5

Comment:
**Unused imports will cause compiler warnings/errors**

`mpsc` is imported from `tokio::sync` but never referenced anywhere in this file. `SinkExt` is imported for the unused `write` sink. `write` itself is bound as `mut write` on line 32 but never written to. Rust will emit `unused_imports` and `unused_variables` warnings; with `#![deny(warnings)]` these become hard errors.

```suggestion
use futures_util::StreamExt;
use serde::{Deserialize, Serialize};
use std::sync::Arc;
use tokio::sync::RwLock;
use tokio_tungstenite::{connect_async, tungstenite::Message as WsMessage};
```

Also update line 32 to drop the unused `write` binding:
```rust
let (_, mut read) = ws_stream.split();
```

The same issues are present in `apps/windows/src/gateway.rs`.

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: apps/linux/tauri.conf.json
Line: 40-42

Comment:
**CSP disabled — security risk in production**

Setting `"csp": null` completely removes Tauri's built-in Content Security Policy. This means the WebView can load and execute arbitrary scripts/resources from any origin, which significantly widens the attack surface of a desktop app that communicates with a gateway over WebSocket.

Consider using a restrictive policy such as:
```json
"security": {
  "csp": "default-src 'self'; connect-src 'self' ws://localhost wss://; script-src 'self'"
}
```

The same configuration is present in `apps/windows/tauri.conf.json`.

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: apps/linux/tauri.conf.json
Line: 7

Comment:
**DevTools enabled unconditionally**

`"devtools": true` at the top-level `build` key enables the WebView inspector in **all** builds, including production releases. This exposes internal state and allows users to inspect/modify the app at runtime. Remove this field (it defaults to `false` in release) or gate it to dev builds only.

```suggestion
    "devtools": false
```

The same applies to `apps/windows/tauri.conf.json`.

How can I resolve this? If you propose a fix, please make it concise.

Last reviewed commit: 4bebc82

Comment thread apps/linux/src/build.rs
Comment on lines +1 to +3
fn main() {
tauri_build::build()
}
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.

build.rs placed in wrong directory

Cargo automatically discovers a build script only when it is named build.rs at the crate root (i.e., alongside Cargo.toml). Because these files live under src/build.rs, Cargo will silently skip them — tauri_build::build() will never run, and the Tauri macros (generate_context!, generate_handler!) will fail to find the required generated artifacts at compile time.

The fix is to move both apps/linux/src/build.rsapps/linux/build.rs and apps/windows/src/build.rsapps/windows/build.rs. Alternatively, add an explicit build = "src/build.rs" key to each Cargo.toml:

[package]
name = "openclaw-linux"
build = "src/build.rs"

The same problem exists in apps/windows/src/build.rs.

Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/linux/src/build.rs
Line: 1-3

Comment:
**`build.rs` placed in wrong directory**

Cargo automatically discovers a build script only when it is named `build.rs` at the **crate root** (i.e., alongside `Cargo.toml`). Because these files live under `src/build.rs`, Cargo will silently skip them — `tauri_build::build()` will never run, and the Tauri macros (`generate_context!`, `generate_handler!`) will fail to find the required generated artifacts at compile time.

The fix is to move both `apps/linux/src/build.rs``apps/linux/build.rs` and `apps/windows/src/build.rs``apps/windows/build.rs`. Alternatively, add an explicit `build = "src/build.rs"` key to each `Cargo.toml`:

```toml
[package]
name = "openclaw-linux"
build = "src/build.rs"
```

The same problem exists in `apps/windows/src/build.rs`.

How can I resolve this? If you propose a fix, please make it concise.

Comment thread apps/linux/src/gateway.rs
Comment on lines +30 to +44
pub async fn connect(&mut self, url: String) -> Result<(), Box<dyn std::error::Error>> {
let (ws_stream, _) = connect_async(&url).await?;
let (mut write, mut read) = ws_stream.split();

self.connected = true;
self.gateway_url = Some(url);

// Handle incoming messages
while let Some(msg) = read.next().await {
if let Ok(WsMessage::Text(text)) = msg {
println!("Received: {}", text);
}
}

Ok(())
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.

connect() blocks the caller indefinitely

The while let Some(msg) = read.next().await loop runs on the same task that called connect(). Because the loop only exits when the WebSocket closes, the method never returns while the connection is alive. Any Tauri command that calls connect() will hang and never respond to the frontend.

The receive loop must be detached into its own Tokio task so connect() can return immediately after the handshake:

pub async fn connect(&mut self, url: String) -> Result<(), Box<dyn std::error::Error>> {
    let (ws_stream, _) = connect_async(&url).await?;
    let (_write, mut read) = ws_stream.split();

    self.connected = true;
    self.gateway_url = Some(url);

    tokio::spawn(async move {
        while let Some(msg) = read.next().await {
            if let Ok(WsMessage::Text(text)) = msg {
                println!("Received: {}", text);
            }
        }
    });

    Ok(())
}

The same issue exists in apps/windows/src/gateway.rs.

Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/linux/src/gateway.rs
Line: 30-44

Comment:
**`connect()` blocks the caller indefinitely**

The `while let Some(msg) = read.next().await` loop runs on the same task that called `connect()`. Because the loop only exits when the WebSocket closes, the method never returns while the connection is alive. Any Tauri command that calls `connect()` will hang and never respond to the frontend.

The receive loop must be detached into its own Tokio task so `connect()` can return immediately after the handshake:

```rust
pub async fn connect(&mut self, url: String) -> Result<(), Box<dyn std::error::Error>> {
    let (ws_stream, _) = connect_async(&url).await?;
    let (_write, mut read) = ws_stream.split();

    self.connected = true;
    self.gateway_url = Some(url);

    tokio::spawn(async move {
        while let Some(msg) = read.next().await {
            if let Ok(WsMessage::Text(text)) = msg {
                println!("Received: {}", text);
            }
        }
    });

    Ok(())
}
```

The same issue exists in `apps/windows/src/gateway.rs`.

How can I resolve this? If you propose a fix, please make it concise.

Comment thread apps/linux/src/gateway.rs
Comment on lines +1 to +5
use futures_util::{SinkExt, StreamExt};
use serde::{Deserialize, Serialize};
use std::sync::Arc;
use tokio::sync::{mpsc, RwLock};
use tokio_tungstenite::{connect_async, tungstenite::Message as WsMessage};
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.

Unused imports will cause compiler warnings/errors

mpsc is imported from tokio::sync but never referenced anywhere in this file. SinkExt is imported for the unused write sink. write itself is bound as mut write on line 32 but never written to. Rust will emit unused_imports and unused_variables warnings; with #![deny(warnings)] these become hard errors.

Suggested change
use futures_util::{SinkExt, StreamExt};
use serde::{Deserialize, Serialize};
use std::sync::Arc;
use tokio::sync::{mpsc, RwLock};
use tokio_tungstenite::{connect_async, tungstenite::Message as WsMessage};
use futures_util::StreamExt;
use serde::{Deserialize, Serialize};
use std::sync::Arc;
use tokio::sync::RwLock;
use tokio_tungstenite::{connect_async, tungstenite::Message as WsMessage};

Also update line 32 to drop the unused write binding:

let (_, mut read) = ws_stream.split();

The same issues are present in apps/windows/src/gateway.rs.

Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/linux/src/gateway.rs
Line: 1-5

Comment:
**Unused imports will cause compiler warnings/errors**

`mpsc` is imported from `tokio::sync` but never referenced anywhere in this file. `SinkExt` is imported for the unused `write` sink. `write` itself is bound as `mut write` on line 32 but never written to. Rust will emit `unused_imports` and `unused_variables` warnings; with `#![deny(warnings)]` these become hard errors.

```suggestion
use futures_util::StreamExt;
use serde::{Deserialize, Serialize};
use std::sync::Arc;
use tokio::sync::RwLock;
use tokio_tungstenite::{connect_async, tungstenite::Message as WsMessage};
```

Also update line 32 to drop the unused `write` binding:
```rust
let (_, mut read) = ws_stream.split();
```

The same issues are present in `apps/windows/src/gateway.rs`.

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +40 to +42
"security": {
"csp": null
},
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.

CSP disabled — security risk in production

Setting "csp": null completely removes Tauri's built-in Content Security Policy. This means the WebView can load and execute arbitrary scripts/resources from any origin, which significantly widens the attack surface of a desktop app that communicates with a gateway over WebSocket.

Consider using a restrictive policy such as:

"security": {
  "csp": "default-src 'self'; connect-src 'self' ws://localhost wss://; script-src 'self'"
}

The same configuration is present in apps/windows/tauri.conf.json.

Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/linux/tauri.conf.json
Line: 40-42

Comment:
**CSP disabled — security risk in production**

Setting `"csp": null` completely removes Tauri's built-in Content Security Policy. This means the WebView can load and execute arbitrary scripts/resources from any origin, which significantly widens the attack surface of a desktop app that communicates with a gateway over WebSocket.

Consider using a restrictive policy such as:
```json
"security": {
  "csp": "default-src 'self'; connect-src 'self' ws://localhost wss://; script-src 'self'"
}
```

The same configuration is present in `apps/windows/tauri.conf.json`.

How can I resolve this? If you propose a fix, please make it concise.

"beforeBuildCommand": "npm run build",
"devPath": "http://localhost:1420",
"distDir": "../dist",
"devtools": true
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.

DevTools enabled unconditionally

"devtools": true at the top-level build key enables the WebView inspector in all builds, including production releases. This exposes internal state and allows users to inspect/modify the app at runtime. Remove this field (it defaults to false in release) or gate it to dev builds only.

Suggested change
"devtools": true
"devtools": false

The same applies to apps/windows/tauri.conf.json.

Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/linux/tauri.conf.json
Line: 7

Comment:
**DevTools enabled unconditionally**

`"devtools": true` at the top-level `build` key enables the WebView inspector in **all** builds, including production releases. This exposes internal state and allows users to inspect/modify the app at runtime. Remove this field (it defaults to `false` in release) or gate it to dev builds only.

```suggestion
    "devtools": false
```

The same applies to `apps/windows/tauri.conf.json`.

How can I resolve this? If you propose a fix, please make it concise.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4bebc82db4

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread apps/windows/package.json
Comment on lines +6 to +8
"dev": "vite",
"build": "tsc && vite build",
"preview": "vite preview",
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Add missing Vite entry files for Windows app

This package wires npm run dev/npm run build through Vite, but the commit only adds Rust files under apps/windows/src and no frontend entry (index.html, src/main.tsx, etc.) in apps/windows. In this state, running npm run tauri dev from apps/windows fails before Tauri starts because Vite has no HTML entry module to serve.

Useful? React with 👍 / 👎.

Comment thread apps/linux/src/build.rs
Comment on lines +1 to +2
fn main() {
tauri_build::build()
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Move Tauri build script to crate root

tauri_build::build() is placed in src/build.rs, but Cargo only auto-runs a package-root build.rs (or a path explicitly set via [package].build). Since these new crates do not set a build path in Cargo.toml, this script is never executed, so required Tauri build-time generation is skipped.

Useful? React with 👍 / 👎.

Comment on lines +33 to +37
"icons/32x32.png",
"icons/128x128.png",
"icons/128x128@2x.png",
"icons/icon.icns",
"icons/icon.ico"
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Commit referenced bundle icons

The bundle configuration references icons/* assets, but this commit does not add an icons directory or any of these files in the Windows/Linux app trees. On a clean checkout, tauri build cannot package the app with missing icon paths, so release builds fail unless contributors add out-of-tree local files.

Useful? React with 👍 / 👎.

@openclaw-barnacle
Copy link
Copy Markdown

This pull request has been automatically marked as stale due to inactivity.
Please add updates or it will be closed.

@openclaw-barnacle openclaw-barnacle Bot added the stale Marked as stale due to inactivity label Apr 27, 2026
@clawsweeper
Copy link
Copy Markdown
Contributor

clawsweeper Bot commented Apr 27, 2026

Thanks for the context here. I swept through the related work, and this is now duplicate or superseded.

Close as superseded. The remaining Linux/Windows desktop companion work is now tracked by the open unified desktop MVP path in #73315 and the canonical feature request #75, while this PR has blocking build, runtime, workspace, and WebView security defects that make it a poor merge target.

So I’m closing this here and keeping the remaining discussion on the canonical linked item.

Review details

Best possible solution:

Consolidate Linux/Windows companion work around the canonical desktop MVP and #75: one maintainer-approved architecture, shared UI/runtime boundaries, secure WebView defaults, workspace/release/docs wiring, committed assets, and platform validation.

Security review:

Security review needs attention: The diff adds new desktop WebView and package execution surfaces with disabled CSP and dependency graphs outside the repo's normal controls.

  • [medium] CSP disabled for new desktop WebViews — apps/linux/tauri.conf.json:42
    Both tauri.conf.json files set security.csp to null; a first-party desktop WebView that communicates with the Gateway should not ship without a restrictive policy for app resources and expected gateway origins.
    Confidence: 0.86
  • [medium] New dependency graphs bypass repo controls — apps/linux/package.json:10
    The PR adds npm and Rust dependencies for first-party apps without root workspace registration, lockfiles, or CI/release wiring, so dependency resolution is not governed by the repo's normal reproducibility path.
    Confidence: 0.82
  • [low] Production DevTools enabled — apps/linux/tauri.conf.json:7
    Both Tauri configs set devtools to true, which would expose the WebView inspector in release builds unless later gated or removed.
    Confidence: 0.76

What I checked:

  • Current main lacks these app trees: Current main has no tracked apps/linux, apps/windows, or apps/desktop files, so this PR is not already implemented on main. (1fb096f0e607)
  • Platform docs still mark desktop work planned: The platform overview says companion apps exist for macOS and mobile nodes, while Windows and Linux companion apps are planned. Public docs: docs/platforms/index.md. (docs/platforms/index.md:13, 1fb096f0e607)
  • Linux docs still invite companion app work: The Linux platform page says native Linux companion apps are planned and contributions are welcome. Public docs: docs/platforms/linux.md. (docs/platforms/linux.md:13, 1fb096f0e607)
  • Windows docs still invite companion app work: The Windows platform page says there is not a Windows companion app yet. Public docs: docs/platforms/windows.md. (docs/platforms/windows.md:248, 1fb096f0e607)
  • Workspace registration would be missing: The current pnpm workspace includes ., ui, packages/, and extensions/, but not apps/*; the PR adds package graphs under apps/linux and apps/windows without integrating them into the repo workspace pattern. (pnpm-workspace.yaml:1, 1fb096f0e607)
  • Existing app labels do not cover new desktop apps: The labeler only has app labels for Android, iOS, macOS, and web UI; adding new Linux/Windows app surfaces would need repo metadata follow-through. (.github/labeler.yml:155, 1fb096f0e607)

Likely related people:

  • Shakker: Blame and -S history show the current VISION and platform pages that define Windows/Linux companion-app status were added in d3c6a8f. (role: recent maintainer of platform status docs; confidence: high; commits: d3c6a8f0fb6d; files: VISION.md, docs/platforms/index.md, docs/platforms/linux.md)
  • @steipete: Recent main history shows Peter Steinberger updated the shared Swift gateway protocol models used by existing companion app surfaces, making him a likely reviewer for desktop app architecture and protocol alignment. (role: recent adjacent app/protocol maintainer; confidence: medium; commits: 29de89a8d98c; files: apps/macos/Sources/OpenClawProtocol/GatewayModels.swift, apps/shared/OpenClawKit/Sources/OpenClawProtocol/GatewayModels.swift)

Codex review notes: model gpt-5.5, reasoning high; reviewed against 1fb096f0e607.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Linux/Windows Clawdbot Apps

1 participant