.github
CI: build against the dev channel on dev-branch runs
Resources
Eau: show clickable Fork-me-on-GitHub ribbon in About panels with github.com URLs
Test
Add EauTest fixture and cut, copy and paste UI tests
Tests
Add EauTest fixture and cut, copy and paste UI tests
.clang-format
clangd files
.clang-tidy
clangd files
.clangd
clangd files
.gitignore
Add .gitignore entries for built Test app bundles and obj dir
AGENTS.md
AGENTS.md: document Eau theme build and swizzle conventions
AppearanceMetrics.h
Pulse default buttons with one timer that rests in background windows
Eau.h
Eau: push application-level menu so windowless apps reach Menu.app
Eau.m
Make Copy work right after Select All from the global menu
Eau+Browser.m
Rename from "Rik" to "Eau"
Eau+Button.h
Pulse default buttons with one timer that rests in background windows
Eau+Button.m
Pulse default buttons with one timer that rests in background windows
Eau+ColorWell.m
Rename from "Rik" to "Eau"
Eau+Drawings.h
Eau: make focus rings follow each button's exact (per-corner) shape
Eau+Drawings.m
Improve window title bar design
Eau+FocusFrame.m
Show focus ring only after Tab, not on window open (verified in NSAlert)
Eau+Menu.m
Draw all menu separators with the same thickness
Eau+ProgressIndicator.m
Keep the WindowManager drawing when the Dock is busy
Eau+ResizeZones.m
GSScaleFactor: scale all decoration metrics, radii, line widths, font, and orb insets
Eau+Scroller.m
Convert to ARC
Eau+Segmented.m
Refactor color handling to use EauSafeCalibratedRGB for consistent color conversion across button and drawing components
Eau+Slider.m
Rename from "Rik" to "Eau"
Eau+Stepper.h
Rename from "Rik" to "Eau"
Eau+Stepper.m
Convert to ARC
Eau+Table.m
Eau: draw outline rows content-first so macOS-style willDisplayCell delegates work
Eau+TabView.m
Eau: add disabled-tab support to tab view (setEnabled/isEnabled on NSTabViewItem)
Eau+TitleBarButtons.h
Add orb (traffic-light) titlebar button style
Eau+TitleBarButtons.m
GSScaleFactor: scale all decoration metrics, radii, line widths, font, and orb insets
Eau+WindowDecoration.m
Eau: use window background for frame border color to avoid dark flash
EauGrowBoxCell.h
Add theme-driven window resize zones and grow box
EauGrowBoxCell.m
Improve grow box appearance and sizing
EauGrowBoxView.h
Add theme-driven window resize zones and grow box
EauGrowBoxView.m
Convert to ARC
EauInfo.plist
Add @okt to theme authors list
EauMenuRelaunchManager.h
Add EauMenuRelaunchManager to restart Menu if it crashes
EauMenuRelaunchManager.m
Add EauMenuRelaunchManager to restart Menu if it crashes
EauMenuScrollManager.h
Eau: fix menu edge and wheel scrolling in the global menu bar
EauMenuScrollManager.m
Eau: fix menu edge and wheel scrolling in the global menu bar
EauMenuWindowFilter.h
Keep the menu bar visible when clicking it with a scale factor other than 1.0
EauMenuWindowFilter.m
Keep the menu bar visible when clicking it with a scale factor other than 1.0
EauProgressView.h
Eau: ease progress fill to new value instead of snapping
EauProgressView.m
Eau: stop progress bar ripple and sound only after long runs finish
EauScrollerArrowCell.h
Rename from "Rik" to "Eau"
EauScrollerArrowCell.m
Improve scroller behavior for resize zones
EauScrollerKnobCell.h
Rename from "Rik" to "Eau"
EauScrollerKnobCell.m
Rename from "Rik" to "Eau"
EauScrollerKnobSlotCell.h
Rename from "Rik" to "Eau"
EauScrollerKnobSlotCell.m
Less chrome for scroll bars, center tabs , left align table headers, no vertical lines in tables
EauSound.h
Eau: play Glass at alert volume when a progress bar completes
EauSound.m
Eau: play Glass at alert volume when a progress bar completes
EauTitleBarButton.h
Add EauTitleBarButton and EauTitleBarButtonCell classes
EauTitleBarButton.m
Add EauTitleBarButton and EauTitleBarButtonCell classes
EauTitleBarButtonCell.h
Add EauTitleBarButton and EauTitleBarButtonCell classes
EauTitleBarButtonCell.m
Improve window title bar design
EauWindowButton.h
Rename from "Rik" to "Eau"
EauWindowButton.m
Rename from "Rik" to "Eau"
EauWindowButtonCell.h
Convert to ARC
EauWindowButtonCell.m
Rename from "Rik" to "Eau"
GNUmakefile
Keep the menu bar visible when clicking it with a scale factor other than 1.0
GNUmakefile.postamble
Fix CI: nested Test install now builds before installing
GSDisplayServer+Eau.m
EauAlertPanel: float above all windows (NSScreenSaverWindowLevel + _NET_WM_WINDOW_TYPE_DIALOG)
GSInfoPanel+Eau.h
Rename from "Rik" to "Eau"
GSInfoPanel+Eau.m
Fix sluggish dialogs: stop repainting the whole window for focus rings
GSStandardDecorationView+Eau.m
Eau: remove UIBridge service and DragTool; fix window-frame drift
GWDialog+Eau.m
Eau: keep long alerts closable and cap their height at 36% of the screen
LICENSE
Create LICENSE
NSAlert+Eau.h
Make default buttons work in NSAlert
NSAlert+Eau.m
Keep long alert text sharp while it scrolls
NSApplication+Eau.m
Defer terminate from eau_lastWindowClosed to the runloop
NSBeep+Eau.m
Eau: play Glass at alert volume when a progress bar completes
NSBox+Eau.m
Use standard NSDebugLog instead of the bespoke EAULOG macro
NSBrowserCell+Eau.m
Use standard NSDebugLog instead of the bespoke EAULOG macro
NSButton+Eau.h
Key handling for buttons
NSButton+Eau.m
Pulse default buttons with one timer that rests in background windows
NSButtonCell+Eau.h
Pulse default buttons with one timer that rests in background windows
NSButtonCell+Eau.m
Pulse default buttons with one timer that rests in background windows
NSCell+Eau.h
Rename from "Rik" to "Eau"
NSCell+Eau.m
Use standard NSDebugLog instead of the bespoke EAULOG macro
NSComboBox+Eau.m
Auto-select the only item of single-choice combo boxes
NSFont+Eau.m
Eau: enforce correct system-font weights for dialogs
NSMenu+Eau.m
Keep the menu bar visible when clicking it with a scale factor other than 1.0
NSMenuItemCell+Eau.h
Rename from "Rik" to "Eau"
NSMenuItemCell+Eau.m
Center menu bar icons with the text next to them
NSMenuView+Eau.h
Rename from "Rik" to "Eau"
NSMenuView+Eau.m
Remove excessive logging
NSPopUpButton+Eau.m
Use standard NSDebugLog instead of the bespoke EAULOG macro
NSProgressIndicator+Eau.m
Eau: stop progress bar ripple and sound only after long runs finish
NSSearchField+Eau.h
Eau: themed search field with cancel button, cursor tracking, Esc handling; transparent field editor for all text fields; blue GNUmakefile document icon
NSSearchField+Eau.m
Post NSControlTextDidChangeNotification when clearing search field via X button
NSSearchFieldCell+Eau.h
Rename from "Rik" to "Eau"
NSSearchFieldCell+Eau.m
Clear search field text when clicking X button or pressing ESC
NSSegmentedCell+Eau.m
Merge remote-tracking branch 'upstream/main' into fix/silence-debug-nslog
NSTableHeaderCell+Eau.h
Improve table header styling
NSTableHeaderCell+Eau.m
Convert to ARC
NSTableView+Eau.h
Improve List View (NSTableView)
NSTableView+Eau.m
Convert to ARC
NSTextFieldCell+Eau.h
Rename from "Rik" to "Eau"
NSTextFieldCell+Eau.m
NSTextField: default bezeled to NO (labels are the common case)
NSTextView+Eau.m
Post NSControlTextDidChangeNotification when clearing search field via X button
NSWindow+Eau.m
Pulse default buttons with one timer that rests in background windows
oldscreen.png
A screenshot of the actual look.
README.md
Document and use Method Swizzling conistently
TestDrawing-screenshot.png
add test program screenshot
TODO
Fix the NSTableView highlight for a selected row

