中文 | English
Windows 默认应用 锁定 工具 —— 只需配置一次文件后缀关联,后台 Windows 服务会持续地把它写回去,让其他安装程序 / Windows 更新 / 各种应用再也偷不走你的默认打开方式。
GUI 配置器 + 后台 Windows 服务,使用 Python 编写,可打包成独立 EXE。
致谢。 本项目是在 3089464667/default-app 的基础上重写并扩展而来。
assoc/ftype+ 直接写注册表 +UserChoice覆盖三层叠加的核心思路,源自该项目。
- 把任意文件后缀锁定到指定应用
- 后台 Windows 服务按可配置的间隔(默认 300 秒)反复写入规则
- 现代化 CustomTkinter 界面,集成系统托盘
- 内置预设分组(常见图片、全部图片、常见视频、全部视频、常见文本、常见文档)
- 两级优先级体系:单条规则可覆盖分组规则
- 支持拖拽排序规则
- 国际化:简体中文 + 英文
- PyInstaller 单 EXE 打包,可选 Inno Setup / NSIS 安装包
Windows 提供了若干种、大多没有正式文档且互相重叠的机制,用来决定某个后缀由哪个应用打开。任何一种都可能被另一种覆盖,且 Windows 自己还会周期性地重写它们。要让一个默认关联真正 生效并保持,本项目把以下所有手段叠加使用,再用一个 Windows 服务按定时器周期性重新写入。
核心实现位于 defaultapplocker/core/locker.py。
对每条规则我们都会调用经典的 Windows 命令:
assoc .jpg=JPGFile
ftype JPGFile="C:\Windows\System32\mspaint.exe" "%1"assoc 把 文件后缀 映射到一个 ProgID(逻辑处理器名),ftype 把 ProgID 映射到具体的命令行。这是历史最久、最被广泛尊重的机制 —— 很多命令行工具和老式应用会查询它。
源码:defaultapplocker/core/locker.py 中的 set_assoc() 与 set_ftype()。
assoc / ftype 只覆盖了注册表的一部分。我们再用 winreg 直接写入:
HKCR\.<ext>→ ProgIDHKCR\<ProgID>\shell\open\command→"<app>" "%1"
资源管理器的"打开方式"对话框、以及大多数现代 Win32 应用,在用户没有显式选择时都会读这里。
源码:defaultapplocker/core/locker.py 中的 set_registry_assoc()。
现代 Windows(8+)允许每个用户在以下位置把后缀绑定到一个应用:
HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\.<ext>\UserChoice
ProgId = <ProgID>
Hash = <SHA-256 派生值>
只要这里有 UserChoice 项,它就会覆盖该用户在 HKCR 下的一切。为了不让第三方应用通过这个键偷走默认值,我们自己写入 UserChoice,Hash 由 ProgID + 用户名 + 时间戳派生。
说明:微软在 Win10/11 中使用了一个未公开的专有算法生成
UserChoice.Hash用于防篡改。我们写入的是尽力而为的 SHA-256 标记 —— 它能满足"只检查 UserChoice 是否存在"的程序,但 Windows 设置下次仍可能弹出"你要如何打开此文件?"。后台服务(第 4 层)通过立即把它写回去来弥补。
源码:defaultapplocker/core/locker.py 中的 set_user_choice()。
即使前面三层都做了,Windows 更新、MSI 安装程序以及"把我设为默认"提示仍能在任何时候改写注册表。本项目附带一个真正的 Windows 服务(DefaultAppLockerService),它会:
- 从
C:\ProgramData\defaultapp-locker\rules.json读取规则 - 对每条已启用的规则,通过
verify_rule()检查当前关联 - 如果当前处理器与期望不一致,重新执行上面四层的写入
- 休眠
checkInterval秒(默认 300)后继续
服务基于 pywin32(win32serviceutil.ServiceFramework)实现,会出现在 services.msc 中,可设为开机自启,注销后仍在运行,以 LocalSystem 身份执行。工作循环跑在守护线程里,用 Win32 事件句柄发出停止信号以保证干净停机。
源码:defaultapplocker/service/locker_service.py。
| 层 | 谁来写 | 谁会读 | 谁能击败它 |
|---|---|---|---|
assoc / ftype |
cmd.exe 内建 |
老式 / CLI 程序 | 重新跑 assoc 或改注册表 |
直接写 HKCR |
winreg |
资源管理器、多数 Win32 程序 | UserChoice、其它安装程序 |
UserChoice(HKCU) |
winreg |
现代 Windows Shell | Windows 设置、微软算法 |
| 后台服务 | pywin32 服务 | 不读 —— 它 重写 一切 | 停掉/卸载这个服务 |
四层叠加,基本覆盖了 Windows 10/11 上读写默认关联的所有路径。
+--------------------+ +-----------------------+
| GUI (gui.py) | 编辑 | rules.json |
| CustomTkinter +---------> C:\ProgramData\ |
| + 系统托盘 | | defaultapp-locker\ |
+--------+-----------+ +-----------+-----------+
| |
| 安装 / 启动 / 停止 服务 | 每隔
| | checkInterval 秒读一次
v v
+--------+-----------+ +-----------+-----------+
| 服务控制 | pywin32 | Windows 服务: |
| (win32serviceutil)+---------> DefaultAppLocker |
+--------------------+ | Service |
+-----------+-----------+
|
| 对每条规则
v
+-----------------+----------------+
| 第 1 层: assoc / ftype |
| 第 2 层: HKCR 直接写 |
| 第 3 层: HKCU UserChoice + Hash |
+----------------------------------+
gui.py—— 配置器 GUI 入口service.py—— Windows 服务入口defaultapplocker/core/locker.py—— 四层锁定原语defaultapplocker/core/config.py—— 规则 JSON 的加载/保存/排序defaultapplocker/core/i18n.py—— 中英双语字符串表defaultapplocker/service/locker_service.py——ServiceFramework子类 + 工作循环defaultapplocker/gui/main_window.py—— 主窗口defaultapplocker/gui/tray.py—— 系统托盘图标(pystray)
| 关注点 | 选型 |
|---|---|
| 语言 | Python 3.8+ |
| GUI 工具包 | customtkinter(现代化 Tk) |
| 系统托盘 | pystray + Pillow |
| Win32 / 服务 | pywin32 |
| 注册表 | winreg(标准库) |
| 命令层 | subprocess.run(带 shell=True,用于 assoc / ftype) |
| Hash | hashlib.sha256(UserChoice 标记) |
| 打包 | PyInstaller(scripts/ 下的 .spec) |
| 安装包 | Inno Setup(首选)+ NSIS(备选) |
从发布页下载 DefaultAppLocker_Setup.exe(或自行打包,见从源码构建)。以管理员身份运行,按向导安装即可,然后从开始菜单启动 Default App Locker。
- Windows 10 或 Windows 11
- 管理员权限(写注册表与安装服务都需要)
所有打包脚本都在 scripts/ 下,文件名全部使用英文。完整流程见 docs/BUILD.md,安装包流程见 docs/INSTALLER.md。
快速上手(项目根目录下执行):
pip install -r requirements.txt
scripts\build.bat会产出:
output\
DefaultAppLocker.exe (GUI)
DefaultAppLockerService.exe (Windows 服务)
如果要 Windows 安装包(需要 Inno Setup 6):
scripts\build_installer.bat不打包直接从源码运行:
python gui.py REM 启动 GUI
python service.py install REM 安装服务(需要管理员)
python service.py start或使用辅助脚本:
scripts\start_gui.bat规则保存在:
C:\ProgramData\defaultapp-locker\rules.json
示例:
{
"rules": [
{
"name": "Common Images",
"ext": ["jpg", "jpeg", "png", "gif", "bmp"],
"app": "C:\\Windows\\System32\\mspaint.exe",
"priority": 1000,
"isGroup": true,
"enabled": true
},
{
"name": "PNG override",
"ext": ["png"],
"app": "C:\\Program Files\\IrfanView\\i_view64.exe",
"priority": 100,
"isGroup": false,
"enabled": true
}
],
"enabled": true,
"checkInterval": 300
}顶层字段:
| 键 | 类型 | 说明 |
|---|---|---|
rules |
数组 | 有序的规则列表 |
enabled |
布尔 | 总开关 —— 为 false 时服务空转 |
checkInterval |
整数 | 两次重写之间的秒数(默认 300) |
规则字段:
| 键 | 类型 | 说明 |
|---|---|---|
name |
字符串 | 显示名 |
ext |
字符串数组 | 后缀(带不带前导点都行) |
app |
字符串 | 目标 .exe 的绝对路径 |
priority |
整数 | 数值越小优先级越高,详见下文 |
isGroup |
布尔 | true 为预设/分组规则,false 为单条覆盖 |
enabled |
布尔 | 单条规则的开关 |
priority 数值越小,优先级越高(按升序排)。
| 区间 | 类型 | 说明 |
|---|---|---|
| 1 – 999 | 单条规则 | 用于覆盖分组规则 |
| 1000+ | 分组规则 | 预设分类或用户分组 |
按 priority 升序排序后依次写入,于是 最后 命中某后缀的规则胜出。实际效果是:单条规则(小数值)先写、分组规则(大数值)后覆盖 —— 但分组规则覆盖范围广,所以 重要的 单后缀覆盖应放进高优先级(大数值)区间,让它能再覆盖回来。GUI 用两个独立的数值范围来强制这个约定。
(参见 defaultapplocker/core/config.py 中的 get_sorted_rules()。)
开箱即用的预设分组(见 defaultapplocker/core/config.py 中的 DEFAULT_CONFIG):
- 常见图片 ——
jpg, jpeg, png, gif, bmp - 全部图片 ——
webp, svg, ico, raw, psd, heic, tiff, tif - 常见视频 ——
mp4, avi, mkv, mov, wmv, flv - 全部视频 ——
webm, m4v, 3gp, ts, m2ts, rmvb - 常见文本 ——
txt, md, json, xml, log, ini, cfg - 常见文档 ——
doc, docx, xls, xlsx, ppt, pptx, pdf
默认只启用 "常见图片" 一组,目标程序为 mspaint.exe。可在 GUI 中编辑。
defaultapp_locker/
+-- gui.py # GUI 入口
+-- service.py # 服务入口
+-- fix_default_app.py # 参考用一次性修复脚本(见鸣谢)
+-- requirements.txt
+-- README.md # 本文件(中文)
+-- README_en.md # English
+-- LICENSE # Apache-2.0
+-- defaultapplocker/ # 主包
| +-- core/
| | +-- locker.py # 四层锁定原语
| | +-- config.py # JSON 配置 + 排序
| | +-- i18n.py # 中英双语字符串
| +-- service/
| | +-- locker_service.py # ServiceFramework + 工作循环
| +-- gui/
| +-- main_window.py
| +-- tray.py
+-- scripts/ # 所有打包辅助脚本(文件名全英文)
| +-- build.bat # 主打包(PyInstaller,两个 EXE,复制到 output\)
| +-- build_quick.bat # 一行 pyinstaller,无 spec
| +-- build_simple.bat # 旧版双二进制打包
| +-- build_installer.bat # Inno Setup 安装包一键生成
| +-- build_gui.spec # GUI 的 PyInstaller spec
| +-- build_service.spec # 服务的 PyInstaller spec
| +-- build.spec # 旧版合并 spec
| +-- fix_default_app.spec # 一次性修复工具的 spec
| +-- setup_inno.iss # Inno Setup 脚本
| +-- setup_script.nsi # NSIS 备选脚本
| +-- start_gui.bat # 从源码运行 GUI
| +-- USAGE.txt # 终端用户使用说明(打包进安装包)
+-- docs/
| +-- BUILD.md # 完整打包指南
| +-- INSTALLER.md # 安装包指南
| +-- ...
+-- resources/ # 图标 / 图标生成器
+-- icons/, imgs/, default-app/ # 图片素材与参考资料
+-- output/ # 由 build.bat 生成(已 gitignore)
+-- dist/ # PyInstaller 原始产物
Apache License 2.0 —— 详见 LICENSE。
- 原始思路与核心注册表锁定技术: 3089464667/default-app —— 本项目是该工作的结构化重写,扩展了 GUI、系统托盘、国际化、预设分组、 拖拽排序、带重写循环的真正 PyWin32 服务,以及一键 PyInstaller / Inno Setup 打包。
customtkinter—— Tom Schimanskypystray—— Moses Palmérpywin32—— Mark Hammond 等