Eau Theme
"Eau" is French for "Aqua". This is the default theme for the Gershwin Desktop Experience.
Installation
- Ensure you have GNUstep installed and configured on your system
- Build the theme bundle:
cd gershwin-eau-theme gmake - Install the theme:
gmake install - The theme will be installed to
$(GNUSTEP_LIBRARY)/Themes/Eau.theme
Build Requirements
- GNUstep development environment
- Objective-C compiler (clang/gcc)
- GNUstep Make (gnustep-make)
References
Developers
Method Swizzling Pattern
The Eau theme uses method swizzling to augment existing GNUstep classes without completely replacing their implementations. This ensures that original behavior is preserved while adding theme-specific enhancements.
Why Method Swizzling?
Direct category overrides (using @implementation ClassName (Category)) replace the original method entirely, which can break inheritance chains and skip important superclass logic. Method swizzling allows you to "wrap" the original implementation with custom logic while ensuring the original code still runs.
Example: NSButton+Eau.m
In NSButton+Eau.m, we need to handle keyboard events (spacebar and Enter/Return) for button activation while preserving the original NSButton behavior for other keys.
Incorrect Approach (Direct Override):
@implementation NSButton (EauKeyboardHandling)
- (void) keyDown: (NSEvent*)theEvent
{
// Custom logic...
[super keyDown: theEvent]; // This skips NSButton's logic!
}
@endCorrect Approach (Method Swizzling):
#import <objc/runtime.h>
@implementation NSButton (EauKeyboardHandling)
+ (void) load
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class cls = [NSButton class];
SEL origSelector = @selector(keyDown:);
SEL swizSelector = @selector(eau_keyDown:);
Method origMethod = class_getInstanceMethod(cls, origSelector);
Method swizMethod = class_getInstanceMethod(cls, swizSelector);
// Attempt to add the method first in case NSButton doesn't implement it directly
BOOL didAddMethod = class_addMethod(cls,
origSelector,
method_getImplementation(swizMethod),
method_getTypeEncoding(swizMethod));
if (didAddMethod)
{
class_replaceMethod(cls,
swizSelector,
method_getImplementation(origMethod),
method_getTypeEncoding(origMethod));
}
else
{
method_exchangeImplementations(origMethod, swizMethod);
}
});
}
- (void) eau_keyDown: (NSEvent*)theEvent
{
// Custom logic for spacebar/Enter handling...
// Call the original implementation (now points to eau_keyDown)
[self eau_keyDown: theEvent];
}
@endKey Benefits:
- Preserves Original Behavior: The original
NSButton'skeyDown:logic is preserved under a new name (eau_keyDown:) - Avoids Superclass Conflicts: Calling
[self eau_keyDown: theEvent]invokes the originalNSButtonimplementation, not skipping toNSControl - Predictable Load Order: Using
+loadensures swizzling happens as soon as the theme bundle loads - Safe for Inheritance: The
class_addMethodcheck handles cases whereNSButtoninheritskeyDown:from its parent
When to Use Swizzling vs. Theme Engine Hooks
- Use Swizzling: For standard AppKit classes like
NSButtonwhere the GSTheme engine doesn't provide override hooks - Use Theme Engine: For classes like
NSPopUpButtonthat provide built-in override mechanisms (e.g.,_overrideNSPopUpButtonMethod_mouseDown:)
Best Practices
- Always use
dispatch_oncein+loadfor thread safety - Include the
class_addMethodcheck for inheritance safety - Document which methods are swizzled and why
- Test thoroughly to ensure original behavior is preserved