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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
xcuserdata/
.DS_Store
14 changes: 7 additions & 7 deletions DiscreteScroll.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,15 @@
objects = {

/* Begin PBXBuildFile section */
C3F94D482C0406330051923E /* main.c in Sources */ = {isa = PBXBuildFile; fileRef = C3F94D472C0406330051923E /* main.c */; };
948CB3262CE4B90D0024F5A6 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 948CB3252CE4B2410024F5A6 /* main.m */; };
C3F94D562C0408930051923E /* LICENSE in Resources */ = {isa = PBXBuildFile; fileRef = C3F94D542C0408930051923E /* LICENSE */; };
C3F94D572C0408930051923E /* README.md in Resources */ = {isa = PBXBuildFile; fileRef = C3F94D552C0408930051923E /* README.md */; };
/* End PBXBuildFile section */

/* Begin PBXFileReference section */
948CB3252CE4B2410024F5A6 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
C3F94D3B2C0406320051923E /* DiscreteScroll.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = DiscreteScroll.app; sourceTree = BUILT_PRODUCTS_DIR; };
C3F94D462C0406330051923E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
C3F94D472C0406330051923E /* main.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = main.c; sourceTree = "<group>"; };
C3F94D542C0408930051923E /* LICENSE */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = LICENSE; sourceTree = "<group>"; };
C3F94D552C0408930051923E /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = "<group>"; };
/* End PBXFileReference section */
Expand Down Expand Up @@ -53,7 +53,7 @@
isa = PBXGroup;
children = (
C3F94D462C0406330051923E /* Info.plist */,
C3F94D472C0406330051923E /* main.c */,
948CB3252CE4B2410024F5A6 /* main.m */,
);
path = DiscreteScroll;
sourceTree = "<group>";
Expand Down Expand Up @@ -84,7 +84,7 @@
C3F94D332C0406320051923E /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 1240;
LastUpgradeCheck = 2600;
TargetAttributes = {
C3F94D3A2C0406320051923E = {
CreatedOnToolsVersion = 12.4;
Expand Down Expand Up @@ -126,7 +126,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
C3F94D482C0406330051923E /* main.c in Sources */,
948CB3262CE4B90D0024F5A6 /* main.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down Expand Up @@ -255,7 +255,7 @@
"$(inherited)",
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 10.9;
MACOSX_DEPLOYMENT_TARGET = 10.13;
MARKETING_VERSION = 1.2.1;
PRODUCT_BUNDLE_IDENTIFIER = com.emreyolcu.DiscreteScroll;
PRODUCT_NAME = "$(TARGET_NAME)";
Expand All @@ -273,7 +273,7 @@
"$(inherited)",
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 10.9;
MACOSX_DEPLOYMENT_TARGET = 10.13;
MARKETING_VERSION = 1.2.1;
PRODUCT_BUNDLE_IDENTIFIER = com.emreyolcu.DiscreteScroll;
PRODUCT_NAME = "$(TARGET_NAME)";
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "2600"
version = "1.7">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES"
buildArchitectures = "Automatic">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "C3F94D3A2C0406320051923E"
BuildableName = "DiscreteScroll.app"
BlueprintName = "DiscreteScroll"
ReferencedContainer = "container:DiscreteScroll.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"
shouldAutocreateTestPlan = "YES">
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "C3F94D3A2C0406320051923E"
BuildableName = "DiscreteScroll.app"
BlueprintName = "DiscreteScroll"
ReferencedContainer = "container:DiscreteScroll.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "C3F94D3A2C0406320051923E"
BuildableName = "DiscreteScroll.app"
BlueprintName = "DiscreteScroll"
ReferencedContainer = "container:DiscreteScroll.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
50 changes: 49 additions & 1 deletion DiscreteScroll/main.c → DiscreteScroll/main.m
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,44 @@ static bool TRUSTED;

static int LINES;

static CFArrayRef EXCEPTIONS;

static NSString *getAppBeingScrolled (CGEventRef event)
{
CGPoint location = CGEventGetLocation(event);
CFArrayRef windows = CGWindowListCopyWindowInfo(
kCGWindowListOptionOnScreenOnly | kCGWindowListOptionIncludingWindow,
kCGNullWindowID
);

NSDictionary *appInfo = nil;
for (NSDictionary *window in (__bridge NSArray *)windows) {
CGRect bounds;
CGRectMakeWithDictionaryRepresentation((__bridge CFDictionaryRef)window[(id)kCGWindowBounds], &bounds);

if (CGRectContainsPoint(bounds, location)) {
appInfo = window;
break;
}
}
CFRelease(windows);

if (appInfo) {
NSString *ownerName = appInfo[(id)kCGWindowOwnerName];
return ownerName;
} else {
return nil;
}
}

static CGEventRef tapCallback(CGEventTapProxy proxy,
CGEventType type, CGEventRef event, void *userInfo)
{
if (CGEventGetIntegerValueField(event, kCGScrollWheelEventIsContinuous) == 0) {
NSString *app = getAppBeingScrolled(event);
CFRange range = CFRangeMake(0, CFArrayGetCount(EXCEPTIONS));
bool exceptFlag = (app && CFArrayContainsValue(EXCEPTIONS, range, (__bridge CFStringRef)app));

if (CGEventGetIntegerValueField(event, kCGScrollWheelEventIsContinuous) == 0 && !exceptFlag) {
int delta = (int)CGEventGetIntegerValueField(event, kCGScrollWheelEventPointDeltaAxis1);
CGEventSetIntegerValueField(event, kCGScrollWheelEventDeltaAxis1, SIGN(delta) * LINES);
}
Expand Down Expand Up @@ -44,6 +78,17 @@ static void notificationCallback(CFNotificationCenterRef center, void *observer,
);
}

static bool getExceptionApps(CFStringRef key)
{
EXCEPTIONS = (CFArrayRef)CFPreferencesCopyAppValue(key, kCFPreferencesCurrentApplication);
bool got = false;
if (EXCEPTIONS && CFGetTypeID(EXCEPTIONS) == CFArrayGetTypeID()) {
got = (int)CFArrayGetCount(EXCEPTIONS);
}

return got;
}

static bool getIntPreference(CFStringRef key, int *valuePtr)
{
CFNumberRef number = (CFNumberRef)CFPreferencesCopyAppValue(
Expand Down Expand Up @@ -78,6 +123,9 @@ int main(void)
CFRunLoopRun();
CFNotificationCenterRemoveObserver(center, &observer, AX_NOTIFICATION, NULL);

if (!getExceptionApps(CFSTR("except")))
EXCEPTIONS = CFArrayCreate(kCFAllocatorDefault, NULL, 0, &kCFTypeArrayCallBacks);

if (!getIntPreference(CFSTR("lines"), &LINES))
LINES = DEFAULT_LINES;

Expand Down
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
MIT License

Copyright (c) 2024 Emre Yolcu
Copyright (c) 2025 Emre Yolcu

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
9 changes: 7 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ a constant number of lines with each notch of the wheel.

### Supported versions

As of November 2024, this application works on macOS versions 10.9–15.0.
As of October 2025, this application works on macOS versions `10.13`–`26.0.99`.

### Installation

Expand Down Expand Up @@ -54,7 +54,12 @@ defaults write com.emreyolcu.DiscreteScroll lines -int LINES
> If you set `lines` to some value other than an integer,
> then the default value of 3 is used as a fallback.

You should restart the application for the setting to take effect.
You may configure exception applications if you do not want DiscreteScroll to run on every application. Running this command repeatedly will append its arguments to the existing list of exception applications. Replace `APPLICATION` with the name of the exception application you would like to configure, e.g. `Finder`.
```
defaults write com.emreyolcu.DiscreteScroll except -array-add "APPLICATION"
```

You should restart the application for changes in settings to take effect.

### Uninstallation

Expand Down