无忧启动论坛

 找回密码
 注册
搜索
系统gho:最纯净好用系统下载站投放广告、加入VIP会员,请联系 微信:wuyouceo
楼主: piaomusic
打印 上一主题 下一主题

[原创] 2025年6月9日更新,我的工具箱1.0,2.0,3.0

  [复制链接]
31#
发表于 6 天前 | 只看该作者
回复

使用道具 举报

32#
发表于 6 天前 | 只看该作者
谢谢楼主分享
回复

使用道具 举报

33#
 楼主| 发表于 6 天前 | 只看该作者
bilvnet 发表于 2025-6-5 07:25
todesk 需要登录吗??

工具可以自己配置。
回复

使用道具 举报

34#
发表于 6 天前 | 只看该作者
只有3.0能自己配置图标吗?感谢分享,用来管理自己绿色软件挺不错。

点评

是的。 还是继续更新  详情 回复 发表于 6 天前
回复

使用道具 举报

35#
 楼主| 发表于 6 天前 | 只看该作者
陌小寞 发表于 2025-6-5 13:00
只有3.0能自己配置图标吗?感谢分享,用来管理自己绿色软件挺不错。

是的。 还是继续更新
回复

使用道具 举报

36#
发表于 6 天前 来自手机 | 只看该作者
感谢分享
回复

使用道具 举报

37#
 楼主| 发表于 6 天前 | 只看该作者
3.0版本添加 工具箱管理功能,现在不需要手动编辑配置文件了

2025-06-05_16-02-50.png (21.65 KB, 下载次数: 1)

2025-06-05_16-02-50.png

2025-06-05_16-02-42.png (26.62 KB, 下载次数: 0)

2025-06-05_16-02-42.png

2025-06-05_16-02-36.png (18.84 KB, 下载次数: 1)

2025-06-05_16-02-36.png
回复

使用道具 举报

38#
发表于 5 天前 | 只看该作者
感谢分享
回复

使用道具 举报

39#
 楼主| 发表于 5 天前 | 只看该作者
本帖最后由 piaomusic 于 2025-6-6 16:19 编辑

我的工具箱

2025-06-06_16-14-55.png (39.79 KB, 下载次数: 1)

2025-06-06_16-14-55.png

2025-06-06_16-14-38.png (48.96 KB, 下载次数: 1)

2025-06-06_16-14-38.png
回复

使用道具 举报

40#
 楼主| 发表于 5 天前 | 只看该作者
奈绪 发表于 2025-6-4 14:48
楼主大佬,能不能增加一个一键安装功能,还有就是,安装路径默认是系统分区吗,能不能指定个安装路径。

http://bbs.wuyou.net/forum.php?m ... d=446332&extra=

点评

谢谢分享,辛苦了,就是我想要的。  详情 回复 发表于 4 天前
回复

使用道具 举报

41#
发表于 4 天前 来自手机 | 只看该作者
谢谢分享
回复

使用道具 举报

42#
发表于 4 天前 | 只看该作者
谢谢!试试看!
回复

使用道具 举报

43#
发表于 4 天前 | 只看该作者
很不错了, 要是再进一步,可以拖动加入并自动以拖动程序为图标就完全完美了,你这个工具箱有一个很好的好处,就是窗口和按钮的大小可以调节,这样有时可用于一些只需要几个按钮的特定程序服务,不好的是这个文件有点大,每次运行要解压python的一些文件,而非直接运行的

点评

不懂编程没办法啊,python读取EXE文件图标好像有问题。本来想直接读取图标的,发现不行。deepseek给了C#源代码都不知道怎么编译。  详情 回复 发表于 4 天前
回复

使用道具 举报

44#
 楼主| 发表于 4 天前 | 只看该作者
cjfcjf111 发表于 2025-6-7 13:36
很不错了, 要是再进一步,可以拖动加入并自动以拖动程序为图标就完全完美了,你这个工具箱有一个很好的好 ...

不懂编程没办法啊,python读取EXE文件图标好像有问题。本来想直接读取图标的,发现不行。deepseek给了C#源代码都不知道怎么编译。
回复

使用道具 举报

45#
发表于 4 天前 | 只看该作者
研究python你还不如研究一下AU3,你研究一段时间就会懂的,python用于爬虫到是很多,另外的局限性太多,基本没什么前途,当然AU3也不是什么好鸟,只不过是加强版的批处理类编程程序,不过比批处理要强大几十倍,重在很好学,真要解决一切,还是要研究真正的编程软件
回复

使用道具 举报

46#
发表于 4 天前 | 只看该作者
piaomusic 发表于 2025-6-6 19:20
http://bbs.wuyou.net/forum.php?mod=viewthread&tid=446332&extra=

谢谢分享,辛苦了,就是我想要的。
回复

使用道具 举报

47#
 楼主| 发表于 前天 12:26 | 只看该作者
本帖最后由 piaomusic 于 2025-6-9 17:58 编辑

0609修改完善 去掉按钮文字无用代码。修改图标路径为相对路径。修改工具箱管理重复打开BUG。
import tkinter as tk
from tkinter import ttk, messagebox, filedialog, simpledialog
import configparser
import os
import sys
import ctypes
import socket
import logging
import webbrowser
import subprocess
from threading import Thread, Lock
from typing import Dict, List, Tuple, Optional, Callable, Any
from ttkbootstrap import Style
from PIL import Image, ImageTk


logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
    filename='toolbox.log',
    encoding='utf-8'
)
logger = logging.getLogger(__name__)


ToolConfig = Tuple[str, str, Optional[str]]
ToolDict = Dict[str, List[ToolConfig]]


def is_admin() -> bool:
    try:
        return ctypes.windll.shell32.IsUserAnAdmin()
    except Exception:
        return False


