MacOS JVM环境中拦截修改滚轮事件时程序立即退出的问题求助
我明白你现在碰到的棘手问题——明明已经给应用开了Accessibility权限,用JNA写的Mac滚轮拦截程序一启动就直接退出,完全没像预期那样停在CFRunLoopRun()这一步挂起监听事件,但你用Kotlin Native写的同款逻辑却能正常工作。我来帮你梳理几个可能的原因和对应的解决思路:
1. 先确认事件Tap是否真的创建成功
你虽然在代码里判断了tap != null,但有时候MacOS的权限系统会有延迟,或者JVM进程的权限上下文没正确生效。建议在tap != null的分支里加个明确的打印:
if (tap != null) { println("Event tap created successfully! Starting run loop...") // 后续的RunLoop初始化逻辑 }
如果没看到这个打印,说明事件Tap创建失败了,哪怕你觉得权限已经给了——这种情况下可以试试:
- 关掉应用后重新去系统设置的“隐私与安全性”里,先移除应用的Accessibility权限,再重新添加并授权。
- 用sudo启动程序(终端里运行),确认是否是权限上下文的问题。
2. 修正JNA的框架引用与常量使用
MacOS的ApplicationServices框架已经被拆分合并到CoreGraphics等框架里,你直接注册ApplicationServices可能存在兼容性问题,建议改成引用CoreGraphics:
init { Native.register(CoreGraphics.COREGRAPHICS_LIBRARY_NAME) }
另外,你手动创建_kCFRunLoopCommonModes字符串的方式很容易出错,直接用JNA CoreFoundation提供的常量更可靠,省去自己创建字符串的步骤:
// 替换原来的CFStringCreateWithCString逻辑 val commonModes = CoreFoundation.CFRunLoopCommonModes CFRunLoopAddSource(CFRunLoopGetCurrent(), runLoopSource, commonModes)
3. 避免JVM垃圾回收回收核心对象
你的runLoopSource、tap这些对象如果只在main函数的局部作用域里,可能会被JVM的垃圾回收器提前回收,导致RunLoop失去依赖而立即退出。建议把这些对象声明为类的静态变量,或者用一个强引用容器持有:
class MacMouseWheelInterceptor { companion object { // 用静态变量持有核心对象,避免GC回收 private var tap: Pointer? = null private var runLoopSource: Pointer? = null // ... 其他native方法 } // ... 拦截逻辑 }
4. 结合AWT事件循环维持JVM进程存活
在JVM中,单纯调用CFRunLoopRun()可能因为主线程没有其他存活的非守护线程,导致RunLoop刚启动就被JVM终止。你可以结合AWT的事件循环来维持进程,因为MacOS的AWT本身依赖CFRunLoop,兼容性更好:
fun main() { // 启动AWT事件循环线程,确保JVM进程不退出 EventQueue.invokeLater { // 在这里初始化你的事件Tap和RunLoop逻辑 val callback = object : CGEventTapCallback { override fun callback(proxy: Pointer?, type: Long, event: Pointer?, refcon: Pointer?): Pointer? { println("Callback triggered with event type $type") if (type == 22L) { // kCGEventScrollWheel val deltaY = CGEventGetDoubleValueField(event, 11L) // kCGScrollWheelEventDeltaAxis1 CGEventSetDoubleValueField(event, 11L, deltaY * 4.0) } return event } } MacMouseWheelInterceptor.tap = MacMouseWheelInterceptor.CGEventTapCreate( 1, // kCGSessionEventTap 0, // kCGHeadInsertEventTap 0, // kCGEventTapOptionDefault 1L shl 22, // CGEventMaskBit(kCGEventScrollWheel) callback, null ) MacMouseWheelInterceptor.tap?.let { tap -> MacMouseWheelInterceptor.runLoopSource = CFMachPortCreateRunLoopSource(null, tap, 0) MacMouseWheelInterceptor.runLoopSource?.let { source -> val commonModes = CoreFoundation.CFRunLoopCommonModes CFRunLoopAddSource(CFRunLoopGetCurrent(), source, commonModes) CGEventTapEnable(tap, true) println("Starting run loop...") CFRunLoopRun() } } } // 让主线程保持存活 Thread.sleep(Long.MAX_VALUE) }
这些调整应该能帮你解决程序立即退出的问题,核心是确保事件Tap创建成功、核心对象不被GC回收,同时维持JVM进程的存活状态。
内容来源于stack exchange

