You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

MacOS JVM环境中拦截修改滚轮事件时程序立即退出的问题求助

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.07 07:50:27