diff --git a/main/Boss-Key.py b/main/Boss-Key.py index 6b3b122..068ea54 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": @@ -43,7 +44,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 +91,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..6a630a4 --- /dev/null +++ b/main/GUI/setting/base.py @@ -0,0 +1,92 @@ +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.Bind_EVT() + self.SetData() + self.SetSize((1500, 800)) + self.Center() + + def init_UI(self): + panel = wx.Panel(self) + 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.notebook, 1, wx.EXPAND | wx.ALL, 5) + + # 创建按钮 + button_sizer = wx.BoxSizer(wx.HORIZONTAL) + self.reset_btn = wx.Button(panel, label="重置设置") + self.save_btn = wx.Button(panel, label="保存设置") + 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: + static_text = wx.StaticText(panel, label="本页面仅在首次启动或内容有更新时自动显示,后续可通过托盘图标打开本页面") + sizer.Add(static_text, 0, wx.EXPAND | wx.BOTTOM, 10) + + panel.SetSizer(sizer) + sizer.Fit(self) + + 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 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..a7e2998 --- /dev/null +++ b/main/GUI/setting/hotkeys_page.py @@ -0,0 +1,75 @@ +import wx +from core.config import Config +import GUI.record as record + +class HotkeysPage(wx.Panel): + def __init__(self, parent): + super().__init__(parent) + self.init_UI() + self.Bind_EVT() + + def init_UI(self): + sizer = wx.BoxSizer(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) + + sizer.Add(grid_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) + + def SetData(self): + self.hide_show_text.SetValue(Config.hide_hotkey) + self.close_text.SetValue(Config.close_hotkey) + + def SaveData(self): + Config.hide_hotkey = self.hide_show_text.GetValue() + Config.close_hotkey = self.close_text.GetValue() + + def Reset(self): + self.hide_show_text.SetValue("Ctrl+Q") + self.close_text.SetValue("Win+Esc") + + 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..72ce557 --- /dev/null +++ b/main/GUI/setting/options_page.py @@ -0,0 +1,264 @@ +import wx +import wx.adv +import webbrowser +from core.config import Config +import os +from core.tools import check_pssuspend_exists, is_admin, run_as_admin + +class OptionsPage(wx.Panel): + def __init__(self, parent): + super().__init__(parent) + self.init_UI() + self.Bind_EVT() + + 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, 20) + + # 创建一个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, 20) + + 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().GetTopWindow().Close() + + 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..b7c4008 100644 --- a/main/GUI/window_restore.py +++ b/main/GUI/window_restore.py @@ -2,46 +2,86 @@ import wx.dataview as dataview import win32gui import win32con -from .setting import SettingWindow +import webbrowser 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() @@ -49,25 +89,134 @@ def RefreshLeftList(self, e=None): 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 on_show_window(self,e=None): + + 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..dcf73ff 100644 --- a/main/core/config.py +++ b/main/core/config.py @@ -38,6 +38,7 @@ class Config: """ history=[] + frozen_pids=[] # 存储已冻结的进程PID times=1 hide_hotkey = "Ctrl+Q" @@ -45,7 +46,9 @@ 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 @@ -53,9 +56,11 @@ class Config: 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 +88,15 @@ 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.click_to_hide= config.get("setting", {}).get("click_to_hide", True) @@ -107,6 +115,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 +126,9 @@ 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 # 保存新配置项 }, # 将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..0358aa3 100644 --- a/main/core/listener.py +++ b/main/core/listener.py @@ -2,11 +2,13 @@ import core.tools as tool from win32gui import GetForegroundWindow, ShowWindow from win32con import SW_HIDE, SW_SHOW +import win32process import sys from pynput import keyboard import multiprocessing import threading import time +import os import wx class HotkeyListener(): @@ -37,9 +39,10 @@ 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() + # wx.FindWindowById(Config.SettingWindowId).Destroy() + # wx.FindWindowById(Config.UpdateWindowId).Destroy() + # Config.TaskBarIcon.Destroy() except Exception as e: print(e) pass @@ -92,6 +95,16 @@ 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: @@ -105,9 +118,10 @@ def ShowWindows(self,load=True): def HideWindows(self): # 隐藏窗口 - + Config.load() needHide=[] + frozen_pids=[] windows=tool.getAllWindows() outer=windows @@ -123,14 +137,30 @@ def HideWindows(self): if tool.isSameWindow(i, j, False, not Config.path_match): 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()) + 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) # 去重 + 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) @@ -139,6 +169,15 @@ def HideWindows(self): ShowWindow(i, SW_HIDE) if Config.mute_after_hide: 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 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() # 获取最新版本信息