def get_local_ip() -> str:
    try:
        hostname = socket.gethostname()
        ip_list = socket.gethostbyname_ex(hostname)[2]
        valid_ips = [ip for ip in ip_list if not ip.startswith('127.') and not ip.startswith('169.254.')]
        return valid_ips[0] if valid_ips else (ip_list[0] if ip_list else "127.0.0.1")
    except Exception as e:
        logger.error(f"获取内网IP失败: {str(e)}")
        return "127.0.0.1"


def center_window(window: tk.Tk, width: int, height: int) -> None:
    window.update_idletasks()
    screen_width = window.winfo_screenwidth()
    screen_height = window.winfo_screenheight()
    x = (screen_width - width) // 2
    y = (screen_height - height) // 2
    window.geometry(f'{width}x{height}+{x}+{y}')


class ToolboxApp:
    DEFAULT_CONFIG = {
        'title_font': 'Microsoft YaHei', 'nav_font': 'Microsoft YaHei',
        'button_font': 'Microsoft YaHei', 'header_bg': '#2c3e50',
        'header_fg': '#ecf0f1', 'nav_bg': '#f8f9fa', 'content_bg': '#ffffff',
        'button_bg': '#ecf0f1', 'button_fg': '#000000', 'active_button_bg': '#d1d8e0',
        'icon_text_padding': 5, 'icon_size': 32, 'window_width': 900,
        'window_height': 600, 'tools_per_row': 3, 'title': '高级工具箱',
        'title_font_size': 16, 'nav_font_size': 13, 'button_font_size': 12,
        'ip_font_size': 12, 'title_bar_height': 50, 'nav_bar_width': 160,
        'nav_button_spacing': 5, 'nav_button_top_margin': 10, 'button_width': 20,
        'button_padx': 15, 'button_pady': 5
    }
   
    CONFIG_MAPPING = {
        'window_width': '窗口宽度', 'window_height': '窗口高度',
        'tools_per_row': '每行工具数', 'title': '窗口标题',
        'title_font_size': '主标题字号', 'nav_font_size': '导航栏字号',
        'button_font_size': '按钮字号', 'ip_font_size': 'IP字号',
        'title_bar_height': '标题栏高度', 'nav_bar_width': '导航栏宽度',
        'nav_button_spacing': '导航按钮间距', 'nav_button_top_margin': '导航栏顶部间距',
        'icon_size': '图标大小', 'button_width': '按钮宽度',
        'button_padx': '水平间距', 'button_pady': '垂直间距'
    }


    PROTECTED_METADATA = {
        '作者': '缘起性空', '版本': '3.0',
        '联系': '32897251@qq.com', '说明': '本工具仅供技术交流使用'
    }


    def __init__(self, master: tk.Tk):
        self.master = master
        self.base_path = self._get_base_path()
        self.config = self.DEFAULT_CONFIG.copy()
        self.tools: ToolDict = {}
        self.icons: Dict[str, ImageTk.PhotoImage] = {}
        self.running_tools: Dict[str, bool] = {}
        self.tool_lock = Lock()
        self.current_section: Optional[str] = None
        self.selected_button: Optional[ttk.Button] = None
        self.selected_button_name: Optional[str] = None
        self._last_config_mtime: Optional[float] = None
        self._toolbox_manager_window = None
        
        self._create_required_dirs()
        self._initialize_app()


    def _create_required_dirs(self) -> None:
        required_dirs = ['icons', 'tools']
        for dir_name in required_dirs:
            dir_path = os.path.join(self.base_path, dir_name)
            if not os.path.exists(dir_path):
                os.makedirs(dir_path)
                logger.info(f"创建目录: {dir_path}")


    def _get_base_path(self) -> str:
        if getattr(sys, 'frozen', False):
            return os.path.dirname(sys.executable)
        return os.path.dirname(os.path.abspath(__file__))


    def _initialize_app(self) -> None:
        try:
            self.master.withdraw()
            self._ensure_metadata()
            self._load_config()
            self._setup_ui()
            self.master.after(100, self._finalize_ui)
        except Exception as e:
            logger.critical(f"应用程序初始化失败: {str(e)}", exc_info=True)
            messagebox.showerror("错误", f"程序初始化失败: {str(e)}")
            sys.exit(1)


    def _ensure_metadata(self) -> None:
        config_path = os.path.join(self.base_path, 'config.ini')
        if not os.path.exists(config_path):
            self._write_protected_metadata(config_path)
            return
            
        parser = configparser.ConfigParser()
        parser.read(config_path, encoding='utf-8')
        
        needs_update = any(
            not parser.has_option('程序信息', key) or parser.get('程序信息', key) != value
            for key, value in self.PROTECTED_METADATA.items()
        )
        
        if needs_update:
            self._write_protected_metadata(config_path, parser)


    def _write_protected_metadata(self, config_path: str, old_parser: Optional[configparser.ConfigParser] = None) -> None:
        try:
            parser = configparser.ConfigParser()
            if old_parser:
                for section in old_parser.sections():
                    if section != '程序信息' and section != '界面设置':
                        parser.add_section(section)
                        for key, value in old_parser.items(section):
                            parser.set(section, key, value)
            
            with open(config_path, 'w', encoding='utf-8') as f:
                f.write("[程序信息]\n")
                for key, value in self.PROTECTED_METADATA.items():
                    f.write(f"{key} = {value}\n")
                f.write("\n")
               
                f.write("[界面设置]\n")
                for internal_key, chinese_key in self.CONFIG_MAPPING.items():
                    f.write(f"{chinese_key} = {self.DEFAULT_CONFIG[internal_key]}\n")
                f.write("\n")
               
                for section in parser.sections():
                    if section not in {'程序信息', '界面设置'}:
                        f.write(f"[{section}]\n")
                        for key, value in parser.items(section):
                            f.write(f"{key} = {value}\n")
                        f.write("\n")
               
            logger.info("配置文件已更新")
        except Exception as e:
            logger.error(f"写入配置文件失败: {str(e)}", exc_info=True)
            raise RuntimeError(f"无法写入配置文件: {str(e)}")


    def _finalize_ui(self) -> None:
        center_window(self.master, self.config['window_width'], self.config['window_height'])
        self._update_ip_display()
        self.master.deiconify()
        if self.tools:
            self.master.after(50, lambda: self.show_tools(next(iter(self.tools)), force=True))


    def _load_config(self) -> None:
        config_path = os.path.join(self.base_path, 'config.ini')
        if not os.path.exists(config_path):
            logger.warning(f"配置文件不存在: {config_path}, 使用默认配置")
            return
            
        current_mtime = os.path.getmtime(config_path)
        if self._last_config_mtime == current_mtime:
            return
               
        self._last_config_mtime = current_mtime
        parser = configparser.ConfigParser()
        parser.read(config_path, encoding='utf-8')
        
        if '界面设置' in parser:
            for internal_key, chinese_key in self.CONFIG_MAPPING.items():
                if parser.has_option('界面设置', chinese_key):
                    try:
                        value = parser.get('界面设置', chinese_key)
                        if internal_key in ['window_width', 'window_height', 'tools_per_row',
                                          'title_font_size', 'nav_font_size', 'button_font_size',
                                          'ip_font_size', 'title_bar_height', 'nav_bar_width',
                                          'nav_button_spacing', 'nav_button_top_margin',
                                          'icon_size', 'button_width',
                                          'button_padx', 'button_pady']:
                            self.config[internal_key] = int(value)
                        else:
                            self.config[internal_key] = value
                    except ValueError:
                        logger.warning(f"配置项 {chinese_key} 格式错误, 使用默认值")
        
        self._load_tools_config(parser)


    def _load_tools_config(self, parser: configparser.ConfigParser) -> None:
        self.tools.clear()
        for section in parser.sections():
            if section in {'程序信息', '界面设置'}:
                continue
               
            self.tools[section] = []
            for name, cmd in parser.items(section):
                parts = cmd.split('|')
                tool_config = (
                    name.strip(),
                    parts[0].strip(),
                    self._normalize_icon_path(parts[1].strip()) if len(parts) > 1 else None
                )
                self.tools[section].append(tool_config)
        
        if not self.tools:
            logger.warning("配置文件中没有定义任何工具分类")


    def _normalize_icon_path(self, path: Optional[str]) -> Optional[str]:
        if not path:
            return None
            
        path = path.lower()
        
        if not path.startswith('icons/'):
            path = f"icons/{os.path.basename(path)}"
            
        return path.replace("\\", "/")


    def _save_config(self) -> None:
        config_path = os.path.join(self.base_path, 'config.ini')
        try:
            old_parser = configparser.ConfigParser()
            if os.path.exists(config_path):
                old_parser.read(config_path, encoding='utf-8')
            
            with open(config_path, 'w', encoding='utf-8') as f:
                f.write("[程序信息]\n")
                for key, value in self.PROTECTED_METADATA.items():
                    f.write(f"{key} = {value}\n")
                f.write("\n[界面设置]\n")
                for internal_key, chinese_key in self.CONFIG_MAPPING.items():
                    if internal_key in self.config:
                        f.write(f"{chinese_key} = {self.config[internal_key]}\n")
                f.write("\n")
               
                for section, tools in self.tools.items():
                    f.write(f"[{section}]\n")
                    for name, cmd, icon_path in tools:
                        value = cmd
                        if icon_path:
                            value += f"|{icon_path}"
                        f.write(f"{name} = {value}\n")
                    f.write("\n")
               
            self._last_config_mtime = os.path.getmtime(config_path)
            logger.info("配置文件已保存")
        except Exception as e:
            logger.error(f"保存配置文件失败: {str(e)}")
            messagebox.showerror("错误", f"保存配置文件失败: {str(e)}")


    def _setup_ui(self) -> None:
        self.style = Style(theme="minty")
        self._configure_styles()
        self.master.title(self.config['title'])
        
        main_frame = ttk.Frame(self.master)
        main_frame.pack(fill=tk.BOTH, expand=True)
        
        self._setup_header(main_frame)
        self._setup_body(main_frame)
        self._setup_button_context_menu()
        
        if sys.platform == 'win32':
            self.master.tk.call('tk', 'scaling', 1.5)
            self.master.option_add('*tearOff', False)


    def _setup_header(self, parent: ttk.Frame) -> None:
        header_frame = ttk.Frame(parent, height=self.config['title_bar_height'], style='Header.TFrame')
        header_frame.pack(fill=tk.X)
        header_frame.pack_propagate(False)
        
        ttk.Label(header_frame, text=self.config['title'], style='Header.TLabel').pack(side=tk.LEFT, padx=20)
        
        info_frame = ttk.Frame(header_frame, style='Header.TFrame')
        info_frame.pack(side=tk.RIGHT, padx=10)
        
        self.ip_label = ttk.Label(info_frame, text="IP: 获取中...", style='IP.TLabel')
        self.ip_label.pack(side=tk.LEFT, padx=5)
        
        ttk.Button(info_frame, text="⚙", command=self._show_toolbox_manager, style='NoHover.TButton').pack(side=tk.LEFT, padx=5)
        ttk.Button(info_frame, text="⭐", command=self.show_about, style='NoHover.TButton').pack(side=tk.LEFT, padx=5)


    def _show_toolbox_manager(self) -> None:
        if self._toolbox_manager_window is not None and self._toolbox_manager_window.winfo_exists():
            self._toolbox_manager_window.lift()
            return
        
        self._toolbox_manager_window = ToolboxManager(
            self.master,
            os.path.join(self.base_path, 'config.ini'),
            self._reload_config
        )
        
        self._toolbox_manager_window.window.protocol(
            "WM_DELETE_WINDOW",
            lambda: self._on_toolbox_manager_close()
        )


    def _on_toolbox_manager_close(self) -> None:
        if self._toolbox_manager_window:
            self._toolbox_manager_window._on_close()
            self._toolbox_manager_window = None


    def _reload_config(self) -> None:
        current_section = self.current_section
        self._load_config()
        
        for widget in self.nav_frame.winfo_children():
            widget.destroy()
        
        ttk.Frame(self.nav_frame, height=self.config['nav_button_top_margin'], style='Nav.TFrame').pack()
        
        for section in self.tools.keys():
            btn = ttk.Button(self.nav_frame, text=section, command=lambda s=section: self.show_tools(s, force=True), style='Nav.TButton')
            btn.pack(fill=tk.X, padx=5, pady=self.config['nav_button_spacing'])
        
        if current_section in self.tools:
            self.show_tools(current_section, force=True)
        elif self.tools:
            self.show_tools(next(iter(self.tools)), force=True)
        else:
            self._clear_content_frame()
            self.current_section = None


    def _setup_body(self, parent: ttk.Frame) -> None:
        body_frame = ttk.Frame(parent)
        body_frame.pack(fill=tk.BOTH, expand=True)
        
        self._setup_navigation(body_frame)
        
        self.content_frame = ttk.Frame(body_frame, style='Content.TFrame')
        self.content_frame.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True, padx=10, pady=10)


    def _setup_navigation(self, parent: ttk.Frame) -> None:
        self.nav_frame = ttk.Frame(parent, width=self.config['nav_bar_width'], style='Nav.TFrame')
        self.nav_frame.pack(side=tk.LEFT, fill=tk.Y)
        self.nav_frame.pack_propagate(False)
        
        ttk.Frame(self.nav_frame, height=self.config['nav_button_top_margin'], style='Nav.TFrame').pack()
        
        for section in self.tools.keys():
            btn = ttk.Button(self.nav_frame, text=section, command=lambda s=section: self.show_tools(s, force=True), style='Nav.TButton')
            btn.pack(fill=tk.X, padx=5, pady=self.config['nav_button_spacing'])


    def _setup_button_context_menu(self) -> None:
        self.button_menu = tk.Menu(self.master, tearoff=0)
        self.button_menu.add_command(label="运行", command=self._run_selected_tool)
        self.button_menu.add_command(label="重命名", command=self._rename_tool)
        self.button_menu.add_command(label="设置图标", command=self._set_tool_icon)
        self.button_menu.add_command(label="删除图标", command=self._remove_tool_icon)
        self.button_menu.add_separator()
        self.button_menu.add_command(label="删除", command=self._delete_tool)
        self.master.bind("<Button-1>", self._clear_selection)


    def _configure_styles(self) -> None:
        self.style.configure('Header.TFrame', background=self.config['header_bg'])
        self.style.configure('Header.TLabel', background=self.config['header_bg'],
                           foreground=self.config['header_fg'],
                           font=(self.config['title_font'], self.config['title_font_size'], 'bold'))
        self.style.configure('IP.TLabel', background=self.config['header_bg'],
                           foreground=self.config['header_fg'],
                           font=(self.config['button_font'], self.config['ip_font_size']))
        self.style.configure('Nav.TFrame', background=self.config['nav_bg'])
        self.style.configure('Content.TFrame', background=self.config['content_bg'])
        self.style.configure('Tool.TFrame', background=self.config['content_bg'])
        self.style.configure('Nav.TButton', font=(self.config['nav_font'], self.config['nav_font_size']),
                           background=self.config['nav_bg'], foreground='#333333',
                           borderwidth=0, padding=6)
        self.style.configure('Primary.TButton', font=(self.config['button_font'], self.config['button_font_size']),
                           background=self.config['button_bg'], foreground=self.config['button_fg'],
                           borderwidth=1, relief="solid",
                           padding=(self.config['icon_text_padding'], self.config['button_pady']),
                           anchor='w', width=self.config['button_width'])
        self.style.map('Primary.TButton',
                      background=[('active', self.config['active_button_bg'])],
                      foreground=[('active', '#000000')])
        self.style.configure('NoHover.TButton', font=(self.config['button_font'], self.config['button_font_size']),
                           foreground=self.config['header_fg'], background=self.config['header_bg'],
                           borderwidth=0, padding=2)
        self.style.map('NoHover.TButton', foreground=[], background=[])


    def show_tools(self, section: str, force: bool = False) -> None:
        if not force and self.current_section == section:
            return
            
        self._clear_content_frame()
        self.current_section = section
        
        if section not in self.tools:
            return
        
        grid_frame = ttk.Frame(self.content_frame, style='Content.TFrame')
        grid_frame.pack(fill=tk.BOTH, expand=True)
        
        self._preload_icons(section)
        
        row, col = 0, 0
        for name, cmd, icon_path in self.tools[section]:
            if col == 0:
                row_frame = ttk.Frame(grid_frame, style='Tool.TFrame')
                row_frame.grid(row=row, column=0, sticky="ew", pady=self.config['button_pady'])
            
            btn = ttk.Button(row_frame, text=name, command=lambda c=cmd, n=name: self.run_tool(c, n), style='Primary.TButton')
            
            if icon_path:
                try:
                    icon = self._load_icon(icon_path)
                    if icon:
                        btn.config(image=icon, compound=tk.LEFT, padding=(self.config['icon_text_padding'], 0, 0, 0))
                        btn.image = icon
                except Exception as e:
                    logger.error(f"设置按钮图标失败: {name} - {str(e)}")
            
            btn.grid(row=0, column=col, padx=self.config['button_padx'], pady=self.config['button_pady'], sticky="nsew")
            btn.bind("<Button-3>", lambda e, b=btn, n=name: self._show_button_context_menu(e, b, n))
            
            col += 1
            if col >= self.config['tools_per_row']:
                col = 0
                row += 1
        
        for i in range(self.config['tools_per_row']):
            grid_frame.columnconfigure(i, weight=1)


    def _preload_icons(self, section: str) -> None:
        for _, _, icon_path in self.tools[section]:
            if icon_path:
                try:
                    self._load_icon(icon_path)
                except:
                    pass


    def _clear_content_frame(self) -> None:
        for widget in self.content_frame.winfo_children():
            if isinstance(widget, ttk.Button) and hasattr(widget, 'image'):
                widget.image = None
            widget.destroy()
        self.master.update_idletasks()


    def _load_icon(self, icon_path: str) -> Optional[ImageTk.PhotoImage]:
        if not icon_path:
            return None
            
        if icon_path in self.icons:
            return self.icons[icon_path]
            
        try:
            abs_path = os.path.join(self.base_path, icon_path.replace("/", os.sep))
            
            if not os.path.exists(abs_path):
                logger.warning(f"图标文件不存在: {abs_path}")
                return None
            
            img = Image.open(abs_path).convert('RGBA')
            img = img.resize((self.config['icon_size'], self.config['icon_size']), Image.LANCZOS)
            photo = ImageTk.PhotoImage(img)
            self.icons[icon_path] = photo
            return photo
            
        except Exception as e:
            logger.error(f"加载图标失败: {icon_path} - {str(e)}")
            return None


    def run_tool(self, command: str, name: str) -> None:
        if "禁用驱动签名" in name or "启用驱动签名" in name:
            if not self._show_driver_signature_warning(name):
                return
            
        if command.startswith(('http://', 'https://')):
            webbrowser.open(command)
        else:
            if command.startswith('tools/'):
                command = os.path.join(self.base_path, command)
                if not os.path.exists(command):
                    logger.error(f"工具不存在: {command}")
                    return
            
            Thread(target=self._execute_command, args=(command, name), daemon=True).start()


    def _execute_command(self, command: str, name: str) -> None:
        try:
            with self.tool_lock:
                if name in self.running_tools:
                    logger.warning(f"工具已在运行: {name}")
                    return
                self.running_tools[name] = True
               
            startupinfo = subprocess.STARTUPINFO()
            startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
            startupinfo.wShowWindow = subprocess.SW_HIDE
            
            process = subprocess.Popen(command, shell=True, creationflags=subprocess.CREATE_NEW_CONSOLE, startupinfo=startupinfo)
            
            def monitor_process():
                try:
                    process.wait(timeout=30)
                except subprocess.TimeoutExpired:
                    logger.warning(f"工具执行超时:{name}")
                    process.kill()
                finally:
                    with self.tool_lock:
                        self.running_tools.pop(name, None)
            
            Thread(target=monitor_process, daemon=True).start()
                    
        except Exception as e:
            logger.error(f"执行失败: {name} - {str(e)}")
            with self.tool_lock:
                self.running_tools.pop(name, None)


    def _show_button_context_menu(self, event: tk.Event, button: ttk.Button, name: str) -> None:
        self.selected_button = button
        self.selected_button_name = name
        try:
            self.button_menu.tk_popup(event.x_root, event.y_root)
        finally:
            self.button_menu.grab_release()


    def _clear_selection(self, event: tk.Event) -> None:
        if not isinstance(event.widget, ttk.Button):
            self.selected_button = None
        self.selected_button_name = None


    def _run_selected_tool(self) -> None:
        if not self.selected_button_name or not self.current_section:
            return
            
        for name, cmd, _ in self.tools[self.current_section]:
            if name == self.selected_button_name:
                self.run_tool(cmd, name)
                break


    def _rename_tool(self) -> None:
        if not self.selected_button_name or not self.current_section:
            return
            
        new_name = simpledialog.askstring("重命名工具", "输入新名称:", initialvalue=self.selected_button_name, parent=self.master)
        
        if not new_name or new_name == self.selected_button_name:
            return
            
        for i, (name, cmd, icon_path) in enumerate(self.tools[self.current_section]):
            if name == self.selected_button_name:
                self.tools[self.current_section] = (new_name, cmd, icon_path)
                break
               
        self._save_config()
        self.show_tools(self.current_section, force=True)


    def _set_tool_icon(self) -> None:
        if not self.selected_button_name or not self.current_section:
            return
            
        initial_dir = os.path.join(self.base_path, "icons")
        if not os.path.exists(initial_dir):
            os.makedirs(initial_dir)
            
        icon_path = filedialog.askopenfilename(
            title="选择图标文件(必须放在icons目录内)",
            initialdir=initial_dir,
            filetypes=[("图标文件", "*.ico *.png *.jpg *.jpeg *.bmp"), ("所有文件", "*.*")]
        )
        
        if not icon_path:
            return
            
        try:
            rel_path = os.path.relpath(icon_path, self.base_path)
            
            if not rel_path.lower().replace("\\", "/").startswith("icons/"):
                messagebox.showerror("错误", "图标必须放在程序目录下的icons文件夹内!")
                return
               
            rel_path = rel_path.replace("\\", "/")
            
            for i, (name, cmd, _) in enumerate(self.tools[self.current_section]):
                if name == self.selected_button_name:
                    self.tools[self.current_section] = (name, cmd, rel_path)
                    break
                    
            self._save_config()
            self.show_tools(self.current_section, force=True)
        except Exception as e:
            logger.error(f"设置图标失败: {str(e)}")
            messagebox.showerror("错误", f"设置图标失败: {str(e)}")


    def _remove_tool_icon(self) -> None:
        if not self.selected_button_name or not self.current_section:
            return
            
        for i, (name, cmd, _) in enumerate(self.tools[self.current_section]):
            if name == self.selected_button_name:
                self.tools[self.current_section] = (name, cmd, None)
                break
               
        self._save_config()
        self.show_tools(self.current_section, force=True)


    def _delete_tool(self) -> None:
        if not self.selected_button_name or not self.current_section:
            return
            
        if not messagebox.askyesno("确认删除", f"确定要删除工具 '{self.selected_button_name}' 吗?"):
            return
            
        self.tools[self.current_section] = [
            (name, cmd, icon_path)
            for name, cmd, icon_path in self.tools[self.current_section]
            if name != self.selected_button_name
        ]
        
        self._save_config()
        self.show_tools(self.current_section, force=True)


    def _show_driver_signature_warning(self, operation_name: str) -> bool:
        warning_dialog = tk.Toplevel(self.master)
        warning_dialog.title("安全警告")
        warning_dialog.resizable(False, False)
        warning_dialog.transient(self.master)
        warning_dialog.grab_set()
        warning_dialog.configure(bg='#f8f9fa')
        
        dialog_width, dialog_height = 400, 250
        warning_dialog.geometry(f"{dialog_width}x{dialog_height}")
        warning_dialog.update_idletasks()
        
        screen_width = warning_dialog.winfo_screenwidth()
        screen_height = warning_dialog.winfo_screenheight()
        x = (screen_width - dialog_width) // 2
        y = (screen_height - dialog_height) // 2
        warning_dialog.geometry(f"+{x}+{y}")
        
        content_frame = tk.Frame(warning_dialog, bg='#f8f9fa', padx=15, pady=15)
        content_frame.pack(fill=tk.BOTH, expand=True)
        
        title_frame = tk.Frame(content_frame, bg='#f8f9fa')
        title_frame.pack(fill=tk.X, pady=(0, 10))
        
        tk.Label(title_frame, text="⚠", font=('Arial', 20), fg='orange', bg='#f8f9fa').pack(side=tk.LEFT, padx=(0, 10))
        tk.Label(title_frame, text="即将执行高风险操作!", font=(self.config['title_font'], self.config['title_font_size'], 'bold'),
                justify=tk.LEFT, bg='#f8f9fa').pack(side=tk.LEFT)


        text_frame = tk.Frame(content_frame, bg='#f8f9fa')
        text_frame.pack(fill=tk.X, padx=(30, 0), pady=(0, 15))
        
        for point in ["系统将重启进入高级启动模式", "临时禁用驱动签名验证", "确定要继续吗?"]:
            point_frame = tk.Frame(text_frame, bg='#f8f9fa')
            point_frame.pack(fill=tk.X, pady=2)
            tk.Label(point_frame, text="•", font=('Arial', 10), bg='#f8f9fa').pack(side=tk.LEFT, padx=(0, 5))
            tk.Label(point_frame, text=point, font=(self.config['button_font'], self.config['button_font_size']),
                    bg='#f8f9fa').pack(side=tk.LEFT)


        result = [False]
        button_frame = tk.Frame(content_frame, bg='#f8f9fa')
        button_frame.pack(fill=tk.X, padx=10, pady=10)
        
        ttk.Button(button_frame, text="是(Y)", command=lambda: [result.__setitem__(0, True), warning_dialog.destroy()],
                  style='Primary.TButton').pack(side=tk.RIGHT, padx=5)
        ttk.Button(button_frame, text="否(N)", command=lambda: [result.__setitem__(0, False), warning_dialog.destroy()],
                  style='Primary.TButton').pack(side=tk.RIGHT, padx=5)


        self.master.wait_window(warning_dialog)
        return result[0]


    def show_about(self) -> None:
        about_text = f"""{self.config['title']}


版本: {self.PROTECTED_METADATA['版本']}
作者: {self.PROTECTED_METADATA['作者']}
联系: {self.PROTECTED_METADATA['联系']}


{self.PROTECTED_METADATA['说明']}"""
        messagebox.showinfo("关于", about_text)


    def _update_ip_display(self) -> None:
        def update_ip():
            try:
                ip = get_local_ip()
                self.ip_label.config(text=f"IP: {ip}")
            except Exception as e:
                logger.error(f"获取IP失败: {str(e)}")
                self.ip_label.config(text="IP: 获取失败")
        
        Thread(target=update_ip, daemon=True).start()


