Skip to content
Open
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
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,18 @@ The built-in Web UI at `http://127.0.0.1:8848` provides:
|:----:|:-------:|
| ![Chat](screenshots/chat.png) | ![Terminal](screenshots/terminal.png) |

### Desktop app (macOS · Windows · Linux)

A native desktop wrapper is available in [`desktop/`](desktop/). It packages
the Web UI in a Tauri v2 shell with a transparent, chrome-free title bar and
native window drag — no browser required.

```bash
cd desktop && npm install && npm run build
```

See [`desktop/README.md`](desktop/README.md) for full setup instructions.

### Remote access

For accessing the Web UI from outside localhost:
Expand Down
11 changes: 11 additions & 0 deletions README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,17 @@ CCCC 实现的是 IM 级消息语义,而不是"往终端里粘贴一段文字"
|:----:|:----:|
| ![Chat](screenshots/chat.png) | ![Terminal](screenshots/terminal.png) |

### 桌面客户端(macOS · Windows · Linux)

[`desktop/`](desktop/) 目录提供原生桌面套壳,基于 Tauri v2。
无需打开浏览器,支持透明无边框标题栏与原生窗口拖动。

```bash
cd desktop && npm install && npm run build
```

详细说明见 [`desktop/README.zh-CN.md`](desktop/README.zh-CN.md)。

### 远程访问

从外部访问 Web UI:
Expand Down
3 changes: 3 additions & 0 deletions desktop/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
node_modules/
src-tauri/target/
src-tauri/gen/
142 changes: 142 additions & 0 deletions desktop/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
# CCCC Desktop

