【开源自荐】Fluent Pass:给 KeePass 装个插件就可以获得 Win11 风格 UI

分享一下自己最近撸的一个小玩具:Fluent Pass,亮点是它是官方客户端的一个插件,而非一个第三方客户端。

目前已经上了 KeePass 官网的社区插件库: Plugins - KeePass

作为 KeePass 死忠,已经用了十多年了,期间也有很多其他同类产品,但是它插件生态足够丰富,也足够安全,也有一定的路径依赖的成分,所以我一直在用它,搭配 WebDAV 可是说是很方便了。

但官方原版的 UI 可以说是很复古了,第三方的 KeePassXC 用起来也不是很爽

于是我就花了一天撸了个 UI 出来,首先我不想重新撸一个客户端,尽可能复用现成客户端的能力,能少花不少重复造轮子的工夫,所以技术选型就定在了官方客户端插件上面。

仅仅一个 .dll,寄生在官方客户端的进程内,没有额外进程,没有任何遥测数据采集,安全性方面也有保障。

上图

典型的三栏布局结构,UI 库用的是 WPF-UI,纯正的 Windows 11 Fluent Design 味道,支持 Mica 风格、深浅跟随系统主题。

:package: 安装方式

irm https://raw.githubusercontent.com/YoungSx/fluentpass/main/scripts/install.ps1 | iex

或者手动安装: GitHub Releases 下载编译好的 FluentPass.dll 放进 KeePass 的 Plugins 目录,重启 KeePass。

项目地址: GitHub - YoungSx/fluentpass: A modern Windows 11 Fluent Design (Mica / Acrylic) desktop interface plugin crafted for KeePass 2.x · GitHub

:technologist: 关于项目

MIT License 开源,开发流程全透明。

我比较在意的是尽量不要重复造轮子,能用插件实现的就不要重新写一个完整客户端,我们就纯粹是一个 UI,快捷键基本也都委托给原生 Keepass 客户端了。

欢迎 KeePass 同好试试,欢迎反馈建议和 Bug。

感谢!

3 个赞

看看

试试试试

看了 README,原版窗口和新 UI 可以同时打开。想问下,新 UI 里正在编辑一个条目、还没保存时,如果原版窗口或 WebDAV 同步更新了同一个条目,这边会怎么处理?会提示冲突,还是等当前编辑保存后再同步?

让 AI 仔细分析了以下这块的实现逻辑:

核心结论

不会弹窗提示冲突,也不会丢失正在编辑的草稿。

当原版窗口或 WebDAV 同步更新了正在编辑的条目时,新 UI 会保留用户当前的编辑草稿与编辑状态,不会被外部变动覆盖;当用户在当前新 UI 点击“保存”(或通过快捷键保存)时,系统会先将底层最新数据自动备份到条目的历史版本中,再将当前编辑内容写入条目并同步通知 KeePass 宿主。


具体处理流程与代码依据

1. 架构前提:同进程共享内存(零 IPC)

根据 ARCHITECTURE.md铁律 1 和铁律 3:FluentPass 与 KeePass 原版运行在同一个 STA 线程中,直接引用内存中的同一个 PwDatabase 和 PwEntry 指针。WebDAV 的网络拉取与冲突合并 100% 由 KeePass 官方内核(KeePassLib)完成。

2. 事件监听与节流刷新

在 FluentWindowManager.cs中,插件监听了宿主主窗口的 FileOpened、FileClosed、UIStateUpdated 和 FileSaved 事件:

_host.MainWindow.FileOpened += OnHostFileOpened;
_host.MainWindow.FileClosed += OnHostFileClosed;
_host.MainWindow.UIStateUpdated += OnHostUIStateUpdated;
_host.MainWindow.FileSaved += OnHostFileSaved;

当原版窗口修改条目或 WebDAV 同步完成时,KeePass 原生会抛出 UIStateUpdated,并通过 50ms 节流计时器调用 MainViewModel.RefreshDatabaseState()。

3. 编辑态守卫:保护未保存草稿

在 MainViewModel.SyncCurrentGroupEntries()中,刷新右侧详情面板的代码明确加入了编辑状态检查:

// 右侧详情面板数据同步
if (CurrentDetail != null)
{
    bool stillExists = db != null && db.RootGroup != null && db.RootGroup.FindEntry(CurrentDetail.Model.Uuid, true) != null;
    if (!stillExists)
    {
        CurrentDetail.HidePassword();
        CurrentDetail = null;
        SelectedEntry = null;
    }
    else if (!CurrentDetail.IsEditing) // 关键守卫:仅在非编辑状态才重载数据
    {
        CurrentDetail.RefreshData();
    }
}
  • 当处于编辑状态(CurrentDetail.IsEditing == true)时:CurrentDetail.RefreshData() 会被直接跳过,用户在文本框中键入的临时草稿(EditTitle、EditUserName、EditPassword、EditUrl 等)继续保留,不会被清空或被底层内存覆盖,也不会有任何中断操作的冲突弹窗。
  • (注:如果原版或 WebDAV 将该条目从数据库彻底删除了,stillExists 为 false,此时才会解绑并清空详情面板)。

