diff --git a/plugins/topwindow/.gitignore b/plugins/topwindow/.gitignore new file mode 100644 index 00000000..0008ed17 --- /dev/null +++ b/plugins/topwindow/.gitignore @@ -0,0 +1,6 @@ +node_modules/ +dist/ +build/ +temp-ztools/ +.env* +*.log diff --git a/plugins/topwindow/CHANGELOG.md b/plugins/topwindow/CHANGELOG.md new file mode 100644 index 00000000..5d557cae --- /dev/null +++ b/plugins/topwindow/CHANGELOG.md @@ -0,0 +1,9 @@ +# Changelog + +## v1.0.0 + +- 窗口置顶/取消置顶功能 +- 通过 PowerShell 调用 Win32 API 实现 +- 系统通知即时反馈操作结果 +- 透明背景图标 +- 仅支持 Windows (win32) 平台 diff --git a/plugins/topwindow/README.md b/plugins/topwindow/README.md new file mode 100644 index 00000000..3a2d229e --- /dev/null +++ b/plugins/topwindow/README.md @@ -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 diff --git a/plugins/topwindow/index.html b/plugins/topwindow/index.html new file mode 100644 index 00000000..50211614 --- /dev/null +++ b/plugins/topwindow/index.html @@ -0,0 +1,66 @@ + + + + + + TopWindow + + + +
+
📌
+

正在切换置顶状态...

+
+

TopWindow 插件已启动

+
+ + diff --git a/plugins/topwindow/logo.png b/plugins/topwindow/logo.png new file mode 100644 index 00000000..b4593aa1 Binary files /dev/null and b/plugins/topwindow/logo.png differ diff --git a/plugins/topwindow/plugin.json b/plugins/topwindow/plugin.json new file mode 100644 index 00000000..32d7e13d --- /dev/null +++ b/plugins/topwindow/plugin.json @@ -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"] + } + ] +} diff --git a/plugins/topwindow/preload.js b/plugins/topwindow/preload.js new file mode 100644 index 00000000..c9fb6a5b --- /dev/null +++ b/plugins/topwindow/preload.js @@ -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 */ } + } else { + // 解析结果: "Pinned||窗口标题" 或 "Unpinned||窗口标题" + const output = stdout.trim(); + const parts = output.split('||'); + const action = parts[0] || ''; + const title = parts[1] || ''; + + 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(); + } +}); diff --git a/plugins/topwindow/toggle-topmost.ps1 b/plugins/topwindow/toggle-topmost.ps1 new file mode 100644 index 00000000..174cac32 --- /dev/null +++ b/plugins/topwindow/toggle-topmost.ps1 @@ -0,0 +1,54 @@ +$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" +}