-
Notifications
You must be signed in to change notification settings - Fork 0
/
ScrollTweak.swift
108 lines (95 loc) · 3.37 KB
/
ScrollTweak.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
import SwiftUI
// Must extend NSObject so selectors can be used
class EventListener: NSObject {
let lines: UnsafeMutablePointer<Int64>
let thread: Thread
var runloop: CFRunLoop!
init?(lines: Int64) {
self.lines = UnsafeMutablePointer<Int64>.allocate(capacity: 1)
self.lines.initialize(to: lines)
// Create the scroll wheel event tap
guard let eventTap = CGEvent.tapCreate(
tap: .cgSessionEventTap,
place: .headInsertEventTap,
options: .defaultTap,
eventsOfInterest: CGEventMask(1 << CGEventType.scrollWheel.rawValue),
callback: { _, _, event, userInfo in
// If this is a continuous i.e. trackpad scroll, do nothing
if 0 == event.getIntegerValueField(CGEventField.scrollWheelEventIsContinuous) {
let delta: Int64 = event.getIntegerValueField(CGEventField.scrollWheelEventPointDeltaAxis1)
event.setIntegerValueField(CGEventField.scrollWheelEventDeltaAxis1, value: delta.signum() * userInfo!.load(as: Int64.self))
}
return Unmanaged.passRetained(event)
},
userInfo: self.lines
) else {
print("Failed to create scroll wheel event tap")
return nil
}
// Create a run loop source from the event tap
guard let eventSource = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, eventTap, 0)
else {
print("Failed to create scroll wheel run loop source")
return nil
}
thread = Thread {
CFRunLoopAddSource(CFRunLoopGetCurrent(), eventSource, .commonModes)
CFRunLoopRun()
}
thread.start()
}
deinit {
print("deinit")
self.lines.deallocate()
self.perform(#selector(stop), on: self.thread, with: nil, waitUntilDone: true)
}
@objc private func stop() {
CFRunLoopStop(CFRunLoopGetCurrent())
}
}
class AppDelegate: NSObject, NSApplicationDelegate {
private var eventListener: EventListener?
func applicationDidFinishLaunching(_ notification: Notification) {
eventListener = EventListener(lines: 3)
}
func toggle(lines: Int64) {
print("toggle")
if eventListener != nil {
print("toggle off")
eventListener = nil
} else {
print("toggle on")
eventListener = EventListener(lines: lines)
}
}
}
struct MyView: View {
@NSApplicationDelegateAdaptor private var appDelegate: AppDelegate
@State private var isOn = true
@State private var lines: Double = 3
var body: some View {
List {
Toggle("ScrollTweak", isOn: $isOn).toggleStyle(.switch)
HStack {
Text("# Lines")
Slider(value: $lines, in: 1...10, step: 1).disabled(isOn)
}
Button("Quit") {
NSApplication.shared.terminate(nil)
}
}.onChange(of: isOn) {
print("isOn=\(isOn)")
appDelegate.toggle(lines: Int64(lines))
}.onChange(of: lines) {
print("lines=\(lines)")
}.listStyle(.sidebar)
}
}
@main
struct ScrollTweak: App {
var body: some Scene {
return MenuBarExtra("", systemImage: "computermouse.fill") {
MyView()
}.menuBarExtraStyle(.window)
}
}