class ToolboxManager:
    def __init__(self, master: tk.Tk, config_path: str = 'config.ini', refresh_callback: Optional[Callable] = None):
        self.master = master
        self.config_path = config_path
        self.refresh_callback = refresh_callback
        self.tools: Dict[str, List[Dict[str, str]]] = {}
        self._load_config()
        
        self.window = tk.Toplevel(master)
        self.window.title("工具箱管理")
        self.window.geometry("450x300")
        self.window.resizable(False, False)
        self.window.protocol("WM_DELETE_WINDOW", self._on_close)
        self._center_window()
        self._create_ui()
        self._refresh_data()
   
    def _on_close(self) -> None:
        if self.refresh_callback:
            self.refresh_callback()
        self.window.destroy()
   
    def _center_window(self) -> None:
        self.window.update_idletasks()
        width, height = self.window.winfo_width(), self.window.winfo_height()
        x = (self.window.winfo_screenwidth() // 2) - (width // 2)
        y = (self.window.winfo_screenheight() // 2) - (height // 2)
        self.window.geometry(f'+{x}+{y}')
   
    def _load_config(self) -> None:
        self.config = configparser.ConfigParser()
        if os.path.exists(self.config_path):
            self.config.read(self.config_path, encoding='utf-8')
            
            for section in self.config.sections():
                if section in {'程序信息', '界面设置'}:
                    continue
                self.tools[section] = []
                for name, value in self.config.items(section):
                    parts = value.split('|')
                    self.tools[section].append({
                        'name': name,
                        'command': parts[0].strip(),
                        'icon_path': parts[1].strip() if len(parts) > 1 else None
                    })


    def _save_config(self) -> None:
        try:
            with open(self.config_path, 'w', encoding='utf-8') as f:
                self.config.write(f)
            if self.refresh_callback:
                self.refresh_callback()
        except Exception as e:
            messagebox.showerror("错误", f"保存配置文件失败: {str(e)}", parent=self.window)
   
    def _create_ui(self) -> None:
        top_frame = ttk.Frame(self.window, padding=10)
        top_frame.pack(fill=tk.X)
        top_frame.columnconfigure(0, weight=1)
        top_frame.columnconfigure(1, weight=1)
        
        ttk.Button(top_frame, text="添加工具", command=self._show_add_tool_panel, style='primary.TButton').grid(row=0, column=0, padx=5, sticky='ew')
        ttk.Button(top_frame, text="管理分类", command=self._show_manage_category_panel, style='primary.TButton').grid(row=0, column=1, padx=5, sticky='ew')
        
        self.main_panel = ttk.Frame(self.window, padding=(10, 10, 10, 10))
        self.main_panel.pack(fill=tk.BOTH, expand=True)
        self._show_add_tool_panel()


    def _show_add_tool_panel(self) -> None:
        self._clear_main_panel()
        main_frame = ttk.Frame(self.main_panel)
        main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
        
        ttk.Label(main_frame, text="选择分类:").grid(row=0, column=0, sticky='w', pady=5)
        categories = [cat for cat in self.tools.keys() if cat not in ['程序信息', '界面设置']]
        self.cat_combobox = ttk.Combobox(main_frame, values=categories, state="readonly")
        self.cat_combobox.grid(row=0, column=1, padx=5, pady=5, sticky='ew')
        if categories:
            self.cat_combobox.current(0)
        
        ttk.Label(main_frame, text="工具名称:").grid(row=1, column=0, sticky='w', pady=5)
        self.tool_name_entry = ttk.Entry(main_frame)
        self.tool_name_entry.grid(row=1, column=1, padx=5, pady=5, sticky='ew')
        
        ttk.Label(main_frame, text="命令/URL:").grid(row=2, column=0, sticky='w', pady=5)
        self.cmd_entry = ttk.Entry(main_frame)
        self.cmd_entry.grid(row=2, column=1, padx=5, pady=5, sticky='ew')
        
        btn_frame = ttk.Frame(main_frame)
        btn_frame.grid(row=3, column=0, columnspan=2, pady=15, sticky='ew')
        btn_frame.columnconfigure(0, weight=1)
        btn_frame.columnconfigure(1, weight=1)
        
        ttk.Button(btn_frame, text="选择文件", command=self._browse_file).grid(row=0, column=0, padx=5, sticky='ew')
        ttk.Button(btn_frame, text="确认", command=self._add_tool, style='danger.TButton').grid(row=0, column=1, padx=5, sticky='ew')
        main_frame.columnconfigure(1, weight=1)


    def _show_manage_category_panel(self) -> None:
        self._clear_main_panel()
        main_frame = ttk.Frame(self.main_panel)
        main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
        
        add_frame = ttk.LabelFrame(main_frame, text="添加新分类", padding=10)
        add_frame.grid(row=0, column=0, padx=5, pady=5, sticky='ew')
        ttk.Label(add_frame, text="新分类名称:").pack(side=tk.LEFT)
        self.new_cat_entry = ttk.Entry(add_frame)
        self.new_cat_entry.pack(side=tk.LEFT, padx=5, fill=tk.X, expand=True)
        ttk.Button(add_frame, text="添加分类", command=self._add_category, style='success.TButton').pack(side=tk.LEFT, padx=5)
        
        del_frame = ttk.LabelFrame(main_frame, text="删除分类", padding=10)
        del_frame.grid(row=1, column=0, padx=5, pady=5, sticky='ew')
        ttk.Label(del_frame, text="选择分类:").pack(side=tk.LEFT)
        categories = [cat for cat in self.tools.keys() if cat not in ['程序信息', '界面设置']]
        self.del_category_combobox = ttk.Combobox(del_frame, values=categories, state="readonly")
        self.del_category_combobox.pack(side=tk.LEFT, padx=5, fill=tk.X, expand=True)
        if categories:
            self.del_category_combobox.current(0)
        ttk.Button(del_frame, text="删除分类", command=self._delete_category, style='danger.TButton').pack(side=tk.LEFT, padx=5)
        
        main_frame.columnconfigure(0, weight=1)
   
    def _clear_main_panel(self) -> None:
        for widget in self.main_panel.winfo_children():
            widget.destroy()
   
    def _refresh_data(self) -> None:
        categories = [cat for cat in self.tools.keys() if cat not in ['程序信息', '界面设置']]
        if hasattr(self, 'cat_combobox') and self.cat_combobox.winfo_exists():
            self.cat_combobox['values'] = categories
            if categories:
                self.cat_combobox.current(0)
        
        if hasattr(self, 'del_category_combobox') and self.del_category_combobox.winfo_exists():
            self.del_category_combobox['values'] = categories
            if categories:
                self.del_category_combobox.current(0)
   
    def _browse_file(self) -> None:
        self.window.attributes('-disabled', True)
        file_path = filedialog.askopenfilename(
            title="选择可执行文件",
            filetypes=[("可执行文件", "*.exe"), ("批处理文件", "*.bat *.cmd"), ("所有文件", "*.*")]
        )
        self.window.attributes('-disabled', False)
        self.window.focus_force()
        self.window.lift()
        
        if file_path:
            self.cmd_entry.delete(0, tk.END)
            self.cmd_entry.insert(0, file_path)
            file_name = os.path.basename(file_path)
            tool_name = os.path.splitext(file_name)[0]
            self.tool_name_entry.delete(0, tk.END)
            self.tool_name_entry.insert(0, tool_name)
   
    def _add_tool(self) -> None:
        category = self.cat_combobox.get()
        name = self.tool_name_entry.get().strip()
        cmd = self.cmd_entry.get().strip()
        
        if not category or not name or not cmd:
            messagebox.showwarning("警告", "请填写完整信息", parent=self.window)
            return
        
        if category not in self.tools:
            self.tools[category] = []
        
        self.tools[category].append({'name': name, 'command': cmd, 'icon_path': None})
        
        if not self.config.has_section(category):
            self.config.add_section(category)
        
        self.config.set(category, name, cmd)
        self._save_config()
        self.tool_name_entry.delete(0, tk.END)
        self.cmd_entry.delete(0, tk.END)
        messagebox.showinfo("成功", "工具添加成功", parent=self.window)
   
    def _add_category(self) -> None:
        new_cat = self.new_cat_entry.get().strip()
        
        if not new_cat or new_cat in ['程序信息', '界面设置']:
            messagebox.showwarning("警告", "无效的分类名称", parent=self.window)
            return
            
        if new_cat in self.tools:
            messagebox.showwarning("警告", "分类已存在", parent=self.window)
            return
        
        self.tools[new_cat] = []
        self.config.add_section(new_cat)
        self._save_config()
        self.new_cat_entry.delete(0, tk.END)
        self._refresh_data()
        messagebox.showinfo("成功", "分类添加成功", parent=self.window)
   
    def _delete_category(self) -> None:
        category = self.del_category_combobox.get()
        
        if not category or category in ['程序信息', '界面设置']:
            messagebox.showwarning("警告", "无效的分类", parent=self.window)
            return
            
        if not messagebox.askyesno("确认删除", f"确定要删除分类 '{category}' 及其所有工具吗?", parent=self.window):
            return
        
        if category in self.tools:
            del self.tools[category]
        
        if self.config.has_section(category):
            self.config.remove_section(category)
        
        self._save_config()
        self._clear_main_panel()
        self._show_manage_category_panel()
        messagebox.showinfo("成功", "分类删除成功", parent=self.window)




def main():
    if not is_admin():
        ctypes.windll.shell32.ShellExecuteW(None, "runas", sys.executable, " ".join(sys.argv), None, 1)
        sys.exit()
   
    root = tk.Tk()
   
    if sys.platform == 'win32':
        ctypes.windll.shcore.SetProcessDpiAwareness(1)
   
    try:
        root.attributes('-alpha', 0.0)
        app = ToolboxApp(root)
        root.after(100, lambda: root.attributes('-alpha', 1.0))
        root.mainloop()
    except Exception as e:
        logger.critical(f"程序崩溃: {str(e)}")


if __name__ == "__main__":
    main()

回复

使用道具 举报

48#
发表于 前天 12:32 | 只看该作者
刚刚下载了0606_3.0编译
Windows11 系统运行目前正常

点评

怎么不用楼上0609的代码。一直在完善。处理BUG呢。  详情 回复 发表于 前天 13:00
回复

使用道具 举报

49#
 楼主| 发表于 前天 13:00 | 只看该作者
进士小站 发表于 2025-6-9 12:32
刚刚下载了0606_3.0编译
Windows11 系统运行目前正常

怎么不用楼上0609的代码。一直在完善。处理BUG呢。
回复

使用道具 举报

50#
发表于 前天 14:54 | 只看该作者
本帖最后由 陌小寞 于 2025-6-9 15:08 编辑

感谢更新
回复

使用道具 举报

51#
发表于 前天 15:07 | 只看该作者

感谢楼主无私分享!
回复

使用道具 举报

52#
发表于 前天 15:12 | 只看该作者
能不能增加一个工具的位置顺序调整功能,还有就是,工具前面的图标也没有添加方法。

点评

更新了,按钮位置可以调整了 http://bbs.wuyou.net/forum.php?mod=viewthread&tid=446396&extra=  详情 回复 发表于 昨天 12:28
工具按钮右键菜单,添加删除图标。  详情 回复 发表于 前天 16:28
回复

使用道具 举报

53#
发表于 前天 15:19 | 只看该作者
分类下面也可以增加几个小分类。
回复

使用道具 举报

54#
 楼主| 发表于 前天 16:27 | 只看该作者
本帖最后由 piaomusic 于 2025-6-9 17:27 编辑

占楼
回复

使用道具 举报

55#
 楼主| 发表于 前天 16:28 | 只看该作者
本帖最后由 piaomusic 于 2025-6-9 16:48 编辑
zhonghua20 发表于 2025-6-9 15:12
能不能增加一个工具的位置顺序调整功能,还有就是,工具前面的图标也没有添加方法。

工具按钮右键菜单,添加删除图标。       按钮位置调整功能再等等。
回复

使用道具 举报

56#
发表于 前天 20:24 | 只看该作者
谢谢楼主分享
回复

使用道具 举报

57#
发表于 昨天 00:12 | 只看该作者
多谢多谢
回复

使用道具 举报

58#
发表于 昨天 00:27 | 只看该作者
谢谢制作分享!
回复

使用道具 举报

59#
发表于 昨天 01:11 | 只看该作者
谢谢分享
回复

使用道具 举报

60#
发表于 昨天 03:48 | 只看该作者
感谢分享。
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

小黑屋|手机版|Archiver|捐助支持|无忧启动 ( 闽ICP备05002490号-1 )

闽公网安备 35020302032614号

GMT+8, 2025-6-11 05:05

Powered by Discuz! X3.3

© 2001-2017 Comsenz Inc.

快速回复 返回顶部 返回列表