diff --git a/Mos/ScrollCore/ScrollCore.swift b/Mos/ScrollCore/ScrollCore.swift index 3a86e319..d0d6a225 100644 --- a/Mos/ScrollCore/ScrollCore.swift +++ b/Mos/ScrollCore/ScrollCore.swift @@ -67,6 +67,9 @@ class ScrollCore { #endif return Unmanaged.passUnretained(event) } + if ScrollUtils.shared.shouldIgnoreScrollSource(event) { + return Unmanaged.passUnretained(event) + } // 滚动事件 let scrollEvent = ScrollEvent(with: event) let hasVerticalDelta = scrollEvent.Y.valid && scrollEvent.Y.usableValue != 0.0 diff --git a/Mos/ScrollCore/ScrollUtils.swift b/Mos/ScrollCore/ScrollUtils.swift index 2ed9ca1c..1d160f2a 100644 --- a/Mos/ScrollCore/ScrollUtils.swift +++ b/Mos/ScrollCore/ScrollUtils.swift @@ -117,6 +117,34 @@ class ScrollUtils { return nil } + // MARK: - Scroll source ignore rules + private var lastScrollSourcePID: pid_t = 0 + private var lastScrollSourceShouldIgnore = false + + func invalidateScrollSourceIgnoreCache() { + lastScrollSourcePID = 0 + lastScrollSourceShouldIgnore = false + } + + /// Checks whether the app that emitted this scroll event should bypass Mos handling. + /// Source-app policy is intentionally separate from target-app scroll settings. + func shouldIgnoreScrollSource(_ event: CGEvent) -> Bool { + let sourcePID = pid_t(event.getIntegerValueField(.eventSourceUnixProcessID)) + if sourcePID == 0 { return false } + + if sourcePID != lastScrollSourcePID { + lastScrollSourcePID = sourcePID + lastScrollSourceShouldIgnore = false + + if let sourceApplication = NSRunningApplication(processIdentifier: sourcePID), + let application = getTargetApplication(from: sourceApplication) { + lastScrollSourceShouldIgnore = application.ignoreAsScrollSource + } + } + + return lastScrollSourceShouldIgnore + } + // MARK: - 远程桌面事件检测 // 远程桌面事件检测缓存 private var lastSourcePID: pid_t = 0 diff --git a/Mos/Windows/PreferencesWindow/ApplicationView/Application.swift b/Mos/Windows/PreferencesWindow/ApplicationView/Application.swift index f46bf790..5bfc5819 100644 --- a/Mos/Windows/PreferencesWindow/ApplicationView/Application.swift +++ b/Mos/Windows/PreferencesWindow/ApplicationView/Application.swift @@ -28,6 +28,12 @@ class Application: Codable, Equatable { var buttons: OPTIONS_BUTTONS_DEFAULT? { didSet { Options.shared.saveOptions() } } + var ignoreAsScrollSource = false { + didSet { + ScrollUtils.shared.invalidateScrollSourceIgnoreCache() + Options.shared.saveOptions() + } + } // 初始化 init(path: String) { self.path = path @@ -46,6 +52,7 @@ class Application: Codable, Equatable { scroll = try container.decodeIfPresent(OPTIONS_SCROLL_DEFAULT.self, forKey: .scroll) ?? OPTIONS_SCROLL_DEFAULT() // 按钮绑定 buttons = try container.decodeIfPresent(OPTIONS_BUTTONS_DEFAULT.self, forKey: .buttons) + ignoreAsScrollSource = try container.decodeIfPresent(Bool.self, forKey: .ignoreAsScrollSource) ?? false } // Equatable diff --git a/Mos/Windows/PreferencesWindow/ApplicationView/PreferencesApplicationViewController.swift b/Mos/Windows/PreferencesWindow/ApplicationView/PreferencesApplicationViewController.swift index e21c2246..73297842 100644 --- a/Mos/Windows/PreferencesWindow/ApplicationView/PreferencesApplicationViewController.swift +++ b/Mos/Windows/PreferencesWindow/ApplicationView/PreferencesApplicationViewController.swift @@ -87,6 +87,7 @@ class PreferencesApplicationViewController: NSViewController { guard let removedApp = Options.shared.application.applications.get(by: tableView.selectedRow) else { return } // 删除 Options.shared.application.applications.remove(at: tableView.selectedRow) + ScrollUtils.shared.invalidateScrollSourceIgnoreCache() // 清除该应用所有 appScroll 占用 (codes=[] -> source 移除 -> Logi divert 同步) clearAppUsage(removedApp) // 重新加载 diff --git a/Mos/Windows/PreferencesWindow/ScrollingView/PreferencesScrollingWithApplicationViewController.swift b/Mos/Windows/PreferencesWindow/ScrollingView/PreferencesScrollingWithApplicationViewController.swift index e5ff11b5..c294b3ba 100644 --- a/Mos/Windows/PreferencesWindow/ScrollingView/PreferencesScrollingWithApplicationViewController.swift +++ b/Mos/Windows/PreferencesWindow/ScrollingView/PreferencesScrollingWithApplicationViewController.swift @@ -20,12 +20,15 @@ class PreferencesScrollingWithApplicationViewController: NSViewController { @IBOutlet weak var currentTargetApplicationIcon: NSImageView! @IBOutlet weak var currentTargetApplicationName: NSTextField! @IBOutlet weak var inheritGlobalSettingCheckBox: NSButton! + private var ignoreScrollSourceCheckBox: NSButton? override func viewDidLoad() { // 初始化显示内容 currentTargetApplicationIcon.image = currentTargetApplication?.getIcon() currentTargetApplicationIcon.toolTip = currentTargetApplication?.path currentTargetApplicationName.stringValue = currentTargetApplication?.getName() ?? "" + currentTargetApplicationName.toolTip = currentTargetApplication?.path + configureIgnoreScrollSourceCheckBox() // 读取设置 syncViewWithOptions() } @@ -39,6 +42,9 @@ class PreferencesScrollingWithApplicationViewController: NSViewController { public func updateTargetApplication(with target: Application?) { currentTargetApplication = target + if isViewLoaded { + syncViewWithOptions() + } if let vaildContentViewController = currentContentViewController, let validTargetApplication = currentTargetApplication { vaildContentViewController.currentTargetApplication = validTargetApplication } @@ -54,7 +60,8 @@ class PreferencesScrollingWithApplicationViewController: NSViewController { if name.count > 0 { currentTargetApplication?.displayName = name if let validParentTableView = parentTableView, let validParentTableRow = parentTableRow { - validParentTableView.reloadData(forRowIndexes: [validParentTableRow], columnIndexes: [0, 1, 2]) + let columnIndexes = IndexSet(integersIn: 0.. Set { let hotkey: ScrollHotkey? = { @@ -108,9 +120,40 @@ class PreferencesScrollingWithApplicationViewController: NSViewController { * 工具函数 **/ extension PreferencesScrollingWithApplicationViewController { + private func configureIgnoreScrollSourceCheckBox() { + guard let headerContentView = inheritGlobalSettingCheckBox.superview, + let headerBox = headerContentView.superview else { return } + + let checkBox = NSButton( + checkboxWithTitle: NSLocalizedString("Ignore Scroll Source", comment: "Per-application option for ignoring scroll events emitted by this app"), + target: self, + action: #selector(ignoreScrollSourceClick) + ) + checkBox.controlSize = .small + checkBox.font = NSFont.menuFont(ofSize: 11) + checkBox.imagePosition = .imageRight + checkBox.translatesAutoresizingMaskIntoConstraints = false + checkBox.toolTip = NSLocalizedString("Pass through scroll events emitted by this app without Mos smoothing", comment: "Tooltip for ignore scroll source checkbox") + headerContentView.addSubview(checkBox) + ignoreScrollSourceCheckBox = checkBox + headerBox.constraints + .filter { $0.firstAttribute == .height } + .forEach { $0.constant = max($0.constant, 58) } + + let nameWidth = currentTargetApplicationName.widthAnchor.constraint(lessThanOrEqualToConstant: 150) + nameWidth.priority = .defaultHigh + NSLayoutConstraint.activate([ + nameWidth, + checkBox.leadingAnchor.constraint(equalTo: inheritGlobalSettingCheckBox.leadingAnchor), + checkBox.topAnchor.constraint(equalTo: inheritGlobalSettingCheckBox.bottomAnchor, constant: 5), + checkBox.widthAnchor.constraint(equalTo: inheritGlobalSettingCheckBox.widthAnchor), + ]) + } + // 同步界面与设置 func syncViewWithOptions() { // 继承 inheritGlobalSettingCheckBox.state = NSControl.StateValue(rawValue: (currentTargetApplication?.inherit ?? false) ? 1 : 0) + ignoreScrollSourceCheckBox?.state = (currentTargetApplication?.ignoreAsScrollSource ?? false) ? .on : .off } }