diff --git a/.gitignore b/.gitignore index 315d011..19a55fa 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,6 @@ config.json user.ini *.lock out/ -test.py \ No newline at end of file +test.py +pssuspend64.exe +.bosskey_state diff --git a/README.md b/README.md index 5d6ae1e..eafe7d8 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Boss-Key -![](/src/static/bannar.jpg) +![Boss-Key logo bannar](/src/static/bannar.jpg) ![Github Release Version](https://img.shields.io/github/v/release/IvanHanloth/Boss-Key) ![Github Repo License](https://img.shields.io/github/license/IvanHanloth/Boss-Key) @@ -13,13 +13,13 @@ 支持多窗口隐藏、多进程隐藏、自定义热键、隐藏活动窗口、静音窗口、暂停视频播放等超多功能,完全免费、开源,无强制弹窗等 ## 应用截图 -![](/src/static/screenshot-1.png) +![Boss-Key设置窗口绑定页](/src/static/screenshot-1.png) -![](/src/static/screenshot-2.png) +![Boss-Key设置热键设置页](/src/static/screenshot-5.png) -![](/src/static/screenshot-3.png) +![Boss-Key设置窗口其他选项页](/src/static/screenshot-3.png) -![](/src/static/screenshot-4.png) +![Boss-Key运行提示](/src/static/screenshot-4.png) ## 使用说明 从v2.0.0版本开始,每个版本都会提供三种类型的程序,可以从[Release页面](https://github.com/IvanHanloth/Boss-Key/releases)下载 @@ -61,6 +61,11 @@ 完成所有热键修改后,**记得点击下方的“保存设置”按钮保存并启用设置** +### 鼠标隐藏 +v2.1.0版本加入了鼠标相关操作隐藏绑定,可以选择鼠标中键、侧键1、侧键2切换串口隐藏状态。 + +可以勾选快速移动鼠标至四角隐藏窗口(启用允许移动恢复功能以允许通过快速移动鼠标至四角恢复窗口) + ### 检查更新 右键点击托盘打开托盘菜单,选择“检查更新”即可打开当前检查更新窗口。 @@ -78,6 +83,7 @@ #### 文件路径匹配功能 Boss Key默认使用窗口标题等多因素进行窗口匹配以确保精确隐藏窗口,但是有时可能出现希望根据可执行文件路径来进行匹配: + 即只要是由同一个程序启动的窗口都隐藏,此时就可以启用“文件路径匹配”选项进行模糊匹配 #### 同时隐藏当前活动窗口 @@ -87,7 +93,15 @@ Boss Key默认使用窗口标题等多因素进行窗口匹配以确保精确隐 启用该功能后,可以通过单击托盘图标来显示或隐藏窗口 ### 设置和关闭开机自启 -如果需要让Boss-Key程序开机自启,可以右键点击托盘图标,在弹出的菜单中选择“开机自启”来切换开机自启状态 +如果需要让Boss-Key程序开机自启,可以右键点击托盘图标,在弹出的菜单中选择“开机自启”来切换开机自启状态,或从窗口菜单->工具->开机自启选项进行切换。 + +### 进程冻结相关功能 +自v2.1.0版本开始,Boss-Key加入了隐藏窗口后冻结进程功能。启用此功能将在隐藏窗口后将对应的进程“冻结”,减轻CPU和内存压力。 +启用此功能需要**以管理员身份运行**。冻结加强功能则额外需要下载pssuspend64.exe文件置于程序目录下以供调用。 + +**如何下载和使用加强冻结功能:** + +您需要手动访问[https://download.sysinternals.com/files/PSTools.zip](https://download.sysinternals.com/files/PSTools.zip)页面,下载由Microsoft提供的PSTools工具包。解压下载得到`PSTools.zip`文件,可以找到`pssuspend64.exe`文件,直接复制该文件,放置于Boss-Key程序的安装根目录即可。 ## 常见问题 **为什么我的电脑运行不了编译后的程序** @@ -124,7 +138,12 @@ Boss-Key │   │   ├── __init__.py 初始化包 │   │   ├── about.py 关于页面 │   │   ├── record.py 录制热键页面 -│   │   ├── setting.py 设置页面 +│   │   ├── setting 设置页面相关子类 +│   │   │  ├── __init__.py 初始化包 +│   │   │  ├── base.py 设置页面基础类 +│   │   │  ├── binding_page.py 窗口绑定页面 +│   │   │  ├── hotkeys_page.py 热键绑定页面 +│   │   │  └── options_page.py 其他选项页面 │   │   ├── taskbar.py 托盘图标 │   │   └── window_restore.py 窗口恢复工具页面 │   └── Boss-Key.py 项目入口文件 @@ -158,7 +177,18 @@ Boss-Key ## 已知问题 - 无法隐藏部分游戏窗口,可能由于游戏窗口加密导致 +## 鸣谢 +感谢雪藏HsFreezer提供的进程冻结实现思路 + ## 更新日志 +**V2.1.0 (更新于2025/4/21)** +- 新增鼠标中键、侧键隐藏功能 +- 新增鼠标移动至四角隐藏功能 +- 新增进程冻结功能 +- 新增自动隐藏功能 +- 优化界面UI设计 +- 优化窗口恢复工具 + **V2.0.4 (更新于2025/4/9)** - 修复无法正确禁音应用的问题 - 优化选项提示 diff --git a/logo.png b/logo.png new file mode 100644 index 0000000..27890c4 Binary files /dev/null and b/logo.png differ diff --git a/main/Boss-Key.py b/main/Boss-Key.py index 6b3b122..7a5e3e0 100644 --- a/main/Boss-Key.py +++ b/main/Boss-Key.py @@ -19,6 +19,7 @@ from core.config import Config import platform import atexit +from GUI.setting.base import SettingWindow if platform.system() == "Windows": if platform.release() == "7": @@ -30,10 +31,19 @@ class APP(wx.App): def __init__(self): wx.App.__init__(self) def clean(): + print("Global Cleaning up...") + try: + Config.HotkeyListener._cleanup() + except: + pass try: Config.HotkeyListener.Close() except: pass + try: + os.remove(os.path.join(Config.root_path,"Boss-Key.lock")) + except: + pass atexit.register(clean) # 设置语言环境为中文 @@ -43,7 +53,7 @@ def clean(): self.SetAppDisplayName(Config.AppName) self.SetVendorName(Config.AppAuthor) - lock=os.path.join(os.path.dirname(sys.argv[0]),"Boss-Key.lock") + lock=os.path.join(Config.root_path,"Boss-Key.lock") if self.is_already_running(lock): ask=wx.MessageBox("Boss Key 可能已在运行\n点击“确定”继续运行新的Boss-Key程序\n点击“取消”直接关闭此窗口","Boss Key", wx.OK | wx.ICON_INFORMATION | wx.CANCEL | wx.CANCEL_DEFAULT) if ask==wx.OK: @@ -90,7 +100,7 @@ def is_already_running(self, name): Config.SettingWindowId = wx.NewIdRef() Config.TaskBarIcon=taskbar.TaskBarIcon() Config.HotkeyListener=listener.HotkeyListener() - setting.SettingWindow(Config.SettingWindowId) + SettingWindow(Config.SettingWindowId) if Config.first_start: wx.FindWindowById(Config.SettingWindowId).Show() app.MainLoop() diff --git a/main/GUI/setting.py b/main/GUI/setting.py deleted file mode 100644 index 25e9d1a..0000000 --- a/main/GUI/setting.py +++ /dev/null @@ -1,459 +0,0 @@ -import wx -import wx.dataview as dataview -from core.config import Config -import GUI.record as record -import core.tools as tool -import wx.lib.buttons as buttons -from core.model import WindowInfo - -class SettingWindow(wx.Frame): - # 定义组件ID常量 - ID_LEFT_TREELIST = wx.NewIdRef() - ID_RIGHT_TREELIST = wx.NewIdRef() - ID_ADD_BINDING_BTN = wx.NewIdRef() - ID_REMOVE_BINDING_BTN = wx.NewIdRef() - ID_REFRESH_BTN = wx.NewIdRef() - ID_HIDE_SHOW_HOTKEY_TEXT = wx.NewIdRef() - ID_HIDE_SHOW_HOTKEY_BTN = wx.NewIdRef() - ID_CLOSE_HOTKEY_TEXT = wx.NewIdRef() - ID_CLOSE_HOTKEY_BTN = wx.NewIdRef() - ID_MUTE_AFTER_HIDE_CHECKBOX = wx.NewIdRef() - ID_SEND_BEFORE_HIDE_CHECKBOX = wx.NewIdRef() - ID_HIDE_CURRENT_CHECKBOX = wx.NewIdRef() - ID_CLICK_TO_HIDE_CHECKBOX = wx.NewIdRef() - ID_HIDE_ICON_AFTER_HIDE_CHECKBOX = wx.NewIdRef() - ID_PATH_MATCH_CHECKBOX = wx.NewIdRef() - ID_RESET_BTN = wx.NewIdRef() - ID_SAVE_BTN = wx.NewIdRef() - - def __init__(self,id=None): - super().__init__(None,id=id, title="设置 - Boss Key", style=wx.DEFAULT_FRAME_STYLE | wx.RESIZE_BORDER) - self.SetIcon(wx.Icon(wx.Image(Config.icon).ConvertToBitmap())) - - self.init_UI() - - self.Bind_EVT() - self.SetData() - self.SetSize((1500, 800)) - - self.Center() - - def init_UI(self): - panel = wx.Panel(self) - - # 主 sizer - main_sizer = wx.BoxSizer(wx.VERTICAL) - - # 上方sizer - top_sizer = wx.BoxSizer(wx.HORIZONTAL) - - # 左边列表 - left_staticbox = wx.StaticBox(panel, label="现有窗口进程") - left_sizer = wx.StaticBoxSizer(left_staticbox, wx.VERTICAL) - self.left_treelist = dataview.TreeListCtrl(panel, self.ID_LEFT_TREELIST, style=dataview.TL_CHECKBOX) - self.left_treelist.AppendColumn('窗口标题', width=300) - self.left_treelist.AppendColumn('窗口句柄', width=100) - self.left_treelist.AppendColumn('进程PID', width=150) - left_sizer.Add(self.left_treelist, 1, wx.EXPAND | wx.ALL, 5) - - - # 中键按钮 - middle_sizer = wx.BoxSizer(wx.VERTICAL) - add_binding_btn = buttons.GenButton(panel, self.ID_ADD_BINDING_BTN, label="添加绑定-->") - remove_binding_btn = buttons.GenButton(panel, self.ID_REMOVE_BINDING_BTN, label="<--删除绑定") - refresh_btn = buttons.GenButton(panel, self.ID_REFRESH_BTN, label="刷新进程") - middle_sizer.Add(add_binding_btn, 0, wx.EXPAND | wx.ALL, 5) - middle_sizer.Add(remove_binding_btn, 0, wx.EXPAND | wx.ALL, 5) - middle_sizer.Add(refresh_btn, 0, wx.EXPAND | wx.ALL, 5) - - # 右边列表 - right_staticbox = wx.StaticBox(panel, label="已绑定进程") - right_sizer = wx.StaticBoxSizer(right_staticbox, wx.VERTICAL) - self.right_treelist = dataview.TreeListCtrl(panel, self.ID_RIGHT_TREELIST, style=dataview.TL_CHECKBOX) - self.right_treelist.AppendColumn('窗口标题', width=300) - self.right_treelist.AppendColumn('窗口句柄', width=100) - self.right_treelist.AppendColumn('进程PID', width=150) - right_sizer.Add(self.right_treelist, 1, wx.EXPAND | wx.ALL, 5) - - # 加到上方的sizer中 - top_sizer.Add(left_sizer, 1, wx.EXPAND | wx.ALL, 5) - top_sizer.Add(middle_sizer, 0, wx.EXPAND | wx.ALL, 5) - top_sizer.Add(right_sizer, 1, wx.EXPAND | wx.ALL, 5) - - # 下方设置 - bottom_staticbox = wx.StaticBox(panel, label="其他设置") - bottom_sizer = wx.StaticBoxSizer(bottom_staticbox, wx.VERTICAL) - - hotkey_sizer=wx.GridSizer(rows=0, cols=2, gap=(10, 10)) - - #设置隐显窗口热键 - hide_show_hotkey_sizer = wx.BoxSizer(wx.HORIZONTAL) - hide_show_hotkey_label = wx.StaticText(panel, label="隐藏/显示窗口热键:") - hide_show_hotkey_text = wx.TextCtrl(panel, self.ID_HIDE_SHOW_HOTKEY_TEXT, value=Config.hide_hotkey) - hide_show_hotkey_btn = wx.Button(panel, self.ID_HIDE_SHOW_HOTKEY_BTN, label="录制热键") - hide_show_hotkey_sizer.Add(hide_show_hotkey_label, proportion=1, flag=wx.EXPAND| wx.ALL, border=10) - hide_show_hotkey_sizer.Add(hide_show_hotkey_text, proportion=1, flag=wx.EXPAND| wx.ALL, border=10) - hide_show_hotkey_sizer.Add(hide_show_hotkey_btn, proportion=1, flag=wx.EXPAND|wx.ALL, border=10) - hotkey_sizer.Add(hide_show_hotkey_sizer, proportion=1, flag=wx.EXPAND|wx.ALL, border=10) - - #设置关闭热键 - close_hotkey_sizer = wx.BoxSizer(wx.HORIZONTAL) - close_hotkey_label = wx.StaticText(panel, label="一键关闭程序热键:") - close_hotkey_text = wx.TextCtrl(panel, self.ID_CLOSE_HOTKEY_TEXT, value=Config.close_hotkey) - close_hotkey_btn = wx.Button(panel, self.ID_CLOSE_HOTKEY_BTN, label="录制热键") - close_hotkey_sizer.Add(close_hotkey_label, proportion=1, flag=wx.EXPAND| wx.ALL, border=10) - close_hotkey_sizer.Add(close_hotkey_text, proportion=1, flag=wx.EXPAND| wx.ALL, border=10) - close_hotkey_sizer.Add(close_hotkey_btn, proportion=1, flag=wx.EXPAND| wx.ALL, border=10) - hotkey_sizer.Add(close_hotkey_sizer, proportion=1, flag=wx.EXPAND| wx.ALL, border=10) - - bottom_sizer.Add(hotkey_sizer, flag=wx.EXPAND| wx.ALL) - - # 创建复选框 - settings_checkbox_sizer = wx.GridSizer(rows=0, cols=3, gap=(10, 10)) - - mute_after_hide_sizer=wx.BoxSizer(wx.HORIZONTAL) - mute_after_hide_label = wx.StaticText(panel, label="隐藏窗口后静音") - mute_after_hide_checkbox = wx.CheckBox(panel, self.ID_MUTE_AFTER_HIDE_CHECKBOX, "") - mute_after_hide_sizer.Add(mute_after_hide_label,proportion=1, flag=wx.EXPAND| wx.ALL) - mute_after_hide_sizer.Add(mute_after_hide_checkbox,proportion=1, flag=wx.EXPAND| wx.ALL) - settings_checkbox_sizer.Add(mute_after_hide_sizer,proportion=1,flag=wx.EXPAND| wx.ALL, border=10) - - send_before_hide_sizer=wx.BoxSizer(wx.HORIZONTAL) - send_before_hide_label = wx.StaticText(panel, label="隐藏前发送暂停键(Beta)") - send_before_hide_label.SetToolTip(wx.ToolTip("隐藏窗口前发送暂停键,用于关闭弹出的输入框等,隐藏窗口会存在一定的延迟")) - send_before_hide_checkbox = wx.CheckBox(panel, self.ID_SEND_BEFORE_HIDE_CHECKBOX, "") - send_before_hide_sizer.Add(send_before_hide_label,proportion=1, flag=wx.EXPAND| wx.ALL) - send_before_hide_sizer.Add(send_before_hide_checkbox,proportion=1, flag=wx.EXPAND| wx.ALL) - settings_checkbox_sizer.Add(send_before_hide_sizer, proportion=1,flag=wx.EXPAND| wx.ALL, border=10) - - hide_current_sizer=wx.BoxSizer(wx.HORIZONTAL) - hide_current_label = wx.StaticText(panel, label="同时隐藏当前活动窗口") - hide_current_checkbox = wx.CheckBox(panel, self.ID_HIDE_CURRENT_CHECKBOX, "") - hide_current_sizer.Add(hide_current_label,proportion=1, flag=wx.EXPAND| wx.ALL) - hide_current_sizer.Add(hide_current_checkbox,proportion=1, flag=wx.EXPAND| wx.ALL) - settings_checkbox_sizer.Add(hide_current_sizer, proportion=1,flag=wx.EXPAND| wx.ALL, border=10) - - click_to_hide_sizer=wx.BoxSizer(wx.HORIZONTAL) - click_to_hide_label = wx.StaticText(panel, label="点击托盘图标切换隐藏状态") - click_to_hide_checkbox = wx.CheckBox(panel, self.ID_CLICK_TO_HIDE_CHECKBOX, "") - click_to_hide_sizer.Add(click_to_hide_label,proportion=1, flag=wx.EXPAND| wx.ALL) - click_to_hide_sizer.Add(click_to_hide_checkbox,proportion=1, flag=wx.EXPAND| wx.ALL) - settings_checkbox_sizer.Add(click_to_hide_sizer, proportion=1,flag=wx.EXPAND| wx.ALL, border=10) - - hide_icon_after_hide_sizer=wx.BoxSizer(wx.HORIZONTAL) - hide_icon_after_hide_label = wx.StaticText(panel, label="隐藏窗口后隐藏托盘图标") - hide_icon_after_hide_checkbox = wx.CheckBox(panel, self.ID_HIDE_ICON_AFTER_HIDE_CHECKBOX, "") - hide_icon_after_hide_sizer.Add(hide_icon_after_hide_label,proportion=1, flag=wx.EXPAND| wx.ALL) - hide_icon_after_hide_sizer.Add(hide_icon_after_hide_checkbox,proportion=1, flag=wx.EXPAND| wx.ALL) - settings_checkbox_sizer.Add(hide_icon_after_hide_sizer, proportion=1,flag=wx.EXPAND| wx.ALL, border=10) - - path_match_sizer=wx.BoxSizer(wx.HORIZONTAL) - path_icon_sizer = wx.BoxSizer(wx.HORIZONTAL) - path_match_tooltip = "启用此选项可以一键隐藏绑定程序的所有窗口\r\n关闭此选项后,将会智能精确隐藏指定窗口" - path_match_label = wx.StaticText(panel, label="文件路径匹配") - path_match_label.SetToolTip(path_match_tooltip) - path_match_checkbox = wx.CheckBox(panel, self.ID_PATH_MATCH_CHECKBOX, "") - path_match_tooltip_icon = wx.StaticBitmap(panel, bitmap=wx.ArtProvider.GetBitmap(wx.ART_INFORMATION, wx.ART_OTHER, self.FromDIP((14, 14)))) - path_match_tooltip_icon.SetToolTip(path_match_tooltip) - path_icon_sizer.Add(path_match_label,flag=wx.EXPAND| wx.ALL) - path_icon_sizer.AddSpacer(5) - path_icon_sizer.Add(path_match_tooltip_icon,flag=wx.EXPAND| wx.ALL) - path_match_sizer.Add(path_icon_sizer, proportion=1,flag=wx.EXPAND| wx.ALL) - path_match_sizer.Add(path_match_checkbox,proportion=1, flag=wx.EXPAND| wx.ALL) - settings_checkbox_sizer.Add(path_match_sizer, proportion=1,flag=wx.EXPAND| wx.ALL, border=10) - - bottom_sizer.Add(settings_checkbox_sizer, flag=wx.EXPAND| wx.ALL, border=10) - - #设置提示 - if Config.first_start: - StaticText2=wx.StaticText(panel,label="本页面仅在首次启动或内容有更新时自动显示,后续可通过托盘图标打开本页面") - bottom_sizer.Add(StaticText2, proportion=1, flag=wx.EXPAND|wx.BOTTOM, border=10) - - # 创建按钮 - button_sizer = wx.BoxSizer(wx.HORIZONTAL) - reset_btn = wx.Button(panel, self.ID_RESET_BTN, size=(100,60), label="重置设置") - save_btn = wx.Button(panel, self.ID_SAVE_BTN, size=(100,60), label="保存设置") - button_sizer.Add(reset_btn, proportion=1, flag=wx.LEFT, border=20) - button_sizer.Add(save_btn, proportion=1, flag=wx.RIGHT, border=20) - bottom_sizer.Add(button_sizer, proportion=1, flag=wx.EXPAND|wx.BOTTOM, border=10) - - # Add top and bottom sizers to the main sizer - main_sizer.Add(top_sizer, 1, wx.EXPAND | wx.ALL, 5) - main_sizer.Add(bottom_sizer, 0, wx.EXPAND | wx.ALL, 5) - - panel.SetSizer(main_sizer) - main_sizer.Fit(self) - - def Bind_EVT(self): - self.Bind(wx.EVT_BUTTON, self.OnSave, id=self.ID_SAVE_BTN) - self.Bind(wx.EVT_BUTTON, self.OnReset, id=self.ID_RESET_BTN) - self.Bind(wx.EVT_BUTTON, self.OnRecordSW, id=self.ID_HIDE_SHOW_HOTKEY_BTN) - self.Bind(wx.EVT_BUTTON, self.OnRecordCL, id=self.ID_CLOSE_HOTKEY_BTN) - self.Bind(wx.EVT_CHECKBOX, self.OnSendBeforeHide, id=self.ID_SEND_BEFORE_HIDE_CHECKBOX) - self.Bind(wx.EVT_BUTTON, self.RefreshLeftList, id=self.ID_REFRESH_BTN) - self.Bind(wx.EVT_BUTTON, self.OnAddBinding, id=self.ID_ADD_BINDING_BTN) - self.Bind(wx.EVT_BUTTON, self.OnRemoveBinding, id=self.ID_REMOVE_BINDING_BTN) - self.left_treelist.Bind(dataview.EVT_TREELIST_ITEM_CHECKED, self.OnToggleCheck) - self.right_treelist.Bind(dataview.EVT_TREELIST_ITEM_CHECKED, self.OnToggleCheck) - - self.Bind(wx.EVT_CLOSE, self.OnClose) - - def SetData(self): - Config.load() - hide_show_hotkey_text = self.FindWindowById(self.ID_HIDE_SHOW_HOTKEY_TEXT) - close_hotkey_text = self.FindWindowById(self.ID_CLOSE_HOTKEY_TEXT) - mute_after_hide_checkbox = self.FindWindowById(self.ID_MUTE_AFTER_HIDE_CHECKBOX) - send_before_hide_checkbox = self.FindWindowById(self.ID_SEND_BEFORE_HIDE_CHECKBOX) - hide_current_checkbox = self.FindWindowById(self.ID_HIDE_CURRENT_CHECKBOX) - click_to_hide_checkbox = self.FindWindowById(self.ID_CLICK_TO_HIDE_CHECKBOX) - hide_icon_after_hide_checkbox = self.FindWindowById(self.ID_HIDE_ICON_AFTER_HIDE_CHECKBOX) - path_match_checkbox = self.FindWindowById(self.ID_PATH_MATCH_CHECKBOX) - - hide_show_hotkey_text.SetValue(Config.hide_hotkey) - close_hotkey_text.SetValue(Config.close_hotkey) - mute_after_hide_checkbox.SetValue(Config.mute_after_hide) - send_before_hide_checkbox.SetValue(Config.send_before_hide) - hide_current_checkbox.SetValue(Config.hide_current) - click_to_hide_checkbox.SetValue(Config.click_to_hide) - hide_icon_after_hide_checkbox.SetValue(Config.hide_icon_after_hide) - path_match_checkbox.SetValue(Config.path_match) - self.InsertTreeList(Config.hide_binding, self.right_treelist, True) - self.RefreshLeftList() - - def OnSave(self, e): - hide_show_hotkey_text = self.FindWindowById(self.ID_HIDE_SHOW_HOTKEY_TEXT) - close_hotkey_text = self.FindWindowById(self.ID_CLOSE_HOTKEY_TEXT) - mute_after_hide_checkbox = self.FindWindowById(self.ID_MUTE_AFTER_HIDE_CHECKBOX) - send_before_hide_checkbox = self.FindWindowById(self.ID_SEND_BEFORE_HIDE_CHECKBOX) - hide_current_checkbox = self.FindWindowById(self.ID_HIDE_CURRENT_CHECKBOX) - click_to_hide_checkbox = self.FindWindowById(self.ID_CLICK_TO_HIDE_CHECKBOX) - hide_icon_after_hide_checkbox = self.FindWindowById(self.ID_HIDE_ICON_AFTER_HIDE_CHECKBOX) - path_match_checkbox = self.FindWindowById(self.ID_PATH_MATCH_CHECKBOX) - - Config.hide_hotkey = hide_show_hotkey_text.GetValue() - Config.close_hotkey = close_hotkey_text.GetValue() - Config.mute_after_hide = mute_after_hide_checkbox.GetValue() - Config.send_before_hide = send_before_hide_checkbox.GetValue() - Config.hide_current = hide_current_checkbox.GetValue() - Config.click_to_hide = click_to_hide_checkbox.GetValue() - Config.hide_icon_after_hide = hide_icon_after_hide_checkbox.GetValue() - Config.path_match = path_match_checkbox.GetValue() - - # 获取Windows对象列表 - Config.hide_binding = self.ItemsData(self.right_treelist, only_checked=False) - - Config.HotkeyListener.ShowWindows(load=False) - Config.save() - try: - Config.HotkeyListener.reBind() - wx.MessageDialog(None, u"保存成功", u"Boss Key", wx.OK | wx.ICON_INFORMATION).ShowModal() - except: - wx.MessageDialog(None, u"热键绑定失败,请重试", u"Boss Key", wx.OK | wx.ICON_ERROR).ShowModal() - - def OnAddBinding(self, e): - left_checked = self.ItemsData(self.left_treelist, only_checked=True) - self.InsertTreeList(left_checked, self.right_treelist, False) - for item in left_checked: - self.RemoveItem(self.left_treelist, item) - - def OnRemoveBinding(self, e): - right_checked = self.ItemsData(self.right_treelist, only_checked=True) - self.InsertTreeList(right_checked, self.left_treelist, False) - for item in right_checked: - self.RemoveItem(self.right_treelist, item) - - def OnReset(self, e): - hide_show_hotkey_text = self.FindWindowById(self.ID_HIDE_SHOW_HOTKEY_TEXT) - close_hotkey_text = self.FindWindowById(self.ID_CLOSE_HOTKEY_TEXT) - mute_after_hide_checkbox = self.FindWindowById(self.ID_MUTE_AFTER_HIDE_CHECKBOX) - send_before_hide_checkbox = self.FindWindowById(self.ID_SEND_BEFORE_HIDE_CHECKBOX) - hide_current_checkbox = self.FindWindowById(self.ID_HIDE_CURRENT_CHECKBOX) - click_to_hide_checkbox = self.FindWindowById(self.ID_CLICK_TO_HIDE_CHECKBOX) - hide_icon_after_hide_checkbox = self.FindWindowById(self.ID_HIDE_ICON_AFTER_HIDE_CHECKBOX) - path_match_checkbox = self.FindWindowById(self.ID_PATH_MATCH_CHECKBOX) - - hide_show_hotkey_text.SetValue("Ctrl+Q") - close_hotkey_text.SetValue("Win+Esc") - mute_after_hide_checkbox.SetValue(True) - send_before_hide_checkbox.SetValue(False) - hide_current_checkbox.SetValue(True) - click_to_hide_checkbox.SetValue(False) - hide_icon_after_hide_checkbox.SetValue(False) - path_match_checkbox.SetValue(False) - self.InsertTreeList([], self.right_treelist, True) - self.RefreshLeftList() - - wx.MessageDialog(None, u"已重置选项,请保存设置以启用", u"Boss Key", wx.OK | wx.ICON_INFORMATION).ShowModal() - - def OnToggleCheck(self, e): - treelist = e.GetEventObject() - item = e.GetItem() - is_checked = treelist.GetCheckedState(item) - - # 递归设置子节点状态 - self.CheckItemRecursively(treelist, item, is_checked) - - # 更新父节点状态 - self.UpdateParentCheckState(treelist, item) - - def CheckItemRecursively(self, treelist, item, check_state): - """递归设置项目及其子项的选中状态""" - treelist.CheckItem(item, check_state) - - # 处理所有子节点 - child = treelist.GetFirstChild(item) - while child.IsOk(): - self.CheckItemRecursively(treelist, child, check_state) - child = treelist.GetNextSibling(child) - - def UpdateParentCheckState(self, treelist, item): - """更新父节点的选中状态""" - parent = treelist.GetItemParent(item) - if parent != treelist.GetRootItem(): - # 检查所有兄弟节点状态 - all_checked = True - all_unchecked = True - - child = treelist.GetFirstChild(parent) - while child.IsOk(): - state = treelist.GetCheckedState(child) - if state != wx.CHK_CHECKED: - all_checked = False - if state != wx.CHK_UNCHECKED: - all_unchecked = False - child = treelist.GetNextSibling(child) - - # 根据子节点状态设置父节点状态 - if all_checked: - treelist.CheckItem(parent, wx.CHK_CHECKED) - elif all_unchecked: - treelist.CheckItem(parent, wx.CHK_UNCHECKED) - else: - treelist.CheckItem(parent, wx.CHK_UNDETERMINED) - - # 递归更新上层父节点 - self.UpdateParentCheckState(treelist, parent) - - def OnSendBeforeHide(self, e): - send_before_hide_checkbox = self.FindWindowById(self.ID_SEND_BEFORE_HIDE_CHECKBOX) - if send_before_hide_checkbox.GetValue(): - wx.MessageDialog(None, u"隐藏窗口前向被隐藏的窗口发送空格,用于暂停视频等。启用此功能可能会延迟窗口的隐藏", u"Boss Key", wx.OK | wx.ICON_INFORMATION).ShowModal() - - def OnRecordSW(self, e): - hide_show_hotkey_text = self.FindWindowById(self.ID_HIDE_SHOW_HOTKEY_TEXT) - hide_show_hotkey_btn = self.FindWindowById(self.ID_HIDE_SHOW_HOTKEY_BTN) - self.recordHotkey(hide_show_hotkey_text, hide_show_hotkey_btn) - - def OnClose(self, e): - self.Hide() - - def OnRecordCL(self, e): - close_hotkey_text = self.FindWindowById(self.ID_CLOSE_HOTKEY_TEXT) - close_hotkey_btn = self.FindWindowById(self.ID_CLOSE_HOTKEY_BTN) - self.recordHotkey(close_hotkey_text, close_hotkey_btn) - - def RefreshLeftList(self, e=None): - windows = tool.getAllWindows() - right = self.ItemsData(self.right_treelist, only_checked=False) - list = [] - for window in windows: - flag = 0 - for i in right: - if tool.isSameWindow(window, i, True): - flag = 1 - break - if not flag: - list.append(window) - self.InsertTreeList(list, self.left_treelist, True) - - def InsertTreeList(self, data: list, treelist: dataview.TreeListCtrl, clear=True): - if clear: - treelist.DeleteAllItems() - root = treelist.GetRootItem() - process_map = {} - for window in data: - # 确保window是WindowInfo对象 - if isinstance(window, dict): - window = WindowInfo.from_dict(window) - - process = window.process - if process not in process_map: - exists_node = self.SearchProcessNode(treelist, process) - if exists_node is None: - process_map[process] = treelist.AppendItem(root, process) - else: - process_map[process] = exists_node - item = treelist.AppendItem(process_map[process], window.title) - treelist.SetItemText(item, 1, str(window.hwnd)) - treelist.SetItemText(item, 2, str(window.PID)) - treelist.SetItemData(item, window) - treelist.Expand(root) - for process in process_map: - treelist.Expand(process_map[process]) - - # 初始化所有父节点的选中状态 - for process in process_map: - self.UpdateParentCheckState(treelist, treelist.GetFirstChild(process_map[process])) - - def SearchProcessNode(self, treelist: dataview.TreeListCtrl, process): - item = treelist.GetRootItem() - while item.IsOk(): - item = treelist.GetNextItem(item) - if not item.IsOk(): - break - data = treelist.GetItemData(item) - if data is not None and hasattr(data, 'process') and data.process == process: - return treelist.GetItemParent(item) - - def RemoveItem(self, treelist: dataview.TreeListCtrl, data): - # 确保data是WindowInfo对象 - if isinstance(data, dict): - data = WindowInfo.from_dict(data) - - node = item = self.SearchProcessNode(treelist, data.process) - if item is not None: - item = treelist.GetFirstChild(item) - while item.IsOk(): - item_data = treelist.GetItemData(item) - if item_data and item_data == data: - treelist.DeleteItem(item) - break - item = treelist.GetNextSibling(item) - - if not treelist.GetFirstChild(node).IsOk(): - # 如果没有子节点了,删除父节点 - treelist.DeleteItem(node) - - def ItemsData(self, treelist: dataview.TreeListCtrl, only_checked=False, item_object=False)->list[WindowInfo]: - res = [] - item = treelist.GetRootItem() - while item.IsOk(): - item = treelist.GetNextItem(item) - if not item.IsOk(): - break - if only_checked and treelist.GetCheckedState(item) != wx.CHK_CHECKED: - continue - if item_object: - res.append(item) - else: - data = treelist.GetItemData(item) - if data is not None and data: - res.append(data) - return res - - def recordHotkey(self, text_ctrl: wx.TextCtrl, btn: wx.Button): - try: - Config.HotkeyListener.stop() - except: - pass - btn.Disable() - btn.SetLabel("录制中...") - record.RecordedHotkey.confirm = False - RecordWindow = record.RecordWindow() - RecordWindow.ShowModal() - btn.Enable() - btn.SetLabel("录制热键") - if record.RecordedHotkey.confirm: - text_ctrl.SetValue(record.RecordedHotkey.final_key) - - diff --git a/main/GUI/setting/__init__.py b/main/GUI/setting/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/main/GUI/setting/base.py b/main/GUI/setting/base.py new file mode 100644 index 0000000..18394b9 --- /dev/null +++ b/main/GUI/setting/base.py @@ -0,0 +1,127 @@ +import wx +from core.config import Config +import core.tools as tool +from .binding_page import BindingPage +from .hotkeys_page import HotkeysPage +from .options_page import OptionsPage + +class SettingWindow(wx.Frame): + def __init__(self, id=None): + super().__init__(None, id=id, title="设置 - Boss Key", style=wx.DEFAULT_FRAME_STYLE | wx.RESIZE_BORDER) + self.SetIcon(wx.Icon(wx.Image(Config.icon).ConvertToBitmap())) + + self.init_UI() + self.InitMenu() + self.Bind_EVT() + self.SetData() + self.SetSize((1500, 800)) + self.Center() + + def init_UI(self): + panel = wx.Panel(self) + self.infobar = wx.InfoBar(panel) + sizer = wx.BoxSizer(wx.VERTICAL) + + # 创建notebook + self.notebook = wx.Notebook(panel) + + # 添加各个设置页面 + self.binding_page = BindingPage(self.notebook) + self.hotkeys_page = HotkeysPage(self.notebook) + self.options_page = OptionsPage(self.notebook) + + self.notebook.AddPage(self.binding_page, "窗口绑定") + self.notebook.AddPage(self.hotkeys_page, "热键设置") + self.notebook.AddPage(self.options_page, "其他选项") + + sizer.Add(self.infobar, 0, wx.EXPAND | wx.ALL, 5) + sizer.Add(self.notebook, 1, wx.EXPAND | wx.ALL, 5) + + # 创建按钮 + button_sizer = wx.BoxSizer(wx.HORIZONTAL) + self.reset_btn = wx.Button(panel, label="重置设置", size=(-1, 50)) + self.save_btn = wx.Button(panel, label="保存设置", size=(-1, 50)) + button_sizer.Add(self.reset_btn, proportion=1, flag=wx.LEFT, border=20) + button_sizer.Add(self.save_btn, proportion=1, flag=wx.RIGHT, border=20) + + sizer.Add(button_sizer, 0, wx.EXPAND | wx.ALL, 10) + + # 设置提示 + if Config.first_start: + self.infobar.ShowMessage("欢迎使用 Boss Key!本页面仅在首次启动或内容有更新时自动显示,后续可通过托盘图标打开本页面", wx.ICON_INFORMATION) + + panel.SetSizer(sizer) + sizer.Fit(self) + + def InitMenu(self): + ## 创建菜单栏 + menuBar = wx.MenuBar() + menu1 = wx.Menu() + menu1.Append(101, "检查更新") + menu1.Append(wx.ID_ABOUT, "关于程序") + menu1.AppendSeparator() + menu1.Append(wx.ID_EXIT, "退出程序") + menuBar.Append(menu1, "关于(&A)") + + menu2 = wx.Menu() + menu2.Append(201, "开机自启", kind=wx.ITEM_CHECK) + menu2.Append(202, "窗口恢复工具") + menuBar.Append(menu2, "工具(&T)") + + # menuBar.Append(300, "帮助(&H)") + + self.SetMenuBar(menuBar) + + ## 绑定菜单事件 + self.Bind(wx.EVT_MENU, Config.TaskBarIcon.onUpdate, id=101) + self.Bind(wx.EVT_MENU, Config.TaskBarIcon.onAbout, id=wx.ID_ABOUT) + self.Bind(wx.EVT_MENU, Config.TaskBarIcon.onExit, id=wx.ID_EXIT) + self.Bind(wx.EVT_MENU, Config.TaskBarIcon.onStartup, id=201) + self.Bind(wx.EVT_MENU_OPEN, self.OnUpdateStartupStatus) + self.Bind(wx.EVT_MENU, Config.TaskBarIcon.onRestore, id=202) + # self.Bind(wx.EVT_MENU, self.OnHelp, id=300) + + def Bind_EVT(self): + self.save_btn.Bind(wx.EVT_BUTTON, self.OnSave) + self.reset_btn.Bind(wx.EVT_BUTTON, self.OnReset) + self.Bind(wx.EVT_CLOSE, self.OnClose) + + def SetData(self): + Config.load() + self.binding_page.SetData() + self.hotkeys_page.SetData() + self.options_page.SetData() + + def OnSave(self, e): + # 从各页面获取数据 + self.binding_page.SaveData() + self.hotkeys_page.SaveData() + self.options_page.SaveData() + + # 应用更改 + Config.HotkeyListener.ShowWindows(load=False) + Config.save() + try: + Config.HotkeyListener.reBind() + wx.MessageDialog(None, "保存成功", "Boss Key", wx.OK | wx.ICON_INFORMATION).ShowModal() + except: + wx.MessageDialog(None, "热键绑定失败,请重试", "Boss Key", wx.OK | wx.ICON_ERROR).ShowModal() + + def OnReset(self, e): + # 重置所有设置 + self.binding_page.Reset() + self.hotkeys_page.Reset() + self.options_page.Reset() + wx.MessageDialog(None, "已重置选项,请保存设置以启用", "Boss Key", wx.OK | wx.ICON_INFORMATION).ShowModal() + + def OnClose(self, e): + self.Hide() + + def OnUpdateStartupStatus(self, e=""): + # 更新菜单项的选中状态 + print(1) + self.GetMenuBar().FindItemById(201).Check(tool.checkStartup("Boss Key Application", Config.file_path)) + + def RefreshLeftList(self, e=None): + """刷新左侧列表,供外部调用""" + self.binding_page.RefreshLeftList() diff --git a/main/GUI/setting/binding_page.py b/main/GUI/setting/binding_page.py new file mode 100644 index 0000000..16d4b95 --- /dev/null +++ b/main/GUI/setting/binding_page.py @@ -0,0 +1,220 @@ +import wx +import wx.dataview as dataview +import wx.lib.buttons as buttons +from core.config import Config +import core.tools as tool +from core.model import WindowInfo + +class BindingPage(wx.Panel): + def __init__(self, parent): + super().__init__(parent) + self.init_UI() + self.Bind_EVT() + + def init_UI(self): + # 主 sizer + main_sizer = wx.BoxSizer(wx.HORIZONTAL) + + # 左边列表 + left_staticbox = wx.StaticBox(self, label="现有窗口进程") + left_sizer = wx.StaticBoxSizer(left_staticbox, wx.VERTICAL) + self.left_treelist = dataview.TreeListCtrl(self, style=dataview.TL_CHECKBOX) + self.left_treelist.AppendColumn('窗口标题', width=300) + self.left_treelist.AppendColumn('窗口句柄', width=100) + self.left_treelist.AppendColumn('进程PID', width=150) + left_sizer.Add(self.left_treelist, 1, wx.EXPAND | wx.ALL, 5) + + # 中键按钮 + middle_sizer = wx.BoxSizer(wx.VERTICAL) + self.add_binding_btn = buttons.GenButton(self, label="添加绑定-->") + self.remove_binding_btn = buttons.GenButton(self, label="<--删除绑定") + self.refresh_btn = buttons.GenButton(self, label="刷新进程") + middle_sizer.Add(self.add_binding_btn, 0, wx.EXPAND | wx.ALL, 5) + middle_sizer.Add(self.remove_binding_btn, 0, wx.EXPAND | wx.ALL, 5) + middle_sizer.Add(self.refresh_btn, 0, wx.EXPAND | wx.ALL, 5) + + # 右边列表 + right_staticbox = wx.StaticBox(self, label="已绑定进程") + right_sizer = wx.StaticBoxSizer(right_staticbox, wx.VERTICAL) + self.right_treelist = dataview.TreeListCtrl(self, style=dataview.TL_CHECKBOX) + self.right_treelist.AppendColumn('窗口标题', width=300) + self.right_treelist.AppendColumn('窗口句柄', width=100) + self.right_treelist.AppendColumn('进程PID', width=150) + right_sizer.Add(self.right_treelist, 1, wx.EXPAND | wx.ALL, 5) + + # 加到主sizer中 + main_sizer.Add(left_sizer, 1, wx.EXPAND | wx.ALL, 5) + main_sizer.Add(middle_sizer, 0, wx.EXPAND | wx.ALL, 5) + main_sizer.Add(right_sizer, 1, wx.EXPAND | wx.ALL, 5) + + self.SetSizer(main_sizer) + + def Bind_EVT(self): + self.add_binding_btn.Bind(wx.EVT_BUTTON, self.OnAddBinding) + self.remove_binding_btn.Bind(wx.EVT_BUTTON, self.OnRemoveBinding) + self.refresh_btn.Bind(wx.EVT_BUTTON, self.RefreshLeftList) + self.left_treelist.Bind(dataview.EVT_TREELIST_ITEM_CHECKED, self.OnToggleCheck) + self.right_treelist.Bind(dataview.EVT_TREELIST_ITEM_CHECKED, self.OnToggleCheck) + + def SetData(self): + self.InsertTreeList(Config.hide_binding, self.right_treelist, True) + self.RefreshLeftList() + + def SaveData(self): + # 获取已绑定窗口列表 + Config.hide_binding = self.ItemsData(self.right_treelist, only_checked=False) + + def Reset(self): + self.InsertTreeList([], self.right_treelist, True) + self.RefreshLeftList() + + def OnAddBinding(self, e): + left_checked = self.ItemsData(self.left_treelist, only_checked=True) + self.InsertTreeList(left_checked, self.right_treelist, False) + for item in left_checked: + self.RemoveItem(self.left_treelist, item) + + def OnRemoveBinding(self, e): + right_checked = self.ItemsData(self.right_treelist, only_checked=True) + self.InsertTreeList(right_checked, self.left_treelist, False) + for item in right_checked: + self.RemoveItem(self.right_treelist, item) + + def RefreshLeftList(self, e=None): + windows = tool.getAllWindows() + right = self.ItemsData(self.right_treelist, only_checked=False) + list = [] + for window in windows: + flag = 0 + for i in right: + if tool.isSameWindow(window, i, True): + flag = 1 + break + if not flag: + list.append(window) + self.InsertTreeList(list, self.left_treelist, True) + + def OnToggleCheck(self, e): + treelist = e.GetEventObject() + item = e.GetItem() + is_checked = treelist.GetCheckedState(item) + + # 递归设置子节点状态 + self.CheckItemRecursively(treelist, item, is_checked) + + # 更新父节点状态 + self.UpdateParentCheckState(treelist, item) + + def CheckItemRecursively(self, treelist, item, check_state): + """递归设置项目及其子项的选中状态""" + treelist.CheckItem(item, check_state) + + # 处理所有子节点 + child = treelist.GetFirstChild(item) + while child.IsOk(): + self.CheckItemRecursively(treelist, child, check_state) + child = treelist.GetNextSibling(child) + + def UpdateParentCheckState(self, treelist, item): + """更新父节点的选中状态""" + parent = treelist.GetItemParent(item) + if parent != treelist.GetRootItem(): + # 检查所有兄弟节点状态 + all_checked = True + all_unchecked = True + + child = treelist.GetFirstChild(parent) + while child.IsOk(): + state = treelist.GetCheckedState(child) + if state != wx.CHK_CHECKED: + all_checked = False + if state != wx.CHK_UNCHECKED: + all_unchecked = False + child = treelist.GetNextSibling(child) + + # 根据子节点状态设置父节点状态 + if all_checked: + treelist.CheckItem(parent, wx.CHK_CHECKED) + elif all_unchecked: + treelist.CheckItem(parent, wx.CHK_UNCHECKED) + else: + treelist.CheckItem(parent, wx.CHK_UNDETERMINED) + + # 递归更新上层父节点 + self.UpdateParentCheckState(treelist, parent) + + def InsertTreeList(self, data: list, treelist: dataview.TreeListCtrl, clear=True): + if clear: + treelist.DeleteAllItems() + root = treelist.GetRootItem() + process_map = {} + for window in data: + # 确保window是WindowInfo对象 + if isinstance(window, dict): + window = WindowInfo.from_dict(window) + + process = window.process + if process not in process_map: + exists_node = self.SearchProcessNode(treelist, process) + if exists_node is None: + process_map[process] = treelist.AppendItem(root, process) + else: + process_map[process] = exists_node + item = treelist.AppendItem(process_map[process], window.title) + treelist.SetItemText(item, 1, str(window.hwnd)) + treelist.SetItemText(item, 2, str(window.PID)) + treelist.SetItemData(item, window) + treelist.Expand(root) + for process in process_map: + treelist.Expand(process_map[process]) + + # 初始化所有父节点的选中状态 + for process in process_map: + if treelist.GetFirstChild(process_map[process]).IsOk(): + self.UpdateParentCheckState(treelist, treelist.GetFirstChild(process_map[process])) + + def SearchProcessNode(self, treelist: dataview.TreeListCtrl, process): + item = treelist.GetRootItem() + while item.IsOk(): + item = treelist.GetNextItem(item) + if not item.IsOk(): + break + data = treelist.GetItemData(item) + if data is not None and hasattr(data, 'process') and data.process == process: + return treelist.GetItemParent(item) + + def RemoveItem(self, treelist: dataview.TreeListCtrl, data): + # 确保data是WindowInfo对象 + if isinstance(data, dict): + data = WindowInfo.from_dict(data) + + node = self.SearchProcessNode(treelist, data.process) + if node is not None: + item = treelist.GetFirstChild(node) + while item.IsOk(): + item_data = treelist.GetItemData(item) + if item_data and item_data == data: + treelist.DeleteItem(item) + break + item = treelist.GetNextSibling(item) + + if not treelist.GetFirstChild(node).IsOk(): + # 如果没有子节点了,删除父节点 + treelist.DeleteItem(node) + + def ItemsData(self, treelist: dataview.TreeListCtrl, only_checked=False, item_object=False)->list[WindowInfo]: + res = [] + item = treelist.GetRootItem() + while item.IsOk(): + item = treelist.GetNextItem(item) + if not item.IsOk(): + break + if only_checked and treelist.GetCheckedState(item) != wx.CHK_CHECKED: + continue + if item_object: + res.append(item) + else: + data = treelist.GetItemData(item) + if data is not None and data: + res.append(data) + return res \ No newline at end of file diff --git a/main/GUI/setting/hotkeys_page.py b/main/GUI/setting/hotkeys_page.py new file mode 100644 index 0000000..d112ee8 --- /dev/null +++ b/main/GUI/setting/hotkeys_page.py @@ -0,0 +1,214 @@ +import wx +from core.config import Config +import GUI.record as record + +import wx.lib.scrolledpanel as scrolled + +class HotkeysPage(scrolled.ScrolledPanel): + def __init__(self, parent): + super().__init__(parent) + self.init_UI() + self.Bind_EVT() + self.SetupScrolling() + + def init_UI(self): + sizer = wx.BoxSizer(wx.VERTICAL) + + # 创建键盘热键设置区域 + keyboard_box = wx.StaticBox(self, label="键盘热键") + keyboard_box_sizer = wx.StaticBoxSizer(keyboard_box, wx.VERTICAL) + + grid_sizer = wx.FlexGridSizer(rows=2, cols=3, gap=(10, 20)) + grid_sizer.AddGrowableCol(1, 1) + + # 隐藏/显示窗口热键 + hide_show_label = wx.StaticText(self, label="隐藏/显示窗口热键:") + self.hide_show_text = wx.TextCtrl(self) + self.hide_show_btn = wx.Button(self, label="录制热键") + + grid_sizer.Add(hide_show_label, 0, wx.ALIGN_CENTER_VERTICAL | wx.ALL, 10) + grid_sizer.Add(self.hide_show_text, 1, wx.EXPAND | wx.ALL, 10) + grid_sizer.Add(self.hide_show_btn, 0, wx.EXPAND | wx.ALL, 10) + + # 一键关闭程序热键 + close_label = wx.StaticText(self, label="一键关闭程序热键:") + self.close_text = wx.TextCtrl(self) + self.close_btn = wx.Button(self, label="录制热键") + + grid_sizer.Add(close_label, 0, wx.ALIGN_CENTER_VERTICAL | wx.ALL, 10) + grid_sizer.Add(self.close_text, 1, wx.EXPAND | wx.ALL, 10) + grid_sizer.Add(self.close_btn, 0, wx.EXPAND | wx.ALL, 10) + + keyboard_box_sizer.Add(grid_sizer, 0, wx.EXPAND | wx.ALL, 5) + sizer.Add(keyboard_box_sizer, 0, wx.EXPAND | wx.ALL, 10) + + # 添加鼠标隐藏选项 + mouse_box = wx.StaticBox(self, label="鼠标隐藏") + mouse_box_sizer = wx.StaticBoxSizer(mouse_box, wx.VERTICAL) + + mouse_grid_sizer = wx.GridSizer(rows=1, cols=3, gap=(10, 10)) + + # 中键选项 + self.middle_button_checkbox = wx.CheckBox(self, label="启用鼠标中键切换隐藏") + self.middle_button_checkbox.SetToolTip(wx.ToolTip("点击鼠标中键可快速隐藏/显示窗口")) + + # 侧键1选项 + self.side_button1_checkbox = wx.CheckBox(self, label="启用鼠标侧键1切换隐藏") + self.side_button1_checkbox.SetToolTip(wx.ToolTip("点击鼠标侧键1(前进键)可快速隐藏/显示窗口")) + + # 侧键2选项 + self.side_button2_checkbox = wx.CheckBox(self, label="启用鼠标侧键2切换隐藏") + self.side_button2_checkbox.SetToolTip(wx.ToolTip("点击鼠标侧键2(后退键)可快速隐藏/显示窗口")) + + mouse_grid_sizer.Add(self.middle_button_checkbox, 0, wx.ALL, 10) + mouse_grid_sizer.Add(self.side_button1_checkbox, 0, wx.ALL, 10) + mouse_grid_sizer.Add(self.side_button2_checkbox, 0, wx.ALL, 10) + + mouse_move_grid_sizer = wx.GridSizer(rows=2, cols=2, gap=(10, 10)) + + # 左上角 + self.top_left_checkbox = wx.CheckBox(self, label="左上角快速隐藏") + self.top_left_checkbox.SetToolTip(wx.ToolTip("快速移动鼠标至左上角以隐藏窗口")) + mouse_move_grid_sizer.Add(self.top_left_checkbox, 0, wx.ALL, 10) + + # 右上角 + self.top_right_checkbox = wx.CheckBox(self, label="右上角快速隐藏") + self.top_right_checkbox.SetToolTip(wx.ToolTip("快速移动鼠标至右上角以隐藏窗口")) + mouse_move_grid_sizer.Add(self.top_right_checkbox, 0, wx.ALL, 10) + + # 左下角 + self.bottom_left_checkbox = wx.CheckBox(self, label="左下角快速隐藏") + self.bottom_left_checkbox.SetToolTip(wx.ToolTip("快速移动鼠标至左下角以隐藏窗口")) + mouse_move_grid_sizer.Add(self.bottom_left_checkbox, 0, wx.ALL, 10) + + # 右下角 + self.bottom_right_checkbox = wx.CheckBox(self, label="右下角快速隐藏") + self.bottom_right_checkbox.SetToolTip(wx.ToolTip("快速移动鼠标至右下角以隐藏窗口")) + mouse_move_grid_sizer.Add(self.bottom_right_checkbox, 0, wx.ALL, 10) + + mouse_box_sizer.Add(mouse_grid_sizer, 0, wx.EXPAND | wx.ALL, 10) + mouse_box_sizer.Add(mouse_move_grid_sizer, 0, wx.EXPAND | wx.ALL, 10) + + # 添加允许移动恢复选项 + self.allow_move_restore_checkbox = wx.CheckBox(self, label="启用移动恢复窗口") + self.allow_move_restore_checkbox.SetToolTip(wx.ToolTip("启用后可通过移动鼠标到同一角落恢复已隐藏的窗口")) + mouse_box_sizer.Add(self.allow_move_restore_checkbox, 0, wx.ALL, 10) + + sizer.Add(mouse_box_sizer, 0, wx.EXPAND | wx.ALL, 10) + + # 添加其他选项区域 + other_box = wx.StaticBox(self, label="其他") + other_box_sizer = wx.StaticBoxSizer(other_box, wx.VERTICAL) + + # 自动隐藏选项 + auto_hide_sizer = wx.BoxSizer(wx.HORIZONTAL) + self.auto_hide_checkbox = wx.CheckBox(self, label="自动隐藏:") + self.auto_hide_checkbox.SetToolTip(wx.ToolTip("在无操作指定时间后自动隐藏窗口")) + auto_hide_sizer.Add(self.auto_hide_checkbox, 0, wx.ALIGN_CENTER_VERTICAL) + + # 自动隐藏时间输入框 + self.auto_hide_time = wx.SpinCtrl(self, min=1, max=120, initial=5) + self.auto_hide_time.SetToolTip(wx.ToolTip("设置多少分钟无操作后自动隐藏")) + auto_hide_sizer.Add(self.auto_hide_time, 0, wx.LEFT, 10) + + # 分钟标签 + minutes_label = wx.StaticText(self, label="分钟") + auto_hide_sizer.Add(minutes_label, 0, wx.ALIGN_CENTER_VERTICAL | wx.LEFT, 5) + + other_box_sizer.Add(auto_hide_sizer, 0, wx.ALL, 10) + + # 灰色文本提示 + auto_hide_tip = wx.StaticText(self, label="当键盘和鼠标在指定时间内无操作时,将自动隐藏窗口") + auto_hide_tip.SetForegroundColour(wx.Colour(128, 128, 128)) # 灰色文本 + other_box_sizer.Add(auto_hide_tip, 0, wx.ALL, 10) + + sizer.Add(other_box_sizer, 0, wx.EXPAND | wx.ALL, 10) + + self.SetSizer(sizer) + + def Bind_EVT(self): + self.hide_show_btn.Bind(wx.EVT_BUTTON, self.OnRecordHideShow) + self.close_btn.Bind(wx.EVT_BUTTON, self.OnRecordClose) + self.auto_hide_checkbox.Bind(wx.EVT_CHECKBOX, self.OnAutoHideToggle) + + def OnAutoHideToggle(self, e): + # 启用或禁用时间输入框 + self.auto_hide_time.Enable(self.auto_hide_checkbox.GetValue()) + + def SetData(self): + self.hide_show_text.SetValue(Config.hide_hotkey) + self.close_text.SetValue(Config.close_hotkey) + self.middle_button_checkbox.SetValue(Config.middle_button_hide if hasattr(Config, 'middle_button_hide') else False) + self.side_button1_checkbox.SetValue(Config.side_button1_hide if hasattr(Config, 'side_button1_hide') else False) + self.side_button2_checkbox.SetValue(Config.side_button2_hide if hasattr(Config, 'side_button2_hide') else False) + + # 设置自动隐藏选项 + auto_hide_enabled = Config.auto_hide_enabled if hasattr(Config, 'auto_hide_enabled') else False + self.auto_hide_checkbox.SetValue(auto_hide_enabled) + + auto_hide_time = Config.auto_hide_time if hasattr(Config, 'auto_hide_time') else 5 + self.auto_hide_time.SetValue(auto_hide_time) + self.auto_hide_time.Enable(auto_hide_enabled) + + # 设置鼠标移动至四角隐藏选项 + self.top_left_checkbox.SetValue(Config.top_left_hide if hasattr(Config, 'top_left_hide') else False) + self.top_right_checkbox.SetValue(Config.top_right_hide if hasattr(Config, 'top_right_hide') else False) + self.bottom_left_checkbox.SetValue(Config.bottom_left_hide if hasattr(Config, 'bottom_left_hide') else False) + self.bottom_right_checkbox.SetValue(Config.bottom_right_hide if hasattr(Config, 'bottom_right_hide') else False) + self.allow_move_restore_checkbox.SetValue(Config.allow_move_restore if hasattr(Config, 'allow_move_restore') else False) + + def SaveData(self): + Config.hide_hotkey = self.hide_show_text.GetValue() + Config.close_hotkey = self.close_text.GetValue() + Config.middle_button_hide = self.middle_button_checkbox.GetValue() + Config.side_button1_hide = self.side_button1_checkbox.GetValue() + Config.side_button2_hide = self.side_button2_checkbox.GetValue() + + # 保存自动隐藏设置 + Config.auto_hide_enabled = self.auto_hide_checkbox.GetValue() + Config.auto_hide_time = self.auto_hide_time.GetValue() + + # 保存鼠标移动至四角隐藏设置 + Config.top_left_hide = self.top_left_checkbox.GetValue() + Config.top_right_hide = self.top_right_checkbox.GetValue() + Config.bottom_left_hide = self.bottom_left_checkbox.GetValue() + Config.bottom_right_hide = self.bottom_right_checkbox.GetValue() + Config.allow_move_restore = self.allow_move_restore_checkbox.GetValue() + + def Reset(self): + self.hide_show_text.SetValue("Ctrl+Q") + self.close_text.SetValue("Win+Esc") + self.middle_button_checkbox.SetValue(False) + self.side_button1_checkbox.SetValue(False) + self.side_button2_checkbox.SetValue(False) + self.auto_hide_checkbox.SetValue(False) + self.auto_hide_time.SetValue(5) + self.auto_hide_time.Enable(False) + + # 重置鼠标移动至四角隐藏设置 + self.top_left_checkbox.SetValue(False) + self.top_right_checkbox.SetValue(False) + self.bottom_left_checkbox.SetValue(False) + self.bottom_right_checkbox.SetValue(False) + self.allow_move_restore_checkbox.SetValue(False) + + def OnRecordHideShow(self, e): + self.recordHotkey(self.hide_show_text, self.hide_show_btn) + + def OnRecordClose(self, e): + self.recordHotkey(self.close_text, self.close_btn) + + def recordHotkey(self, text_ctrl: wx.TextCtrl, btn: wx.Button): + try: + Config.HotkeyListener._stop() + except: + pass + btn.Disable() + btn.SetLabel("录制中...") + record.RecordedHotkey.confirm = False + RecordWindow = record.RecordWindow() + RecordWindow.ShowModal() + btn.Enable() + btn.SetLabel("录制热键") + if record.RecordedHotkey.confirm: + text_ctrl.SetValue(record.RecordedHotkey.final_key) \ No newline at end of file diff --git a/main/GUI/setting/options_page.py b/main/GUI/setting/options_page.py new file mode 100644 index 0000000..7acc378 --- /dev/null +++ b/main/GUI/setting/options_page.py @@ -0,0 +1,265 @@ +import wx +import wx.adv +import webbrowser +from core.config import Config +import wx.lib.scrolledpanel as scrolled +from core.tools import check_pssuspend_exists, is_admin, run_as_admin + +class OptionsPage(scrolled.ScrolledPanel): + def __init__(self, parent): + super().__init__(parent) + self.init_UI() + self.Bind_EVT() + self.SetupScrolling() + + def init_UI(self): + sizer = wx.BoxSizer(wx.VERTICAL) + + # 创建网格布局 + # 创建一个StaticBox用于包含常规选项 + general_box = wx.StaticBox(self, label="常规选项") + general_box_sizer = wx.StaticBoxSizer(general_box, wx.VERTICAL) + + grid_sizer = wx.GridSizer(rows=3, cols=2, gap=(10, 20)) # 减少行数为3,冻结选项将单独放置 + + # 添加复选框 + # 1. 隐藏窗口后静音 + self.mute_checkbox = wx.CheckBox(self, label="隐藏窗口后静音") + grid_sizer.Add(self.mute_checkbox, 0, wx.ALL, 10) + + # 2. 隐藏前发送暂停键(Beta) + pause_sizer = wx.BoxSizer(wx.HORIZONTAL) + self.send_pause_checkbox = wx.CheckBox(self, label="隐藏前发送暂停键(Beta)") + self.send_pause_checkbox.SetToolTip(wx.ToolTip("隐藏窗口前发送暂停键,用于关闭弹出的输入框等,隐藏窗口会存在一定的延迟")) + pause_sizer.Add(self.send_pause_checkbox) + grid_sizer.Add(pause_sizer, 0, wx.ALL, 10) + + # 3. 同时隐藏当前活动窗口 + self.hide_current_checkbox = wx.CheckBox(self, label="同时隐藏当前活动窗口") + grid_sizer.Add(self.hide_current_checkbox, 0, wx.ALL, 10) + + # 4. 点击托盘图标切换隐藏状态 + self.click_hide_checkbox = wx.CheckBox(self, label="点击托盘图标切换隐藏状态") + grid_sizer.Add(self.click_hide_checkbox, 0, wx.ALL, 10) + + # 5. 隐藏窗口后隐藏托盘图标 + self.hide_icon_checkbox = wx.CheckBox(self, label="隐藏窗口后隐藏托盘图标") + grid_sizer.Add(self.hide_icon_checkbox, 0, wx.ALL, 10) + + # 6. 文件路径匹配 + path_sizer = wx.BoxSizer(wx.HORIZONTAL) + self.path_match_checkbox = wx.CheckBox(self, label="文件路径匹配") + path_tooltip = "启用此选项可以一键隐藏绑定程序的所有窗口\n关闭此选项后,将会智能精确隐藏指定窗口" + self.path_match_checkbox.SetToolTip(wx.ToolTip(path_tooltip)) + info_icon = wx.ArtProvider.GetBitmap(wx.ART_INFORMATION, wx.ART_OTHER, self.FromDIP((14, 14))) + info_bitmap = wx.StaticBitmap(self, bitmap=info_icon) + info_bitmap.SetToolTip(wx.ToolTip(path_tooltip)) + path_sizer.Add(self.path_match_checkbox) + path_sizer.AddSpacer(5) + path_sizer.Add(info_bitmap) + grid_sizer.Add(path_sizer, 0, wx.ALL, 10) + + general_box_sizer.Add(grid_sizer, 0, wx.EXPAND | wx.ALL, 10) + sizer.Add(general_box_sizer, 0, wx.EXPAND | wx.ALL, 10) + + # 创建一个StaticBox用于包含冻结相关的选项 + freeze_box = wx.StaticBox(self, label="进程冻结选项") + freeze_box_sizer = wx.StaticBoxSizer(freeze_box, wx.VERTICAL) + + # 创建进程冻结选项 + freeze_grid = wx.GridSizer(rows=2, cols=1, gap=(5, 5)) + + # 7. 隐藏窗口时冻结进程 + freeze_sizer = wx.BoxSizer(wx.HORIZONTAL) + self.freeze_checkbox = wx.CheckBox(self, label="隐藏窗口时冻结进程(beta)") + freeze_tooltip = "启用此选项将在隐藏窗口时同时冻结其进程,减少CPU占用\n注意:某些程序可能对冻结操作反应异常" + self.freeze_checkbox.SetToolTip(wx.ToolTip(freeze_tooltip)) + freeze_info_icon = wx.ArtProvider.GetBitmap(wx.ART_INFORMATION, wx.ART_OTHER, self.FromDIP((14, 14))) + freeze_info_bitmap = wx.StaticBitmap(self, bitmap=freeze_info_icon) + freeze_info_bitmap.SetToolTip(wx.ToolTip(freeze_tooltip)) + freeze_sizer.Add(self.freeze_checkbox) + freeze_sizer.AddSpacer(5) + freeze_sizer.Add(freeze_info_bitmap) + freeze_grid.Add(freeze_sizer, 0, wx.ALL, 5) + + # 8. 增强冻结(使用pssuspend64) + enhanced_freeze_sizer = wx.BoxSizer(wx.HORIZONTAL) + self.enhanced_freeze_checkbox = wx.CheckBox(self, label="使用增强冻结(需要pssuspend64.exe与管理员权限)") + enhanced_freeze_tooltip = "使用Microsoft的pssuspend64工具执行进程冻结操作,提供更稳定的冻结效果\n需要在程序根目录放置pssuspend64.exe文件并使用管理员身份启动BossKey" + self.enhanced_freeze_checkbox.SetToolTip(wx.ToolTip(enhanced_freeze_tooltip)) + enhanced_freeze_info_icon = wx.ArtProvider.GetBitmap(wx.ART_INFORMATION, wx.ART_OTHER, self.FromDIP((14, 14))) + enhanced_freeze_info_bitmap = wx.StaticBitmap(self, bitmap=enhanced_freeze_info_icon) + enhanced_freeze_info_bitmap.SetToolTip(wx.ToolTip(enhanced_freeze_tooltip)) + enhanced_freeze_sizer.Add(self.enhanced_freeze_checkbox) + enhanced_freeze_sizer.AddSpacer(5) + enhanced_freeze_sizer.Add(enhanced_freeze_info_bitmap) + freeze_grid.Add(enhanced_freeze_sizer, 0, wx.ALL, 5) + + freeze_box_sizer.Add(freeze_grid, 0, wx.ALL | wx.EXPAND, 5) + + # 添加下载链接和功能按钮 + link_buttons_sizer = wx.BoxSizer(wx.HORIZONTAL) + + # 下载链接 + self.download_link = wx.adv.HyperlinkCtrl(self, -1, "下载 pssuspend64", "https://download.sysinternals.com/files/PSTools.zip") + link_buttons_sizer.Add(self.download_link, 0, wx.ALIGN_CENTER_VERTICAL, 0) + + # 添加空白间距 + link_buttons_sizer.AddSpacer(20) + + # 重新检测按钮 + self.redetect_btn = wx.Button(self, label="重新检测", size=(-1, -1)) + link_buttons_sizer.Add(self.redetect_btn, 0, wx.ALIGN_CENTER_VERTICAL, 0) + + # 管理员权限按钮 + if not is_admin(): + link_buttons_sizer.AddSpacer(10) + self.admin_btn = wx.Button(self, label="以管理员身份启动", size=(-1, -1)) + link_buttons_sizer.Add(self.admin_btn, 0, wx.ALIGN_CENTER_VERTICAL, 0) + + freeze_box_sizer.Add(link_buttons_sizer, 0, wx.ALL, 10) + + sizer.Add(freeze_box_sizer, 0, wx.EXPAND | wx.ALL, 10) + + self.SetSizer(sizer) + + def Bind_EVT(self): + self.send_pause_checkbox.Bind(wx.EVT_CHECKBOX, self.OnSendBeforeHide) + self.freeze_checkbox.Bind(wx.EVT_CHECKBOX, self.OnFreezeAfterHide) + self.enhanced_freeze_checkbox.Bind(wx.EVT_CHECKBOX, self.OnEnhancedFreeze) + + # 绑定新按钮事件 + self.redetect_btn.Bind(wx.EVT_BUTTON, self.OnRedetectPssuspend) + if not is_admin(): + self.admin_btn.Bind(wx.EVT_BUTTON, self.OnRequestAdmin) + + def SetData(self): + self.mute_checkbox.SetValue(Config.mute_after_hide) + self.send_pause_checkbox.SetValue(Config.send_before_hide) + self.hide_current_checkbox.SetValue(Config.hide_current) + self.click_hide_checkbox.SetValue(Config.click_to_hide) + self.hide_icon_checkbox.SetValue(Config.hide_icon_after_hide) + self.path_match_checkbox.SetValue(Config.path_match) + self.freeze_checkbox.SetValue(Config.freeze_after_hide) + + # 设置增强冻结选项 + if hasattr(Config, 'enhanced_freeze'): + self.enhanced_freeze_checkbox.SetValue(Config.enhanced_freeze) + else: + self.enhanced_freeze_checkbox.SetValue(False) + + # 检查pssuspend64是否存在和管理员权限 + admin_status = is_admin() + has_pssuspend = check_pssuspend_exists() + + if not has_pssuspend: + self.enhanced_freeze_checkbox.SetValue(False) + self.enhanced_freeze_checkbox.Enable(False) + self.enhanced_freeze_checkbox.SetToolTip(wx.ToolTip("需要pssuspend64.exe才能启用此功能")) + elif not admin_status: + self.enhanced_freeze_checkbox.SetValue(False) + self.enhanced_freeze_checkbox.Enable(False) + self.enhanced_freeze_checkbox.SetToolTip(wx.ToolTip("需要管理员权限才能启用此功能")) + else: + self.enhanced_freeze_checkbox.Enable(True) + + def SaveData(self): + Config.mute_after_hide = self.mute_checkbox.GetValue() + Config.send_before_hide = self.send_pause_checkbox.GetValue() + Config.hide_current = self.hide_current_checkbox.GetValue() + Config.click_to_hide = self.click_hide_checkbox.GetValue() + Config.hide_icon_after_hide = self.hide_icon_checkbox.GetValue() + Config.path_match = self.path_match_checkbox.GetValue() + Config.freeze_after_hide = self.freeze_checkbox.GetValue() + Config.enhanced_freeze = self.enhanced_freeze_checkbox.GetValue() + + def Reset(self): + self.mute_checkbox.SetValue(True) + self.send_pause_checkbox.SetValue(False) + self.hide_current_checkbox.SetValue(True) + self.click_hide_checkbox.SetValue(False) + self.hide_icon_checkbox.SetValue(False) + self.path_match_checkbox.SetValue(False) + self.freeze_checkbox.SetValue(False) + self.enhanced_freeze_checkbox.SetValue(False) + + def OnSendBeforeHide(self, e): + if self.send_pause_checkbox.GetValue(): + wx.MessageDialog(None, "隐藏窗口前向被隐藏的窗口发送空格,用于暂停视频等。启用此功能可能会延迟窗口的隐藏", "Boss Key", wx.OK | wx.ICON_INFORMATION).ShowModal() + + def OnFreezeAfterHide(self, e): + if self.freeze_checkbox.GetValue(): + wx.MessageDialog(None, "隐藏窗口时将冻结进程,可以减少CPU占用但某些程序可能会出现异常。\n恢复窗口时会自动解冻进程。", "Boss Key", wx.OK | wx.ICON_INFORMATION).ShowModal() + + def OnEnhancedFreeze(self, e): + if self.enhanced_freeze_checkbox.GetValue(): + # 检查pssuspend64是否存在 + if not check_pssuspend_exists(): + dlg = wx.MessageDialog(self, + "未检测到pssuspend64.exe文件!\n请先下载并放置到程序根目录,然后重新启用此选项。\n\n您可以从以下链接下载:\nhttps://download.sysinternals.com/files/PSTools.zip", + "无法启用增强冻结", wx.OK | wx.ICON_ERROR) + dlg.SetOKLabel("确定") + dlg.SetOKCancelLabels("确定", "下载") + result = dlg.ShowModal() + if result == wx.ID_CANCEL: + webbrowser.open("https://download.sysinternals.com/files/PSTools.zip") + dlg.Destroy() + self.enhanced_freeze_checkbox.SetValue(False) + return + + # 检查管理员权限 + if not is_admin(): + result = wx.MessageDialog(None, "增强冻结功能需要管理员权限才能使用!\n是否以管理员身份重启程序?", + "权限不足", wx.YES_NO | wx.ICON_WARNING).ShowModal() + self.enhanced_freeze_checkbox.SetValue(False) + + if result == wx.ID_YES: + run_as_admin() + wx.GetApp().GetTopWindow().Close() + return + + wx.MessageDialog(None, "增强冻结功能将使用Microsoft提供的pssuspend64工具,提供更稳定的进程冻结效果。\n此功能需要启用\"隐藏窗口时冻结进程\"选项才会生效。", + "增强冻结已启用", wx.OK | wx.ICON_INFORMATION).ShowModal() + # 自动勾选冻结进程选项 + self.freeze_checkbox.SetValue(True) + + def OnRequestAdmin(self, e=None): + """请求管理员权限并重启程序""" + wx.MessageBox("程序将重启并请求管理员权限", "提示", wx.OK | wx.ICON_INFORMATION) + run_as_admin() + wx.GetApp().ExitMainLoop() + + def OnRedetectPssuspend(self, e=None): + """重新检测pssuspend64.exe是否存在""" + has_pssuspend = check_pssuspend_exists() + admin_status = is_admin() + + if has_pssuspend and admin_status: + self.enhanced_freeze_checkbox.Enable(True) + self.enhanced_freeze_checkbox.SetToolTip(wx.ToolTip("使用Microsoft的pssuspend64工具执行进程冻结操作,提供更稳定的冻结效果")) + wx.MessageBox("检测到pssuspend64.exe文件,增强冻结功能已启用!", "检测成功", wx.OK | wx.ICON_INFORMATION) + elif not has_pssuspend: + self.enhanced_freeze_checkbox.SetValue(False) + self.enhanced_freeze_checkbox.Enable(False) + self.enhanced_freeze_checkbox.SetToolTip(wx.ToolTip("需要pssuspend64.exe才能启用此功能")) + + dlg = wx.MessageDialog(self, + "未检测到pssuspend64.exe文件!\n请先下载并放置到程序根目录,然后重新检测。\n\n您可以从以下链接下载:\nhttps://download.sysinternals.com/files/PSTools.zip", + "检测失败", wx.OK | wx.ICON_ERROR) + dlg.SetOKLabel("确定") + dlg.SetOKCancelLabels("确定", "下载") + result = dlg.ShowModal() + if result == wx.ID_CANCEL: + webbrowser.open("https://download.sysinternals.com/files/PSTools.zip") + dlg.Destroy() + elif not admin_status: + self.enhanced_freeze_checkbox.SetValue(False) + self.enhanced_freeze_checkbox.Enable(False) + self.enhanced_freeze_checkbox.SetToolTip(wx.ToolTip("需要管理员权限才能启用此功能")) + + result = wx.MessageDialog(None, "检测到pssuspend64.exe文件,但增强冻结功能需要管理员权限才能使用!\n是否以管理员身份重启程序?", + "权限不足", wx.YES_NO | wx.ICON_WARNING).ShowModal() + + if result == wx.ID_YES: + run_as_admin() + wx.GetApp().ExitMainLoop() \ No newline at end of file diff --git a/main/GUI/window_restore.py b/main/GUI/window_restore.py index f4cccbc..16c9dad 100644 --- a/main/GUI/window_restore.py +++ b/main/GUI/window_restore.py @@ -2,72 +2,238 @@ import wx.dataview as dataview import win32gui import win32con -from .setting import SettingWindow +import win32process +import webbrowser +import psutil from core import tools as tool +from core.model import WindowInfo +from core.config import Config +from GUI.setting.binding_page import BindingPage +from core.tools import check_pssuspend_exists, is_admin, run_as_admin -class WindowRestoreDialog(SettingWindow): - def __init__(self, id): - super().__init__(id=id) - self.SetSize((700, 600)) - - self.SetTitle("窗口恢复") - - self.Center() +class WindowRestorePanel(BindingPage): + """继承BindingPage实现窗口恢复的面板""" + def __init__(self, parent): + super().__init__(parent) def init_UI(self): - self.window_info = [] - # 创建界面 - panel = wx.Panel(self) - vbox = wx.BoxSizer(wx.VERTICAL) + main_sizer = wx.BoxSizer(wx.VERTICAL) - # 树形列表 - self.left_treelist = dataview.TreeListCtrl(panel, style=dataview.TL_CHECKBOX) + # 树形列表 - 复用BindingPage的列表设置方式 + self.left_treelist = dataview.TreeListCtrl(self, style=dataview.TL_CHECKBOX) self.left_treelist.AppendColumn('窗口标题', width=300) self.left_treelist.AppendColumn('窗口句柄', width=100) self.left_treelist.AppendColumn('进程PID', width=150) - # 按钮区域 - btn_sizer = wx.BoxSizer(wx.HORIZONTAL) - self.show_btn = wx.Button(panel, label="显示窗口") - self.hide_btn = wx.Button(panel, label="隐藏窗口") - btn_sizer.Add(self.show_btn, proportion=1, flag=wx.RIGHT, border=5) - btn_sizer.Add(self.hide_btn, proportion=1, flag=wx.LEFT, border=5) + # 第一行按钮区域: 显示窗口,隐藏窗口,刷新窗口 + btn_sizer1 = wx.BoxSizer(wx.HORIZONTAL) + self.show_btn = wx.Button(self, label="显示窗口") + self.hide_btn = wx.Button(self, label="隐藏窗口") + self.refresh_btn = wx.Button(self, label="刷新窗口") + + btn_sizer1.Add(self.show_btn, proportion=1, flag=wx.RIGHT, border=5) + btn_sizer1.Add(self.hide_btn, proportion=1, flag=wx.LEFT|wx.RIGHT, border=5) + btn_sizer1.Add(self.refresh_btn, proportion=1, flag=wx.LEFT, border=5) + + # 第二行按钮区域: 冻结进程,解冻进程 + btn_sizer2 = wx.BoxSizer(wx.HORIZONTAL) + self.hide_freeze_btn = wx.Button(self, label="冻结进程") + self.resume_btn = wx.Button(self, label="解冻进程") + + btn_sizer2.Add(self.hide_freeze_btn, proportion=1, flag=wx.RIGHT, border=5) + btn_sizer2.Add(self.resume_btn, proportion=1, flag=wx.LEFT, border=5) # 布局 - vbox.Add(self.left_treelist, proportion=1, flag=wx.EXPAND|wx.LEFT|wx.RIGHT, border=5) - vbox.Add(btn_sizer, flag=wx.EXPAND|wx.ALL, border=10) + main_sizer.Add(self.left_treelist, proportion=1, flag=wx.EXPAND|wx.LEFT|wx.RIGHT, border=5) + main_sizer.Add(btn_sizer1, flag=wx.EXPAND|wx.ALL, border=10) + main_sizer.Add(btn_sizer2, flag=wx.EXPAND|wx.LEFT|wx.RIGHT|wx.BOTTOM, border=10) + + self.SetSizer(main_sizer) - panel.SetSizer(vbox) + def Bind_EVT(self): + self.show_btn.Bind(wx.EVT_BUTTON, self.on_show_window) + self.hide_btn.Bind(wx.EVT_BUTTON, self.on_hide_window) + self.refresh_btn.Bind(wx.EVT_BUTTON, self.on_refresh_window) + self.left_treelist.Bind(dataview.EVT_TREELIST_ITEM_CHECKED, self.OnToggleCheck) + self.hide_freeze_btn.Bind(wx.EVT_BUTTON, self.on_hide_freeze_window) + self.resume_btn.Bind(wx.EVT_BUTTON, self.on_resume_process) def SetData(self): self.RefreshLeftList() + + # 检查 pssuspend64 是否存在和管理员权限 + admin_status = is_admin() + has_pssuspend = check_pssuspend_exists() + + if not has_pssuspend: + self.hide_freeze_btn.SetToolTip(wx.ToolTip("需要 pssuspend64.exe 才能使用此功能")) + self.resume_btn.SetToolTip(wx.ToolTip("需要 pssuspend64.exe 才能使用此功能")) + elif not admin_status: + self.hide_freeze_btn.SetToolTip(wx.ToolTip("需要管理员权限才能使用此功能")) + self.resume_btn.SetToolTip(wx.ToolTip("需要管理员权限才能使用此功能")) + else: + self.hide_freeze_btn.Enable() + self.resume_btn.Enable() + + # 如果没有管理员权限,添加请求管理员权限按钮 + if not admin_status: + admin_button_sizer = wx.BoxSizer(wx.HORIZONTAL) + self.admin_btn = wx.Button(self, label="获取管理员权限") + self.admin_btn.Bind(wx.EVT_BUTTON, self.on_request_admin) + admin_button_sizer.Add(self.admin_btn, proportion=1, flag=wx.ALL, border=5) + self.GetSizer().Add(admin_button_sizer, flag=wx.EXPAND|wx.LEFT|wx.RIGHT|wx.BOTTOM, border=10) + self.Layout() def RefreshLeftList(self, e=None): - windows = tool.getAllWindows() - list = [] - for window in windows: - list.append(window) - self.InsertTreeList(list, self.left_treelist, True) - - def Bind_EVT(self): - self.show_btn.Bind(wx.EVT_BUTTON, self.on_show_window) - self.hide_btn.Bind(wx.EVT_BUTTON, self.on_hide_window) - self.left_treelist.Bind(dataview.EVT_TREELIST_ITEM_CHECKED, self.OnToggleCheck) + def enumHandler(hwnd, windows): + title = tool.hwnd2windowName(hwnd) + + pid = win32process.GetWindowThreadProcessId(hwnd)[1] + process_name = psutil.Process(pid).name() + process_path = psutil.Process(pid).exe() + + windows.append(WindowInfo( + title=title, + hwnd=int(hwnd), + process=process_name, + PID=int(pid), + path=process_path + )) + return True - def on_show_window(self,e=None): + windows = [] + win32gui.EnumWindows(enumHandler, windows) + windows.sort(key=lambda x: x.title) + self.InsertTreeList(windows, self.left_treelist, True) + + def on_refresh_window(self, e=None): + """刷新窗口列表""" + self.RefreshLeftList() + + def on_show_window(self, e=None): windows = self.ItemsData(self.left_treelist, only_checked=True) + if not windows: + wx.MessageBox("请先选择要显示的窗口", "提示", wx.OK | wx.ICON_INFORMATION) + return + result = wx.MessageBox(f"将恢复{len(windows)}个窗口\r\n恢复未知的窗口可能会导致窗口出错", "警告", wx.OK | wx.CANCEL | wx.ICON_WARNING) if result != wx.OK: return for window in windows: win32gui.ShowWindow(window.hwnd, win32con.SW_SHOW) - def on_hide_window(self,e=None): + def on_hide_window(self, e=None): windows = self.ItemsData(self.left_treelist, only_checked=True) + if not windows: + wx.MessageBox("请先选择要隐藏的窗口", "提示", wx.OK | wx.ICON_INFORMATION) + return + result = wx.MessageBox(f"将隐藏{len(windows)}个窗口\r\n隐藏未知的窗口可能会导致窗口出错", "警告", wx.OK | wx.CANCEL | wx.ICON_WARNING) if result != wx.OK: return for window in windows: win32gui.ShowWindow(window.hwnd, win32con.SW_HIDE) - \ No newline at end of file + + def on_hide_freeze_window(self, e=None): + # 检查 pssuspend64 是否存在 + if not check_pssuspend_exists(): + dlg = wx.MessageDialog(self, + "未检测到pssuspend64.exe文件!\n请先下载并放置到程序根目录,然后重试。\n\n您可以从以下链接下载:\nhttps://download.sysinternals.com/files/PSTools.zip", + "无法冻结进程", wx.OK | wx.ICON_ERROR) + dlg.SetOKLabel("确定") + dlg.SetOKCancelLabels("确定", "下载") + result = dlg.ShowModal() + if result == wx.ID_CANCEL: + webbrowser.open("https://download.sysinternals.com/files/PSTools.zip") + dlg.Destroy() + return + + # 检查管理员权限 + if not is_admin(): + wx.MessageBox("此功能需要管理员权限才能使用!\n请点击\"获取管理员权限\"按钮重启程序。", + "权限不足", wx.OK | wx.ICON_ERROR) + return + + windows = self.ItemsData(self.left_treelist, only_checked=True) + if not windows: + wx.MessageBox("请先选择要冻结的窗口进程", "提示", wx.OK | wx.ICON_INFORMATION) + return + + result = wx.MessageBox(f"将冻结{len(windows)}个进程\n冻结未知的进程可能会导致系统不稳定", "警告", + wx.OK | wx.CANCEL | wx.ICON_WARNING) + if result != wx.OK: + return + + for window in windows: + try: + if hasattr(window, 'PID') and window.PID: + tool.suspend_process_enhanced(window.PID) + except Exception as e: + wx.MessageBox(f"冻结失败: {str(e)}", "错误", wx.OK | wx.ICON_ERROR) + + def on_resume_process(self, e=None): + # 检查 pssuspend64 是否存在 + if not check_pssuspend_exists(): + dlg = wx.MessageDialog(self, + "未检测到pssuspend64.exe文件!\n请先下载并放置到程序根目录,然后重试。\n\n您可以从以下链接下载:\nhttps://download.sysinternals.com/files/PSTools.zip", + "无法解冻进程", wx.OK | wx.ICON_ERROR) + dlg.SetOKLabel("确定") + dlg.SetOKCancelLabels("确定", "下载") + result = dlg.ShowModal() + if result == wx.ID_CANCEL: + webbrowser.open("https://download.sysinternals.com/files/PSTools.zip") + dlg.Destroy() + return + + # 检查管理员权限 + if not is_admin(): + wx.MessageBox("此功能需要管理员权限才能使用!\n请点击\"获取管理员权限\"按钮重启程序。", + "权限不足", wx.OK | wx.ICON_ERROR) + return + + windows = self.ItemsData(self.left_treelist, only_checked=True) + if not windows: + wx.MessageBox("请先选择要解冻的窗口进程", "提示", wx.OK | wx.ICON_INFORMATION) + return + + result = wx.MessageBox(f"将解冻{len(windows)}个进程", "确认", wx.OK | wx.CANCEL | wx.ICON_QUESTION) + if result != wx.OK: + return + + for window in windows: + try: + if hasattr(window, 'PID') and window.PID: + tool.resume_process_enhanced(window.PID) + except Exception as e: + wx.MessageBox(f"解冻失败: {str(e)}", "错误", wx.OK | wx.ICON_ERROR) + + def on_request_admin(self, e=None): + """请求管理员权限""" + wx.MessageBox("程序将重启并请求管理员权限", "提示", wx.OK | wx.ICON_INFORMATION) + run_as_admin() + wx.GetApp().GetTopWindow().Close() + wx.GetApp().ExitMainLoop() + + +class WindowRestoreDialog(wx.Frame): + def __init__(self, id): + wx.Frame.__init__(self, None, id=id, title="窗口恢复工具 - Boss Key", style=wx.DEFAULT_FRAME_STYLE | wx.RESIZE_BORDER) + self.SetIcon(wx.Icon(wx.Image(Config.icon).ConvertToBitmap())) + self.SetSize((700, 600)) + self.Center() + + # 使用继承自BindingPage的面板 + self.panel = WindowRestorePanel(self) + + # 创建框架布局 + sizer = wx.BoxSizer(wx.VERTICAL) + sizer.Add(self.panel, 1, wx.EXPAND) + self.SetSizer(sizer) + + # 设置初始数据 + self.panel.SetData() + + def Restore(self): + """恢复窗口,用于taskbar.py中调用""" + self.Show() \ No newline at end of file diff --git a/main/core/config.py b/main/core/config.py index ad40c73..77c41fd 100644 --- a/main/core/config.py +++ b/main/core/config.py @@ -8,8 +8,8 @@ class Config: AppName = "Boss Key" - AppVersion = "v2.0.4.0" - AppReleaseDate = "2025-04-09" + AppVersion = "v2.1.0.0" + AppReleaseDate = "2025-04-21" AppAuthor = "IvanHanloth" AppDescription = "老板来了?快用Boss-Key老板键一键隐藏静音当前窗口!上班摸鱼必备神器" AppCopyRight = "Copyright © 2022-2025 Ivan Hanloth All Rights Reserved." @@ -38,6 +38,7 @@ class Config: """ history=[] + frozen_pids=[] # 存储已冻结的进程PID times=1 hide_hotkey = "Ctrl+Q" @@ -45,17 +46,33 @@ class Config: mute_after_hide = True send_before_hide = False - hide_current=True + hide_current = True + freeze_after_hide = False # 新增配置项:隐藏后冻结进程 + enhanced_freeze = False # 新增配置项:使用增强冻结(pssuspend64) click_to_hide = True hide_icon_after_hide = False path_match = True + middle_button_hide = False # 新增:鼠标中键隐藏功能开关 + side_button1_hide = False # 鼠标侧键1隐藏功能开关 + side_button2_hide = False # 鼠标侧键2隐藏功能开关 + auto_hide_enabled = False # 自动隐藏功能开关 + auto_hide_time = 5 # 自动隐藏等待时间(分钟) + + # 添加鼠标四角隐藏配置 + top_left_hide = False # 左上角隐藏窗口 + top_right_hide = False # 右上角隐藏窗口 + bottom_left_hide = False # 左下角隐藏窗口 + bottom_right_hide = False # 右下角隐藏窗口 + allow_move_restore = False # 允许移动恢复窗口 hide_binding = [] - config_path = os.path.join(os.getcwd(), "config.json") - icon=BytesIO(get_icon()) + root_path = os.path.dirname(sys.argv[0]) + config_path = os.path.join(root_path, "config.json") file_path=sys.argv[0] + + icon=BytesIO(get_icon()) # 判断是否为首次启动 first_start = not os.path.exists(config_path) @@ -83,12 +100,27 @@ def load(): config = {} # 避免出现配置文件损坏导致程序无法启动 Config.history = config.get("history", []) + Config.frozen_pids = config.get("frozen_pids", []) Config.mute_after_hide = config.get("setting", {}).get("mute_after_hide", True) Config.send_before_hide = config.get("setting", {}).get("send_before_hide", False) Config.hide_current = config.get("setting", {}).get("hide_current", True) Config.hide_icon_after_hide = config.get("setting", {}).get("hide_icon_after_hide", False) Config.path_match = config.get("setting", {}).get("path_match", False) + Config.freeze_after_hide = config.get("setting", {}).get("freeze_after_hide", False) # 加载新配置项 + Config.enhanced_freeze = config.get("setting", {}).get("enhanced_freeze", False) # 加载新配置项 + Config.middle_button_hide = config.get("setting", {}).get("middle_button_hide", False) # 加载鼠标中键隐藏设置 + Config.side_button1_hide = config.get("setting", {}).get("side_button1_hide", False) # 加载鼠标侧键1隐藏设置 + Config.side_button2_hide = config.get("setting", {}).get("side_button2_hide", False) # 加载鼠标侧键2隐藏设置 + Config.auto_hide_enabled = config.get("setting", {}).get("auto_hide_enabled", False) # 加载自动隐藏功能开关 + Config.auto_hide_time = config.get("setting", {}).get("auto_hide_time", 5) # 加载自动隐藏等待时间 + + # 加载鼠标四角隐藏设置 + Config.top_left_hide = config.get("setting", {}).get("top_left_hide", False) + Config.top_right_hide = config.get("setting", {}).get("top_right_hide", False) + Config.bottom_left_hide = config.get("setting", {}).get("bottom_left_hide", False) + Config.bottom_right_hide = config.get("setting", {}).get("bottom_right_hide", False) + Config.allow_move_restore = config.get("setting", {}).get("allow_move_restore", False) Config.click_to_hide= config.get("setting", {}).get("click_to_hide", True) @@ -107,6 +139,7 @@ def save(): config = { 'version': Config.AppVersion, 'history': Config.history, + 'frozen_pids': Config.frozen_pids, 'hotkey': { 'hide_hotkey': Config.hide_hotkey, 'close_hotkey': Config.close_hotkey @@ -117,7 +150,21 @@ def save(): 'hide_current': Config.hide_current, 'click_to_hide': Config.click_to_hide, 'hide_icon_after_hide': Config.hide_icon_after_hide, - 'path_match': Config.path_match + 'path_match': Config.path_match, + 'freeze_after_hide': Config.freeze_after_hide, # 保存新配置项 + 'enhanced_freeze': Config.enhanced_freeze, # 保存新配置项 + 'middle_button_hide': Config.middle_button_hide, # 保存鼠标中键隐藏设置 + 'side_button1_hide': Config.side_button1_hide, # 保存鼠标侧键1隐藏设置 + 'side_button2_hide': Config.side_button2_hide, # 保存鼠标侧键2隐藏设置 + 'auto_hide_enabled': Config.auto_hide_enabled, # 保存自动隐藏功能开关 + 'auto_hide_time': Config.auto_hide_time, # 保存自动隐藏等待时间 + + # 保存鼠标四角隐藏设置 + 'top_left_hide': Config.top_left_hide, + 'top_right_hide': Config.top_right_hide, + 'bottom_left_hide': Config.bottom_left_hide, + 'bottom_right_hide': Config.bottom_right_hide, + 'allow_move_restore': Config.allow_move_restore }, # 将WindowInfo对象列表转换为字典列表用于JSON序列化 "hide_binding": [item.to_dict() if isinstance(item, WindowInfo) else item for item in Config.hide_binding] diff --git a/main/core/listener.py b/main/core/listener.py index 1a40a21..9eb3c20 100644 --- a/main/core/listener.py +++ b/main/core/listener.py @@ -2,25 +2,49 @@ import core.tools as tool from win32gui import GetForegroundWindow, ShowWindow from win32con import SW_HIDE, SW_SHOW +import win32process +import win32api import sys -from pynput import keyboard +from pynput import keyboard, mouse import multiprocessing import threading import time +import os import wx class HotkeyListener(): def __init__(self): + # 先定义所有属性 + self.Queue = multiprocessing.Queue() + self.listener = None + self.mouse_listener = None + self.mouse_move_listener = None # 鼠标移动监听器 + self.keyboard_activity_listener = None + self.mouse_activity_listener = None + self.last_activity_time = time.time() + self.auto_hide_timer = None + self.shared_state_file = os.path.join(Config.root_path, ".bosskey_state") + self.end_flag = False + + # 角落边界检测参数 + self.corner_threshold = 10 # 角落检测的阈值(像素) + self.corner_cooldown = 1.0 # 角落触发的冷却时间(秒) + self.last_corner_trigger = 0 # 上次角落触发的时间戳 + # 使用win32api获取屏幕尺寸 + self.screen_width = win32api.GetSystemMetrics(0) # SM_CXSCREEN常量为0 + self.screen_height = win32api.GetSystemMetrics(1) # SM_CYSCREEN常量为1 + try: self.ShowWindows() except: pass tool.sendNotify("Boss Key正在运行!", "Boss Key正在为您服务,您可通过托盘图标看到我") - self.Queue = multiprocessing.Queue() - self.listener = None + self.reBind() - self.end_flag=False - threading.Thread(target=self.listenToQueue,daemon=True).start() + threading.Thread(target=self.listenToQueue, daemon=True).start() + + # 启动自动隐藏监控(如果启用) + self.start_auto_hide_monitor() def listenToQueue(self): exit_flag = False @@ -37,9 +61,7 @@ def listenToQueue(self): tool.sendNotify("Boss Key已停止服务", "Boss Key已成功退出") self._stop() try: - wx.FindWindowById(Config.SettingWindowId).Destroy() - Config.TaskBarIcon.Destroy() - wx.FindWindowById(Config.UpdateWindowId).Destroy() + wx.GetApp().ExitMainLoop() except Exception as e: print(e) pass @@ -54,8 +76,167 @@ def listenToQueue(self): def reBind(self): self._stop() self.BindHotKey() + # 如果启用了任何鼠标按键隐藏,则添加鼠标监听 + if (hasattr(Config, 'middle_button_hide') and Config.middle_button_hide) or \ + (hasattr(Config, 'side_button1_hide') and Config.side_button1_hide) or \ + (hasattr(Config, 'side_button2_hide') and Config.side_button2_hide): + self.start_mouse_listener() + + # 如果启用了任何屏幕角落隐藏,则添加鼠标移动监听 + if (hasattr(Config, 'top_left_hide') and Config.top_left_hide) or \ + (hasattr(Config, 'top_right_hide') and Config.top_right_hide) or \ + (hasattr(Config, 'bottom_left_hide') and Config.bottom_left_hide) or \ + (hasattr(Config, 'bottom_right_hide') and Config.bottom_right_hide): + self.start_mouse_move_listener() + + # 启动自动隐藏监控(如果启用) + self.start_auto_hide_monitor() + + def start_auto_hide_monitor(self): + """启动自动隐藏监控""" + # 停止之前的监控 + self.stop_auto_hide_monitor() + + # 检查是否启用了自动隐藏 + if hasattr(Config, 'auto_hide_enabled') and Config.auto_hide_enabled: + # 启动活动监听器 + self.start_activity_listeners() + + # 启动定时器,每5秒检查一次是否需要自动隐藏 + self.auto_hide_timer = threading.Timer(5, self.check_auto_hide) + self.auto_hide_timer.daemon = True + self.auto_hide_timer.start() + + def stop_auto_hide_monitor(self): + """停止自动隐藏监控""" + # 停止定时器 + if self.auto_hide_timer: + self.auto_hide_timer.cancel() + self.auto_hide_timer = None + + # 停止活动监听器 + self.stop_activity_listeners() + + def start_activity_listeners(self): + """启动键盘和鼠标活动监听器""" + # 停止之前的监听器 + self.stop_activity_listeners() + + # 启动键盘活动监听器 + self.keyboard_activity_listener = keyboard.Listener(on_press=self.on_activity) + self.keyboard_activity_listener.daemon = True + self.keyboard_activity_listener.start() + + # 启动鼠标活动监听器 + self.mouse_activity_listener = mouse.Listener( + on_move=self.on_activity, + on_click=self.on_activity, + on_scroll=self.on_activity + ) + self.mouse_activity_listener.daemon = True + self.mouse_activity_listener.start() + + def stop_activity_listeners(self): + """停止键盘和鼠标活动监听器""" + if self.keyboard_activity_listener: + self.keyboard_activity_listener.stop() + self.keyboard_activity_listener = None + + if self.mouse_activity_listener: + self.mouse_activity_listener.stop() + self.mouse_activity_listener = None + + def on_activity(self, *args, **kwargs): + """记录最后一次活动时间""" + self.last_activity_time = time.time() + + def check_auto_hide(self): + """检查是否需要自动隐藏""" + try: + # 重新加载配置 + if hasattr(Config, 'auto_hide_enabled') and Config.auto_hide_enabled: + # 计算闲置时间(秒) + idle_time = time.time() - self.last_activity_time + # 转换自动隐藏时间为秒 + auto_hide_seconds = Config.auto_hide_time * 60 + + # 如果闲置时间超过设定的自动隐藏时间,且窗口当前是显示状态 + if idle_time >= auto_hide_seconds and self.get_windows_state() == 1: + # 执行隐藏操作,修复了这里的错误调用 + wx.CallAfter(self.onHide) + + finally: + # 如果仍然启用了自动隐藏,则设置下一次检查 + if hasattr(Config, 'auto_hide_enabled') and Config.auto_hide_enabled: + self.auto_hide_timer = threading.Timer(5, self.check_auto_hide) + self.auto_hide_timer.daemon = True + self.auto_hide_timer.start() + + def start_mouse_listener(self): + """启动鼠标监听器""" + if self.mouse_listener is None or not self.mouse_listener.is_alive(): + self.mouse_listener = mouse.Listener(on_click=self.on_mouse_click) + self.mouse_listener.daemon = True + self.mouse_listener.start() + + def on_mouse_click(self, x, y, button, pressed): + """鼠标点击事件处理""" + if pressed: # 只在按下时触发,不在松开时触发 + if (button == mouse.Button.middle and Config.middle_button_hide) or \ + (button == mouse.Button.x1 and Config.side_button1_hide) or \ + (button == mouse.Button.x2 and Config.side_button2_hide): + # 在主线程中执行onHide + wx.CallAfter(self.onHide) + + def start_mouse_move_listener(self): + """启动鼠标移动监听器""" + if self.mouse_move_listener is None or not self.mouse_move_listener.is_alive(): + self.mouse_move_listener = mouse.Listener(on_move=self.on_mouse_move) + self.mouse_move_listener.daemon = True + self.mouse_move_listener.start() - def ListenerProcess(self,hotkey): + def on_mouse_move(self, x, y): + """鼠标移动事件处理,检测四个角落""" + now = time.time() + # 如果冷却时间未过,则不处理 + if now - self.last_corner_trigger < self.corner_cooldown: + return + + # 获取当前窗口状态,1=显示,0=隐藏 + current_state = self.get_windows_state() + + # 检测是否在角落区域 + corner_detected = None + + # 左上角 + if x <= self.corner_threshold and y <= self.corner_threshold: + if Config.top_left_hide: + corner_detected = "top_left" + + # 右上角 + elif x >= self.screen_width - self.corner_threshold and y <= self.corner_threshold: + if Config.top_right_hide: + corner_detected = "top_right" + + # 左下角 + elif x <= self.corner_threshold and y >= self.screen_height - self.corner_threshold: + if Config.bottom_left_hide: + corner_detected = "bottom_left" + + # 右下角 + elif x >= self.screen_width - self.corner_threshold and y >= self.screen_height - self.corner_threshold: + if Config.bottom_right_hide: + corner_detected = "bottom_right" + + # 如果检测到角落并且满足条件 + if corner_detected: + # 根据当前状态和恢复设置决定是否执行操作 + if current_state == 1 or (current_state == 0 and Config.allow_move_restore): + wx.CallAfter(self.onHide) + self.last_corner_trigger = now + + def ListenerProcess(self, hotkey): + """键盘热键监听进程""" try: with keyboard.GlobalHotKeys(hotkey) as listener: self.end_flag = False @@ -68,7 +249,8 @@ def ListenerProcess(self,hotkey): print("热键监听已停止") except Exception as e: - self.ShowWindows(False) + # 热键监听出错时尝试恢复窗口 + self.set_windows_state(1) # 强制设置状态为显示 print(f"热键监听出错: {e}") def BindHotKey(self): @@ -78,59 +260,110 @@ def BindHotKey(self): } hotkeys = tool.keyConvert(hotkeys) - self.listener = multiprocessing.Process(target=self.ListenerProcess,daemon=True,args=(hotkeys,),name="Boss-Key热键监听进程") + self.listener = multiprocessing.Process(target=self.ListenerProcess, daemon=True, args=(hotkeys,), name="Boss-Key热键监听进程") self.listener.start() - def onHide(self,e=""): - if Config.times == 1: + def get_windows_state(self): + """获取窗口状态,1=显示,0=隐藏""" + try: + if os.path.exists(self.shared_state_file): + with open(self.shared_state_file, 'r') as f: + return int(f.read().strip() or '1') + return 1 # 默认状态为显示 + except: + return 1 # 出错时默认状态为显示 + + def set_windows_state(self, state): + """设置窗口状态,1=显示,0=隐藏""" + try: + with open(self.shared_state_file, 'w') as f: + f.write(str(state)) + Config.times = state # 同时更新内存中的状态 + except Exception as e: + print(f"设置窗口状态失败: {e}") + + def onHide(self, e=""): + """根据当前状态切换窗口显示/隐藏""" + # 从共享状态文件获取当前状态 + current_state = self.get_windows_state() + + if current_state == 1: # 隐藏窗口 self.HideWindows() else: + # 显示窗口 self.ShowWindows() - def ShowWindows(self,load=True): - # 显示窗口 + def ShowWindows(self, load=True): + """显示之前隐藏的窗口""" if load: Config.load() + + # 如果有冻结的进程,先解冻 + if Config.freeze_after_hide and Config.frozen_pids: + for pid in Config.frozen_pids: + try: + tool.resume_process(pid) + except Exception as e: + print(f"解冻进程失败: {e}") + Config.frozen_pids = [] + for i in Config.history: ShowWindow(i, SW_SHOW) if Config.mute_after_hide: - tool.changeMute(i,0) + tool.changeMute(i, 0) if Config.hide_icon_after_hide: self.Queue.put("showTaskBarIcon") - Config.times = 1 + # 更新状态 + self.set_windows_state(1) Config.save() def HideWindows(self): - # 隐藏窗口 - + """隐藏指定的窗口""" Config.load() - needHide=[] - windows=tool.getAllWindows() + needHide = [] + frozen_pids = [] + windows = tool.getAllWindows() - outer=windows - inner=Config.hide_binding + outer = windows + inner = Config.hide_binding - #减少循环次数,选择相对较少的做外循环 + # 减少循环次数,选择相对较少的做外循环 if len(Config.hide_binding) < len(windows): - outer=Config.hide_binding - inner=windows + outer = Config.hide_binding + inner = windows for i in outer: for j in inner: if tool.isSameWindow(i, j, False, not Config.path_match): - if outer==Config.hide_binding: # 此时i是绑定的元素,j是窗口元素,需要隐藏j + if outer == Config.hide_binding: # 此时i是绑定的元素,j是窗口元素,需要隐藏j needHide.append(j.hwnd) + if Config.freeze_after_hide and hasattr(j, 'PID') and j.PID: + frozen_pids.append(j.PID) else: needHide.append(i.hwnd) + if Config.freeze_after_hide and hasattr(i, 'PID') and i.PID: + frozen_pids.append(i.PID) break - if Config.hide_current: # 插入当前窗口的句柄 - needHide.append(GetForegroundWindow()) + if Config.hide_current: # 插入当前窗口的句柄 + hwnd = GetForegroundWindow() + needHide.append(hwnd) + # 如果需要冻结进程,获取当前窗口的PID + if Config.freeze_after_hide: + try: + pid = win32process.GetWindowThreadProcessId(hwnd)[1] + current_pid = win32process.GetCurrentProcessId() # 获取当前程序的PID + if pid != current_pid and pid != os.getpid(): # 如果当前窗口的pid与本程序的pid相同,则不冻结 + frozen_pids.append(pid) + except: + pass - needHide=tool.remove_duplicates(needHide) # 去重 + needHide = tool.remove_duplicates(needHide) # 去重 + frozen_pids = tool.remove_duplicates(frozen_pids) if Config.freeze_after_hide else [] # 去重 + for i in needHide: if Config.send_before_hide: time.sleep(0.2) @@ -138,15 +371,25 @@ def HideWindows(self): ShowWindow(i, SW_HIDE) if Config.mute_after_hide: - tool.changeMute(i,1) + tool.changeMute(i, 1) + + # 冻结进程 + if Config.freeze_after_hide and frozen_pids: + for pid in frozen_pids: + try: + tool.suspend_process(pid) + except Exception as e: + print(f"冻结进程失败: {e}") + Config.frozen_pids = frozen_pids - Config.history=needHide - Config.times = 0 + Config.history = needHide + # 更新状态 + self.set_windows_state(0) if Config.hide_icon_after_hide: self.Queue.put("hideTaskBarIcon") Config.save() - def Close(self,e=""): + def Close(self, e=""): self.Queue.put("closeApp") def _stop(self): @@ -154,7 +397,7 @@ def _stop(self): 直接关闭listener,应该使用Close """ if self.listener is not None: - self.end_flag=True + self.end_flag = True try: self.listener.terminate() self.listener.join() @@ -162,3 +405,32 @@ def _stop(self): pass finally: self.listener = None + + # 停止鼠标按键监听器 + if hasattr(self, 'mouse_listener') and self.mouse_listener is not None: + try: + self.mouse_listener.stop() + self.mouse_listener = None + except: + pass + + # 停止鼠标移动监听器 + if hasattr(self, 'mouse_move_listener') and self.mouse_move_listener is not None: + try: + self.mouse_move_listener.stop() + self.mouse_move_listener = None + except: + pass + + # 停止自动隐藏监控 + self.stop_auto_hide_monitor() + self._cleanup() + + def _cleanup(self): + # 清理状态文件 + try: + if hasattr(self, 'shared_state_file') and os.path.exists(self.shared_state_file): + os.remove(self.shared_state_file) + print("已清理状态文件") + except Exception as e: + print(f"清理状态文件失败: {e}") diff --git a/main/core/tools.py b/main/core/tools.py index 199b278..d7f58a0 100644 --- a/main/core/tools.py +++ b/main/core/tools.py @@ -9,9 +9,108 @@ import requests import json import pythoncom +import ctypes +import os +import subprocess +from ctypes import wintypes +import sys from core.model import WindowInfo +# 定义 NtSuspendProcess 和 NtResumeProcess 的类型 +NtSuspendProcess = ctypes.WINFUNCTYPE(wintypes.LONG, wintypes.HANDLE) +NtResumeProcess = ctypes.WINFUNCTYPE(wintypes.LONG, wintypes.HANDLE) + +# 加载 ntdll.dll +ntdll = ctypes.WinDLL("ntdll") +nt_suspend_process = NtSuspendProcess(("NtSuspendProcess", ntdll)) +nt_resume_process = NtResumeProcess(("NtResumeProcess", ntdll)) + +def is_admin(): + try: + return ctypes.windll.shell32.IsUserAnAdmin() + except: + return False + +def run_as_admin(): + if is_admin(): + print("Already running as administrator.") + else: + print("Requesting administrator privileges...") + ctypes.windll.shell32.ShellExecuteW(None, "runas", sys.executable, " ".join(sys.argv), None, 1) + +# 检查pssuspend64是否存在 +def check_pssuspend_exists(): + """检查pssuspend64.exe是否在程序根目录下存在""" + pssuspend_path =os.path.join(Config.root_path,"pssuspend64.exe") + return os.path.exists(pssuspend_path) + +# 使用pssuspend64冻结进程 +def suspend_process_enhanced(pid): + """使用pssuspend64.exe冻结指定PID的进程""" + try: + pssuspend_path = os.path.join(Config.root_path,"pssuspend64.exe") + result = subprocess.run([pssuspend_path, str(pid)], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + creationflags=subprocess.CREATE_NO_WINDOW) + if result.returncode != 0: + raise RuntimeError(f"pssuspend64执行失败: {result.stderr}") + except Exception as e: + raise RuntimeError(f"无法使用pssuspend64冻结进程: {str(e)}") + +# 使用pssuspend64解冻进程 +def resume_process_enhanced(pid): + """使用pssuspend64.exe解冻指定PID的进程""" + try: + pssuspend_path = os.path.join(Config.root_path,"pssuspend64.exe") + result = subprocess.run([pssuspend_path, "-r", str(pid)], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + creationflags=subprocess.CREATE_NO_WINDOW) + if result.returncode != 0: + raise RuntimeError(f"pssuspend64执行失败: {result.stderr}") + except Exception as e: + raise RuntimeError(f"无法使用pssuspend64解冻进程: {str(e)}") + +# 冻结进程 (Suspend Process) +def suspend_process(pid): + """冻结指定PID的进程""" + # 如果启用了增强冻结且pssuspend64存在,则使用pssuspend64 + if hasattr(Config, 'enhanced_freeze') and Config.enhanced_freeze and check_pssuspend_exists() and is_admin(): + return suspend_process_enhanced(pid) + + process_handle = ctypes.windll.kernel32.OpenProcess(0x001F0FFF, False, pid) # PROCESS_ALL_ACCESS + if not process_handle: + raise RuntimeError(f"无法打开进程,PID: {pid}") + + try: + nt_status = nt_suspend_process(process_handle) + if nt_status != 0: + raise RuntimeError(f"NtSuspendProcess 调用失败,状态码: {nt_status}") + finally: + ctypes.windll.kernel32.CloseHandle(process_handle) + +# 解冻进程 (Resume Process) +def resume_process(pid): + """解冻指定PID的进程""" + # 如果启用了增强冻结且pssuspend64存在,则使用pssuspend64 + if hasattr(Config, 'enhanced_freeze') and Config.enhanced_freeze and check_pssuspend_exists() and is_admin(): + return resume_process_enhanced(pid) + + process_handle = ctypes.windll.kernel32.OpenProcess(0x001F0FFF, False, pid) # PROCESS_ALL_ACCESS + if not process_handle: + raise RuntimeError(f"无法打开进程,PID: {pid}") + + try: + nt_status = nt_resume_process(process_handle) + if nt_status != 0: + raise RuntimeError(f"NtResumeProcess 调用失败,状态码: {nt_status}") + finally: + ctypes.windll.kernel32.CloseHandle(process_handle) + def checkUpdate(): requests.packages.urllib3.disable_warnings() # 获取最新版本信息 diff --git a/src/static/screenshot-1.png b/src/static/screenshot-1.png index cd56144..e79108c 100644 Binary files a/src/static/screenshot-1.png and b/src/static/screenshot-1.png differ diff --git a/src/static/screenshot-2.png b/src/static/screenshot-2.png deleted file mode 100644 index 3485f07..0000000 Binary files a/src/static/screenshot-2.png and /dev/null differ diff --git a/src/static/screenshot-3.png b/src/static/screenshot-3.png index 476c04a..a054c88 100644 Binary files a/src/static/screenshot-3.png and b/src/static/screenshot-3.png differ diff --git a/src/static/screenshot-5.png b/src/static/screenshot-5.png new file mode 100644 index 0000000..ac0251e Binary files /dev/null and b/src/static/screenshot-5.png differ