- 桌面应用
- UI组件
【免费下载链接】FlatLaf
FlatLaf - Swing Look and Feel (with Darcula/IntelliJ themes support)
FlatLaf 是一个为 Swing 提供扁平化外观的 Look and Feel 库,其部分高级特性(如 Windows 原生窗口边框、Linux 窗口移动与系统菜单)依赖操作系统原生 API。为了在 Java 中先行验证这些 API 的调用方式,FlatLaf 仓库中保留了一个专门的研发子项目——flatlaf-natives/flatlaf-natives-jna,它通过 JNA(Java Native Access)直接访问 Windows 与 Linux 的系统级接口。阅读本文,你将理解这个"研发原型"子项目的定位、核心源码实现、它与最终 C++ 原生库之间的演进关系,以及如何在开发环境中构建与使用它来验证原生 API 行为。
子项目定位:为什么 FlatLaf 需要一个"JNA 原型库"
在 flatlaf-natives-jna/README.md 中,项目作者对子项目做了三点明确说明:
- 该子项目包含使用 JNA 访问原生操作系统 API的源代码;
- 关键约束:这里的代码并未被 FlatLaf 正式库使用(
Code in this sub-project is not used in FlatLaf libraries); - 它的实际用途是研发与测试——在 Java 中借助 JNA 验证某些原生操作系统 API 的用法,验证通过后再将其转换为 C++ 实现,进入正式的
flatlaf-natives-*原生库。
也就是说,flatlaf-natives-jna是 FlatLaf 原生能力研发流程中的"探路者":先用 JNA 快速验证 API 参数、结构体布局与消息处理逻辑,再以性能与部署更优的 C++/JNI 方式落地。理解这一点,是阅读本子项目全部代码的前提——它的价值在于研究参考,而不是直接依赖。
从仓库结构看,settings.gradle.kts 将四个原生子项目并列纳入构建:flatlaf-natives-windows、flatlaf-natives-macos、flatlaf-natives-linux与flatlaf-natives-jna;而 flatlaf-natives/README.md 明确将flatlaf-natives-jna标注为 "for development only"(仅用于开发),进一步印证了其研发原型的定位。
项目结构与构建配置
子项目目录结构非常精简,仅包含两个平台下的核心原型类:
flatlaf-natives/flatlaf-natives-jna/ ├── README.md ├── build.gradle.kts └── src/main/java/com/formdev/flatlaf/natives/jna/ ├── linux/X11WmUtils.java └── windows/FlatWindowsNativeWindowBorder.java其构建脚本 build.gradle.kts 声明了两个依赖:
implementation( project( ":flatlaf-core" ) ):依赖 FlatLaf 核心模块,以便复用SystemInfo、UIScale等工具类以及FlatNativeWindowBorder.Provider接口;implementation( libs.jna )与implementation( libs.jna.platform ):JNA 及其平台封装库。版本统一由 gradle/libs.versions.toml 管理,当前为JNA 5.19.1。
其中jna-platform提供了User32、GDI32、Shell32、X11、Advapi32Util等现成的 Win32/X11 声明,大幅降低了手写原生函数签名的成本——这正是原型验证阶段选择 JNA 的核心原因之一。
Windows 原型:FlatWindowsNativeWindowBorder 的窗口过程接管
Windows 侧的原型类是 FlatWindowsNativeWindowBorder.java,它实现了FlatNativeWindowBorder.Provider接口(该接口定义于 FlatNativeWindowBorder.java)。其目标是:在应用使用自定义装饰时,隐藏 Windows 10 标题栏(含最小化/最大化/关闭按钮),但保留缩放边框与投影,且不影响系统的窗口对齐(Snap)功能。
Provider 接口与生命周期管理
FlatWindowsNativeWindowBorder以单例方式提供(getInstance()),内部用IdentityHashMap<Window, WndProc>维护"窗口 → 自定义窗口过程"的映射,核心生命周期如下:
setHasCustomDecoration(window, true)触发install(window):仅当运行于 Windows 10 及以上、窗口是JFrame/JDialog且不是 undecorated时才安装;setHasCustomDecoration(window, false)触发uninstall(window):移除自定义窗口过程并恢复原生标题栏;updateTitleBarInfo(...):由 Swing 侧告知标题栏高度、应用图标与三个窗口按钮(最小化/最大化/关闭)的点击区域矩形,供命中测试使用;showWindow(window, cmd):包装User32.INSTANCE.ShowWindow,命令常量(SW_MAXIMIZE=3、SW_MINIMIZE=6、SW_RESTORE=9)与 Win32 API 保持一致。
WndProc:替换窗口过程
内部类WndProc implements WindowProc是原型的技术核心。它在构造时:
- 通过
Native.getComponentPointer(window)取得窗口的HWND; - 按架构区分调用
SetWindowLongPtr(x86_64)或SetWindowLong(x86)替换窗口过程(GWLP_WNDPROC = -4),并保存原始过程defaultWndProc; - 调用
updateFrame()通过SetWindowPos(... SWP_FRAMECHANGED ...)触发WM_NCCALCSIZE,从而移除系统标题栏; - 根据
window.getBackground()用GDI32Ex.CreateSolidBrush创建背景画刷,避免窗口缩放/移动时背景闪烁。
原型类中定义了完整的 Windows 消息常量,包括:
WM_NCCALCSIZE = 0x0083 WM_NCHITTEST = 0x0084 WM_ENTERSIZEMOVE = 0x0231 WM_EXITSIZEMOVE = 0x0232 WM_DPICHANGED = 0x02E0 WM_DWMCOLORIZATIONCOLORCHANGED = 0x0320 HTCLIENT=1 HTCAPTION=2 HTSYSMENU=3 HTMINBUTTON=8 HTMAXBUTTON=9 HTTOP=12 HTCLOSE=20WM_NCCALCSIZE:隐藏标题栏与修正客户区
WmNcCalcSize处理流程(与 Microsoft Terminal 的NonClientIslandWindow::_OnNcCalcSize()思路一致):
- 先保存默认窗口过程计算前的原始
rgrc[0].top; - 调用默认窗口过程应用系统默认边框;
- 把顶部恢复为原始值,即让客户区顶到窗口顶部,实现标题栏隐藏;
- 若窗口处于最大化且非全屏状态:将顶部加上缩放手柄高度(
SM_CXPADDEDBORDER + SM_CYSIZEFRAME),并处理任务栏自动隐藏(ABM_GETAUTOHIDEBAREX)情况下的边距修正; - 在 Windows 11 上,通过
DwmGetWindowAttribute(hwnd, DWMWA_VISIBLE_FRAME_BORDER_THICKNESS=37, ...)获取可见边框厚度并加到顶部,避免窗口顶部出现黑线。
WM_NCHITTEST:命中测试与窗口按钮
WmNcHitTest决定鼠标所在位置属于哪个窗口区域(标题栏、缩放边框、应用图标、窗口按钮等):
- 先调用默认窗口过程处理左右下三边;若结果不是
HTCLIENT则直接返回; - 将屏幕坐标换算为窗口坐标,再按
GraphicsConfiguration.getDefaultTransform()做降采样(scaleDown),与 AWT 在 HiDPI 下的坐标处理保持一致; - 顶部缩放边框被限制在
UIScale.scale(4)像素内(现代 WinUI 应用的标准),并根据鼠标位置返回HTTOPLEFT/HTTOPRIGHT/HTTOP; - 依次命中测试应用图标(
HTSYSMENU)、最小化(HTMINBUTTON)、最大化(HTMAXBUTTON)、关闭(HTCLOSE)按钮区域; - 标题栏区域通过
captionHitTestCallback回调让 Swing 判断该点是否落在处理鼠标事件的组件上,从而返回HTCLIENT(Swing 可处理)或HTCAPTION(可拖动窗口)。
注释中还记录了平台差异细节:Windows 11 上鼠标悬停HTMAXBUTTON会弹出 Snap Layouts 菜单;系统菜单(HTSYSMENU)区域左键单击弹菜单、双击发送WM_CLOSE。
DWM 颜色化信息与系统菜单
原型还实现了两处系统集成:
- 颜色化查询:从注册表
SOFTWARE\Microsoft\Windows\DWM读取ColorPrevalence、ColorizationColor、ColorizationColorBalance,用于计算激活窗口顶边颜色(对应 flatlaf-core 中FlatNativeWindowBorder.WindowTopBorder的配色逻辑);收到WM_DWMCOLORIZATIONCOLORCHANGED时通过fireStateChangedLaterOnce()在 AWT 事件线程上延迟 300ms 合并触发一次ChangeListener; - 系统菜单:右键标题栏/图标时,用
GetSystemMenu获取系统菜单,按窗口状态(最大化、有无缩放边框等)更新SC_RESTORE/SC_MOVE/SC_SIZE/SC_MINIMIZE/SC_MAXIMIZE/SC_CLOSE的启用状态,并将SC_CLOSE设为默认项,再以TrackPopupMenu(TPM_RETURNCMD)弹出,选中后PostMessage(WM_SYSCOMMAND)执行。
Linux 原型:X11WmUtils 与窗口管理器协议消息
Linux 侧原型类是 X11WmUtils.java,目标是通过 X11 客户端消息(ClientMessage)与窗口管理器通信,实现两个功能:
xMoveOrResizeWindow(window, x, y, direction):发送_NET_WM_MOVERESIZE(freedesktop EWMH 规范定义的协议),direction取值 0~7 对应八个缩放方向、8 对应移动;xShowWindowMenu(window, x, y):发送_GTK_SHOW_WINDOW_MENU(GTK 私有协议),请求窗口管理器在指定坐标弹出系统窗口菜单。
sendEvent的调用链展示了 JNA 与 X11 交互的完整套路:
- 用
sun.awt.SunToolkit.awtLock()加 AWT 锁,保证与 AWT 的互斥; XOpenDisplay(null)打开默认 Display,XDefaultRootWindow获取根窗口;- 用
Native.getComponentPointer(window)取 AWT 窗口的 X11 Window ID; XUngrabPointer/XUngrabKeyboard解除指针与键盘捕获,让窗口管理器能够接管;- 构造
XClientMessageEvent:message_type = XInternAtom(display, atom_name, false),format = 32,data.l[0..4]填入参数(移动缩放时含按钮Button1与 source indication=1); XSendEvent发给根窗口(掩码SubstructureNotifyMask | SubstructureRedirectMask),随后XFlush与XCloseDisplay。
需要特别注意的是,源码注释中给出了明确的警告:
Warning: Although the implementation of this method is (nearly) identical to the C++ implementation, this one does not work correctly. DO NOT USE.
即该 JNA 版本虽然与最终 C++ 实现逻辑几乎一致,但实际不能正常工作(原型中保留了大量System.out.println调试输出与// TODO标记可佐证其"未完成/仅供研究"状态),这正是它没有进入正式库的原因之一。
从 JNA 原型到 C++ 实现的演进路径
对比 JNA 原型与其对应的正式 C++ 实现,可以清晰还原"验证 → 落地"的演进过程。
Linux 侧:正式实现位于 flatlaf-natives-linux/src/main/cpp/X11WmUtils.cpp,导出Java_com_formdev_flatlaf_ui_FlatNativeLinuxLibrary_xMoveOrResizeWindow与xShowWindowMenu两个 JNI 方法。与 JNA 版相比,C++ 版补齐了原型缺失的关键环节:
- 通过
JAWT_GetAWT获取 AWT 接口、getWindowHandle取得Display*与窗口句柄,并用awt.Lock/awt.Unlock同步; - 新增
isWMHintSupported():读取根窗口的_NET_SUPPORTED属性,先检查窗口管理器是否支持该协议,不支持则直接返回 false——这是 JNA 原型没有的健壮性处理; - 事件结构体
XClientMessageEvent直接栈上初始化,无 JNA 结构体往返开销。
Java 侧调用方是 FlatNativeLinuxLibrary.java,其中moveOrResizeWindow/showWindowMenu的实现体里保留了一段被注释掉的 JNA 反射调用代码(对应原型类X11WmUtils),实际执行的则是private static native声明的 JNI 方法——这是"原型验证后转为 C++"的最直接证据。
Windows 侧:正式实现位于 flatlaf-natives-windows/src/main/cpp/FlatWndProc.cpp。FlatWndProc::WindowProc的WM_NCCALCSIZE/WM_NCHITTEST处理与 JNA 原型逻辑一致,但采用 C++ 原生实现 + JNI 回调的方式:原生代码通过onNcHitTestMID(GetMethodID(cls, "onNcHitTest", "(IIZ)I"))回调 Java 侧的命中测试逻辑,将"原生窗口消息处理"与"Swing 坐标/组件判断"分离。Windows 正式类 FlatWindowsNativeWindowBorder.java 的getInstance()会检查FlatNativeWindowsLibrary.isLoaded()(即原生库是否成功加载),而不是直接使用 JNA 类。
集成边界:为什么正式库不使用 JNA
除了 README 的明文说明,源码中还能找到多处"JNA 已被 C++ 取代"的痕迹:
- FlatNativeWindowBorder.java 的
initialize()中,反射加载 JNA 类com.formdev.flatlaf.natives.jna.windows.FlatWindowsNativeWindowBorder的代码整体被注释,实际改为调用 flatlaf-core 包内的FlatWindowsNativeWindowBorder.getInstance()(JNI 版本); - FlatNativeLinuxLibrary.java 中同样保留了被注释的 JNA 反射分支,正式路径走
native方法; - flatlaf-demo/build.gradle.kts 与 flatlaf-testing/build.gradle.kts 中对
:flatlaf-natives-jna的依赖均处于注释状态。
从工程角度,这种演进是合理的:正式库需要通过 FlatNativeLibrary.java 按 OS/架构(windows-x86_64、linux-arm64等 classifier)加载.dll/.so/.dylib,并从java.library.path、jar 旁目录或 jar 内临时解压三种途径获取原生库,还会校验getApiVersion()的 API 版本号——这套部署与加载机制更适合静态编译的 JNI 库,而非运行时绑定 JNA。
在开发环境中构建与使用该子项目
虽然正式应用不应依赖flatlaf-natives-jna,但作为研究工具,开发者可以按以下方式在本地构建:
# 在仓库根目录执行,编译该子项目 ./gradlew :flatlaf-natives-jna:compileJava # 查看其依赖(确认 JNA 5.19.1 已解析) ./gradlew :flatlaf-natives-jna:dependencies该子项目依赖flatlaf-core(复用SystemInfo/UIScale等工具)以及 JNA/JNA-Platform 5.19.1(见 gradle/libs.versions.toml)。若要临时启用它做实验,可参照仓库中已被注释的用法,在 flatlaf-demo/build.gradle.kts 中放开implementation( project( ":flatlaf-natives-jna" ) )并加入对应调用。
需要强调的注意事项:
- 不要在正式应用中使用该子项目:README 明确声明其代码不用于 FlatLaf 库;Linux 的
X11WmUtils源码更直接标注 "DO NOT USE"; - 平台前提:Windows 侧代码依赖 Windows 10+ 的窗口消息体系与 DWM 注册表键,Linux 侧依赖 X11(
XToolkit)与支持 EWMH 协议的窗口管理器(GNOME Mutter、KDE KWin 等),且部分行为随窗口管理器实现而异; - 研究价值:该子项目保留了完整的消息常量、结构体布局(如
NCCALCSIZE_PARAMS、MENUITEMINFO的@FieldOrder声明)与注释中的参考链接指向,是学习"如何用 JNA 与 Win32/X11 交互"的高质量示例——包括坐标换算、HiDPI 缩放、注册表读取、系统菜单操作等细节。
效果验证与进一步探索
FlatLaf 仓库的测试模块提供了与该原生能力直接相关的验证入口:
- FlatNativeWindowBorderTest.java:创建
JFrame与模态JDialog,用于验证原生窗口边框行为; - FlatWindowDecorationsTest.java:提供 "FlatLaf window decorations" 复选框,可交互式开关
FlatLaf.setUseNativeWindowDecorations(...),直观对比系统装饰与 FlatLaf 装饰; - 仓库中的实拍截图 Windows 10 decorations.png 与 Windows 11 decorations.png,展示了该特性在两种系统上的实际渲染效果。


总而言之,flatlaf-natives-jna是 FlatLaf 原生窗口能力研发链路中承上启下的一环:它用 JNA 在 Java 侧快速验证了 Windows 窗口过程接管、命中测试、DWM 颜色化与 Linux 窗口管理器协议消息等复杂原生交互,随后这些验证成果被移植为高性能的 C++/JNI 实现并集成进正式库。对研究 Swing 与操作系统原生层互操作的开发者而言,它是一份不可多得的、带有完整调试轨迹的活教材。
- 桌面应用
- UI组件
【免费下载链接】FlatLaf
FlatLaf - Swing Look and Feel (with Darcula/IntelliJ themes support)
相关推荐
FlatLaf 原生库(Native Libraries)全解析:Windows / Linux / macOS 构建、分发与加载机制
FlatLaf 原生库(Native Libraries)全解析:Windows / Linux / macOS 构建、分发与加载机制 FlatLaf 的 fl
桌面应用UI组件AppFlowy桌面端开发:Windows/macOS/Linux原生体验
AppFlowy桌面端开发:Windows/macOS/Linux原生体验 AppFlowy作为Notion的开源替代品,其桌面端开发采用了Flutter框架结
前端后端企业应用内容协同知识管理AI 应用Coco App 在 Windows 系统上实现原生圆角窗口的最佳实践
Coco App 在 Windows 系统上实现原生圆角窗口的最佳实践 背景介绍 在现代桌面应用开发中,窗口样式是用户体验的重要组成部分。随着 Windows
AI 应用交互助手前端桌面应用搜索引擎
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考