Eau Theme

"Eau" is French for "Aqua". This is the default theme for the Gershwin Desktop Experience.

Installation

  1. Ensure you have GNUstep installed and configured on your system
  2. Build the theme bundle:
    cd gershwin-eau-theme
    gmake
  3. Install the theme:
    gmake install
  4. 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!
}

@end

Correct 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];
}

@end

Key Benefits:

  1. Preserves Original Behavior: The original NSButton's keyDown: logic is preserved under a new name (eau_keyDown:)
  2. Avoids Superclass Conflicts: Calling [self eau_keyDown: theEvent] invokes the original NSButton implementation, not skipping to NSControl
  3. Predictable Load Order: Using +load ensures swizzling happens as soon as the theme bundle loads
  4. Safe for Inheritance: The class_addMethod check handles cases where NSButton inherits keyDown: from its parent

When to Use Swizzling vs. Theme Engine Hooks

  • Use Swizzling: For standard AppKit classes like NSButton where the GSTheme engine doesn't provide override hooks
  • Use Theme Engine: For classes like NSPopUpButton that provide built-in override mechanisms (e.g., _overrideNSPopUpButtonMethod_mouseDown:)

Best Practices

  • Always use dispatch_once in +load for thread safety
  • Include the class_addMethod check for inheritance safety
  • Document which methods are swizzled and why
  • Test thoroughly to ensure original behavior is preserved