Skip to content

Commit 1a36e71

Browse files
Merge dev branch for v0.0.3 release
- Fix Zoom crash by removing initWithContentRect swizzle - Implement notification-based corner radius application - Enhance window detection to protect system UI elements - Add dock radius control functionality - Update documentation for new features - Improve testing with qBittorrent and Zoom
2 parents b7ff1d7 + d1d9bc8 commit 1a36e71

File tree

10 files changed

+600
-239
lines changed

10 files changed

+600
-239
lines changed

.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,4 +25,6 @@ xcuserdata/
2525
.ccls-cache/
2626

2727
# Log files
28-
*.log
28+
*.log
29+
30+
Squircles_Support/

CLI.md

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,21 @@ sharpener off
1212
# Toggle
1313
sharpener toggle
1414

15-
# Set radius
15+
# Set window radius
1616
sharpener -r 40
1717
sharpener --radius=40
1818

19+
# Set dock radius
20+
sharpener -d 20
21+
sharpener --dock-radius=20
22+
1923
# Show current settings
2024
sharpener -s
25+
sharpener --status
2126

2227
# Show version
2328
sharpener -v
29+
sharpener --version
2430
```
2531

2632
## Commands
@@ -31,25 +37,31 @@ sharpener -v
3137

3238
## Options
3339

34-
- `-r, --radius <value>` — Set the sharpening radius (integer `>= 0`)
35-
- `--radius=<value>` — Alternative syntax to set radius
36-
- `-s, --status` — Show current radius and status (`on`/`off`)
40+
- `-r, --radius <value>` — Set the window sharpening radius (integer `>= 0`)
41+
- `--radius=<value>` — Alternative syntax to set window radius
42+
- `-d, --dock-radius <value>` — Set the dock radius (integer `>= 0`)
43+
- `--dock-radius=<value>` — Alternative syntax to set dock radius
44+
- `-s, --status` — Show current window radius, dock radius, and status (`on`/`off`)
3745
- `-v, --version` — Show CLI version
3846
- `-h, --help` — Show built‑in help
3947

4048
## Examples
4149

4250
```bash
43-
# Set radius to 0 for sharp (square) corners
51+
# Set window radius to 0 for sharp (square) corners
4452
sharpener -r 0
4553

46-
# Set radius to 40 and enable immediately
54+
# Set dock radius to 15
55+
sharpener -d 15
56+
57+
# Set window radius to 40 and enable immediately
4758
sharpener on && sharpener -r 40
4859

4960
# Query current status
5061
sharpener -s
5162
# Output example:
5263
# Current radius: 40
64+
# Current dock radius: 15
5365
# Status: on
5466

5567
# Show version

Makefile

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,10 @@ INSTALL_DIR = /var/ammonia/core/tweaks
4141
CLI_INSTALL_DIR = /usr/local/bin
4242

4343
# Source files
44-
DYLIB_SOURCES = $(SOURCE_DIR)/sharpener/sharpener.m ZKSwizzle/ZKSwizzle.m
44+
DYLIB_SOURCES = $(SOURCE_DIR)/sharpener/sharpener.m \
45+
$(SOURCE_DIR)/sharpener/Windows/window.m \
46+
$(SOURCE_DIR)/sharpener/Dock/dock.m \
47+
ZKSwizzle/ZKSwizzle.m
4548
DYLIB_OBJECTS = $(DYLIB_SOURCES:%.m=$(BUILD_DIR)/%.o)
4649

4750
# CLI tool source and object
@@ -113,18 +116,22 @@ install: $(BUILD_DIR)/$(DYLIB_NAME) $(BUILD_DIR)/$(CLI_NAME) ## Install dylib an
113116

114117
# Test target that builds, installs, and relaunches test applications
115118
test: install ## Build, install, and restart test applications
116-
@echo "Force quitting test applications..."
117-
$(eval TEST_APPS := Spotify "System Settings" Chess soffice "Brave Browser" Beeper Safari Finder)
119+
@echo "Force quitting test applications and Dock..."
120+
$(eval TEST_APPS := Spotify "System Settings" Chess soffice "Brave Browser" Beeper Safari Finder qBittorrent zoom.us)
118121
@for app in $(TEST_APPS); do \
119122
pkill -9 "$$app" 2>/dev/null || true; \
120123
done
124+
@echo "Killing Dock to reload with new dylib..."
125+
@pkill -9 "Dock" 2>/dev/null || true
121126
@echo "Relaunching test applications..."
122127
@for app in $(TEST_APPS); do \
123-
if [ "$$app" != "soffice" ]; then \
128+
if [ "$$app" != "soffice" ] && [ "$$app" != "zoom.us" ]; then \
124129
open -a "$$app" 2>/dev/null || true; \
130+
elif [ "$$app" = "zoom.us" ]; then \
131+
open -a "zoom.us" 2>/dev/null || true; \
125132
fi; \
126133
done
127-
@echo "Test applications restarted with new dylib loaded"
134+
@echo "Test applications and Dock restarted with new dylib loaded"
128135

