Skip to content

sashavoloshanov/CustomColorMaster

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

15 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Custom Color Master

Download on the App Store

A lightweight macOS menu bar utility for picking and converting colors. It lives as an icon in the status bar, doesn't take up space in the Dock, and keeps a history of colors with conversion into 19 formats.

Download on the Mac App Store →

Features

  • Pick any pixel on screen using the system color sampler (NSColorSampler)
  • Paste a color value in any supported format and convert it to another — converted entries are marked with a badge in the history
  • History of colors, persisted between app launches
  • Display format is chosen per history entry (a picker in each row), not globally for the whole list
  • Copy a color's value by clicking the row or a dedicated button
  • Clipboard is checked when the window opens: a recognized color value is prefilled into the input field automatically, anything else is ignored
  • Newly added entries flash briefly so they're easy to spot
  • Configurable default format for new entries, persisted across launches
  • Resizable window; right-click the menu bar icon for a quick menu (Open / Quit), plus a standard App Menu with Quit (⌘Q)

Supported color formats

Category Formats
Web / CSS HEX, RGB, RGBA, HSL, HWB, OKLCH, CSS Variable
Color models HSBA, CMYK, RGB Float (0-1)
Swift / Apple SwiftUI Color, UIColor, UIColor (Objective-C syntax)
Numeric Hex (no #), Hex Integer
Other developer-friendly formats Kotlin-style, XML-style, Java AWT, and more

Full list — ColorFormatType.swift. Every format works in both directions: as display output, and as input the parser recognizes.

Requirements

  • macOS 13.0 (Ventura) or later — minimum deployment target due to modern AppKit APIs used
  • Xcode 15+ (only needed if building from source — see below)

Installation

Easiest: download from the Mac App Store.

From source:

  1. Clone the repository:
    git clone https://github.com/sashavoloshanov/CustomColorMaster.git
  2. Open CustomColorMaster.xcodeproj in Xcode.
  3. Cmd+R.

The app icon (an eyedropper) will appear in the menu bar. The app doesn't show up in the Dock or Cmd+Tab — that's intentional (LSUIElement = YES).

Usage

Picking a color from the screen

  1. Click the eyedropper icon in the menu bar → the window opens.
  2. Choose Color → the window hides, the cursor turns into the system loupe → click any pixel on screen to capture its color.
  3. The window reappears automatically, with the new color at the top.

Converting a value you already have

  1. Paste a color value into the input field at the top of the window (⌘V, the paste button, or right-click → Paste). If your clipboard already held a color when you opened the window, it's prefilled for you.
  2. Pick the target format in the dropdown next to the field.
  3. Convert → the result is added to the history, marked with a badge showing it came from manual input rather than the screen.

Values that aren't recognizable as colors are rejected rather than pasted, so unrelated clipboard text never ends up in the field.

Working with the history

Each row shows a swatch, the value in that row's current format, a dropdown to change the format for just that entry, and a copy button — clicking anywhere on the row copies too. Right-click a row to delete it, or use Clear to wipe the whole history.

The window's close and minimize buttons both just put the window away; bring it back by clicking the menu bar icon. To quit, right-click the icon and choose Quit Custom Color Master, or press ⌘Q while the app is active.

Architecture

CustomColorMaster/
├── CustomColorMasterApp.swift      — entry point, just an AppDelegate, no SwiftUI window scenes
├── AppDelegate.swift                — builds the App Menu (Quit, Edit), creates StatusBarController
├── StatusBarController.swift        — NSStatusItem, window visibility, Space handling, NSColorSampler
├── MainWindowController.swift       — builds a fresh NSWindow on every open (see below)
├── AppSettings.swift                — @Published defaultFormat, persisted via UserDefaults
├── Models/
│   ├── ColorFormatType.swift        — enum of all formats + NSColor → text conversion
│   └── PickedColor.swift            — a history entry; format and origin are stored on the entry
├── Services/
│   ├── ColorHistoryStore.swift      — @Published history array, auto-saves to a JSON file
│   ├── ColorParser.swift            — text → NSColor, the inverse of ColorFormatType
│   └── ColorSamplerService.swift    — thin wrapper around NSColorSampler
└── Views/
    ├── ColorHistoryListView.swift   — window content: header, input section, history list
    ├── ColorInputView.swift         — paste field, target format picker, Convert button
    ├── ColorRowView.swift           — a single row: swatch + value + format picker + copy
    └── NativeTextField.swift        — AppKit-backed text field with layout-independent shortcuts

Why not MenuBarExtra

The first version used SwiftUI's MenuBarExtra(.window). The problem: MenuBarExtra automatically closes its window as soon as the app resigns active — which is exactly what NSColorSampler does the moment it's invoked. Result: the window closed (and even interrupted the sampler itself) before the user could click a pixel.

The fix is a plain NSStatusItem plus a manually managed NSWindow: StatusBarController hides the window before starting NSColorSampler and shows it again once the sample completes.

A fresh window on every open

Reusing one NSWindow turned out to be unworkable across Spaces: the window server keeps a permanent Space assignment for a window, so showing it again would drag the user back to whichever Space it was first opened on. MainWindowController therefore destroys and rebuilds the window each time it's shown — a brand-new window has no prior assignment and simply appears where the user currently is. The window size is carried over so resizing isn't lost. Switching Spaces hides the window, so the next click on the menu bar icon always opens it fresh on the current Space.

Keyboard shortcuts and keyboard layouts

NSTextField shortcuts are handled in performKeyEquivalent(with:) and matched on event.keyCode rather than charactersIgnoringModifiers. The latter returns the character for the current keyboard layout, so on a Cyrillic layout ⌘V arrives as "м" and never matches a "v" comparison — key codes are layout-independent and always match.

Persistence

Color history is stored as a JSON file:

~/Library/Application Support/<bundle id>/colorHistory.json

Writes are debounced (0.3s), and history is capped at 200 entries (ColorHistoryStore.historyLimit, easy to change or remove).

The default format for new entries is stored separately in UserDefaults (AppSettings).

Format is a property of the entry, not a global setting

PickedColor.format is a plain model field. Changing the format on one row (ColorHistoryStore.updateFormat(id:to:)) affects only that entry and is persisted with it in the JSON file — the rest of the history keeps its own formats. PickedColor.isConverted works the same way, recording whether an entry came from the screen or from manual input.

Permissions

Uses NSColorSampler, no additional permissions required.

Links