A native desktop wrapper for the CCCC web UI, built with [Tauri v2](https://tauri.app/).

> **Status**: Community contribution — macOS tested and working. Windows / Linux
> builds should work but have not been verified yet.

## What this adds

| Feature | Detail |
|---------|--------|
| Native `.app` / `.exe` | No browser required — launch CCCC like any other desktop app |
| Transparent title bar (macOS) | `titleBarStyle: Overlay` gives a seamless, chrome-free look |
| Native window drag | Uses `NSWindow.setMovableByWindowBackground` so the entire top bar is draggable, even though the UI is served from a remote origin (`localhost:8848`) |
| Packaged DMG | `tauri build` produces a signed-ready `.app` bundle and `.dmg` installer |

### Screenshot

> The transparent title bar blends into the CCCC dark UI — no grey chrome, no
> separate title-bar colour mismatch.

![cccc desktop](../screenshots/desktop-macos.png)

---

## Prerequisites

| Tool | Version |
|------|---------|
| [Rust](https://rustup.rs/) | 1.77+ |
| [Node.js](https://nodejs.org/) | 18+ |
| [CCCC daemon](https://github.com/ChesterRa/cccc) | running on `localhost:8848` |

macOS also needs Xcode Command Line Tools:

```bash
xcode-select --install
```

---

## Quick start

```bash
# 1. Install the Tauri CLI
cd desktop
npm install

# 2. Make sure the CCCC daemon is running first
cccc # or: cccc daemon start

# 3. Launch in dev mode (hot-reload from localhost:8848)
npm run dev

# 4. Build a release bundle
npm run build
# → desktop/src-tauri/target/release/bundle/macos/cccc.app
# → desktop/src-tauri/target/release/bundle/dmg/cccc_x.y.z_x64.dmg
```

---

## How it works

The wrapper is a **pure shell** — it contains no frontend code of its own.
`tauri.conf.json` points both the dev URL and the release URL at
`http://localhost:8848/ui/`, so the Tauri webview simply renders whatever
the CCCC daemon serves.

### Transparent title bar + drag (macOS)

macOS's `titleBarStyle: Overlay` hides the default chrome and exposes the
traffic-light buttons over the content area. Because the webview loads a
**remote** origin, Tauri's `data-tauri-drag-region` attribute and the JS
`startDragging()` API are both blocked by the renderer sandbox. We work
around this with two layers:

1. **CSS shim** (injected via `on_page_load`): a 28 px transparent overlay div
is appended to every page so the UI body is pushed down below the
traffic-light buttons.

2. **Native NSWindow API** (Rust `setup`): `setMovableByWindowBackground: YES`
tells macOS to treat any unobstructed window background pixel as a drag
handle, which works regardless of content origin.

```
src-tauri/src/lib.rs
├── INJECT_SCRIPT — CSS shim injected on every page load
└── setup()
└── #[cfg(target_os = "macos")]
└── NSWindow::setMovableByWindowBackground(true)
```

---

## Configuration

All Tauri settings live in `src-tauri/tauri.conf.json`. The most likely
things you might want to change:

```jsonc
{
"build": {
// Change if your CCCC daemon runs on a different port
"devUrl": "http://localhost:8848/ui/"
},
"app": {
"windows": [{
"width": 1280, // initial window size
"height": 800,
"minWidth": 900,
"minHeight": 600
}]
}
}
```

---

## Project layout

```
desktop/
├── package.json npm wrapper (only @tauri-apps/cli)
├── src-tauri/
│ ├── Cargo.toml Rust crate (cccc-desktop)
│ ├── tauri.conf.json Tauri app configuration
│ ├── capabilities/
│ │ └── default.json permission set
│ ├── icons/ app icons (all sizes + .icns / .ico)
│ └── src/
│ ├── main.rs binary entry point
│ └── lib.rs Tauri builder + shim injection
└── README.md this file
```

---

## Contributing

Issues and PRs welcome. If you verify the wrapper on **Windows** or
**Linux**, please open an issue or PR to update this README with your findings.
84 changes: 84 additions & 0 deletions desktop/README.zh-CN.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# CCCC Desktop(桌面端)

基于 [Tauri v2](https://tauri.app/) 构建的 CCCC Web UI 原生桌面套壳。

> **状态**:社区贡献 — macOS 已测试可用。Windows / Linux 构建理论可行,尚未验证。

## 新增功能

| 功能 | 说明 |
|------|------|
| 原生 `.app` / `.exe` | 无需打开浏览器,像普通桌面应用一样启动 CCCC |
| 透明标题栏(macOS) | `titleBarStyle: Overlay`,界面无边框,视觉更简洁 |
| 原生窗口拖动 | 通过 `NSWindow.setMovableByWindowBackground` 实现,即使 UI 由远程来源(`localhost:8848`)提供服务也能正常拖动 |
| 打包 DMG | `tauri build` 生成可签名的 `.app` 包和 `.dmg` 安装包 |

---

## 前置条件

| 工具 | 版本 |
|------|------|
| [Rust](https://rustup.rs/) | 1.77+ |
| [Node.js](https://nodejs.org/) | 18+ |
| [CCCC 守护进程](https://github.com/ChesterRa/cccc) | 运行于 `localhost:8848` |

macOS 还需要 Xcode 命令行工具:

```bash
xcode-select --install
```

---

## 快速开始

```bash
# 1. 安装 Tauri CLI
cd desktop
npm install

# 2. 先确保 CCCC 守护进程正在运行
cccc # 或: cccc daemon start

# 3. 开发模式启动(热更新来自 localhost:8848)
npm run dev

# 4. 构建发布包
npm run build
# → desktop/src-tauri/target/release/bundle/macos/cccc.app
# → desktop/src-tauri/target/release/bundle/dmg/cccc_x.y.z_x64.dmg
```

---

## 工作原理

本套壳是一个**纯壳**——自身不包含任何前端代码。`tauri.conf.json` 将开发 URL 和发布 URL 都指向 `http://localhost:8848/ui/`,Tauri webview 直接渲染 CCCC 守护进程提供的页面。

### 透明标题栏 + 拖动(macOS)

macOS 的 `titleBarStyle: Overlay` 会隐藏默认的窗口边框,并在内容区域上方叠加红绿灯按钮。由于 webview 加载的是**远程来源**,Tauri 的 `data-tauri-drag-region` 属性和 JS `startDragging()` API 都会被渲染器沙箱阻断。我们通过两层机制解决:

1. **CSS 垫片**(通过 `on_page_load` 注入):在每个页面追加一个 28px 透明覆盖 div,让 UI body 向下偏移,避开红绿灯按钮区域。

2. **原生 NSWindow API**(Rust `setup`):`setMovableByWindowBackground: YES` 告知 macOS 将所有未被遮挡的窗口背景像素视为拖动区域,不受内容来源限制。

---

## 项目结构

```
desktop/
├── package.json npm 包装器(仅含 @tauri-apps/cli)
├── src-tauri/
│ ├── Cargo.toml Rust crate(cccc-desktop)
│ ├── tauri.conf.json Tauri 应用配置
│ ├── capabilities/
│ │ └── default.json 权限集
│ ├── icons/ 应用图标(各尺寸 + .icns / .ico)
│ └── src/
│ ├── main.rs 可执行文件入口
│ └── lib.rs Tauri builder + 垫片注入
└── README.md 英文文档
```
14 changes: 14 additions & 0 deletions desktop/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"name": "cccc-desktop",
"private": true,
"version": "0.1.0",
"type": "module",
"scripts": {
"tauri": "tauri",
"build": "tauri build",
"dev": "tauri dev"
},
"devDependencies": {
"@tauri-apps/cli": "^2"
}
}
26 changes: 26 additions & 0 deletions desktop/src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
[package]
name = "cccc-desktop"
version = "0.1.0"
description = "Native desktop wrapper for the CCCC web UI (Tauri v2)"
authors = []
license = "Apache-2.0"
edition = "2021"
rust-version = "1.77.2"

[lib]
name = "app_lib"
crate-type = ["staticlib", "cdylib", "rlib"]

[build-dependencies]
tauri-build = { version = "2", features = [] }

[dependencies]
tauri = { version = "2", features = [] }
tauri-plugin-log = "2"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
log = "0.4"

# macOS-only: native NSWindow drag support for remote-origin webviews
[target.'cfg(target_os = "macos")'.dependencies]
objc = "0.2"
3 changes: 3 additions & 0 deletions desktop/src-tauri/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
fn main() {
tauri_build::build()
}
12 changes: 12 additions & 0 deletions desktop/src-tauri/capabilities/default.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"$schema": "../gen/schemas/desktop-schema.json",
"identifier": "default",
"description": "enables the default permissions",
"windows": [
"main"
],
"permissions": [
"core:default",
"core:window:allow-start-dragging"
]
}
10 changes: 10 additions & 0 deletions desktop/src-tauri/capabilities/remote-localhost.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"$schema": "../gen/schemas/desktop-schema.json",
"identifier": "remote-localhost",
"description": "Allow startDragging IPC from localhost daemon UI",
"remote": {
"urls": ["http://localhost:8848/**"]
},
"windows": ["main"],
"permissions": ["core:window:allow-start-dragging"]
}
Binary file added desktop/src-tauri/icons/128x128.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added desktop/src-tauri/icons/128x128@2x.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added desktop/src-tauri/icons/32x32.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added desktop/src-tauri/icons/64x64.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added desktop/src-tauri/icons/Square107x107Logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added desktop/src-tauri/icons/Square142x142Logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added desktop/src-tauri/icons/Square150x150Logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added desktop/src-tauri/icons/Square284x284Logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added desktop/src-tauri/icons/Square30x30Logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added desktop/src-tauri/icons/Square310x310Logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added desktop/src-tauri/icons/Square44x44Logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added desktop/src-tauri/icons/Square71x71Logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added desktop/src-tauri/icons/Square89x89Logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added desktop/src-tauri/icons/StoreLogo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added desktop/src-tauri/icons/icon.icns
Binary file not shown.
Binary file added desktop/src-tauri/icons/icon.ico
Binary file not shown.
Binary file added desktop/src-tauri/icons/icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
48 changes: 48 additions & 0 deletions desktop/src-tauri/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/// Inject a lightweight drag-region shim into every page loaded inside the
/// webview. We cannot rely on `data-tauri-drag-region` here because the web UI
/// is served from a remote origin (`http://localhost:8848`), and Tauri only
/// honors that attribute for locally bundled content. Instead we inject a thin
/// transparent overlay div at the top of the page and use the macOS-native
/// `setMovableByWindowBackground` API as the primary drag mechanism.
const INJECT_SCRIPT: &str = r#"
(function () {
if (document.getElementById('_tauri_drag_region')) return;
var s = document.createElement('style');
s.textContent = [
'body { padding-top: 28px !important; box-sizing: border-box; }',
'#_tauri_drag_region {',
' position: fixed; top: 0; left: 0; right: 0; height: 28px;',
' z-index: 999999; cursor: default;',
'}'
].join('');
document.head.appendChild(s);
var d = document.createElement('div');
d.id = '_tauri_drag_region';
d.addEventListener('mousedown', function (e) {
if (e.buttons !== 1 || !window.__TAURI__?.window?.getCurrentWindow) return;
window.__TAURI__.window.getCurrentWindow().startDragging();
});
document.body.appendChild(d);
})();
"#;

#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
tauri::Builder::default()
.on_page_load(|window, _payload| {
let _ = window.eval(INJECT_SCRIPT);
})
.setup(|app| {
if cfg!(debug_assertions) {
app.handle().plugin(
tauri_plugin_log::Builder::default()
.level(log::LevelFilter::Info)
.build(),
)?;
}

Ok(())
})
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
7 changes: 7 additions & 0 deletions desktop/src-tauri/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// Prevents an additional console window on Windows in release mode.
// DO NOT REMOVE this attribute.
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]

fn main() {
app_lib::run();
}
Loading