All widgets
Color Picker
Comes with Chopstick
Pick colors anywhere on the screen with the eyedropper. Each one is copied as a hex code and added to a palette.
Already installed? It's in your widget library.



What it runs
A widget is a folder of plain files. Here is all of its code.
widget.json
{
"id": "color-picker",
"name": "Color Picker",
"icon": "icon.png",
"description": "Pick colors anywhere on the screen with the eyedropper. Each one is copied as a hex code and added to a palette.",
"anytime": false,
"initial": "picking",
"states": {
"picking": {
"task": true,
"on_enter": [ { "run": "pick.sh", "then": "copied", "else": "cancelled" } ],
"compact": { "leading": { "symbol": "eyedropper" }, "trailing": "spinner" }
},
"copied": {
"task": true,
"open": true,
"dismiss_after": 8,
"on_enter": [ { "copy": "{{result.hex}}" } ],
"compact": { "leading": { "symbol": "circle.fill", "tint": "{{result.hex}}" }, "trailing": { "text": "{{result.hex}}" } },
"expanded": {
"size": "compact",
"header": { "symbol": "circle.fill", "tint": "{{result.hex}}", "title": "{{result.hex}} copied" },
"blocks": [
{ "list": { "from": "result.palette", "limit": 8, "title": "{{item.hex}}", "subtitle": "rgb({{item.rgb}})", "image": "{{item.image}}",
"do": [ { "copy": "{{item.hex}}" }, "dismiss" ] } },
{ "row": [
{ "button": { "symbol": "doc.on.doc", "label": "Copy All", "do": [ { "copy": "{{result.all}}" }, "dismiss" ] } },
{ "button": { "symbol": "trash", "label": "Clear", "style": "plain", "do": [ { "run": "clear.sh" }, "dismiss" ] } }
] }
]
},
"keys": [
{ "key": "cmd+p", "label": "Pick Another", "do": [ { "go": "picking" } ] },
{ "key": "cmd+c", "label": "Copy All", "when": "nothing_copied", "do": [ { "copy": "{{result.all}}" }, "dismiss" ] }
]
},
"cancelled": {
"dismiss_after": 2,
"compact": { "leading": { "symbol": "eyedropper" }, "trailing": { "text": "Cancelled" } }
}
},
"preview": {
"states": [ "copied", "cancelled" ],
"data": { "result": { "hex": "#3A7BFF", "rgb": "58, 123, 255", "all": "#3A7BFF\n#FF6B35",
"palette": [ { "hex": "#3A7BFF", "rgb": "58, 123, 255" }, { "hex": "#FF6B35", "rgb": "255, 107, 53" } ] } }
}
}
clear.sh
# Empties the palette. rm -f "$CHOPSTICK_STORAGE/palette.json"
palette.js
ObjC.import('AppKit')
// Puts the color in argv[0] (#RRGGBB) at the front of the palette kept in CHOPSTICK_STORAGE
// (newest first, up to 8), and prints the palette with a swatch image for each color.
function run(argv) {
const storage = $.NSProcessInfo.processInfo.environment.objectForKey('CHOPSTICK_STORAGE').js
const file = storage + '/palette.json'
const saved = $.NSString.stringWithContentsOfFileEncodingError(file, $.NSUTF8StringEncoding, null)
const hexes = saved.isNil() ? [] : JSON.parse(saved.js)
const hex = argv[0].toUpperCase()
const palette = [hex].concat(hexes.filter(h => h !== hex)).slice(0, 8)
$(JSON.stringify(palette)).writeToFileAtomicallyEncodingError(file, true, $.NSUTF8StringEncoding, null)
const colors = palette.map(h => {
const rgb = [1, 3, 5].map(i => parseInt(h.slice(i, i + 2), 16))
return { hex: h, rgb: rgb.join(', '), image: swatch(storage, h, rgb) }
})
return JSON.stringify({ hex: hex, rgb: colors[0].rgb, palette: colors, all: palette.join('\n') })
}
function swatch(storage, hex, rgb) {
const path = storage + '/swatches/' + hex.slice(1) + '.png'
if ($.NSFileManager.defaultManager.fileExistsAtPath(path)) return path
$.NSFileManager.defaultManager.createDirectoryAtPathWithIntermediateDirectoriesAttributesError(storage + '/swatches', true, $(), null)
const image = $.NSImage.alloc.initWithSize($.NSMakeSize(40, 40))
image.lockFocus
$.NSColor.colorWithSRGBRedGreenBlueAlpha(rgb[0] / 255, rgb[1] / 255, rgb[2] / 255, 1).setFill
$.NSRectFill($.NSMakeRect(0, 0, 40, 40))
image.unlockFocus
$.NSBitmapImageRep.imageRepWithData(image.TIFFRepresentation)
.representationUsingTypeProperties($.NSBitmapImageFileTypePNG, $()).writeToFileAtomically(path, true)
return path
}
pick.js
ObjC.import('AppKit')
ObjC.import('stdlib')
// Shows the system eyedropper; prints the clicked color as #RRGGBB. Esc exits 1.
function run() {
$.NSApplication.sharedApplication.setActivationPolicy($.NSApplicationActivationPolicyAccessory)
let picked = null, done = false
$.NSColorSampler.alloc.init.showSamplerWithSelectionHandler(function (color) {
if (color && !color.isNil()) {
const c = color.colorUsingColorSpace($.NSColorSpace.sRGBColorSpace)
picked = [c.redComponent, c.greenComponent, c.blueComponent]
.map(n => Math.round(n * 255).toString(16).padStart(2, '0').toUpperCase()).join('')
}
done = true
})
while (!done) $.NSRunLoop.currentRunLoop.runUntilDate($.NSDate.dateWithTimeIntervalSinceNow(0.05))
if (!picked) $.exit(1)
return '#' + picked
}
pick.sh
# Shows the system eyedropper and adds the color clicked to the palette. Esc exits 1. hex=$(osascript -l JavaScript pick.js) || exit 1 osascript -l JavaScript palette.js "$hex"