Skip to content
Draft
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
6 changes: 6 additions & 0 deletions plugins/topwindow/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
node_modules/
dist/
build/
temp-ztools/
.env*
*.log
9 changes: 9 additions & 0 deletions plugins/topwindow/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Changelog

## v1.0.0

- 窗口置顶/取消置顶功能
- 通过 PowerShell 调用 Win32 API 实现
- 系统通知即时反馈操作结果
- 透明背景图标
- 仅支持 Windows (win32) 平台
41 changes: 41 additions & 0 deletions plugins/topwindow/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# TopWindow - ZTools 窗口置顶插件

一个简单高效的 Windows 窗口置顶插件,适用于 [ZTools](https://github.com/ZToolsCenter/ZTools)。

## 功能

- **一键置顶**:将当前活动窗口设为置顶(Always on Top)
- **一键取消**:再次执行即可取消置顶
- **窗口识别**:系统通知中显示被操作窗口的标题名称
- **即时反馈**:通过系统通知提示操作结果

## 使用方式

1. 呼出 ZTools
2. 输入 `置顶`、`TopWindow` 或 `窗口置顶`
3. 当前窗口即被置顶/取消置顶,并收到系统通知

## 技术实现

- 通过 PowerShell 调用 Win32 API (`SetWindowPos`, `GetForegroundWindow`, `GetWindowText`)
- 使用 `preload.js` 的 Node.js 能力执行系统级操作
- 无需额外安装任何依赖

## 文件结构

```
TopWindow-ztools/
├── plugin.json # 插件配置
├── preload.js # 插件入口(Node.js 环境)
├── toggle-topmost.ps1 # 核心 PowerShell 脚本
├── index.html # 插件界面
└── logo.png # 插件图标
```

## 仅支持 Windows

本插件使用 Windows 原生 API,仅支持 Windows 系统。

## License

MIT
66 changes: 66 additions & 0 deletions plugins/topwindow/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>TopWindow</title>
<style>
body {
margin: 0;
padding: 0;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
height: 100vh;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
overflow: hidden;
}
.container {
text-align: center;
background: rgba(255, 255, 255, 0.1);
padding: 2rem;
border-radius: 20px;
backdrop-filter: blur(10px);
box-shadow: 0 8px 32px 0 rgba(31, 38, 135, 0.37);
border: 1px solid rgba(255, 255, 255, 0.18);
}
.icon {
font-size: 3rem;
margin-bottom: 1rem;
}
h1 {
margin: 0;
font-size: 1.5rem;
font-weight: 300;
}
p {
opacity: 0.8;
font-size: 0.9rem;
}
.loader {
border: 3px solid rgba(255, 255, 255, 0.3);
border-radius: 50%;
border-top: 3px solid #fff;
width: 24px;
height: 24px;
animation: spin 1s linear infinite;
margin: 1rem auto;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
</style>
</head>
<body>
<div class="container">
<div class="icon">📌</div>
<h1>正在切换置顶状态...</h1>
<div class="loader"></div>
<p>TopWindow 插件已启动</p>
</div>
</body>
</html>
Binary file added plugins/topwindow/logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
20 changes: 20 additions & 0 deletions plugins/topwindow/plugin.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"name": "topwindow",
"title": "窗口置顶",
"description": "一个简单的窗口置顶插件,支持切换当前窗口的置顶状态。",
"author": "evanwen97-ops",
"version": "1.0.0",
"logo": "logo.png",
"main": "index.html",
"preload": "preload.js",
"platform": ["win32"],
"features": [
{
"code": "topwindow",
"explain": "将当前窗口置顶或取消置顶",
"icon": "logo.png",
"cmds": ["TopWindow", "窗口置顶", "置顶"],
"platform": ["win32"]
}
]
}
60 changes: 60 additions & 0 deletions plugins/topwindow/preload.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
const { exec } = require('child_process');
const path = require('path');

/**
* 切换当前窗口的置顶状态
* 调用同目录下的 toggle-topmost.ps1 脚本完成:
* 1. 获取当前前台窗口句柄和窗口标题
* 2. 切换 TOPMOST 状态
* 3. 弹出一个自动消失的 Toast 提示(含窗口名称)
*/
function toggleTopMost() {
// 隐藏 ZTools 主窗口,将焦点还给之前的窗口
try {
window.ztools.hideMainWindow(true);
} catch (e) {
console.error('hideMainWindow failed:', e);
}

// 等待焦点完全切换回目标窗口
setTimeout(() => {
// 获取 ps1 脚本的绝对路径(与 preload.js 同目录)
const scriptPath = path.join(__dirname, 'toggle-topmost.ps1');
const command = 'powershell -NoProfile -ExecutionPolicy Bypass -File "' + scriptPath + '"';

exec(command, (error, stdout, stderr) => {
if (error) {
console.error('TopWindow exec error:', error.message);
try {
window.ztools.showNotification('TopWindow 执行失败: ' + error.message);
} catch (e) { /* ignore */ }
Comment on lines +26 to +30
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

在处理 exec 执行错误时,建议同时记录或显示 stderr 的内容。PowerShell 的具体错误详情(如权限不足、脚本内部错误等)通常会输出到 stderr,这对于排查插件执行失败的原因非常有帮助。

} else {
// 解析结果: "Pinned||窗口标题" 或 "Unpinned||窗口标题"
const output = stdout.trim();
const parts = output.split('||');
const action = parts[0] || '';
const title = parts[1] || '';
Comment on lines +34 to +36
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

使用 split('||') 解析输出结果时,如果窗口标题本身包含 || 字符串(虽然少见但可能存在),会导致标题显示不完整。建议使用解构赋值配合 join 来确保获取完整的标题内容。

Suggested change
const parts = output.split('||');
const action = parts[0] || '';
const title = parts[1] || '';
const [action, ...titleParts] = output.split('||');
const title = titleParts.join('||');


if (action === 'Pinned') {
window.ztools.showNotification('已置顶: ' + title);
} else if (action === 'Unpinned') {
window.ztools.showNotification('已取消置顶: ' + title);
} else if (action === 'NoWindow') {
window.ztools.showNotification('未找到活动窗口');
}
}

// 退出插件
setTimeout(() => {
try { window.ztools.outPlugin(); } catch (e) { /* ignore */ }
}, 200);
});
}, 350);
}

// 插件进入时触发
window.ztools.onPluginEnter(({ code, type, payload }) => {
if (code === 'topwindow') {
toggleTopMost();
}
});
54 changes: 54 additions & 0 deletions plugins/topwindow/toggle-topmost.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
$code = @'
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

建议在脚本开头显式设置输出编码为 UTF-8。由于窗口标题可能包含中文字符,如果 PowerShell 使用默认的系统 ANSI 编码输出,Node.js 在读取 stdout 时可能会出现乱码。

[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
$code = @'

using System;
using System.Text;
using System.Runtime.InteropServices;
public class Win32Top {
[DllImport("user32.dll")]
public static extern IntPtr GetForegroundWindow();

[DllImport("user32.dll")]
public static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags);

[DllImport("user32.dll")]
public static extern int GetWindowLong(IntPtr hWnd, int nIndex);

[DllImport("user32.dll", CharSet = CharSet.Unicode)]
public static extern int GetWindowText(IntPtr hWnd, StringBuilder text, int count);

public const int GWL_EXSTYLE = -20;
public const int WS_EX_TOPMOST = 0x00000008;
public const uint SWP_NOSIZE = 0x0001;
public const uint SWP_NOMOVE = 0x0002;
public static readonly IntPtr HWND_TOPMOST = new IntPtr(-1);
public static readonly IntPtr HWND_NOTOPMOST = new IntPtr(-2);

public static string GetTitle(IntPtr hwnd) {
StringBuilder sb = new StringBuilder(512);
GetWindowText(hwnd, sb, sb.Capacity);
return sb.ToString();
}
}
'@
try { Add-Type -TypeDefinition $code -ErrorAction Stop } catch {}

$hwnd = [Win32Top]::GetForegroundWindow()
if ($hwnd -eq [IntPtr]::Zero) {
Write-Output "NoWindow||"
exit
}

$windowTitle = [Win32Top]::GetTitle($hwnd)
if ([string]::IsNullOrWhiteSpace($windowTitle)) {
$windowTitle = "(unnamed)"
}

$exStyle = [Win32Top]::GetWindowLong($hwnd, [Win32Top]::GWL_EXSTYLE)
$flags = [Win32Top]::SWP_NOSIZE -bor [Win32Top]::SWP_NOMOVE

if (($exStyle -band [Win32Top]::WS_EX_TOPMOST) -eq [Win32Top]::WS_EX_TOPMOST) {
[Win32Top]::SetWindowPos($hwnd, [Win32Top]::HWND_NOTOPMOST, 0, 0, 0, 0, $flags) | Out-Null
Write-Output "Unpinned||$windowTitle"
} else {
[Win32Top]::SetWindowPos($hwnd, [Win32Top]::HWND_TOPMOST, 0, 0, 0, 0, $flags) | Out-Null
Write-Output "Pinned||$windowTitle"
}