Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Mos/ScrollCore/ScrollCore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
28 changes: 28 additions & 0 deletions Mos/ScrollCore/ScrollUtils.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
// 重新加载
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
Expand All @@ -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
}
Expand All @@ -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..<validParentTableView.numberOfColumns)
validParentTableView.reloadData(forRowIndexes: [validParentTableRow], columnIndexes: columnIndexes)
}
}
}
Expand All @@ -73,6 +80,11 @@ class PreferencesScrollingWithApplicationViewController: NSViewController {
}
}

@objc func ignoreScrollSourceClick(_ sender: NSButton) {
guard let validTargetApplication = currentTargetApplication else { return }
validTargetApplication.ignoreAsScrollSource = sender.state == .on
}

// MARK: - Logi usage helpers
private func collectAppScrollCodes(app: Application, role: ScrollRole) -> Set<UInt16> {
let hotkey: ScrollHotkey? = {
Expand Down Expand Up @@ -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
}
}