129136
# Clean build files
130137
clean: ## Remove build directory and artifacts
@@ -133,11 +140,13 @@ clean: ## Remove build directory and artifacts
133140

134141
# Delete installed files
135142
delete: ## Delete installed files and relaunch Finder
136-
@echo "Force quitting test applications..."
137-
$(eval TEST_APPS := Spotify "System Settings" Chess soffice "Brave Browser" Beeper Safari Finder)
143+
@echo "Force quitting test applications and Dock..."
144+
$(eval TEST_APPS := Spotify "System Settings" Chess soffice "Brave Browser" Beeper Safari Finder qBittorrent zoom.us)
138145
@for app in $(TEST_APPS); do \
139146
pkill -9 "$$app" 2>/dev/null || true; \
140147
done
148+
@echo "Killing Dock..."
149+
@pkill -9 "Dock" 2>/dev/null || true
141150
@sleep 2 && open -a "Finder" || true
142151
@sudo rm -f $(INSTALL_PATH)
143152
@sudo rm -f $(CLI_INSTALL_PATH)

README.md

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ View more screenshots: [GALLERY.md](./GALLERY.md)
1616
- Square corners for application windows with configurable radius (`0` for sharp corners)
1717
- Live control via CLI: `on`, `off`, `toggle` (no app restart needed)
1818
- Live radius adjustment via `-r/--radius` with immediate effect across open windows
19-
- Status query via `-s/--show-radius` (shows current radius and on/off state)
19+
- Status query via `-s/--status` (shows current radius and on/off state)
2020
- Persists enabled state and radius across apps via `notifyd` channels
2121
- Preserves system UI (menus, popovers, HUD/utility windows) by targeting standard app windows only
2222
- Fullscreen-safe behavior; uses `0` radius in fullscreen to avoid visual artifacts
@@ -81,7 +81,17 @@ Did you follow this [readme](./README.md#requirements) carefully?
8181

8282
## How It Works
8383

84-
Apple Sharpener uses method swizzling to modify the window corner mask and titlebar decoration view behavior of macOS applications. It specifically targets application windows while preserving the native appearance of menus, popovers, and other system UI elements.
84+
Apple Sharpener uses method swizzling to modify the window corner mask and titlebar decoration view behavior of macOS applications.
85+
86+
### Technical Implementation
87+
88+
- **Safe Window Targeting**: Uses notification-based corner radius application to avoid recursion crashes
89+
- **Intelligent Filtering**: Only targets standard titled windows with minimum size requirements
90+
- **System UI Preservation**: Excludes context menus, tooltips, HUD windows, and other transient UI elements
91+
- **Window Level Checking**: Respects macOS window hierarchy to avoid affecting floating panels and system overlays
92+
- **Lifecycle-Aware**: Applies corner radius when windows become active, ensuring proper initialization
93+
94+
The implementation specifically addresses compatibility issues with complex applications like Zoom while maintaining the native appearance of system UI elements.
8595

8696
## Contributing
8797

src/sharpener/Dock/dock.h

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
#ifndef DOCK_H
2+
#define DOCK_H
3+
4+
#import <Foundation/Foundation.h>
5+
#import <AppKit/AppKit.h>
6+
#import <QuartzCore/QuartzCore.h>
7+
8+
/**
9+
* Dock sharpening API for apple-sharpener
10+
* Provides functions to control dock corner radius modification
11+
*/
12+
13+
/**
14+
* Check if the current process is the Dock
15+
* @return YES if running in Dock process, NO otherwise
16+
*/
17+
BOOL isDockProcess(void);
18+
19+
/**
20+
* Toggle square corners for the dock
21+
* @param enable Whether to enable custom corner radius
22+
* @param radius The corner radius to apply (0 for square corners)
23+
*/
24+
void toggleDockCorners(BOOL enable, NSInteger radius);
25+
26+
#endif /* DOCK_H */

src/sharpener/Dock/dock.m

Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
1+
#import "dock.h"
2+
#import "ZKSwizzle.h"
3+
#import <notify.h>
4+
#import <QuartzCore/QuartzCore.h>
5+
#import <objc/runtime.h>
6+
7+
// Debug logging disabled by default; enable with APPLE_SHARPENER_DEBUG
8+
#ifdef APPLE_SHARPENER_DEBUG
9+
static void ASDebugLog(const char *fmt, ...) {
10+
va_list args; va_start(args, fmt);
11+
vfprintf(stderr, fmt, args);
12+
fprintf(stderr, "\n");
13+
va_end(args);
14+
}
15+
#else
16+
#define ASDebugLog(...) do {} while (0)
17+
#endif
18+
19+
/**
20+
* Dock sharpening implementation for apple-sharpener
21+
* Hooks into CALayer layoutSublayers to target DockCore.ModernFloorLayer
22+
*/
23+
24+
#pragma mark - Constants
25+
26+
NSString * const kDockBundleIdentifier = @"com.apple.dock";
27+
const CGFloat kDockDefaultRadius = 16.0;
28+
const CGFloat kDockSquareRadius = 0.0;
29+
30+
#pragma mark - Global State
31+
32+
static BOOL enableDockSharpener = YES;
33+
static NSInteger dockCustomRadius = 0;
34+
35+
#pragma mark - Helper Functions
36+
37+
BOOL isDockProcess(void) {
38+
NSString *bundleId = [[NSBundle mainBundle] bundleIdentifier];
39+
return [bundleId isEqualToString:kDockBundleIdentifier];
40+
}
41+
42+
static void DoDock(CALayer *layer) {
43+
if (!layer || !enableDockSharpener) return;
44+
45+
ASDebugLog("DoDock on %s, radius=%ld", [layer.className UTF8String], (long)dockCustomRadius);
46+
47+
// Apply corner radius to the dock layer
48+
CGFloat targetRadius = enableDockSharpener ? dockCustomRadius : kDockDefaultRadius;
49+
layer.cornerRadius = targetRadius;
50+
layer.masksToBounds = YES;
51+
52+
// Also apply to sublayers that might need it
53+
for (CALayer *sublayer in layer.sublayers) {
54+
NSString *subClass = sublayer.className;
55+
if ([subClass containsString:@"Dock"] ||
56+
[subClass containsString:@"Floor"] ||
57+
[subClass containsString:@"Background"] ||
58+
[subClass containsString:@"Backdrop"] ||
59+
[subClass containsString:@"Portal"] ||
60+
[subClass containsString:@"SDF"]) {
61+
ASDebugLog("Apply radius to sublayer: %s", [subClass UTF8String]);
62+
sublayer.cornerRadius = targetRadius;
63+
sublayer.masksToBounds = YES;
64+
[sublayer setNeedsLayout];
65+
[sublayer setNeedsDisplay];
66+
}
67+
}
68+
}
69+
70+
#pragma mark - CALayer Hook
71+
72+
// Store original method implementation
73+
static IMP __LayoutSublayers = NULL;
74+
75+
// Optional WALayerKitWindow hook (root layer provider)
76+
static IMP __WALayerKitWindow_layer = NULL;
77+
static CALayer* _PatchedWALayerKitWindow_layer(id self, SEL _cmd) {
78+
CALayer *root = ((CALayer*(*)(id,SEL))__WALayerKitWindow_layer)(self, _cmd);
79+
ASDebugLog("WALayerKitWindow layer root: %p", root);
80+
return root;
81+
}
82+
83+
// Hooked layoutSublayers method
84+
static void _PatchedLayoutSublayers(id self, SEL _cmd) {
85+
// Call original implementation first
86+
if (__LayoutSublayers) {
87+
((void(*)(id, SEL))__LayoutSublayers)(self, _cmd);
88+
}
89+
90+
// Only process if we're in the dock and sharpening is enabled
91+
if (!isDockProcess() || !enableDockSharpener) return;
92+
93+
// Debug: Log all layer classes we encounter
94+
NSString *className = [self className];
95+
ASDebugLog("CALayer layout: %s", [className UTF8String]);
96+
97+
// Check if this is the DockCore.ModernFloorLayer we're looking for
98+
if ([className isEqualToString:@"DockCore.ModernFloorLayer"]) {
99+
ASDebugLog("Found DockCore.ModernFloorLayer, applying radius=%ld", (long)dockCustomRadius);
100+
DoDock((CALayer *)self);
101+
}
102+
// Also try broader detection for dock-related layers
103+
else if ([className containsString:@"Dock"] ||
104+
[className containsString:@"Floor"] ||
105+
[className containsString:@"Background"] ||
106+
[className containsString:@"Modern"] ||
107+
[className containsString:@"Backdrop"] ||
108+
[className containsString:@"Portal"] ||
109+
[className containsString:@"SDF"]) {
110+
ASDebugLog("Found potential dock layer: %s, applying radius=%ld", [className UTF8String], (long)dockCustomRadius);
111+
DoDock((CALayer *)self);
112+
}
113+
}
114+
115+
#pragma mark - Public API
116+
117+
void toggleDockCorners(BOOL enable, NSInteger radius) {
118+
if (!isDockProcess()) return;
119+
120+
enableDockSharpener = enable;
121+
dockCustomRadius = MAX(0, radius);
122+
123+
// Force layout update on all layers to trigger our hook
124+
NSArray *windows = [[NSApplication sharedApplication] windows];
125+
for (NSWindow *window in windows) {
126+
if (window.contentView && window.contentView.layer) {
127+
[window.contentView.layer setNeedsLayout];
128+
}
129+
}
130+
}
131+
132+
#pragma mark - Dock View Swizzling
133+
134+
// Remove old placeholder swizzling - we're using direct method hooking instead
135+
136+
#pragma mark - Notification Setup
137+
138+
static void setupDockNotifications(void) __attribute__((constructor));
139+
static void setupDockNotifications(void) {
140+
// Only setup if we're in the Dock process
141+
if (!isDockProcess()) {
142+
NSLog(@"[AppleSharpener] Not in Dock process (bundle ID: %@), skipping setup", [[NSBundle mainBundle] bundleIdentifier]);
143+
return;
144+
}
145+
146+
NSLog(@"[AppleSharpener] Setting up dock notifications in process: %@", [[NSBundle mainBundle] bundleIdentifier]);
147+
148+
// Load persisted settings from NSUserDefaults
149+
NSUserDefaults *defaults = [[NSUserDefaults alloc] initWithSuiteName:@"com.aspauldingcode.apple_sharpener"];
150+
enableDockSharpener = [defaults boolForKey:@"enabled"];
151+
dockCustomRadius = [defaults integerForKey:@"dock_radius"];
152+
153+
// Hook CALayer's layoutSublayers method
154+
Class layerClass = [CALayer class];
155+
Method originalMethod = class_getInstanceMethod(layerClass, @selector(layoutSublayers));
156+
if (originalMethod) {
157+
__LayoutSublayers = method_getImplementation(originalMethod);
158+
method_setImplementation(originalMethod, (IMP)_PatchedLayoutSublayers);
159+
ASDebugLog("Hooked CALayer layoutSublayers");
160+
} else {
161+
ASDebugLog("Failed to find CALayer layoutSublayers");
162+
}
163+
164+
// Optionally hook WALayerKitWindow - root layer provider for Dock windows
165+
Class walClass = NSClassFromString(@"WALayerKitWindow");
166+
if (walClass) {
167+
Method walLayerMethod = class_getInstanceMethod(walClass, @selector(layer));
168+
if (walLayerMethod) {
169+
__WALayerKitWindow_layer = method_getImplementation(walLayerMethod);
170+
method_setImplementation(walLayerMethod, (IMP)_PatchedWALayerKitWindow_layer);
171+
ASDebugLog("Hooked WALayerKitWindow layer method");
172+
} else {
173+
ASDebugLog("WALayerKitWindow layer method not found");
174+
}
175+
} else {
176+
ASDebugLog("WALayerKitWindow class not present");
177+
}
178+
179+
dispatch_queue_t queue = dispatch_get_main_queue();
180+
181+
static const char *kNotifyEnabled = "com.aspauldingcode.apple_sharpener.enabled";
182+
static const char *kNotifyDockRadius = "com.aspauldingcode.apple_sharpener.dock.set_radius";
183+
184+
int tokenEnabled;
185+
if (notify_register_dispatch(kNotifyEnabled, &tokenEnabled, dispatch_get_main_queue(), ^(int token) {
186+
uint64_t state;
187+
notify_get_state(token, &state);
188+
enableDockSharpener = (state != 0);
189+
[defaults setBool:enableDockSharpener forKey:@"enabled"];
190+
[defaults synchronize];
191+
toggleDockCorners(enableDockSharpener, dockCustomRadius);
192+
ASDebugLog("Updated enabled=%d", enableDockSharpener);
193+
}) != NOTIFY_STATUS_OK) {
194+
ASDebugLog("Failed to register for enabled notification");
195+
}
196+
197+
int tokenRadius;
198+
if (notify_register_dispatch(kNotifyDockRadius, &tokenRadius, dispatch_get_main_queue(), ^(int token) {
199+
uint64_t state;
200+
notify_get_state(token, &state);
201+
dockCustomRadius = (NSInteger)state;
202+
[defaults setInteger:dockCustomRadius forKey:@"dock_radius"];
203+
[defaults synchronize];
204+
toggleDockCorners(enableDockSharpener, dockCustomRadius);
205+
ASDebugLog("Updated dock radius=%ld", (long)dockCustomRadius);
206+
}) != NOTIFY_STATUS_OK) {
207+
ASDebugLog("Failed to register for dock radius notification");
208+
}
209+
210+
// Initial application
211+
toggleDockCorners(enableDockSharpener, dockCustomRadius);
212+
}

0 commit comments

Comments
 (0)