4. 用户保存当前编辑时的处理机制

当用户在编辑卡片点击“保存”按钮(调用 EntryDetailViewModel.SaveEditInternal)或按 Ctrl+S 全局保存(调用 MainViewModel.SaveDatabase)时:

  1. 自动创建历史备份:调用 Model.CreateBackup(_host.Database)。此时底层 Model 已经包含了外部或原版窗口刚刚更新的数据,因此外部更新的状态会被完整保存到条目的**历史版本(History)**中,防止数据意外丢失。
  2. 覆写并标记更新:将新 UI 编辑框中的字段覆写到 Model,调用 Model.Touch(true, false) 更新时间戳,并将 _host.Database.Modified = true。
  3. 通知宿主全双工刷新:调用 _host.MainWindow.UpdateUI(...) 通知原版 KeePass 主窗口同步更新,随后新 UI 退出编辑状态(IsEditing = false)并刷新展示。

测试用例依据

在项目的自动化集成测试中,已有专门的用例验证了这一行为:
详见 ExternalSyncTests.cs:L256-L283 的 ExternalChange_WhileEditingDetail_DoesNotOverwriteUnsavedDraft():

[Test]
public void ExternalChange_WhileEditingDetail_DoesNotOverwriteUnsavedDraft()
{
    // ...
    // Start editing in FluentPass UI
    _mainVm.CurrentDetail!.BeginEdit();
    _mainVm.CurrentDetail.EditTitle = "User Is Typing Something New";
    _mainVm.CurrentDetail.EditUrl = "https://custom-typing.com";
    Assert.That(_mainVm.CurrentDetail.IsEditing, Is.True);

    // Native KeePass changes background data
    entry.Strings.Set(PwDefs.NotesField, new ProtectedString(false, "New notes from native"));

    // Background event triggers RefreshDatabaseState()
    _mainVm.RefreshDatabaseState();

    // Should remain in editing mode without destroying the user's active inputs
    Assert.That(_mainVm.CurrentDetail.IsEditing, Is.True);
    Assert.That(_mainVm.CurrentDetail.EditTitle, Is.EqualTo("User Is Typing Something New"));
    Assert.That(_mainVm.CurrentDetail.EditUrl, Is.EqualTo("https://custom-typing.com"));
}
2 个赞

谢谢,机制清楚了。按这个说明,外部更新会留在历史里,但保存后当前条目仍可能被旧草稿覆盖。你贴的测试验证了“刷新不打断编辑”,建议再补上保存后的断言:新 UI 只改标题、原版改备注,保存后备注究竟保留哪一版,历史里能否恢复另一版。若目前是整份草稿覆写,保存前提示一下“编辑期间条目已被外部修改”会更安心。

1 个赞

这下颜值跟上了

插件形态这点很讨喜,不用换客户端就能换个 Win11 风格 UI。KeePass 老用户了,晚点去官网插件库试试。

2 个赞

插件依赖 KeePass 官方接口,万一未来版本更新导致兼容性问题怎么办?还有,UI 调整会不会影响原有快捷键逻辑?

兼容性:

  1. 目前来看 Keepass 2.x 不会有太大的破坏性更新,而破坏性更新 Keepass 官方会以版本区分,比如 Keepass 官网目前插件会标注适用于 2.x/1.x
  2. 任何插件都会存在这个问题,但是由于我自己也重度使用,所以我也会一直更新
  3. 本插件的 UI 是可以与原生 UI 共存的,并没有暴力替换原生 UI,可以唤出原生 UI 兜底

快捷键:插件的设计哲学是尽量轻薄,除极少数必要,大部分快捷键都没有重新注册,而是继续由 Keepass 主程序接管

很优秀的项目,收藏起来

插件不用换客户端这点确实省事,我平时也是 KeePass 加 WebDAV 这样用。就是编辑的时候原版那边要是同时改了同一条,保存后会不会把另一边的改动盖掉,这个我还挺在意的。

已经用 bitwarden 快六七年了,之前一直用的 keepass,印象里还只是本地管理,不能浏览器插件,也没有云服务

Keepass 你说的这些一直都有啊,不过他官方不做,都是靠强大的社区插件,浏览器填充插件很多,云同步支持 WebDAV、FTP、网盘等,我一直用坚果云的WebDAV同步(不依赖坚果云客户端)

1 个赞

可以看一下前面的回复,有朋友问过类似问题