# EditorPreload API 参考文档

> **版本**: CYSOCore v1.0.0\
> **适用环境**: CYSOEditor 桌面版 (Electron)\
> **更新日期**: 2026-05-30

***

## 📖 目录

1. [快速开始](#快速开始)
2. [基础检测](#基础检测)
3. [文件操作](#文件操作)
4. [文件夹操作](#文件夹操作)
5. [系统路径](#系统路径)
6. [命令执行](#命令执行)
7. [通知系统](#通知系统)
8. [全局快捷键](#全局快捷键)
9. [屏幕覆盖窗口](#屏幕覆盖窗口)
10. [屏幕捕获](#屏幕捕获)
11. [高级窗口](#高级窗口)
12. [硬件状态](#硬件状态)
13. [权限管理](#权限管理)
14. [完整示例](#完整示例)
15. [常见问题](#常见问题)

***

## 快速开始

### 什么是 EditorPreload？

**EditorPreload** 是 CYSOEditor 桌面版提供的 JavaScript API，让扩展（Extension）能够安全地访问计算机的文件系统、硬件和系统功能。

### 如何使用？

```javascript
// 第一步：检查是否在桌面版运行
if (typeof EditorPreload === 'undefined') {
    console.log('❌ 此功能仅在桌面版可用');
    return;
}

// 第二步：调用 API（所有方法都是异步的）
const result = await EditorPreload.readFile('C:\\test.txt');

// 第三步：检查结果
if (result.success) {
    console.log('✅ 文件内容:', result.content);
} else {
    console.log('❌ 错误:', result.error);
}
```

### 返回值格式

所有 API 方法都返回一个对象：

```javascript
// ✅ 成功时
{
    success: true,
    // ... 其他数据（根据方法不同）
}

// ❌ 失败时
{
    success: false,
    error: '错误描述信息'
}
```

***

## 基础检测

### 检查是否在桌面版

```javascript
const isDesktop = typeof EditorPreload !== 'undefined';
// true = 桌面版, false = 网页版或其他环境
```

**最佳实践**：在每个使用 EditorPreload 的方法开头都进行检查：

```javascript
class MyExtension {
    constructor() {
        this.isDesktop = typeof EditorPreload !== 'undefined';
    }

    async myMethod(args) {
        if (!this.isDesktop) {
            return '错误：此功能需要桌面版';
        }
        // ... 继续执行
    }
}
```

***

## 文件操作

### 读取文件内容

**所需权限**: `file-read`

```javascript
const result = await EditorPreload.readFile(filePath);
```

**参数**:

- `filePath` (string): 文件的绝对路径，支持路径变量（如 `%DESKTOP%`）

**返回值**:

```javascript
{
    success: true,
    content: "文件的文本内容",
    filePath: "完整的文件路径"
}
```

**示例**:

```javascript
async function readMyFile() {
    const result = await EditorPreload.readFile('%DESKTOP%\\notes.txt');
    
    if (result.success) {
        return result.content;  // 返回文件内容
    } else {
        return `错误：${result.error}`;
    }
}
```

***

### 写入文件

**所需权限**: `file-write`

```javascript
const result = await EditorPreload.writeFile(filePath, content);
```

**参数**:

- `filePath` (string): 要写入的文件路径
- `content` (string): 要写入的内容

**返回值**:

```javascript
{
    success: true,
    filePath: "写入成功的文件路径"
}
```

**安全限制**：

- ❌ 不能写入系统关键目录：`C:\Windows`、`C:\Program Files`、`C:\ProgramData`、`%SYSTEMROOT%`，以及 Linux/macOS 的 `/etc`、`/usr`、`/System` 等
- ✅ 只能写入用户目录（桌面、文档、下载、图片、音乐、视频、AppData、用户数据、临时目录等）
- ✅ 即便路径"位于"某个风险目录之下，只要它同时也落在上述**用户安全目录**内（例如在 `AppData` 下的项目文件夹），仍然允许访问
- ✅ 会自动创建不存在的父文件夹

**示例**:

```javascript
async function saveData(data) {
    const result = await EditorPreload.writeFile(
        '%DESKTOP%\\output.txt', 
        JSON.stringify(data, null, 2)
    );
    
    if (result.success) {
        console.log('✅ 文件已保存到:', result.filePath);
    }
}
```

***

### 删除文件

**所需权限**: `file-delete`

```javascript
const result = await EditorPreload.deleteFile(filePath);
```

**参数**:

- `filePath` (string): 要删除的文件路径

**返回值**:

```javascript
{
    success: true,
    filePath: "已删除的文件路径"
}
```

**安全限制**：

- ❌ 不能删除系统关键目录下的文件
- ❌ 如果文件不存在会返回错误

**示例**:

```javascript
async function cleanup() {
    const result = await EditorPreload.deleteFile('%DESKTOP%\\temp.txt');
    
    if (result.success) {
        console.log('🗑️ 文件已删除');
    } else {
        console.log('删除失败:', result.error);
    }
}
```

***

### 检查文件是否存在

**所需权限**: `file-read`

```javascript
const result = await EditorPreload.fileExists(filePath);
```

**返回值**:

```javascript
{
    success: true,
    exists: true   // true=存在, false=不存在
}
```

**示例**:

```javascript
async function checkConfig() {
    const result = await EditorPreload.fileExists('%DESKTOP%\\config.json');
    
    if (result.success && result.exists) {
        console.log('⚙️ 配置文件存在');
        return true;
    } else {
        console.log('⚙️ 配置文件不存在');
        return false;
    }
}
```

***

### 获取文件详细信息

**所需权限**: `file-metadata`

```javascript
const result = await EditorPreload.getFileStats(filePath);
```

**返回值**:

```javascript
{
    success: true,
    stats: {
        size: 1024,              // 文件大小（字节）
        isFile: true,            // 是否是文件
        isDirectory: false,      // 是否是文件夹
        created: "2024-01-01T...", // 创建时间
        modified: "2024-01-02T...", // 修改时间
        accessed: "2024-01-03T..."  // 访问时间
    }
}
```

**示例**:

```javascript
async function getFileInfo(path) {
    const result = await EditorPreload.getFileStats(path);
    
    if (result.success) {
        const stats = result.stats;
        return `
            大小: ${(stats.size / 1024).toFixed(2)} KB
            类型: ${stats.isFile ? '文件' : '文件夹'}
            修改时间: ${stats.modified}
        `;
    }
}
```

***

## 文件夹操作

### 创建文件夹

```javascript
const result = await EditorPreload.createFolder(folderPath);
```

**参数**:

- `folderPath` (string): 要创建的文件夹路径

**返回值**:

```javascript
{
    success: true,
    folderPath: "创建成功的文件夹路径"
}
```

**特点**：

- ✅ 自动创建所有不存在的父文件夹（递归创建）
- ✅ 如果文件夹已存在不会报错

**示例**:

```javascript
async function createProjectFolder(projectName) {
    const path = `%DESKTOP%\\我的项目\\${projectName}`;
    const result = await EditorPreload.createFolder(path);
    
    if (result.success) {
        console.log('📁 文件夹已创建:', result.folderPath);
    }
}
```

***

### 读取文件夹内容

```javascript
const result = await EditorPreload.readLocalFolder(folderPath);
```

**返回值**:

```javascript
{
    success: true,
    files: [
        {
            name: "文件名.txt",
            path: "完整路径",
            isDirectory: false,   // 是否是文件夹
            size: 1024,           // 大小（字节）
            mtime: "2024-..."     // 修改时间
        },
        // ... 更多文件/文件夹
    ],
    folderPath: "被读取的文件夹路径"
}
```

**示例**:

```javascript
async function listDesktopFiles() {
    const result = await EditorPreload.readLocalFolder('%DESKTOP%');
    
    if (result.success) {
        const fileList = result.files.map(file => 
            `${file.isDirectory ? '📁' : '📄'} ${file.name}`
        ).join('\n');
        
        return `桌面内容:\n${fileList}`;
    }
}
```

***

## 系统路径

### 获取常用系统路径

```javascript
const result = await EditorPreload.getPath(name);
```

**参数** (`name` 可选值):

| 名称            | 说明     | Windows 示例                  |
| ------------- | ------ | --------------------------- |
| `'desktop'`   | 桌面     | `C:\Users\用户名\Desktop`      |
| `'documents'` | 文档     | `C:\Users\用户名\Documents`    |
| `'downloads'` | 下载     | `C:\Users\用户名\Downloads`    |
| `'music'`     | 音乐     | `C:\Users\用户名\Music`        |
| `'pictures'`  | 图片     | `C:\Users\用户名\Pictures`     |
| `'videos'`    | 视频     | `C:\Users\用户名\Videos`       |
| `'appData'`   | AppData | `C:\Users\用户名\AppData`      |
| `'userData'`  | 用户数据   | 程序用户数据目录                   |
| `'temp'`      | 临时目录   | `C:\Users\用户名\AppData\Local\Temp` |
| `'cache'`     | 缓存目录   | 程序缓存目录                     |
| `'logs'`      | 日志目录   | 程序日志目录                     |
| `'home'`      | 用户主目录  | `C:\Users\用户名`              |
| `'exe'`       | 程序目录   | 当前程序所在目录                   |

**返回值**:

```javascript
{
    success: true,
    path: "C:\\Users\\用户名\\Desktop"
}
```

**示例**:

```javascript
async function getDesktopPath() {
    const result = await EditorPreload.getPath('desktop');
    
    if (result.success) {
        console.log('桌面路径:', result.path);
        return result.path;
    }
}
```

**💡 提示**: 在其他 API 中可以使用路径变量 `%DESKTOP%`、`%DOCUMENTS%` 等代替实际路径。

***

## 命令执行

### 执行系统命令

**所需权限**: `system-command` ⚠️ **高风险**（⚠️ 该权限出厂默认是「拒绝」，需用户在 CYSO Core 控制中心手动开启）

```javascript
const result = await EditorPreload.executeCommand(command, options);
```

**参数**:

- `command` (string): 要执行的命令（如 `dir`、`echo Hello`、`python script.py`）
- `options` (object, 可选):
  - `cwd`: 工作目录（默认当前目录）
  - `timeout`: 超时时间（毫秒，默认 30000）

**返回值**:

```javascript
{
    success: true,
    stdout: "标准输出内容",    // 命令的正常输出
    stderr: "标准错误输出",    // 命令的错误信息
    cwd: "工作目录"
}
```

**⚠️ 安全限制**:

以下命令会被**完全禁止**（直接返回错误，不会执行）：

- **基础危险命令**（按命令名拦截，含其任意参数）：`rm`、`del`、`format`、`fdisk`、`mkfs`、`dd`、`reg`、`bcdedit`、`bootcfg`、`diskpart`、`sudo`、`runas`、`su`、`sc`、`systemctl`、`netsh`、`iptables`、`route`、`taskkill`、`kill`、`pkill`、`icacls`、`chown`、`schtasks`
- **危险模式**（正则匹配）：`net user/localgroup/group/share/service`、`net start/stop`、`sc create/delete/config/start/stop`、`schtasks /create`、`chmod 777`、`powershell/pwsh -ExecutionPolicy Bypass`、`pwsh -ep bypass`、`-w hidden`（隐藏窗口）、`powershell -Enc/-EncodedCommand`（编码命令）、`certutil -urlcache`、`wscript`/`cscript`

以下命令会弹出**确认对话框**（用户确认后才执行）：

- 包管理器：`npm install/uninstall`、`pip install/uninstall`、`apt(-get) install/remove/update/upgrade`、`yum install/remove`
- 软件安装：`winget`、`choco`

> 💡 命令过滤基于字符串匹配，可能被绕过（例如拆分变量、编码）。它只是"降低风险"的软防线，并非绝对安全。高风险场景请谨慎授予 `system-command` 权限。

**示例**:

```javascript
async function runDirCommand() {
    const result = await EditorPreload.executeCommand('dir %DESKTOP%', {
        timeout: 5000
    });
    
    if (result.success) {
        console.log('✅ 命令输出:\n', result.stdout);
        return result.stdout;
    } else {
        console.log('❌ 执行失败:', result.error);
        return '';
    }
}

async function runPythonScript(scriptPath) {
    const result = await EditorPreload.executeCommand(`python "${scriptPath}"`, {
        cwd: '%DESKTOP%',
        timeout: 60000  // 60秒超时
    });
    
    return result.stdout || result.stderr;
}
```

***

## 通知系统

### 显示系统通知

```javascript
const result = await EditorPreload.showNotification(options);
```

**参数** (`options` 对象):

- `title` (string): 通知标题
- `body` (string): 通知内容
- `icon` (string, 可选): 通知图标路径
- `silent` (boolean, 可选): 是否静音（默认 false）

**返回值**:

```javascript
{ success: true }
```

**示例**:

```javascript
async function notifyUser(message) {
    await EditorPreload.showNotification({
        title: '🎮 游戏提醒',
        body: message,
        // icon: '%DESKTOP%\\icon.png'
    });
}

// 使用示例
await notifyUser('恭喜你完成了第 10 关！');
await notifyUser('你的血量已恢复到 100%！');
```

***

## 全局快捷键

**所需权限**: `global-shortcut`

### 注册全局快捷键

```javascript
const result = await EditorPreload.registerGlobalShortcut(accelerator, eventName);
```

**参数**:

- `accelerator` (string): 快捷键组合，格式：
  - 单键: `'F1'`, `'F12'`
  - 组合键: `'Ctrl+Shift+P'`, `'Alt+F4'`, `'CmdOrCtrl+A'`
  - 支持修饰符: `Ctrl`, `Alt`, `Shift`, `CmdOrCtrl`, `Super`
- `eventName` (string): 事件名称（用于识别哪个快捷键被触发）

**返回值**:

```javascript
{ success: true }
```

### 注销全局快捷键

```javascript
const result = await EditorPreload.unregisterGlobalShortcut(accelerator);
```

### 监听快捷键触发事件

```javascript
// 在渲染进程中监听事件
window.addEventListener('global-shortcut-triggered', (event) => {
    const { key, eventName } = event.detail;
    console.log(`快捷键 ${key} 触发，事件: ${eventName}`);
    
    // 根据 eventName 执行不同操作
    if (eventName === 'my-pause-event') {
        pauseGame();
    }
});
```

**示例**:

```javascript
class GameControls {
    constructor() {
        this.setupShortcuts();
        this.listenForEvents();
    }

    async setupShortcuts() {
        // 注册游戏控制快捷键
        await EditorPreload.registerGlobalShortcut('Ctrl+Shift+P', 'game-pause');
        await EditorPreload.registerGlobalShortcut('Ctrl+Shift+R', 'game-restart');
        
        console.log('✅ 快捷键已注册');
    }

    listenForEvents() {
        window.addEventListener('global-shortcut-triggered', (e) => {
            switch(e.detail.eventName) {
                case 'game-pause':
                    this.pauseGame();
                    break;
                case 'game-restart':
                    this.restartGame();
                    break;
            }
        });
    }

    pauseGame() { /* 暂停逻辑 */ }
    restartGame() { /* 重启逻辑 */ }

    async cleanup() {
        // 记得注销快捷键！
        await EditorPreload.unregisterGlobalShortcut('Ctrl+Shift+P');
        await EditorPreload.unregisterGlobalShortcut('Ctrl+Shift+R');
    }
}
```

**⚠️ 注意事项**:

- 全局快捷键在**整个系统**范围内生效（即使不在编辑器窗口）
- 同一个快捷键只能注册一个，重复注册会替换之前的
- 应用退出前应注销所有快捷键
- 某些系统快捷键可能无法覆盖（如 Ctrl+Alt+Delete）

### 通过自定义帽子积木触发（推荐模式）

如果你希望在 Scratch 里用“当快捷键 [EVENT] 触发”这样的**帽子积木**来响应快捷键（而不是在回调里直接调方法），需要把事件转成一次帽子触发。这里有一个**极易踩坑**的关键点：

> 🔴 **扩展帽子的 opcode 在运行时是带扩展 ID 前缀的**：`运行时._hats` 里注册的键是 `扩展ID_原始opcode`（例如扩展 `id` 为 `myExt`、积木 `opcode` 为 `shortcutTriggered`，则真实 opcode 为 `myExt_shortcutTriggered`）。调用 `runtime.startHats` **必须传带前缀的完整 opcode**，传裸 `opcode` 会匹配不到任何帽子、安静失败。

完整可运行模式如下：

```javascript
class MyExt {
    constructor() {
        this.isDesktop = typeof EditorPreload !== 'undefined';
        this._pending = {};   // 待触发事件标记
    }

    getInfo() {
        return {
            id: 'myExt',
            name: '快捷键帽子示例',
            permissions: ['global-shortcut'],
            blocks: [
                {
                    opcode: 'shortcutTriggered',
                    blockType: Scratch.BlockType.HAT,
                    isEdgeActivated: false,            // ✅ 推荐显式写成 false：让编译器把它当“谓词帽子”（HAT_PREDICATE），函数返回 true 时帽子体执行一次。
                                                       //    不写时在本运行时里也是谓词帽子（edgeActivated 取 undefined 视为 falsy），但显式写明最清晰稳妥。
                                                       //    ⚠️ 无论哪种路径，帽子函数都必须返回 true 才会触发，返回 undefined/false 都会“永不触发”。
                    shouldRestartExistingThreads: true,
                    text: '当快捷键 [EVENT] 触发',
                    arguments: {
                        EVENT: { type: Scratch.ArgumentType.STRING, defaultValue: 'my-event' }
                    }
                },
                // ……其他积木
            ]
        };
    }

    // 帽子积木：作为“谓词”被 VM 求值。返回 true 时帽子体执行一次。
    shortcutTriggered(args) {
        const event = ((args && args.EVENT) || '').toUpperCase();
        if (event && this._pending[event]) {
            this._pending[event] = false;   // 消费，保证只触发一次
            return true;
        }
        return false;
    }

    setup() {
        if (!this.isDesktop) return;

        // 推荐用 onShortcutTriggered（基于 IPC，比 window 事件更可靠）
        EditorPreload.onShortcutTriggered((data) => {
            const vm = window.Scratch?.vm || (typeof Scratch !== 'undefined' && Scratch.vm);
            if (!vm || !vm.runtime) return;
            const event = ((data && data.eventName) || '').toUpperCase();

            // 1) 标记该事件为待触发
            this._pending[event] = true;

            // 2) 用带前缀的完整 opcode 触发帽子（不要传裸 'shortcutTriggered'）
            const hats = vm.runtime._hats || {};
            const hatKey = Object.keys(hats).find(k => k.endsWith('_shortcutTriggered')) || 'myExt_shortcutTriggered';
            vm.runtime.startHats(hatKey, {});
        });
    }
}

if (typeof Scratch !== 'undefined') {
    const ext = new MyExt();
    Scratch.extensions.register(ext);
    ext.setup();
}
```

**要点总结**：

1. 帽子积木定义里推荐显式写 `isEdgeActivated: false`，让编译器把它当“谓词帽子”（VM 求值时调用帽子函数，返回 `true` 才执行帽子体）。在本运行时里不写也会按谓词帽子处理（`edgeActivated` 为 `undefined` 视为 falsy），但显式写明最清晰。无论是否显式写，帽子函数都必须返回 `true` 才会触发。
2. 帽子函数（`shortcutTriggered`）在 VM 把它当**谓词**求值时被调用，需要返回 `true` 才会让帽子体执行；用“待触发标记”消费一次即可避免重复触发。
3. 触发时 `runtime.startHats` 的第一个参数必须是带扩展 ID 前缀的完整 opcode（如 `myExt_shortcutTriggered`），不是裸 `opcode`。

***

## 屏幕覆盖窗口

**所需权限**: `draw-window`

> **用途**: 创建透明的、始终置顶的窗口，用于显示 HUD、实时数据、悬浮工具等。

### 创建覆盖窗口

```javascript
const result = await EditorPreload.createOverlayWindow(id, x, y, width, height);
```

**参数**:

- `id` (string): 窗口唯一标识符（用于后续操作）
- `x` (number): X 坐标（距屏幕左边的像素）
- `y` (number): Y 坐标（距屏幕顶部的像素）
- `width` (number): 窗口宽度（像素）
- `height` (number): 窗口高度（像素）

**返回值**:

```javascript
{ success: true }
```

**窗口特性**:

- ✅ 透明背景
- ✅ 无边框
- ✅ 始终置顶（在其他窗口之上）
- ✅ 不在任务栏显示
- ✅ 支持显示 HTML 内容

### 设置窗口内容

```javascript
const result = await EditorPreload.setOverlayContent(id, htmlContent);
```

**参数**:

- `id` (string): 窗口 ID
- `htmlContent` (string): HTML 内容（可以是任何合法的 HTML/CSS/JS）

### 关闭覆盖窗口

```javascript
const result = await EditorPreload.closeOverlayWindow(id);
```

**示例 - 创建游戏 HUD**:

```javascript
async function showGameHUD(score, lives) {
    // 1. 创建窗口（右上角）
    await EditorPreload.createOverlayWindow('game-hud', 1500, 10, 300, 150);
    
    // 2. 设置内容（带样式的 HTML）
    const html = `
        <div style="
            padding: 20px;
            font-family: Arial, sans-serif;
            color: white;
            font-size: 24px;
            text-shadow: 2px 2px 4px rgba(0,0,0,0.8);
        ">
            <div>🎯 分数: ${score}</div>
            <div>❤️ 生命: ${lives}</div>
            <div>⏱️ 时间: ${Date.now()}</div>
        </div>
    `;
    
    await EditorPreload.setOverlayContent('game-hud', html);
}

async function hideGameHUD() {
    await EditorPreload.closeOverlayWindow('game-hud');
}

// 使用
await showGameHUD(1250, 3);
// ... 游戏结束后 ...
await hideGameHUD();
```

**示例 - 实时时钟悬浮窗**:

```javascript
async function showFloatingClock() {
    await EditorPreload.createOverlayWindow('clock', 10, 10, 200, 80);
    
    const updateClock = () => {
        const now = new Date();
        const timeStr = now.toLocaleTimeString('zh-CN');
        
        EditorPreload.setOverlayContent('clock', `
            <div style="
                width: 100%;
                height: 100%;
                display: flex;
                align-items: center;
                justify-content: center;
                background: rgba(0, 0, 0, 0.7);
                color: #0f0;
                font-family: 'Courier New', monospace;
                font-size: 32px;
                border-radius: 10px;
            ">
                ${timeStr}
            </div>
        `);
    };
    
    // 每秒更新
    setInterval(updateClock, 1000);
    updateClock(); // 立即显示一次
}
```

***

## 屏幕捕获

**所需权限**: `screen-capture` ⚠️ **高风险**

### 截取整个屏幕或窗口

```javascript
const result = await EditorPreload.captureScreen(type);
```

**参数** (`type`):

- `'screen'`: 截取整个屏幕
- `'window'`: 截取当前活动窗口

**返回值**:

```javascript
{
    success: true,
    dataUrl: "data:image/png;base64,iVBORw0KGgo..."  // Base64 编码的 PNG 图片
}
```

### 截取指定区域

```javascript
const result = await EditorPreload.captureRegion(x, y, width, height);
```

**参数**:

- `x`, `y`: 区域左上角坐标
- `width`, `height`: 区域宽高

**返回值**: 与 `captureScreen` 相同

**示例**:

```javascript
async function takeScreenshot() {
    // 截取全屏
    const result = await EditorPreload.captureScreen('screen');
    
    if (result.success) {
        // result.dataUrl 可以直接用作图片 src
        document.getElementById('screenshot').src = result.dataUrl;
        
        // 或者保存到文件
        await EditorPreload.writeFile(
            '%DESKTOP%\\screenshot.png',
            result.dataUrl
        );
        
        console.log('✅ 截图已保存');
    }
}

async function captureGameArea() {
    // 只截取游戏的某个区域（假设游戏在坐标 100,100 位置，大小 800x600）
    const result = await EditorPreload.captureRegion(100, 100, 800, 600);
    
    if (result.success) {
        return result.dataUrl;  // 返回图片数据
    }
}
```

**💡 使用场景**:

- 游戏截图功能
- 录制屏幕（定时截取）
- 远程监控
- 图像处理应用

***

## 高级窗口

**所需权限**: `advanced-window`

> **用途**: 创建具有特殊属性的标准窗口（不同于覆盖窗口，这是真正的浏览器窗口）。

### 创建高级窗口

```javascript
const result = await EditorPreload.createAdvancedWindow(id, options);
```

**参数**:

- `id` (string): 窗口唯一标识符
- `options` (object):
  - `width` (number): 宽度（默认 400）
  - `height` (number): 高度（默认 300）
  - `frameless` (boolean): 无边框（默认 false）
  - `transparent` (boolean): 透明背景（默认 false）
  - `alwaysOnTop` (boolean): 始终置顶（默认 false）
  - `x` (number, 可选): X 坐标
  - `y` (number, 可选): Y 坐标
  - `url` (string, 可选): 加载的 URL（默认 about:blank）

**返回值**:

```javascript
{ success: true }
```

### 修改窗口属性

```javascript
const result = await EditorPreload.setWindowProperty(id, property, value);
```

**可修改的属性**:

| 属性               | 类型      | 说明              |
| ---------------- | ------- | --------------- |
| `'alwaysOnTop'`  | boolean | 是否始终置顶          |
| `'clickThrough'` | boolean | 鼠标点击穿透（点击会穿过窗口） |
| `'draggable'`    | boolean | 是否可拖动           |

**注意**: `transparent` 和 `frameless` 在创建后无法修改！

### 关闭高级窗口

```javascript
const result = await EditorPreload.closeAdvancedWindow(id);
```

**示例**:

```javascript
async function openHelpWindow() {
    // 创建一个帮助窗口
    await EditorPreload.createAdvancedWindow('help-win', {
        width: 600,
        height: 400,
        frameless: false,
        alwaysOnTop: true,
        url: 'https://example.com/help.html'
    });
}

async function openMiniPlayer() {
    // 创建一个迷你音乐播放器（无边框 + 透明 + 置顶）
    await EditorPreload.createAdvancedWindow('mini-player', {
        width: 300,
        height: 150,
        frameless: true,
        transparent: true,
        alwaysOnTop: true,
        x: 100,
        y: 100
    });
    
    // 设置内容
    await EditorPreload.setWindowProperty('mini-player', 'draggable', true);
    // 注意：高级窗口不能像覆盖窗口那样用 setOverlayContent 设置内容
    // 需要通过 url 参数加载页面
}
```

**覆盖窗口 vs 高级窗口对比**:

| 特性    | 覆盖窗口 (Overlay) | 高级窗口 (Advanced) |
| ----- | -------------- | --------------- |
| 透明背景  | ✅ 默认透明         | ⚙️ 可选           |
| 无边框   | ✅ 总是无边框        | ⚙️ 可选           |
| 置顶    | ✅ 总是置顶         | ⚙️ 可选           |
| 内容设置  | ✅ 直接设置 HTML    | 🔗 通过 URL 加载    |
| 任务栏显示 | ❌ 不显示          | ✅ 正常显示          |
| 适用场景  | HUD、悬浮提示       | 子窗口、工具面板        |

***

## 硬件状态

**所需权限**: `hardware-status`

### 获取硬件信息

```javascript
const result = await EditorPreload.getHardwareStatus(device);
```

**参数** (`device`):

| 设备          | 返回的信息             |
| ----------- | ----------------- |
| `'cpu'`     | CPU 使用率、型号、核心数、主频 |
| `'memory'`  | 内存总量、已用、空闲、使用率    |
| `'gpu'`     | GPU 信息（⚠️ 仅占位，返回 `info: 'GPU info requires additional native modules'`） |
| `'network'` | 网络接口名称列表            |
| `'disk'`    | 磁盘空间信息            |

> ⚠️ `'battery'`（电池）**暂未实现**，调用会返回 `{ success: false, error: "Unknown device: battery" }`。

**返回值**:

```javascript
{
    success: true,
    data: {
        // 根据 device 不同而不同
    }
}
```

#### 各设备详细返回格式

**CPU 信息**:

```javascript
{
    usage: 45,              // CPU 使用率 (0-100)
    model: "Intel Core i7", // CPU 型号
    cores: 8,               // 核心数
    speed: 3200             // 主频 (MHz)
}
```

**内存信息**:

```javascript
{
    total: 17179869184,     // 总内存 (字节)
    free: 8589934592,       // 空闲内存 (字节)
    used: 8589934592,       // 已用内存 (字节)
    usage: 50               // 使用率 (0-100)
}
```

**磁盘信息**:

```javascript
{
    disks: [
        {
            drive: "C:",           // 盘符
            total: 512000000000,    // 总容量 (字节)
            free: 200000000000,     // 可用空间 (字节)
            used: 312000000000,     // 已用空间 (字节)
            usage: 61               // 使用率 (0-100)
        },
        // ... 更多分区
    ]
}
```

**网络信息**:

```javascript
{
    interfaces: ["Wi-Fi", "以太网", "Loopback"]  // 网络接口名称列表
}
```

**示例**:

```javascript
async function showSystemInfo() {
    // 获取 CPU 信息
    const cpu = await EditorPreload.getHardwareStatus('cpu');
    console.log(`CPU: ${cpu.data.model} (${cpu.data.cores}核)`);
    console.log(`使用率: ${cpu.data.usage}%`);
    
    // 获取内存信息
    const mem = await EditorPreload.getHardwareStatus('memory');
    const totalGB = (mem.data.total / 1024 / 1024 / 1024).toFixed(1);
    const usedGB = (mem.data.used / 1024 / 1024 / 1024).toFixed(1);
    console.log(`内存: ${usedGB}GB / ${totalGB}GB (${mem.data.usage}%)`);
    
    // 获取磁盘信息
    const disk = await EditorPreload.getHardwareStatus('disk');
    disk.data.disks.forEach(d => {
        console.log(`${d.drive}: 已用 ${(d.usage)}%`);
    });
}

// 返回格式化的系统信息字符串
async function getFormattedSystemInfo() {
    const [cpu, mem] = await Promise.all([
        EditorPreload.getHardwareStatus('cpu'),
        EditorPreload.getHardwareStatus('memory')
    ]);
    
    return `
系统状态报告
━━━━━━━━━━━━━━━━━━
CPU: ${cpu.data.usage}% | ${cpu.data.model}
内存: ${mem.data.usage}% | ${(mem.data.total / 1073741824).toFixed(1)} GB
━━━━━━━━━━━━━━━━━━
时间: ${new Date().toLocaleString()}
    `.trim();
}
```

***

## 权限管理

### 查询权限状态（只读）

```javascript
// 获取当前所有权限设置
const permissions = await EditorPreload.getPermissions();
// 返回: { 'file-read': 'ask', 'system-command': 'deny', ... }

// 获取权限默认设置
const defaults = await EditorPreload.getDefaults();

// 获取某个扩展的权限
const extPerms = await EditorPreload.getExtensionPermissions(extensionId);

// 获取某个扩展某个权限的状态
const status = await EditorPreload.getExtensionPermissionStatus(extensionId, 'file-read');
// 返回: { action: 'allow' } / { action: 'deny' } / { action: 'ask' } / { action: 'deny', notRequested: true }

// 获取全部扩展的权限状态
const all = await EditorPreload.getAllPermissionsStatus();
```

**权限值说明**:

- `'always'`: 始终允许（不再询问）
- `'ask'`: 每次都询问用户
- `'deny'`: 始终拒绝（含 `notRequested`：扩展根本没申请过该权限，直接拒绝、不弹窗）

### 检查特定权限

```javascript
const result = await EditorPreload.checkPermission(extensionId, permissionType);
// 第一个参数必须是"扩展 ID"，第二个是权限名
// 返回: { action: 'allow' } 或 { action: 'deny' }
```

### 获取/设置 CYSO Core 开关状态

```javascript
// 检查 CYSO Core 是否启用
const enabled = await EditorPreload.getCYSOCoreEnabled();
// 返回: true 或 false

// 启用/禁用 CYSO Core
await EditorPreload.setCYSOCoreEnabled(true);  // 启用
await EditorPreload.setCYSOCoreEnabled(false); // 禁用
```

**💡 说明**:
- CYSO Core 是所有高级功能的总开关。当禁用时，所有需要权限的操作都会失败。
- ⚠️ **扩展无法在代码里修改权限**：没有"设置权限"的 API（旧的 `setPermissions` 等已被移除）。权限的 `always`/`ask`/`deny` 只能由用户在 **CYSO Core 控制中心**（编辑器设置）里手动设置。
- 📌 `system-command`（执行命令）出厂默认是 **「拒绝」**，需用户在控制中心手动开启后才能使用；其余权限默认是「询问」。

***

## 完整示例

### 示例 1: 系统监控仪表盘

这个例子展示如何创建一个实时显示系统信息的悬浮窗：

```javascript
class SystemMonitor {
    constructor() {
        this.isDesktop = typeof EditorPreload !== 'undefined';
        this.running = false;
        this.intervalId = null;
    }

    async start() {
        if (!this.isDesktop) {
            alert('请使用桌面版！');
            return;
        }

        this.running = true;

        // 1. 创建悬浮窗（右上角）
        await EditorPreload.createOverlayWindow('sys-monitor', 1200, 10, 350, 200);

        // 2. 开始更新循环
        this.updateDisplay();
        this.intervalId = setInterval(() => this.updateDisplay(), 2000);

        console.log('✅ 系统监控已启动');
    }

    async updateDisplay() {
        if (!this.running) return;

        try {
            // 并行获取多个硬件状态
            const [cpuResult, memResult] = await Promise.all([
                EditorPreload.getHardwareStatus('cpu'),
                EditorPreload.getHardwareStatus('memory')
            ]);

            const cpu = cpuResult.data;
            const mem = memResult.data;

            // 格式化显示
            const usageColor = cpu.usage > 80 ? '#f44336' : '#4caf50';
            const memColor = mem.usage > 80 ? '#f44336' : '#2196f3';

            const html = `
                <div style="
                    padding: 15px;
                    font-family: 'Segoe UI', sans-serif;
                    background: rgba(20, 20, 30, 0.9);
                    color: white;
                    border-radius: 12px;
                    box-shadow: 0 4px 20px rgba(0,0,0,0.5);
                    font-size: 14px;
                ">
                    <div style="font-weight: bold; margin-bottom: 10px; color: #fff;">
                        💻 系统监控
                    </div>
                    
                    <div style="margin: 8px 0;">
                        <span>CPU:</span>
                        <span style="color: ${usageColor}; font-weight: bold;">
                            ${cpu.usage}%
                        </span>
                        <div style="
                            background: #333;
                            height: 6px;
                            border-radius: 3px;
                            margin-top: 4px;
                        ">
                            <div style="
                                width: ${cpu.usage}%;
                                background: ${usageColor};
                                height: 100%;
                                border-radius: 3px;
                                transition: width 0.3s;
                            "></div>
                        </div>
                    </div>

                    <div style="margin: 8px 0;">
                        <span>内存:</span>
                        <span style="color: ${memColor}; font-weight: bold;">
                            ${(mem.used / 1073741824).toFixed(1)} / 
                            ${(mem.total / 1073741824).toFixed(1)} GB
                        </span>
                        <div style="
                            background: #333;
                            height: 6px;
                            border-radius: 3px;
                            margin-top: 4px;
                        ">
                            <div style="
                                width: ${mem.usage}%;
                                background: ${memColor};
                                height: 100%;
                                border-radius: 3px;
                                transition: width 0.3s;
                            "></div>
                        </div>
                    </div>

                    <div style="
                        margin-top: 10px;
                        font-size: 11px;
                        color: #888;
                    ">
                        ${new Date().toLocaleTimeString('zh-CN')}
                    </div>
                </div>
            `;

            await EditorPreload.setOverlayContent('sys-monitor', html);

        } catch (error) {
            console.error('更新失败:', error);
        }
    }

    async stop() {
        this.running = false;
        
        if (this.intervalId) {
            clearInterval(this.intervalId);
            this.intervalId = null;
        }

        await EditorPreload.closeOverlayWindow('sys-monitor');
        console.log('⏹️ 系统监控已停止');
    }
}

// 使用方式
const monitor = new SystemMonitor();
await monitor.start();

// 10秒后停止（仅示例）
// setTimeout(() => monitor.stop(), 10000);
```

***

### 示例 2: 文件管理器

展示文件读写、文件夹操作的完整用法：

```javascript
class SimpleFileManager {
    constructor() {
        this.isDesktop = typeof EditorPreload !== 'undefined';
        this.currentPath = '%DESKTOP%';
    }

    async listFiles() {
        if (!this.isDesktop) return '❌ 需要桌面版';

        const result = await EditorPreload.readLocalFolder(this.currentPath);
        
        if (!result.success) {
            return `错误: ${result.error}`;
        }

        let output = `📂 ${this.currentPath}\n`;
        output += '━'.repeat(40) + '\n';

        for (const file of result.files) {
            const icon = file.isDirectory ? '📁' : '📄';
            const size = file.isDirectory 
                ? '<DIR>' 
                : this.formatSize(file.size);
            
            output += `${icon} ${file.name.padEnd(25)} ${size}\n`;
        }

        return output;
    }

    async readFile(filename) {
        if (!this.isDesktop) return '❌ 需要桌面版';

        const fullPath = `${this.currentPath}\\${filename}`;
        const result = await EditorPreload.readFile(fullPath);

        if (result.success) {
            return result.content;
        } else {
            return `❌ 读取失败: ${result.error}`;
        }
    }

    async saveFile(filename, content) {
        if (!this.isDesktop) return;

        const fullPath = `${this.currentPath}\\${filename}`;
        const result = await EditorPreload.writeFile(fullPath, content);

        if (result.success) {
            console.log(`✅ 已保存: ${fullPath}`);
        } else {
            console.error(`❌ 保存失败: ${result.error}`);
        }
    }

    async createFolder(name) {
        if (!this.isDesktop) return;

        const fullPath = `${this.currentPath}\\${name}`;
        const result = await EditorPreload.createFolder(fullPath);

        console.log(result.success ? '✅ 文件夹已创建' : `❌ ${result.error}`);
    }

    async deleteFile(filename) {
        if (!this.isDesktop) return;

        const fullPath = `${this.currentPath}\\${filename}`;
        const result = await EditorPreload.deleteFile(fullPath);

        console.log(result.success ? '🗑️ 已删除' : `❌ ${result.error}`);
    }

    formatSize(bytes) {
        if (bytes < 1024) return bytes + ' B';
        if (bytes < 1048576) return (bytes / 1024).toFixed(1) + ' KB';
        return (bytes / 1048576).toFixed(1) + ' MB';
    }
}
```

***

## 常见问题

### Q1: 为什么我的扩展不工作？

**检查清单**:

1. ✅ 确认在 **CYSOEditor 桌面版** 中运行（不是网页版）
2. ✅ 代码末尾有 `Scratch.extensions.register(new MyExtension())`
3. ✅ `getInfo()` 返回格式正确
4. ✅ 按 F12 打开开发者工具查看控制台错误
5. ✅ 如果使用了 EditorPreload，确认已声明对应的 `permissions`

### Q2: 权限请求弹窗太烦人怎么办？

扩展**不能**在代码里预先设置权限。请在扩展说明中引导用户手动设置：

```javascript
console.log('请在 CYSO Core 控制中心将以下权限设置为"始终允许":');
console.log('- file-read');
console.log('- hardware-status');
```

> 💡 用户打开编辑器设置里的 CYSO Core 控制中心，即可把常用权限设为「始终允许」，之后不再弹窗。

### Q3: 路径变量有哪些？

| 变量            | 含义     |
| ------------- | ------ |
| `%DESKTOP%`   | 用户桌面   |
| `%DOCUMENTS%` | 用户文档   |
| `%DOWNLOADS%` | 用户下载   |
| `%MUSIC%`     | 用户音乐   |
| `%PICTURES%`  | 用户图片   |
| `%VIDEOS%`    | 用户视频   |
| `%APPDATA%`   | AppData |
| `%USERDATA%`  | 用户数据   |
| `%TEMP%`      | 临时目录   |
| `%CACHE%`     | 缓存目录   |
| `%LOGS%`      | 日志目录   |
| `%HOME%`      | 用户主目录  |
| `%EXE%`       | 程序目录   |

**建议**: 优先使用路径变量而不是硬编码路径，这样在不同电脑上都能正常工作。

### Q4: 如何调试扩展？

1. 打开 CYSOEditor
2. 按 `F12` 或 `Ctrl+Shift+I` 打开开发者工具
3. 切换到 **Console**（控制台）标签
4. 在扩展代码中使用 `console.log()` 输出调试信息
5. 查看 Console 中的输出和错误信息

**调试技巧**:

```javascript
async myMethod(args) {
    console.log('🔍 方法被调用，参数:', args);  // 调试日志
    
    try {
        const result = await EditorPreload.someAPI(args);
        console.log('✅ API 返回:', result);  // 查看返回值
        
        return result.content;
    } catch (error) {
        console.error('❌ 发生错误:', error);  // 错误追踪
        throw error;
    }
}
```

### Q5: 哪些操作需要权限？如何声明？

在扩展的 `getInfo()` 中添加 `permissions` 数组：

```javascript
getInfo() {
    return {
        id: 'my-extension',
        name: '我的扩展',
        permissions: [
            'file-read',        // 读取文件
            'file-write',       // 写入文件
            'hardware-status'   // 获取硬件信息
            // ... 根据需要添加
        ],
        blocks: [...]
    };
}
```

**权限清单速查表**:

| 功能    | 所需权限              | 风险等级 |
| ----- | ----------------- | ---- |
| 读取文件  | `file-read`       | 🟢 低 |
| 写入文件  | `file-write`      | 🟡 中 |
| 删除文件  | `file-delete`     | 🔴 高 |
| 文件信息  | `file-metadata`   | 🟢 低 |
| 执行命令  | `system-command`  | 🔴 高 |
| 全局快捷键 | `global-shortcut` | 🟡 中 |
| 覆盖窗口  | `draw-window`     | 🟡 中 |
| 屏幕捕获  | `screen-capture`  | 🔴 高 |
| 高级窗口  | `advanced-window` | 🟡 中 |
| 硬件状态  | `hardware-status` | 🟢 低 |

### Q6: async/await 是什么？必须用吗？

**是的，必须使用！** 因为所有 EditorPreload 方法都是异步的（返回 Promise）。

**简单理解**:

- `async` 表示这个函数是异步的
- `await` 表示"等待这个操作完成再继续"

**错误写法**（会得到 Promise 对象而不是结果）:

```javascript
function badExample() {
    const result = EditorPreload.readFile('test.txt');  // ❌ 缺少 await
    return result.content;  // undefined! 因为还没完成
}
```

**正确写法**:

```javascript
async function goodExample() {
    const result = await EditorPreload.readFile('test.txt');  // ✅ 等待完成
    return result.content;  // 得到真正的结果
}
```

***

## 最佳实践总结

### ✅ 推荐做法

1. **总是检查环境**
   ```javascript
   if (!this.isDesktop) return '错误: 需要桌面版';
   ```
2. **总是检查返回值**
   ```javascript
   const result = await EditorPreload.readFile(path);
   if (!result.success) {
       console.error('操作失败:', result.error);
       return;
   }
   ```
3. **使用 try-catch 捕获异常**
   ```javascript
   try {
       const result = await EditorPreload.someMethod();
   } catch (error) {
       console.error('发生意外错误:', error);
   }
   ```
4. **合理使用路径变量**
   ```javascript
   // ✅ 好：可移植
   await EditorPreload.readFile('%DESKTOP%\\config.json');

   // ❌ 不好：硬编码
   await EditorPreload.readFile('C:\\Users\\张三\\Desktop\\config.json');
   ```
5. **及时清理资源**
   ```javascript
   // 注销快捷键
   await EditorPreload.unregisterGlobalShortcut('Ctrl+Shift+P');

   // 关闭窗口
   await EditorPreload.closeOverlayWindow('my-window');
   ```

### ❌ 避免的做法

1. 不要在非异步函数中使用 await
2. 不要忽略错误处理
3. 不要忘记检查 `success` 字段
4. 不要在循环中使用 await（除非必要，会影响性能）
5. 不要注册过多全局快捷键（占用系统资源）

***

## 相关链接

- [CYSOEditor 扩展开发教程](./CYSOEditor扩展开发教程.md) - 从零开始学习开发扩展
- [cyso-core-demo-extension.js](./cyso-core-demo-extension.js) - 完整的功能演示扩展（参考实现）
- [TurboWarp 官方文档](https://github.com/TurboWarp) - 底层引擎文档
- [Scratch 扩展文档](https://extensions.turbowarp.org/) - 标准 Scratch 扩展开发指南

***

## 版本历史

- **v1.0.0** (2026-05-30): 初始版本，基于 CYSOCore v1.0.0

***

> **文档维护**: 如发现文档与实际 API 不符，欢迎提交 Issue 或 Pull Request！

